Garden of Eden


  • Home

  • Archives

PaddlePaddle 入门 第一篇

Posted on 2021-01-24

PaddlePaddle 基本介绍

今天开始PaddlePaddle学习的第一篇——入门基本介绍。主要会从两个方面讲解,一个是Paddle自身的两种编程方式以及其对应的优缺点,还有一个就是对其中两个大类数据结构的构成的简略介绍。

1. PaddlePaddle的编程方式

PaddlePaddle的编程方式主要有两种,

  1. 声明式编程(静态图)
  2. 命令式编程(动态图)

其中前者呢,主要是遵循先编译后执行的流程进行的,因此最终可以达到一个性能和显存都得到比较大优化的目的,而后者呢,比较遵循我们一般的编程习惯,像Python一样可以一边调试一边执行,非常方便。二者的优缺点对比如下,

声明式编程(静态图) 命令式编程(动态图)
性能和显存 先编译后执行,进行全局图优化。性能和显存达到最佳。 /
部署 无缝衔接部署 需要先转为静态图再部署
调试 先编译后执行,调试不方便。 边执行边调试
学习门槛 较高 较低
网络结构定义 对于一些树结构处理起来较难 更为灵活

2. 两大基本数据结构

我们知道,神经网络可以定义为如下公式,
$$
神经网络 = 网络结构 + 数据
$$
那么,在PaddlePaddle中网络结构一般是用OP算子去定义的,而数据则是由Variable去表示的。这就是PaddlePaddle中常用的两大基本数据结构了。

分割中的评价指标

Posted on 2020-12-24

IOU

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def normalize(mask, threshold):
"""
对mask进行二值化,
Args:
mask:
threshold: 二值化选择的阈值

Returns: 二值化后的mask

"""
mask[mask <= threshold] = 1
mask[mask > threshold] = 0
return mask

def iou(gt, pred):
"""
计算真实mask和估计mask之间的iou
Args:
gt: 真实mask
pred: 估计mask

Returns: IOU(gt, pred)

"""
normalized_gt = normalize(gt, 127)
normalized_pred = normalize(pred, 127)

and_area = cv2.bitwise_and(gt, pred)
or_area = cv2.bitwise_or(gt, pred)
return np.sum(and_area) / np.sum(or_area)

MAE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def normalize2(mask):
"""
对mask进行二值化,直接除以最大值
Args:
mask:

Returns:

"""
return mask / np.amax(mask + 1e-8)


def mae(gt, pred):
"""
计算两张mask之间的MAE,平均绝对误差
Args:
gt: 真实mask
pred: 预测mask

Returns:

"""
if len(gt.shape) < 2 or len(pred.shape) < 2:
print("ERROR: Mask1 or mask2 is not matrix!")
exit()
if len(gt.shape) > 2:
gt = gt[:, :, 0]
if len(pred.shape) > 2:
pred = pred[:, :, 0]
if gt.shape != pred.shape:
print("ERROR: The shapes of mask1 and mask2 are different!")
exit()

h, w = gt.shape[:2]
gt = normalize2(gt)
pred = normalize2(pred)
sum_error = np.sum(np.absolute(gt.astype(np.float32) - pred.astype(np.float32)))
mae_error = sum_error / (float(h) * float(w) + 1e-8)

return mae_error

Precision & Recall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def precision_recall(gt, pred):
"""
计算两张mask之间在不同阈值下的precision和recall
Args:
gt:
pred:

Returns:

"""
if len(gt.shape) < 2 or len(pred.shape) < 2:
print("ERROR: Mask1 or mask2 is not matrix!")
exit()
if len(gt.shape) > 2:
gt = gt[:, :, 0]
if len(pred.shape) > 2:
pred = pred[:, :, 0]
if gt.shape != pred.shape:
print("ERROR: The shapes of mask1 and mask2 are different!")
exit()

gt_num = gt[gt > 128].size # 统计gt中一共有多少个前景点
pp = pred[gt > 128] # 统计在gt为前景区域的像素点处,预测的mask的像素值
nn = pred[gt <= 128] # 统计在gt为背景区域的像素点处,预测的mask的像素值

mybins = np.arange(0, 256) # 将分布图的横坐标划分成【0,255】

pp_hist, pp_edges = np.histogram(pp, bins=mybins) # 绘制在gt前景区域部分,预测mask在[0, 255]区间内的分布情况
assert pp.size == pp_hist.sum()

nn_hist, nn_edges = np.histogram(nn, bins=mybins) # 绘制在gt背景区域部分,预测mask在[0, 255]区间内的分布情况
assert nn.size == nn_hist.sum()

pp_hist_flip = np.flipud(pp_hist) # 将区间倒置变为[255, 254], [254, 253], ..., [1, 0] 主要是因为cumsum不能从后往前
nn_hist_flip = np.flipud(nn_hist) # 将区间倒置变为[255, 254], [254, 253], ..., [1, 0]

pp_hist_flip_cumsum = np.cumsum(pp_hist_flip) # 将区间像素数进行累加 [255, 254], [254, 253], ..., [1, 0]
nn_hist_flip_cumsum = np.cumsum(nn_hist_flip) # 将区间像素数进行累加 [255, 254], [254, 253], ..., [1, 0]

precision = pp_hist_flip_cumsum / (pp_hist_flip_cumsum + nn_hist_flip_cumsum + 1e-8) # TP/(TP+FP)
recall = pp_hist_flip_cumsum / (gt_num + 1e-8) # TP/(TP+FN)

precision[np.isnan(precision)] = 0.0
recall[np.isnan(recall)] = 0.0

return precision[::-1], recall[::-1]

F_Measure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def F_measure(gt, pred, beta=pow(3, 0.5)):

if len(gt.shape) < 2 or len(pred.shape) < 2:
print("ERROR: Mask1 or mask2 is not matrix!")
exit()
if len(gt.shape) > 2:
gt = gt[:, :, 0]
if len(pred.shape) > 2:
pred = pred[:, :, 0]
if gt.shape != pred.shape:
print("ERROR: The shapes of mask1 and mask2 are different!")
exit()

pr, re = precision_recall(gt, pred)
mean_pr = np.mean(pr) # average pre over the whole dataset at every threshold
mean_re = np.mean(re) # average pre over the whole dataset at every threshold
f_measure = (1 + pow(beta, 2)) * mean_pr * mean_re / (pow(beta, 2) * mean_pr + mean_re + 1e-8)
return f_measure

Weighted F_Measure

参考论文:https://arxiv.org/pdf/1708.06433.pdf

代码链接:https://github.com/jiwei0921/Saliency-Evaluation-Toolbox/blob/master/saliency_evaluation/WFb.m

Relax F_Measure

参考链接:https://github.com/NathanUA/BASNet/issues/20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def boundary_extraction(mask, k_size=2):
"""
对结果进行腐蚀,进而得到边缘
"""

(h, w) = mask.shape
mask_pad = np.zeros((h+10, w+10))
mask_pad[5:h+5, 5:w+5] = mask

mask_pad_erd = ndimage.grey_erosion(mask_pad, size=(k_size, k_size))
mask_pad_edge = np.logical_xor(mask_pad, mask_pad_erd)

return mask_pad_edge[5:h+5, 5:w+5]


def precision_recall_f1(gt, pred):
gt[gt <= 2*np.mean(gt)] = 0
gt[gt > 2*np.mean(gt)] = 1

pred[pred <= 127] = 0
pred[pred > 127] = 1

pred_edge = boundary_extraction(pred)
gt_edge = boundary_extraction(gt)

# distance_transform_edt 用于距离转换,计算图像中非零点到最近背景点(即0)的距离
# 这里先将背景和前景互换,即背景变成1,前景变成0,则变为计算背景中距离最近边缘的距离
pred_dist = distance_transform_edt(np.logical_not(pred_edge))
gt_dist = distance_transform_edt(np.logical_not(gt_edge))

# The relaxed boundary recall (relaxRecallb) measures the fraction of ground truth boundary pixels
# that are within ρ pixels of predicted boundary pixels. Here ρ sets to 3.
buffer = 3
pred_edge_buffer = np.zeros_like(pred_edge)
pred_edge_buffer = pred_edge.copy()

# precision = TP / 总数
try:
pred_edge_buffer[gt_dist > buffer] = 0 # buffer 之外的点(即距离最近的边缘超过3个像素的点不考虑在内)
except:
return 0.0, 0.0, 0.0

precision_edge = np.sum(pred_edge_buffer).astype(np.float)/(np.sum(pred_edge).astype(np.float) + 1e-8)

# recall = TP / (TP + FN)
gt_edge_buffer = np.zeros_like(gt_edge)
gt_edge_buffer = gt_edge.copy()

try:
gt_edge_buffer[pred_dist > buffer] = 0 #
except:
return 0.0, 0.0, 0.0

recall_edge = np.sum(gt_edge_buffer).astype(np.float)/(np.sum(gt_edge).astype(np.float) + 1e-8)

# f1 = (1 + \{beta}2) * precision * recall / \{beta}2 * precision + recall
f1_edge = (1 + 0.3) * precision_edge * recall_edge / (0.3 * precision_edge + recall_edge + 1e-8)

return precision_edge, recall_edge, f1_edge

S_Measure

参考论文:https://arxiv.org/pdf/1708.00786.pdf

代码链接:https://github.com/zzhanghub/eval-co-sod/blob/7dd23832f84c01ccc2f876b4ce73b7acd8b82224/evaluator.py#L278

Mean Boundary Accuracy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def get_disk_kernel(radius):
return cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (radius * 2 + 1, radius * 2 + 1)) # 获得一个圆形的核

def mean_boundary_acc(gt, pred):
if len(gt.shape) < 2 or len(pred.shape) < 2:
print("ERROR: Mask1 or mask2 is not matrix!")
exit()
if len(gt.shape) > 2:
gt = gt[:, :, 0]
if len(pred.shape) > 2:
pred = pred[:, :, 0]
if gt.shape != pred.shape:
print("ERROR: The shapes of mask1 and mask2 are different!")
exit()

gt = normalize(gt)
pred = normalize(pred)

h, w = gt.shape

min_radius = 1
max_radius = (w + h) / 300
num_steps = 5

mask_acc = [0] * num_steps

for i in range(num_steps):
cur_radius = min_radius + int((max_radius - min_radius) / num_steps * i)
kernel = get_disk_kernel(cur_radius)
boundary_area = cv2.morphologyEx(gt, cv2.MORPH_GRADIENT, kernel)

gt_bound_area = gt[boundary_area]
pred_bound_area = pred[boundary_area]

num_edges = np.sum(boundary_area)
num_true_pos = np.sum((gt_bound_area * pred_bound_area) + (1 - gt_bound_area) * (1 - pred_bound_area))

mask_acc[i] = num_true_pos / num_edges

return sum(mask_acc) / num_steps

U Square Net

Posted on 2020-12-23

资源链接

  1. 代码:https://github.com/NathanUA/U-2-Net
  2. 论文:https://arxiv.org/pdf/2005.09007.pdf
  3. 评价指标:https://github.com/NathanUA/Binary-Segmentation-Evaluation-Tool

论文题目

U Square Net: Going Deeper with Nested U-Structure for Salient Object Detection

摘要

设计优点

  1. RSU block使得stage内可以捕获到更多scale的contextual的信息;
  2. RSU不会在计算量上增加过多主要得益于RSU内部的pooling操作;

模型特点

  1. U^{n} Net, where n = 2;

  2. 可以train from scratch,且效果和那些用预训练的模型效果差不多甚至更好;

  3. 提供了一个重版但效果最好和轻量版效果稍微差一点点的模型,模型框架基本一致,主要在于通道参数不同。

介绍

  1. 作者认为之前SOD的设计一直都存在一种common pattern,即借助于一些用于分类任务的预训练模型作为backbone进行SOD模型finetune的基础,但这其中存在的问题是,分类任务更关注的是全局语义信息而损失了SOD任务额外需要的局部细节信息和全局对比信息;此外,实际训练过程中的目标数据可能和ImageNet这种大型公开数据库的分布相差很大。由此,作者发问,是否可以设计出来一种模型结构,可以非常简单地train from scratch,同时可以媲美现有sota甚至超过它们。
  2. 作者还觉得现在的SOD模型设计越来越复杂,而且为了加深网络模型不惜让feature map越来越步入到 low-res 的境地,而 high-res在SOD中是非常重要的。因此,作者再次发文,是否存在一种方法可以在 go deeper的同时,不过多损失resolution且计算量是可接受的。

对于问题1,作者设计了nested U-Net这种结构,可以train from scratch;对于问题2,作者设计了RSU block,保证了intra-stage的特征图resolution的不变性,同时intra-stage内可以捕获多scale的特征。

RSU Block

先上一张对比图,

作者认为图中a-c的卷积核主要以3*3为主,难以捕捉到全局信息,而d的话虽然想要通过引入dilated conv,但是计算量和显存都会增大;虽然PoolNet想要通过池化的方法降低计算量,但是作者认为将特征图直接upsample之后相加对high-res的特征是无益的。

一个RSU block主要由三部分组成,如图所示,1为输入卷积层,2是一个U-Net like的操作,3是一个残差链接。更形象地表达方式如下图中的右图所示,

U Square Net

nested instead of stack.

从上图可以看出,模型结构主要由三部分组成,encoder(最左边)+decoder(中间,多监督)+fusion module(最右边)。

需要注意的点,En_5和En_6的输入特征图res太低,如果进一步downsample的话,会损失context信息,因此作者这时候选择的是dilated conv。同理De_5。

损失函数

其中每一个loss都是交叉熵损失函数。

Rethinking the Usage of Batch Normalization and Dropout in the Training of Deep Neural Networks 阅读笔记与实战

Posted on 2019-05-22

阅读札记

Abstract

  1. 结果:提出了一个全新的技巧提升神经网络训练效率;
  2. 基本思想:对神经网络输入进行白化可以达到一个更快的收敛速度;
  3. 做法:在每一个权重层前面引入了一个全新的 Independent-Component(IC) layer;
  4. 寻找这种方式前遇到的问题:对输入进行白化处理是一个计算量很大的操作;
  5. 具体的做法与结果:将两个流行的方法( Dropout 和 Batch Normalization )结合,最终使得神经元之间的互信息可以减小为原来的 $p^2$倍,相关系数可以减小为原来的$p$倍,最终实现了更稳定的训练流程,更快的收敛速度以及更好地表现;

1. Introduction

  1. 一个重要的发现:神经系统的表征能力随着网络中独立神经元数量的增多而增强;
  2. independent component analysis (ICA) = zero-phase component analysis (ZCA) + rotates the whitened activations to obtain the independent ones;
  3. 什么是 ZCA:可以参考我的上一篇文章 ZCA;
  4. 文中所谓的 Independent Component (IC) layer 指的是:{-BatchNorm-Dropout-};
  5. IC layer 在需要放置在神经网络的什么位置:权重层之前,具体会做实验来验证;
  6. 文章的两大贡献:
    • 通过结合两种流行做法 BN 和 Dropout,提出了一种新的层 —— IC 层;并严格证明了 IC 层可以降低神经元之间的互信息和相关系数,最终加快了模型的收敛速度;
    • 除了理论证明,作者同时在 CIFAR10/100 以及 ILSVRC2012 数据集上进行了实验,结果显示 IC 层实现了更稳定的训练流程,更快的收敛速度以及更好地表现;

2. Preliminary: the Influence of Uncorrelated Components

为了简单起见,我们在这里考虑一个最基础的神经网络,它只由线性层组成。$A$ 记为近似函数的参数,输入为 $x$,输出为 $y$。以下是我们想要最小化的目标函数$$ min_A \Sigma_{i=1}^{n} \left | y_i - Ax_i \right | ,(1)$$ 那么基于梯度下降方法,最优方法中的 $A$ 必须满足 $$A \Sigma_{i=1}^{n} x_ix_i^T = \Sigma_{i=1}^{n} yx_i^T。 (2)$$

那么,假设所有数据都位于局部子空间中,则$\Sigma_{i=1}^{n} x_ix_i^T$就可以由一个低维矩阵表示,这也就意味着存在多种方法来优化 Eq(2)。那么神经网络的训练过程就会变得很不稳定。此外,当我们用梯度下降法对 $A$ 进行优化时,它的收敛速度依赖于 $max(Eigen_i)/min(Eigen_i)$(其中,$Eigen_i$ 为海塞矩阵的特征值)。因此如果 $x_i$ 之间的相关性很小,那么收敛速度会大大提高。

3. Generating Independent Components: IC Layer

由于 independent component analysis (ICA) = zero-phase component analysis (ZCA) + rotates the whitened activations。文中作者选择了 BN 来替代 ZCA,Dropout 来替代 rotates the whitened activations to obtain the independent ones。

IC layer 在 Keras 中的实现也并不复杂。

from keras.layers.normalization import BatchNormalization as BatchNorm
from keras.layers.core import Dropout

def IC(inputs, p):
    x = BatchNorm(inputs) # replace ZCA
    x = Dropout(p)(x)   # replace rotation
    return x

下面主要讨论一下 Dropout 在这其中扮演的角色。

3.1 Mutual Information and Entropy

互信息可以用来度量两个信息源之间的依赖关系,即$$I(x;y) = \Sigma_(x,y) P(x,y) log\frac{P(x,y)}{P(x)P(y)}。$$也就是说只有当 $I(x;y) = 0$ 时,$x$和$y$才是独立的。假设有一个随机门$g_i$,它同$x_i$是相互独立的,它的作用是来修改标准激活值$x_i$,对于$g_i$,它以概率$p$保持为一个常数,其他情况下为0。

Theorem 1. 假设 $g_i$ 是由一个均值为$p$的伯努利分布产生的一系列独立的随机变量。设 $\hat{x_i} = g_ix_i$,那么有$$I(\hat{x_i};\hat{x_j}) = p^2 I(x_i;x_j), {\forall} i \neq j, $$ $$H(\hat{x_i}) = pH(x_i) + \epsilon_p, $$
其中,$H$ 代表香农熵,$\epsilon_p$代表伯努利分布的熵。

接下来就是对定理一的证明了,如图所示。
证明1

这样互信息就减少为原来的$p^2$倍了。

那么我们再来看一下香农熵的变化。(这一部分的证明还有一部存疑,如果大家想通了可以告知我)
证明2

3.2. Correlation Coefficient and Expectation

此部分进一步说明了 Dropout 在优化过程中的作用。
假设第 $i$ 个神经元与第 $j$ 个神经元的标准激活输出为$x_i$和$x_j$,则他们之间的相关系数可以表示为,$$c_{ij} = E(x_ix_j)。(4)$$ 那么经过 Dropout 操作之后,我们有$$\hat{c_{ij}} = \frac{1}{\sigma_i\sigma_j}E(g_ix_ig_jx_j),(5)$$
其中,$\sigma_i = \sqrt{E(g_ix_i)^2}$表示$x_i$的标准差。

有由于$g_i$和$x_i$之间相互独立,则我们有$\sigma_i^2 = Eg_i^2x_i^2 = Eg_i^2Ex_i^2 = p^2$。又因为 $g_i$ 之间是相互独立的,且与$x_i$相互独立,因此$g_ig_j$与$x_ix_j$之间相互独立,因此有$$E[(g_ig_j - v_{ij})(x_ix_j - \mu_{ij})] = 0, (6)$$
其中,$v_{ij}$和$\mu_{ij}$分别是$g_ig_j$与$x_ix_j$的均值,易得$v_{ij} = p^2$,$\mu_{ij} = 0$,因此Eq(6)可以变成$$E[(g_ig_j - p^2)x_ix_j] = 0 (7)$$
再结合(5)有,$$\hat{c_{ij}} = \frac{1}{\sigma_i\sigma_j}p^2E(x_ix_j) = pc_{ij}。(8)$$

因此当引入 Dropout 操作之后,任意两个神经元之间的相关系数都减小到原来的 $p$ 倍。但是这种方法同样存在缺点,因为当引入 Dropout 操作时,$x_i$的均值就变成了$px_i$。这样就意味着,如果当我们为了使得神经元之间的相关性更小而选择更小的$p$时,我们同时也损失了更多的信息。

4. Experiments and Results

4.1. Reformulating ResNet Architectures

这篇文章选择了 3 种不同的残差单元进行试验,每种的不通点在于它们的 short path,这样做主要是为了找到表现最好的残差单元。

(转载)ZCA 白化 和 CPA 白化之间有什么区别?

Posted on 2019-05-21

假设现在 $X$ 是一个 $n$ x $d$ 维度的矩阵,这个矩阵中存储着你的全部数据,其中 $n$ 代表数据量的大小,$d$ 代表变量个数。同时假设 $E$ 的每一列为协方差矩阵 $C = X^TX/n$ 的特征向量,$D$ 的对角线元素为 $C$ 的特征值。因此,我们有$$ C = EDE^T。$$

所以,我们对传统 PCA 白化的定义为 $$ W_{PCA} = D^{-1/2}E^T。 $$

但是这种 PCA 白化并不唯一。因为我们知道对任意白化数据旋转之后,其依然保持白化。即,$$ W = RW_{PCA},$$其中 $R$ 为正交矩阵,此时 W 仍为白化数据。

而在 ZCA 白化则选择了 $E$ 作为这个正交矩阵,即$$ W_{ZCA} = EW_{PCA} = ED^{-1/2}E^T。$$

ZCA 白化也叫做马哈拉诺比斯变换,它有一个特点是经过 ZCA 白化后的数据与原始数据(至少在平方距离上)是很相近的。换句话说,若已知 $XA^{T}$ 白化的条件下,要求使得最小化$\left | X-XA^{T} \right |$ 的平方,则 $A = W_{ZCA}$。

从上面这张图我们可以有一个更深刻的了解。最左边的这张图展示了原始数据的分布与主成分轴,注意右上角部分分布着一个dark shading,它表示数据的方向。中间这张图展示了$W_{PCA}$的行数据,即原始数据的投影向量。对其白化之后,分布如中间偏下图所示,分布呈圆形,但是需要注意到的是它仍然呈倾斜状态, dark corner位于东边,而不是位于东北边(同原始数据一样)。第三张图展示了$W_{ZCA}$的行数据(它们并不相互正交)。白化之后,分布呈圆形,而且数据方向与原始数据方向一致。当然,如前所述,我们可以用$E$对$W_{PCA}$旋转得到$W_{ZCA}$。

ZCA 最早是在 Bell and Sejnowski 1996(https://www-n.oca.eu/Bijaoui/doc_ab/cardon/edge.pdf)中关于ICA(Independent Component Analysis)部分提到的,它的全称是zero-phase component analysis。在图像处理中经常可以看到 ZCA 的身影。当将 PCA 运用到一批图片上的时候,主成分轴看起来像频率不断增加的傅里叶成分,可以从下图中的第一列看出,它们呈现出一种“全局感”。而当将 ZCA 运用到一批图片上的时候,结果则呈现出一种“局部感”。这主要是因为 ZCA 会尽可能少地对原始数据进行转换。

更多关于 ZCA 滤波和用 ZCA 对图片进行转换的例子可以在Krizhevsky 2009年的Learning Multiple Layers of Features from Tiny Images(http://www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf)文章中知晓。回答此问题的作者认为从这些例子可以得出,ZCA 白化应该是优于 PCA 白化的。即, ZCA 白化过后的图片更像正常的图片,而 PCA 白化过后的图片则不是这样。这对于卷积神经网络的算法是很重要的,因为在 CNN 中相邻像素点是被同时处理的,因此 CNN 对于正常图片的局部特征依赖很大。对大多数机器学习算法而言,数据之间是需要保证绝对不相关的。

Java 类之间的关系

Posted on 2019-05-06

类之间的关系

概述

在类之间,最常见的关系有依赖(“uses-a”),聚合(“has-a”),继承(“is-a”)。
其中,依赖关系,是一种最明显、最常见的关系。如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另一个类。一般而言,依赖关系在 Java 语言中体现为局域变量、方法的形参,或者对静态方法的调用。在实际中,应该尽可能地将相互依赖的类减到最少,即让类之间的耦合度最小。
聚合关系,是一种具体且易于理解的关系。聚合关系意味着类A的对象包含类B的对象。它是关联关系的一种(关联关系将在下面介绍),是强关联关系。聚合是整体和个体之间的关系。例如,汽车类与引擎类、轮胎类,以及其他的零件类之间的关系就是整体与个体的关系。与关联关系一样,聚合关系也是通过实例变量实现的。但是关联关系所涉及的两个类是处在同一层次上的,而在聚合关系中,两个类处在不平等层次上的,一个代表整体,一个代表部分。
继承关系,是一种用于表示特殊与一般的关系。一般而言,如果类 A 扩展类 B ,类 A 不但包含从类 B 继承的方法,还会拥有一些额外的功能。
除了上述3种关系之外,类之间还存在关联关系和组合关系。
关联关系是类与类之间的连接,它意味着一个类知道另一个类的属性和方法。关联可以是双向的,也可以是单向的。在 Java 语言中,关联关系一般使用成员变量来实现。
组合关系是关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分对象的生命周期,组合关系是不能共享的。代表整体的对象需要负责保持代表部分的对象及其存活,在一些情况下将负责代表部分的对象湮灭掉。代表整体的对象可以将代表部分的对象传递给另一个对象,由后者负责此对象的生命周期。换言之,代表部分的对象在每一个时刻只能与一个对象发生组合关系(组合关系不能共享),由后者排他地负责生命周期。部分和整体的生命周期一致。

关系在 UML 中的表示方法

表 4-1 表达类关系的 UML 符号

MQTT 简介

Posted on 2019-05-05

简介

MQTT 全称 MQ Telemetry Transport,主要用于在移动应用程序之间发送消息。移动应用程序通过调用 MQTT 库,使用 MQTT 来发送和接收消息。消息通过 MQTT 传递服务器进行交换。

适用情景

该协议用于无线或低带宽网络。 MQTT 客户机和服务器可应对为移动应用程序可靠传递消息的复杂性,并将网络管理成本保持在较低水平。它的灵活性使得为 IoT 设备和服务的多样化应用场景提供支持成为可能。

为什么选择 MQTT 而非 HTTP

  1. HTTP 是一种同步协议。客户端需要等待服务器响应。Web 浏览器具有这样的要求,但这样做的代价是牺牲了可伸缩性。在 IoT 领域,设备量多大以及很可能不可靠或高延迟的网络使得同步通信成为难题。异步消息协议更适合 IoT 应用程序。传感器发送读数,让网络确定将其传送到目标设备和服务的最佳路线和时间。
  2. HTTP 是单向的。客户端必须发起连接。在 IoT 应用程序中,设备或传感器通常是客户端,这意味着它们无法被动地接收来自网络的命令。
  3. HTTP 是一种 1-1 协议。客户端发出请求,服务器进行响应。但在 IoT 应用程序中,比较常见的是需要将消息传送到网络上的所有设备上,这样不仅困难,成本也是很高的。
  4. HTTP 是一种有许多标头和规则的重量级协议。不适合受限的网络。
    除了 HTTP 外其实还有很多其他的协议,比如 AMQP(高级消息排队协议)和 XMPP(可扩展消息和状态协议)等。但是 AMQP 致力于在企业应用程序中实现可靠性和互操作性。它拥有庞大的特性集,但不适合资源受限的 IoT 应用程序。而 XMPP 是一种对等即时消息(IM)协议。它高度依赖于支持 IM 用例的特性,比如存在状态和介质连接。与 MQTT 相比,它在设备和网络上需要的资源都要多得多。

作用

  1. MQTT 应用程序在移动设备上运行,比如智能手机和平板电脑;
  2. MQTT 还用于遥测,以接收来自传感器的数据并对传感器进行远程控制;

MQTT 客户机库特点

  1. MQTT 客户机库很小,其充当邮箱功能,通过连接到 MQTT 服务器的其他 MQTT 应用程序发送和接收消息;
  2. 客户机库通过运行 MQTT V3.1 协议的 MQTT 服务器向其他设备发送消息;
  3. 通过 MQTT 客户机库,使用者可以向特定的客户机发送消息,或者使用发布/订阅消息传递来连接多个设备;

MQTT 协议特点

  1. MQTT 协议为轻量级;
  2. MQTT 协议支持可靠的传送和即发即弃的传输;
  3. 在 MQTT 协议中,消息传送与应用程序脱离,脱离应用程序的程序取决于写入 MQTT 客户机和 MQTT 服务器的方式。脱离式传送能够将应用程序从任何服务器连接和等待消息中解脱出来。交互模式与电子邮件相似,但在应用程序编程方面进行了优化。

此外 MQTT 协议还有以下特点。

  1. 它是一种发布/预订协议,除提供一对多消息分发外,发布/预订也脱离了应用程序。对于具有多个客户机的应用程序来说,这些功能非常有用;
  2. 它与消息内容没有任何关系;
  3. 它通过 TCP/IP 运行,TCP/IP 可以提供基本网络连接;
  4. 它针对消息传送提供三种服务质量:“至多一次”,“至少一次”,“刚好一次”;
    “至多一次”:消息根据底层因特网协议网络尽最大努力进行传递。 可能会丢失消息。例如,将此服务质量与通信环境传感器数据一起使用。 对于是否丢失个别读取或是否稍后立即发布新的读取并不重要。
    “至少一次”:保证消息抵达,但可能会出现重复。
    “刚好一次”:确保只收到一次消息。例如,将此服务质量与记帐系统一起使用。 重复或丢失消息可能会导致不便或收取错误费用。
  5. 它是一种管理网络中消息流的经济方式。例如,固定长度的标题仅2个字节长度,并且协议交换可最大程度减少网络流量。
  6. 它具有一种“遗嘱”功能,该功能通知订户客户机从 MQTT 服务器异常断开连接。

实际案例

智慧电力度量

* 一条 MQTT 消息,其中包含发送给服务供应商的能源使用率数据。
* 遥测应用程序发送基于能源使用率数据分析的控制命令。
* 有关更多信息,请参阅遥测场景:家庭能源监控。

智慧健康状况监测

* 遥测应用程序向医院和医生发送您的健康数据。
* 可以基于健康状况数据的分析发送 MQTT 消息警报和反馈。
* 有关更多信息,请参阅遥测场景:家庭患者监视。

发布和订阅模型

MQTT 协议在网络中定义了两种实体类型:一个消息代理和一些客户端。代理是一个服务器,它从客户端接收所有消息,然后将这些消息路由到相关的目标客户端。客户端是能够与代理交互来发送和接收消息的任何事物。客户端可以是现场的 IoT 传感器,或者是数据中心内处理 IoT 数据的应用程序。

其主要流程如下,

  1. 客户端连接到代理。它可以订阅代理中的任何消息“主题”。此连接可以是简单的TCP/IP连接,也可以是用于发送敏感消息的加密 TLS 连接。
  2. 客户端通过将消息和主题发送给代理,发布某个主体范围内的消息。
  3. 代理然后将消息转发给所有订阅该主题的客户端。

因为 MQTT 消息是按主题进行组织的,所以应用程序开发人员能灵活地指定某些客户端只能与某些消息进行交互。例如,传感器将在”sensor_data”主题范围内发布读数,并订阅”config_change”主题。将传感器数据保存到后端数据库中的数据处理应用程序会订阅”sensor_data”主题。管理控制台应用程序能接受系统管理员的命令来调整传感器的配置,比如灵敏度和采样频率,并将这些更改发布到”config_change”主题。

此外,MQTT 是轻量级的。它有一个用来指定消息类型的简单标头,一个基于文本的主题,以及一个任意的二进制有效负载。应用程序可对有效负载采用任何数据格式,比如 JSON、XML、加密二进制或 Base64,只要目标客户端能够解析该有效负载。

了解 MQTT 协议

MQTT 是一种连接协议,它制定了如何组织数据字节并通过 TCP/IP 网络传输它们。但实际上,我们并不需要了解这个连接协议。我们只需要知道,每条消息都有一个命令和数据有效负载。该命令定义消息类型(例如 CONNECT 消息或 SUBSCRIBE 消息)。所有 MQTT 库和工具都提供了直接处理这些消息的简单方法,并能自动填充一些必须的字段,比如消息和客户端 ID。
首先,客户端发送一条 CONNECT 消息来连接代理。CONNECT 消息要求建立从客户端到代理的连接。CONNECT 消息包含以下内容参数。

表 1. CONNECT 消息参数

参数 说明
cleanSession 此标志指定连接是否是持久性的。持久会话会将所有订阅和可能丢失的消息(具体取决于 QoS) 都存储在代理中。
username 代理的身份验证和授权凭证。
password 代理的身份验证和授权凭证。
lastWillTopic 连接意外中断时,代理会自动向某个主题发送一条 “last will” 消息。
lastWillQos “last will” 消息的 QoS。
lastWillMessage “last will” 消息本身。
keepAlive 这是客户端通过 ping 代理来保持连接有效所需的时间间隔。

客户端收到来自代理的一条 CONNACK 消息。CONNACK 消息包含以下内容参数。

表 2. CONNACK 消息参数

参数 说明
sessionPresent 此参数表明连接是否已有一个持久会话。也就是说,连接已订阅了主题,而且会接收丢失的消息。
username 0 表示成功。其他值指出了失败的原因。

建立连接后,客户端随后会向代理发送一条或多条 SUBSCRIBE 消息,表明它会从代理接收针对某些主题的消息。消息可以包含一个或多个重复的参数。

表 3. SUBSCRIBE 消息参数

参数 说明
qos qos(服务质量或 QoS)标志表明此主题范围内的消息传送到客户端所需的一致程度。
值 0:不可靠,消息基本上仅传送一次,如果当时客户端不可用,则会丢失该消息。
值 1:消息应传送至少 1 次。
值 2:消息仅传送一次。
topic 要订阅的主题。一个主题可以有多个级别,级别之间用斜杠字符分隔。例如,“dw/demo” 和 “ibm/bluemix/mqtt” 是有效的主题。

客户端成功订阅某个主题后,代理会返回一条 SUBACK 消息,其中包含一个或多个 returnCode 参数。

表 4. SUBACK 消息参数

参数 说明
returnCode SUBCRIBE 命令中的每个主题都有一个返回代码。返回值如下所示。
值 0 - 2:成功达到相应的 QoS 级别。(参阅 表 3 进一步了解 QoS。)
值 128:失败。

与 SUBSCRIBE 消息对应,客户端也可以通过 UNSUBSCRIBE 消息取消订阅一个或多个主题。

表 5. UNSUBSCRIBE 消息参数

参数 说明
topic 此参数可重复用于多个主题。

客户端可向代理发送 PUBLISH 消息。该消息包含一个主题和数据有效负载。代理然后将消息转发给所有订阅该主题的客户端。

表 6. PUBLISH 消息参数

参数 说明
topicName 发布的消息的相关主题。
qos 消息传递的服务质量水平。
retainFlag 此标志表明代理是否保留该消息作为针对此主题的最后一条已知消息。
payload 消息中的实际数据。它可以是文本字符串或二进制大对象数据。

技巧和解决方法

MQTT 的优势在于它的简单性。在可以使用的主题类型或消息有效负载上没有任何限制。这支持一些有趣的用例。例如,请考虑以下问题:

如何使用 MQTT 发送 1-1 消息?

双方可以协商使用一个特定于它们的主题。例如,主题名称可以包含两个客户端的 ID,以确保它的唯一性。

客户端如何传输它的存在状态?

系统可以为”presence”主题协商一个命名约定。例如,“presence/client-id” 主题可以拥有客户端的存在状态信息。当客户端建立连接时,将该消息设置为 true,在断开连接时,该消息设置为 false。客户端也可以将一条 last will 消息设置为 false,以便在连接丢失时设置该消息。代理可以保留该消息,让新客户端能够读取该主题并找到存在状态。

如何保护通信?

客户端与代理的连接可以采用加密 TLS 连接,以保护传输中的数据。此外,因为 MQTT 协议对有效负载数据格式没有任何限制,所以系统可以协商一种加密方法和密钥更新机制。在这之后,有效负载中的所有内容可以是实际 JSON 或 XML 消息的加密二进制数据。

Android Studio 为新设备添加lib并导入

Posted on 2019-04-25

创建新模块(以创建 library 为例)

  1. 点击 File > New > New Module;
  2. 在出现的 Create New Module 窗口中,选择 Android Library;
  3. 在 Configure your new module 表单中,输入下列详情:
  4. 这样就成功地建立了一个名为 library 的 module。

导入新创建的模块

  1. 在要作为lib导入的module 的build.gradle文件中添加一行 apply plugin: ‘com.android.library’ ;
  2. 在要使用的module中将导入的module作为依赖库
  3. 在被添加 lib module 的 app module 的 build.gradle 中加入以下代码块

    1
    implementation project(path: ':library')
  4. 最后就可以在其他文件中利用 import 导入 library 中的代码文件了,使用方法如下

    1
    import com.example.library.LogUtils;

Peng Yuyan

8 posts
7 tags
© 2021 Peng Yuyan
Powered by Hexo
|
Theme — NexT.Muse v5.1.4