broad learning

宽度学习

如今的神经网络越来越复杂,层数也是越来越深。由此带来的弊端是训练时间越来越久,当需要更改模型时,又必须重新训练,耗费时间和资源。澳门大学教授陈俊龙老师提出了一种宽度学习的方法,通过增加网络宽度、减少网络深度,以此减少训练时间,这种别样的设计思路,让人眼目一新。

论文解读

阅读这个论文一周有余,其中查阅论文官网、网络相关资料,阅读论文代码,甚至与陈老师论文组成员交流,在经过一番自我消化后,最终对这篇论文有了深刻的理解。

根据我本人的理解,此论文的解读,主要分为以下三个方面。

  1. 论文所涉及知识点的前期学习与准备
  2. 论文本身的解读与自我理解
  3. 结合论文源码的解读

接下来,我们循序渐进、逐步学习和认识这个崭新的学习模型。

论文所涉及知识点的前期学习与准备

对于神经网络,我们较为熟悉的是全连接神经网络(MLP),这种神经网络的特点,是前一层(N)和下一层(N+1)之间是完全连接。其实还有一种随机向量函数链接神经网络,这种网络的特点是允许前一层网络(N)与下下一层网络(N+2)相互连接,也就是说,连接不具有层递性。这个网络是 Yoh-Han Pao教授在1992年提出的。为什么要介绍这个网络?因为宽度学习网络就是基于这种网络,也就是说它具有随机连接网络的特性,所以先认识随机连接网络,是有助于我们接下来对整体论文的理解。

随机向量函数链接神经网络的原理,其实很简单,掌握两种节点。

  • 初始化节点,等同于feature input。
  • 强化节点,等同于隐藏层节点,也就是经过 feature mapping 后的节点。

把以上两种节点直接连接输出层,也就是说只有三层网络。进一步说,可以进行压缩网络结构,把强化节点和初始化节点同视为 input node ,然后连接到 output node ,两层结构,更加简单。

随机向量函数链接神经网络(random vector functional link neural network,RVFLNN)的网络结构图如下,我们可以看到,将经过初始化节点映射后的强化节点,并排在初始化节点层上,瞬间变成了两层网络。输入为$X$,函数$ϕi(XWei+βei)$ 映射后为强化节点输出,调整网络结构后,统一形成输入$A$.

QQ截图20181120104621

接下来补充一点数学知识,矩阵的逆。这是一个很基础的知识点,在这篇论文中,多次出现并运用。为了唤醒模糊的学习印象,最好的复习办法就是跟着公式走。我们先回到最初的起点,矩阵的意义是什么?矩阵的作用,本质上来说就是空间中的线性变换。

矩阵的逆,一个n阶方阵A称为可逆的,或非奇异的,如果存在一个n阶方阵B,使得$AB = BA = E$,则称B是A的一个逆矩阵。A的逆矩阵记作$A ^ {-1}$。

那么如何求一个可逆矩阵的逆呢?有几种通用的方法,这里我们介绍SVD分解法。SingularValue Decomposition分解法也叫做奇异值分解,是线性代数中一种矩阵分解方法。SVD分解能将矩阵A分解为三个矩阵的乘积,分别为:正交矩阵U、对角矩阵W、正交矩阵V的转置矩阵$ V ^{T}$。

已知

结论

推论过程 依据正交矩阵性质,所以 $ U^{-1} = U^{T},V^{-1} = V^{T}$

$ A^{-1} = (U(WV^{T}))^{-1} = (WV^{T})^{-1}U^{-1} = (V^{T})^{-1} W^{-1} U^{-1} = V W^{-1} U^{T} $ 推导结束

看起来很顺利,但实际上有个问题,并不是所有的矩阵都存在逆的,以上的推论,是建立在符合特殊条件下存在逆的矩阵情况下。那么,如何从特殊推推广到一般呢,这个不和谐的问题已在20世纪初被数学家E. H. Moore等人解决掉了,他们提出了广义逆(伪逆)的概念。

伪逆矩阵$A^{+}$的极限形式定义:

$A^{+}= \lim\limits_{x \to 0}{(A∗A+δI)^{−1}A^{∗} }$

伪逆矩阵有更加常用的定义,让我们基于SVD奇异值进行分解。

$ A^{+} = V W^{+} U^{*}$ , 其中$ W^{+}$求伪逆的方法是对角元素取整数,再将整个矩阵转置一次。

同样我们也回顾一下分块矩阵,尝试着求分块矩阵的逆,这里不展开细说,因为搜一下分块矩阵求逆会有很多公式,一般都是划分为一些特殊小矩阵再进行操作,所以直接参考求得即可。

文本身的解读与自我理解

论文introduction写的很好,逻辑连贯,比我等写的自然很多,很值得细读。在第一章节的最后提到了论文结构。第二章节将会介绍预备知识,第三章节是宽度学习模型、第四章节是实验、第五章节是后续讨论。

预备知识

作者的意图也很明显,必须先了解预备知识才有利于整体文章阅读。首先提到的预备知识就是随机连接神经网络,这点在上一段我们已经了解。接下来文章提出了第一个知识点,动态逐步更新,也就是这篇文章的亮点之一,如何进行增量式的训练学习。文章提出的动态逐步更新思想,是保留之前训练的模型网络,利用新的训练为已有模型添砖加瓦,这是一种非常科学和使用的思想。仔细想想,其实这就对应了分块矩阵的知识点,比如说,我们在第一次训练模型式,也就是在旧的矩阵$A_1$上,得出了$ W_1$,现在有了新的训练数据的增加,矩阵变成了大矩阵$[A_1,A_2]$,我们需要求得新的$ W$,那我们是否可以利用之前的$ W_1$呢?答案是可以的,因为$ W$就是由$ W_1$和$ W_2$构成。

其中论文中这样描述:

QQ截图20181122094442

论文里提到的第二个预备知识,是伪逆和岭回归。伪逆就是上一节我们提到的广义逆,我们从矩阵的逆出发就延伸和理解,伪逆的存在也就不难理解,就是一个基本的拓展概念。同样的学习思路我们可以运用在岭回归,想认识岭回归必须要回头认识一下线性回归或者最小二乘法。文章中并未详述,确实是机器学习入门基础,在这里我们也是简单一提。先上结论,在机器学习中,线性回归模型采用了最小二乘法作为损失函数(抛出一个小思索,巩固一下基础,线性回归的损失函数为什么用最小二乘不用似然函数?)而岭回归就是一种改良的最小二乘法!在论文中的表述是这样的,可以看出就是加了二范数正则化的最小二乘法。

QQ截图20181122094645

从数学角度上来说,其实就是$ X^{T}X$ 不满秩,并不完全可逆,各变量之间存在多重共线性,估计参数的方差会变大,岭回归就是牺牲了部分偏差,使得参数方差波动范围变小,变的稳定点(抛出小思索,可以从贝叶斯角度分析最大后验和岭回归的关系)。

普通最小二乘法 $ \beta = (X^{T}X)^{-1}X^{T}Y$

岭回归 $\beta = (X^{T}X + \gamma I )^{-1}X^{T}Y$

论文中第三个预备知识是提到了稀疏编码器,论文中说到尽量使得矩阵数据变得稀疏,不仅对模型性能有加强作用,而且有利于计算。这个知识点可以归为优化问题,在阅读论文时不懂也可以先跳,任何模型的优化问题都是一种完善,不妨碍整体模型的运行。在这片论文里,采用了ADMM(Alternating Direction Method of Multipliers)交替方向乘子算法,文中也附上了参考文献。这块知识点属于凸优化(convex optimization),(这块领域现在还不熟悉但是在计划学习中,等有了一定理解回来补充理论。但是这里我们可以先理解它如何使用即可)看完回来了,ADMM是目前机器学习分布式优化计算的最成熟的一种框架,这里找到一篇文章,很好的解释了ADMM原理,大家可以去学习一下(http://shijun.wang/2016/01/19/admm-for-distributed-statistical-learning/)

最后一个知识是奇异值分解,奇异值分解是一种常用的方法,这里不细说了。在这篇论文里,可以理解为降维或者是特征提取的一种机制了。

宽度学习模型正文

第二章节 A 、B 部分,正文一上来就很清楚的介绍了模型的结构,通过看数学公式,可以很快的知道,模型就是将初始化输入节点和强化节点结合成新的输出节点,直接全连接输出节点。论文在这里还论证了两种连接方式,最后的结论当输出结果都进行归一化处理时,两种连接方式都是相同的效果,即得到相同结果。具体论证过程可以从公式10和公式11开始看。个人直觉是这两种连接方式只是改变矩阵内的行列,最后线性运算出的结果,必定也存在线性关系,所以再统一归一化时,会消除之间的线性等倍差之类的,得到一致结果。

æ¾³é—¨å¤§å­¦é™ˆä¿Šé¾™ï¼šæ— éœ€æ·±åº¦ç»“æž„çš„é«˜æ•ˆå¢žé‡å­¦ä¹ ç³»ç»Ÿ

C、D、E 部分,都在围绕增量(incremental)做文章,可以从各部分看出,每次都使用了分块矩阵求伪逆,目的就是使得模型有在线学习的特点。仔细看,数学推导过程几乎一模一样,不要被吓坏了,一通百通!我们可以从更加直觉的角度去感受,为什么这里会拓展出这么多亮点,原因都是因为输出特征、强化节点、特征节点都能影响最后输入矩阵的形状大小,改变形状大小不要紧呀,我们有分块矩阵求伪逆,一切都是刚刚好!

æ¾³é—¨å¤§å­¦é™ˆä¿Šé¾™ï¼šæ— éœ€æ·±åº¦ç»“æž„çš„é«˜æ•ˆå¢žé‡å­¦ä¹ ç³»ç»Ÿ

F 部分,介绍了低秩(low-rank)处理,经过阅读可知,在这篇论文里,有多处用到了矩阵数据,文章中说到,为了矩阵数据变得冗余,使用低秩技术分别对特征节点、强化节点、条件强化节点构成的矩阵进行优化处理。低秩技术仍然采用了SVD方法。(抛出小思索,矩阵低秩的意义和如何求低秩逼近矩阵)

接下来就进入实验部分,作者用了两个公开数据集,与一众出名的方法进行了比较,比较维度有常用指标和速度,没错,这个模型就是速度快,在实验部分可以看到,即使精度有一丢丢落后于常规,但是速度是无可比拟的优势。

结合论文源码的解读

纸上得来终觉浅,觉知此事要躬行。

光读论文理论怎么能行?我们试着将代码撸一下。

在陈老师的宽度学习官网里,有提供源代码下载,我们可以下载下来细细观摩。

论文源代码其实写的很详细了,其中还有中文注释。

系统代码主要分为四个部分和一些辅助函数。

第一部分是基础版本的宽度学习,其余三部分是分别增加了矩阵中数量(如增加强化节点、特征节点)的变形的宽度学习,是基于第一部分的,主要有区别的是在处理weight那部分代码。

先解读一下基础版本的代码,自己额外加了一些注释。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
def BLS(train_x,train_y,test_x,test_y,s,c,N1,N2,N3):
L = 0
# 归一处理
train_x = preprocessing.scale(train_x,axis = 1)
# 加一列1
FeatureOfInputDataWithBias = np.hstack([train_x, 0.1 * np.ones((train_x.shape[0],1))])
# 初始化特征节点权重
OutputOfFeatureMappingLayer = np.zeros([train_x.shape[0],N2*N1])
Beta1OfEachWindow = []
distOfMaxAndMin = []
minOfEachWindow = []
ymin = 0
ymax = 1
train_acc_all = np.zeros([1,L+1])
test_acc = np.zeros([1,L+1])
train_time = np.zeros([1,L+1])
test_time = np.zeros([1,L+1])
time_start=time.time()#计时开始
for i in range(N2):
random.seed(i)
#生成每个窗口的权重系数,最后一行为偏差
weightOfEachWindow = 2 * random.randn(train_x.shape[1]+1,N1)-1;
#生成每个窗口的特征
FeatureOfEachWindow = np.dot(FeatureOfInputDataWithBias,weightOfEachWindow)
#压缩每个窗口特征到[-1,1]
scaler1 = preprocessing.MinMaxScaler(feature_range=(0, 1)).fit(FeatureOfEachWindow)
FeatureOfEachWindowAfterPreprocess = scaler1.transform(FeatureOfEachWindow)
#通过稀疏化计算映射层每个窗口内的最终权重
betaOfEachWindow = sparse_bls(FeatureOfEachWindowAfterPreprocess,FeatureOfInputDataWithBias).T
#存储每个窗口的系数化权重
Beta1OfEachWindow.append(betaOfEachWindow)
#每个窗口的输出 T1
outputOfEachWindow = np.dot(FeatureOfInputDataWithBias,betaOfEachWindow)
# 最大减去最小
distOfMaxAndMin.append(np.max(outputOfEachWindow,axis =0) - np.min(outputOfEachWindow,axis=0))
# 添加最小
minOfEachWindow.append(np.min(outputOfEachWindow,axis = 0))
# 除以最大进行放缩归一
outputOfEachWindow = (outputOfEachWindow-minOfEachWindow[i])/distOfMaxAndMin[i]
OutputOfFeatureMappingLayer[:,N1*i:N1*(i+1)] = outputOfEachWindow
del outputOfEachWindow
del FeatureOfEachWindow
del weightOfEachWindow
#生成强化层
#以下为映射层输出加偏置(强化层输入)
InputOfEnhanceLayerWithBias = np.hstack([OutputOfFeatureMappingLayer, 0.1 * np.ones((OutputOfFeatureMappingLayer.shape[0],1))])
#生成强化层权重
if N1*N2>=N3:
random.seed(67797325)
weightOfEnhanceLayer = LA.orth(2 * random.randn(N2*N1+1,N3))-1
else:
random.seed(67797325)
weightOfEnhanceLayer = LA.orth(2 * random.randn(N2*N1+1,N3).T-1).T
tempOfOutputOfEnhanceLayer = np.dot(InputOfEnhanceLayerWithBias,weightOfEnhanceLayer)
parameterOfShrink = s/np.max(tempOfOutputOfEnhanceLayer)
OutputOfEnhanceLayer = tansig(tempOfOutputOfEnhanceLayer * parameterOfShrink)
#生成最终输入
# OutputOfFeatureMappingLayer 特征节点权重
# OutputOfEnhanceLayer 强化节点权重
InputOfOutputLayer = np.hstack([OutputOfFeatureMappingLayer,OutputOfEnhanceLayer])
# 求input伪逆
pinvOfInput = pinv(InputOfOutputLayer,c)
# 求权重
OutputWeight = np.dot(pinvOfInput,train_y)
#训练完成
time_end=time.time()
trainTime = time_end - time_start
#训练输出
OutputOfTrain = np.dot(InputOfOutputLayer,OutputWeight)
trainAcc = show_accuracy(OutputOfTrain,train_y)
print('Training accurate is' ,trainAcc*100,'%')
print('Training time is ',trainTime,'s')
train_acc_all[0][0] = trainAcc
train_time[0][0] = trainTime
#测试过程
test_x = preprocessing.scale(test_x,axis = 1)#,with_mean = True,with_std = True) #处理数据 x = (x-mean(x))/std(x) x属于[-1,1]
FeatureOfInputDataWithBiasTest = np.hstack([test_x, 0.1 * np.ones((test_x.shape[0],1))])
OutputOfFeatureMappingLayerTest = np.zeros([test_x.shape[0],N2*N1])
time_start=time.time()#测试计时开始
# 映射层
for i in range(N2):
outputOfEachWindowTest = np.dot(FeatureOfInputDataWithBiasTest,Beta1OfEachWindow[i])
OutputOfFeatureMappingLayerTest[:,N1*i:N1*(i+1)] =(ymax-ymin)*(outputOfEachWindowTest-minOfEachWindow[i])/distOfMaxAndMin[i]-ymin
# 强化层
InputOfEnhanceLayerWithBiasTest = np.hstack([OutputOfFeatureMappingLayerTest, 0.1 * np.ones((OutputOfFeatureMappingLayerTest.shape[0],1))])
tempOfOutputOfEnhanceLayerTest = np.dot(InputOfEnhanceLayerWithBiasTest,weightOfEnhanceLayer)
# 强化层输出
OutputOfEnhanceLayerTest = tansig(tempOfOutputOfEnhanceLayerTest * parameterOfShrink)
# 最终层输入
InputOfOutputLayerTest = np.hstack([OutputOfFeatureMappingLayerTest,OutputOfEnhanceLayerTest])
# 最终测试输出
OutputOfTest = np.dot(InputOfOutputLayerTest,OutputWeight)
time_end=time.time() #训练完成
testTime = time_end - time_start
testAcc = show_accuracy(OutputOfTest,test_y)
print('Testing accurate is' ,testAcc * 100,'%')
print('Testing time is ',testTime,'s')
test_acc[0][0] = testAcc
test_time[0][0] = testTime
return test_acc,test_time,train_acc_all,train_time

变形版的代码改动主要权重方面,如下列的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 对应公式13
D = pinvOfInput.dot(OutputOfEnhanceLayerAdd)
# 对应公式14
C = OutputOfEnhanceLayerAdd - InputOfOutputLayer.dot(D)
# 对应公式14
if C.all() == 0:
w = D.shape[1]
B = np.mat(np.eye(w) - np.dot(D.T,D)).I.dot(np.dot(D.T,pinvOfInput))
else:
B = pinv(C,c)
# 求得input的伪逆 也就是X的逆
pinvOfInput = np.vstack([(pinvOfInput - D.dot(B)),B])
# W= X.T * Y
# 在代码里居然是通过Y和X来求W的伪逆,既然已经推导出来了,我觉得可以直接使用了,在另份代码是直接使用的。这里间接使用,似乎是等价的。
OutputWeightEnd = pinvOfInput.dot(train_y)

虽然代码写的很完善了,但是呢,从工程上出发总觉得不够优雅。

这里我又找到了另一位大神的复现代码,可以看出代码习惯非常好了。整体思路一样的,估计也是参考了源码然后再加以优化(https://github.com/LiangjunFeng/Broad-Learning-System/tree/master/BroadLearning)

回顾总结

宽度学习一种新的概念,在这个方向上还有很多可以挖掘和完善的点。在起初看文章时,一堆数学让人却步。但是在深入阅读文章后,发现一切又是那么水到渠成。数学确实可以将一片论文的核心思想简明扼要的表达出来,经过阅读这篇论文后,我觉得自己的数学功底有了小小的进步,同时串联起很多的机器学习知识点。共勉!

参考阅读材料

https://www.leiphone.com/news/201801/ao0L64n4J2CXSpg7.html

https://zhuanlan.zhihu.com/p/35442846

https://blog.csdn.net/itnerd/article/details/82871734

http://shijun.wang/2016/01/19/admm-for-distributed-statistical-learning/

https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=7987745 论文地址

坚持原创技术分享,您的支持将鼓励我继续创作!