Feature Learning
特征学习可以分为监督特征(Supervised)学习和无监督(Unsupervised)特征学习:
监督特征学习包括监督字典学习、神经网络、多层感知机;无监督特征学习包括K-means聚类、主成分分析、局部线性嵌入、独立成分分析、无监督字典学习等各种形式的聚类/降维算法。
监督学习
监督字典学习(Supervised Dictionary Learning)
字典学习是从输入数据中学习一组代表元素的字典,其中每个数据都可以表示为代表元素的加权和。通过最小化带有L1正则项的平均误差来确定字典元素和权重,并保证权重稀疏。监督字典学习利用输入数据和标签的隐含结构来优化字典元素。
监督字典学习理论知识
不妨把数据集考虑成一个矩阵,其每行对应一个样本,每列对应一个特征。特征选择所考虑的问题是特征具有“稀疏性”,即矩阵中的许多列与当前学习任务无关,通过特惠总能选择去除这些列,则学习期训练过程仅需在较小的矩阵上进行,学习的任务难度可能会有所降低设计的计算和存储开销会减少,学得模型的可解释性也会提高。
现在我们来考虑另一种稀疏性:所对应的矩阵中存在很多零元素,但这些零元素并不是以整列整行形式存在的。在不少的现实应用中我们会遇到这样的情形,比如在文档分类任务中,通常将每个文档看做一个样本,每个字(词)作为一个特征,字(词)在文档中出现的频率或者次数作为特征的取值;换言之,所对应的矩阵的每行是一个文档,每列是一个字(词),行、列交汇处就是某字(词)在文档中出现的频率或次数。那么,这个矩阵有多少列呢?以汉语为例,《康熙字典》中有47035个汉字,这就意味着该矩阵有3500列。然而给定一个文档,相当多的字是不出现在这个文档中的,于是矩阵的每一行都有大量的零元素;对于不同的文档,零元素出现的列往往很不相同。
当样本具有这样的稀疏表达形式时,对学习任务来说会有不少的好处,例如线性支持向量机之所以能在文本数据上有很好的性能,恰是由于文本数据在使用上述的字频后具有高度的稀疏性,使大多数问题变得线性可分。同时,稀疏样本并不会造成存储上的巨大负担,因为稀疏矩阵已有很多高效的存储方法。
那么若是给定数据集是稠密的,即普通非稀疏数据,能否将其转化为“稀疏表示”形式“,从而享有稀疏性所带来的好处呢?需要注意的是,我们所希望的稀疏表示是“恰当稀疏”,而不是”过度稀疏“。仍以汉语文档为例,基于《现代汉语常用字表》得到的可能是恰当稀疏,即其稀疏性足以让学习任务变得简单可行;而基于《康熙字典》则可能是过度稀疏,与前者相比,也许并未给学习任务带来更多的好处。
显然,在一般的学习任务中(例如图像分类)并没有《现代汉语常用字表》可用,我们需学习出这样一个“字典”。为普通稠密表达的样本找到合适的字典,将样本转化为合适的系数表示形式,从而使学习任务得以简化,模型复杂度得以降低,通常称为“字典学习”,亦称“稀疏编码”。这连个称谓稍微有差别,“字典学习”更侧重于学的字典的过程,而“稀疏编码“则更侧重于对样本进行稀疏表达的过程。由于两者通常是在同一个优化求解过程中完成的,因此下面我们不做进一步区分,笼统的称为字典学习。
给定数据集,字典学习最简单的形式为 其中为字典矩阵,称为字典的词汇量,通常由用户指定,则是样本的稀疏表示。显然,式(1)的第一项是希望由能够很好地重构,第二项则是希望尽量稀疏。
与LASSO相比,式(1)显然麻烦的多,因为除了类似于式(3)中的还需学习字典矩阵。不过,受LASSO的启发,我们可以采用变量你给交替优化的策略来求解式(1)。
首先在第一步,我们固定住字典,若将式(1)按分量展开,可看出其中不涉及这样的交叉项,于是可参照LASSO的解法求解下式,从而为每个样本找到相应的: 在第二步,我们以为初值来更新字典,此时可将式(1)写为 其中, 是矩阵的Frobenius范数。式(3)有多种求解方法,常用的有基于逐列更新策略的KSVD。令$b_i$表示字典矩阵的第行,表示稀疏矩阵的第列,式(3)可重写为 在更新字典的第$i$列时,其他各列都是固定的,因此是固定的,于是最小化(4)原则上只需对进行奇异值分解以取得最大奇异值所对应的正交向量。然而,直接对进行奇异值分解会同时修改和,从而可能破话的稀疏性。为避免发生这种情况,KSVD对和进行专门处理:仅保留非零元素,则仅保留和的非零元素的乘积项,然后再进行奇异值分解,这样就保持了第一步所得到稀疏性。
初始化字典矩阵之后反复迭代上述两步,最终即可求得字典和样本的稀疏表示。在上述字典学习过程中,用户能够通过设置词汇量的大小来控制字典的规模,从而影响到稀疏程度。
字典学习举例
下图展示一个手写字体的数据集,和用某种算法学习到的字典:
下图展示如何用学习到的字典D和系数向量a来表示信号x:
该数据集中的一个信号x,可以用D与a的乘积,因为a是稀疏的(大部分数据为0),因而只用到字典中的少数元素就可以表示信号x。如图:
仅仅用到字典中的三个元素就可以近似表示信号x,这对之后对信号的处理,如分类,都可以减少计算量,提高效率。
字典学习的python实现
#更新字典第k列,<span style="font-family: Arial, Helvetica, sans-serif;">phi为字典,y为样本集、sparse为上面稀疏编码矩阵</span>
def dict_update(phi, matrix_y, matrix_sparse, k):
indexes = np.where(matrix_sparse[k, :] != 0)[0]#取出稀疏编码中,第k行不为0的列的索引
phi_temp = phi
sparse_temp = matrix_sparse
if len(indexes) > 0:
phi_temp[:, k][:] = 0#把即将更新的字典的第k列先给设置为0
matrix_e_k = matrix_y[:, indexes] - phi_temp.dot(sparse_temp[:, indexes])#取出样本数据中,字典第k列有贡献的值,并求Ek
u, s, v = svds(np.atleast_2d(matrix_e_k), 1)
phi_temp[:, k] = u[:, 0]
sparse_temp[k, indexes] = np.asarray(v)[0] * s[0]
return phi_temp, sparse_temp
神经网络(Neural Networks)
神经网络(Neural Network)的构筑理念是受到生物神经网络功能的运作启发而产生的。人工神经网络通常是通过一个基于数学统计学类型的学习方法得以优化,所以人工神经网络也是数学统计学方法的一种实际应用。
和其他机器学习方法一样,神经网络已经被用于解决各种各样的问题,例如机器视觉和语音识别。这些问题都是很难被传统基于规则的编程所解决的。
什么是神经网络?
机器学习领域所说的神经网络指的是一种模仿生物神经网络的结构和功能而建立的数学或计算模型,用于对函数进行估计或近似。
例如,给定一些关于市面上房子的面积及价格的数据,要你根据这些数据建立一个房价预测模型,即输入一个房子的面积,希望通过这个模型输出一个房价的预测值。显然,这是一个线性回归问题,因为一般情况下房价和房子的面积都成正相关。这时,我们可以将已知数据的关系表现在平面坐标系中:
对数据进行线性拟合,且房价永远不会是负数,得到图中的ReLU函数(Rectified Linear Unit,修正线性单元)。
在这个简单的例子中,房子的面积作为输入,房价作为输出,而ReLU函数便充当一个神经元的作用,来产生输出。
然而房价除了受房子的面积影响之外,还会受卧室的数量、房子的位置以及地区的财富水平等因素的影响,这时就需要构建一个更为复杂的神经网络模型。
这就构成了一个神经网络模型基本结构,神经网络会自动生成隐藏层(Hidden Units)来处理输入,生成输出。这个问题中,只要拥有足够的训练数据,就能生成一个较好的神经网络模型,得到较为精确的结果。
简单而言,深度学习便是更为复杂的神经网络。
在Logistic回归问题中,我们通过建立一个简单的神经网络模型,输入训练样本(x,y),希望得出一个预测值尽可能等于y。训练的流程如下:
在这个模型中,我们先建立损失函数,进而不断采用梯度下降法找到参数w和b的最优解。采用这种算法编写的猫识别器最终的准确率只有,想要进一步提高识别的精准度,就需要建立起一个多层的神经网络来训练样本。
符号约定
如图所示的神经网络中,前面为输入层,中间为隐藏层 ,最后为输出层。中间层被称为隐藏层的原因是因为在训练过程中,将看到输入的样本有哪些,输出的结果是什么,中间层中的神经节点产生的真实值无法被观察到。所以中间层被称为隐藏层,只是因为你不会在训练集中看到它。此前,我们使用特征向量X来表示输入,在此前我们用符号 。最后,输出层输出的值表示为:
神经网络中的符号约定中,方括号上标明确指出了激活a来源于哪一层,而且,图中的这个神经网络也被称为两层神经网络,原因是当我么计算神经网络的层数时,通常不计算输入层。所以这个神经网络中,隐藏层是第一次层,输出层是第二层,而输入层为第零层。
图中的隐藏层中,将存在参数w和b,它们将分别表示为是个1*1矩阵。
神经网络的表示
如图所示,将样本输入隐藏层中的第一个节点后,可得下图,以此类推,将它们都表示成矩阵形式,即:
进过隐藏层后进入输出层,又有:
在losgistic回归,通常将参数初始化为零。而在神经网络中,通常将参数w进行随机初始化,参数b则初始化为0。此外,除w、b外的各种参数,如学习率及隐藏层中用的哪种激活函数,都称为超参数(Hyper Parameters),因为它们的值决定了参数w、b最后的值。
激活函数
建立一个神经网络时,需要关心的一个问题是,在每个不同的独立层中应当采用哪种激活函数。Logistic回归中,一直采用sigmoid函数作为激活函数,其实还有一些更好的选择。
tanh函数(Hyperbolic Tangent Function,双曲正切函数) 几乎总比sigmoid函数的效果更好,它的表达式为: 函数图像:
tanh函数其实是sigmoid函数的移位版本。对于隐藏单元,选用tanh函数作为激活函数的话,效果总比sigmoid函数好,因为tanh函数的值在-1到1之间,最后输出的结果的平均值更趋近于0,而不是采用sigmoid函数时的0.5,这实际上可以使得下一层的学习变得更加轻松。对于二分类问题,为确保输出在0到1之间,将仍然采用sigmiod函数作为输出的激活函数。
然而sigmoid函数和tanh函数都具有的缺点之一是,在z接近无穷大或无穷小时,这两个函数的导数也就是梯度变得非常小,此时梯度下降的速度也会变得非常慢。
线性修正单元,也就是上面举例解释什么是神经网络时用到的ReLU函数也是机器学习中常用到的激活函数之一,它的表达式为: 函数图像:
当z大于0时是,ReLu函数的导数一直为1,所以采用ReLU函数作为激活函数时,随机梯度下降的收敛速度会比sigmoid及tanh快得多,但负数轴的数据都丢失了。
此外,还有另一个版本的ReLU函数,称为Leaky-ReLU,其表达式为: 函数图像:
其中是一个很小的常数,用来保留一部非负数轴的值。
前向传播和反向传播
如图,通过输入样本,这样一个从前往后递进传播的过程,就称为前向传播(Forward Propagation)。
在训练过程中,经过前向传播后得到的最终结果跟训练样本的真实值总是存在一定误差,这个误差便是损失函数。想要减小这个误差,当前应用最广的一个算法便是梯度下降,于是用损失函数,从后往前,依次求各个参数的偏导,这就是所谓的反向传播(Back Propagation),一般简称这种算法为BP算法
在具体的算法实现过程中,还是需要采用logistic回归中用到梯度下降的方法,将各个参数进行向量化、取平均值,不断进行更新。
深度神经网络
深度神经网络含有多个隐藏层,构建方法如前面所述,训练时根据实际情况选择激活函数,进行前向传播获得成本函数进而采用BP算法,进行反向传播,梯度下降缩小损失值。
拥有多个隐藏层的深度神经网络能更好得解决一些问题。如图,例如利用神经网络建立一个人脸识别系统,输入一张人脸照片,深度神经网络的第一层可以是一个特征探测器,它负责寻找照片里的边缘方向,卷积神经网络(Convolutional Nero Networks,CNN)专门用来做这种识别。
深度神经网络的第二层可以去探测照片中组成面部的各个特征部分,之后一层可以根据前面获得的特征识别不同的脸型的等等。这样就可以将这个深度神经网络的前几层当做几个简单的探测函数,之后将这几层结合在一起,组成更为复杂的学习函数。从小的细节入手,一步步建立更大更复杂的模型,就需要建立深度神经网络来实现。
神经网络python实现举例
花瓣颜色分类器的实现
- 生成样本数据
#生成样本数据
def load_planar_dataset():
m = 400 #总样本数
N = int(m/2) #每种样本数
D = 2 #维数
a = 4 #花瓣延伸的最大长度
X = np.zeros((m,D)) #初始化样本矩阵
Y = np.zeros((m,1), dtype='uint8') #初始化标签矩阵,0为红色,1为蓝色
#随机分配样本坐标,使样本组成一朵花形
for j in range(2):
ix = range(N*j,N*(j+1))
t = np.linspace(j*3.12,(j+1)*3.12,N) + np.random.randn(N)*0.2 #角度
r = a*np.sin(4*t) + np.random.randn(N)*0.2 #半径
X[ix] = np.c_[r*np.sin(t),r*np.cos(t)]
Y[ix] = j
X = X.T
Y = Y.T
plt.scatter(X[0,:], X[1,:], c=Y, s=40, cmap=plt.cm.Spectral);
return X,Y
这些样本数据组成一朵杂带红蓝颜色的花朵图片:
- 采用logistic回归进行分类
运用sklearn中的提供logistic回归模型,对花朵样本进行分类
#生成分类器的边界
def plot_decision_boundary(model, X, y):
x_min, x_max = X[0,:].min() - 1, X[0,:].max() + 1
y_min, y_max = X[1,:].min() - 1, X[0,:].max() + 1
h = 0.01
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z = model(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
plt.ylabel('x2')
plt.xlabel('x1')
plt.scatter(X[0,:], X[1,:], c=y, cmap=plt.cm.Spectral)
#采用sklearn中的logistic回归模型进行分类
clf = sklearn.linear_model.LogisticRegressionCV() #初始化分类器
clf.fit(X.T,Y.T.ravel()) #数据拟合
plot_decision_boundary(lambda x: clf.predict(x), X, Y)
plt.title("Logistic Regression")
LR_predictions = clf.predict(X.T)
print('logistic回归的精确度:%d' % float((np.dot(Y,LR_predictions) + np.dot(1-Y, 1-LR_predictions))/float(Y.size)*100) + "%")
logistic回归分类后的效果为:
- 建立神经网络模型
#建立神经网络模型
def nn_model(X, Y, n_h, num_iterations = 10000, print_cost = False):
np.random.seed(3)
n_x = layer_sizes(X, Y)[0]
n_y = layer_sizes(X, Y)[2]
parameters = initialize_parameters(n_x, n_h, n_y);
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
for i in range(0, num_iterations):
A2, cache = forward_propagation(X, parameters)
cost = compute_cost(A2, Y, parameters)
grads = backward_propagation(parameters, cache, X, Y)
parameters = update_parameters(parameters, grads)
if print_cost and i % 1000 == 0:
print("循环%i次后的成本: %f" %(i, cost))
return parameters
#预测结果
def predict(parameters, X):
A2, cache= forward_propagation(X, parameters)
predictions = (A2 > 0.5)
return predictions
#将数据输入神经网络模型
parameters = nn_model(X, Y, n_h = 4, num_iterations = 10000, print_cost=True)
predictions = predict(parameters, X)
print ('准确度: %d' % float((np.dot(Y,predictions.T) + np.dot(1-Y,1-predictions.T))/float(Y.size)*100) + '%') #打印精确度
plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y)
plt.title("Decision Boundary for hidden layer size " + str(4))
得到的结果为:
采用一层隐含神经网络进行分类,准确度达到了。
- 不同隐藏层节点数下分类
#不同隐藏层节点数下分类效果
plt.figure(figsize=(16, 32))
hidden_layer_sizes = [1, 2, 3, 4, 5, 20, 50, 100]
for i, n_h in enumerate(hidden_layer_sizes):
plt.subplot(5, 2, i+1)
plt.title('Hidden Layer of size %d' % n_h)
parameters = nn_model(X, Y, n_h, num_iterations = 5000)
plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y)
predictions = predict(parameters, X)
accuracy = float((np.dot(Y,predictions.T) + np.dot(1-Y,1-predictions.T))/float(Y.size)*100)
print ("节点数为{}时的分类准确度为 : {} %".format(n_h, accuracy))
得到的结果为:
可以发现,随着隐藏层节点数的增加,分类的准确度进一步提高,然而隐藏层节点数过多时,过度学习反而使分类的准确度下降。
无监督学习
K-means聚类
K-means算法简介
K-means聚类是一种矢量量化的方法,给定n个向量,K-means算法以k为参数,将这些数据分成K个簇,使得每个向量属于其最近的均值所在的簇。由此得到的K个簇,簇内具有较高的相似度, 而簇间的相似度较低。
K-means算法过程如下:
- 随机选择K个点作为初始的聚类中心;
- 对于剩下的点,根据其与聚类中心的欧式距离,将其归入最近的簇;
- 对每个簇,计算所有点的均值作为新的聚类中心;
- 重复步骤2、3,直到聚类中心不再发生改变。
在特征学习中,K-means算法可以将一些没有标签的输入数据进行聚类,然后使用每个类别的质心来生成新的特征。最简单的方法是在每个输入样本中加入K个二元特征,其中当且仅当第j个质心距离采样数据最近时,第j个特征置为1。另一种方式是利用到簇的距离作为特征,或者是经过径向基函数进行转换的簇的距离。
K-means Python实现
>>> from sklearn.cluster import KMeans
>>> import numpy as np
>>> X = np.array([[1, 2], [1, 4], [1, 0],
... [4, 2], [4, 4], [4, 0]])
>>> kmeans = KMeans(n_clusters=2, random_state=0) #创建K-means实例
>>> kmeans.fit(X) #训练模型
>>> kmeans.labels_
array([0, 0, 0, 1, 1, 1], dtype=int32)
>>> kmeans.predict([[0, 0], [4, 4]]) #预测
array([0, 1], dtype=int32)
>>> kmeans.cluster_centers_
array([[ 1., 2.],
[ 4., 2.]])
在KMeans()函数中,主要有以下参数:
- n_clusters:用于指定聚类中心的个数。
- init:初始聚类中心的初始化方法, 默认值是k-means++,取‘random’值时随机生成k个聚类中心。
- max_iter:最大的迭代次数,默认值为300。
- random_state:用于生成随机种子。
- labels_:返回每个数据所在簇的编号。
- clustercenters:返回最后得到的聚类中心。
主成分分析(PCA)
PCA算法简介
主成分分析主要用于降维,通常用于高维数据集的探索与可视化, 还可以用作数据压缩和预处理等。PCA可以把具有相关性的高维变量合成为线性无关的低维变量,称为主成分,主成分能够尽可能保留原始数据的信息。
给定无标签的数据集,PCA生成p个奇异值向量(p远远小于数据的维度),对应数据矩阵中p个最大的奇异值。这p个奇异值向量是从输入数据中学习的特征向量,它们代表了数据具有最大方差的方向。PCA是一种线性特征学习方法,因为p个奇异值向量是数据矩阵的线性方程。
主成分分析的算法过程:
- 预处理:让每个原始数据减去其对应的均值,使得最后得到的数据均值为0;
- 求训练样本的特征的协方差矩阵;
- 求协方差矩阵的特征值和特征向量;
- 选择前k大特征值对应的特征向量分别作为列向量,组成特征向量矩阵;
- 将样本点投影到选取的特征向量上,得到新的k个特征。
PCA技术的一个很大的优点是,它是完全无参数限制的。在PCA的计算过程中完全不需要人为的设定参数或是根据任何经验模型对计算进行干预,最后的结果只与数据相关,与用户是独立的。
PCA有几点局限:首先,它假设最大方差的方向是最感兴趣的,而实际上很多应用中可能不是。PCA依赖于原始数据的正交变换,它只挖掘了数据的一阶、二阶矩,这并没有很好的表征数据分布。最后,PCA只有在输入数据向量是相关的情况下才能很好地降维。
PCA Python实现
引用scikit-learn官网上的例子:
>>> import numpy as np
>>> from sklearn.decomposition import PCA
>>> X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], ... [2, 1], [3, 2]])
>>> pca = PCA(copy=True, n_components=2, whiten=False)
>>> pca.fit(X)
>>> print (pca.explained_variance_)
[ 6.61628593 0.05038073]
>>> print(pca.explained_variance_ratio_)
[ 0.99244289 0.00755711]
在PCA()函数中,主要有以下参数:
- n_components: 这个参数可以帮我们指定希望PCA降维后的特征维度数目。当n_components是一个整数时,它表示直接指定降维到的维度数目;当n_components是一个(0,1]之间的小数时,它指定主成分的方差之和所占的最小比例阈值;让PCA类自己去根据样本特征方差来决定降维到的维度数;也可以把参数值设为n_components='mle',PCA类会用MLE算法根据特征的方差分布情况自己去选择一定数量的主成分特征来降维。缺省时默认值为n_components=min(样本数,特征数)。
- copy: bool 类型,表示是否在运行算法时,将原始训练数据复制一份。若为True,则运行PCA算法后,原始训练数据的值不会有任何改变,只在原始数据的副本上进行运算;若为False,则运行PCA算法后,会在原始数据上进行降维计算,原始训练数据的值会改。缺省时默认为True。
- whiten: bool 类型,判断是否进行白化。所谓白化,就是对降维后的数据的每个特征进行归一化,让方差都为1。对于PCA降维本身来说,一般不需要白化。如果你PCA降维后有后续的数据处理动作,可以考虑白化。默认值是False,即不进行白化。
局部线性嵌入(LLE)
LLE 算法简介
局部线性嵌入(LLE)是一种非线性的非监督学习方法,用来从未标注的高维输入中生成低维的近邻保持来表征。
LLE利用线性重构的局部对称性找出高维数据空间中的非线性结构,并在保持各数据点临近位置关系情况下,把高维空间数据点映射为低维空间对应的数据点,它能够使降维后的数据保持原有的流形结构。
LLE的一般思想是,通过保持原有数据集的部分集合特性的低维数据来重构原始高维数据。LLE包含两个主要步骤,第一步是近邻保持(neighbor-preserving),其中每个输入数据Xi通过K近邻数据点的加权和重构,并且通过最小化平均平方重构误差(average squared reconstruction error)找到最优权重;第二步是降维(dimension reduction),在低维空间中寻找向量,该向量使用第一步的权重可以最小化表示误差。
LLE算法步骤:
- 求每个样本点的k个最近邻点;
- 从每个样本点的近邻点计算该样本点的局部权值矩阵;
- 由样本点的近邻点和样本点重建权值矩阵来计算样本点的输出值,使得把样本点映射到低维空间达到降维目的,同时学习到新的特征。
LLE是广泛使用的图形图像降维方法,它实现简单,但是对数据的流形分布特征有严格的要求,比如不能是闭合流形,不能是稀疏的数据集,不能是分布不均匀的数据集等等,这限制了它的应用。相比PCA,LLE对于利用数据的隐含结构能力更强大。
LLE Python实现
具体见“特征提取-非线性特征降维-LLE局部线性嵌入”这一章节。
独立成分分析(ICA)
ICA 算法简介
独立成分分析是从多元(多维)统计数据中寻找潜在因子或成分的一种方法。ICA与其它的方法重要的区别在于,它寻找满足统计独立和非高斯的成分。非高斯前提的强制条件是因为当所有成分满足高斯分布时权重无法唯一确定。
ICA又称盲源分离(Blind source separation, BSS),它假设观察到的随机信号x服从模型$x=As$,其中s为未知源信号,其分量相互独立,A为未知混合矩阵。ICA的目的是通过且仅通过观察x来估计混合矩阵A以及源信号s。ICA认为一个信号可以被分解成若干个统计独立的分量的线性组合,而后者携带更多的信息。我们可以证明,只要源信号非高斯,那么这种分解是唯一的。若源信号为高斯的话,那么显然可能有无穷多这样的分解。
大多数ICA的算法需要进行“数据预处理”:先利用PCA得到y,再把y的各个分量标准化(即让各分量除以自身的标准差)得到z。预处理后得到的z满足下面性质:1. z的各个分量不相关;2. z的各个分量的方差都为1。
有许多不同的ICA算法可以通过z把A和s估计出来()。以著名的FastICA算法为例,该算法寻找方向使得随机变量的某种“非高斯性”(non-Gaussianity)的度量最大化。一种常用的非高斯性的度量是四阶矩。类似PCA的流程,我们首先找使得最大;然后在与正交的空间里找,使得最大,以此类推直到找到所有的 。可以证明,用这种方法得到的是相互独立的。
独立成分分析与主成分分析的区别:
- 主成分分析假设源信号间彼此非相关,独立成分分析假设源信号间彼此独立;
- 主成分分析认为主元之间彼此正交,样本呈高斯分布;独立成分分析则不要求样本呈高斯分布。
- ICA认为观测信号是若干个统计独立的分量的线性组合,ICA要做的是一个解混过程。而PCA是一个信息提取的过程,将原始数据降维,现已成为ICA将数据标准化的预处理步骤。
ICA的Python实现
具体见“特征提取-线性特征降维-ICA独立成分分析”这一章节。
无监督字典学习
无监督字典学习算法原理
与监督字典学习不同的是,非监督字典学习不利用数据的标签,只是利用数据的潜在结构来优化字典元素。字典学习(dictionary learning)就是为普通稠密表达的样本找到合适的字典,将样本转化为合适的稀疏表达形式,从而使学习任务得以简化,模型复杂度得以降低,亦称‘稀疏编码’(sparse coding)。
表达为优化问题的话,字典学习的最简单形式为:
其中为第i个样本,D为字典矩阵,为的稀疏表示,为大于0的参数。上式中第一个累加项说明了字典学习的第一个目标是字典矩阵与稀疏表示的线性组合尽可能的还原样本;第二个累加项说明了应该尽可能的稀疏。之所以用L1范式是因为L1范式正则化更容易获得稀疏解。字典学习便是学习出满足上述最优化问题的字典D以及样本的稀疏表示。
无监督字典学习的例子是稀疏编码,它用来从无标签数据中学习用于数据表示的基函数(即字典元素)。稀疏编码可以用来学习超完备字典(overcomplete dictionary),“超完备”就是字典元素的数目(即基向量的个数)要远远大于输入数据的维度。K-SVD是用于从无标记数据中学习数据稀疏表示的字典。
字典学习算法理论包含两个阶段:字典构建阶段(Dictionary Generate)和利用字典(稀疏的)表示样本阶段(Sparse coding with a precomputed dictionary)。这两个阶段(如下图)的每个阶段都有许多不同算法可供选择,每种算法的诞生时间都不一样。
求解字典学习最优化问题的步骤为:
- 初始化字典D优化稀疏采样W
在优化后的W上更新字典D
重复上述两步,求得最终D以及X的稀疏表示W。
字典学习的第一个好处是它实质上是对于庞大数据集的一种降维表示。第二,正如同字是句子最质朴的特征一样,字典学习总是尝试学习蕴藏在样本背后最质朴的特征(假如样本最质朴的特征就是样本最好的特征)。稀疏表示的本质:用尽可能少的资源表示尽可能多的知识,这种表示还能带来一个附加的好处,即计算速度快。我们希望字典里的字可以尽能的少,但是却可以尽可能的表示最多的句子,这样的字典最容易满足稀疏条件。也就是说,这个“字典”是这个“稀疏”私人订制的。
字典学习的Python实现
样例:图片的稀疏字典学习(这段代码来源于Python的Dictionary Learning的官方文献教材,主要用途是教会用户通过字典学习对图片进行滤波处理。)
Step1:首先是各种工具包的导入和测试样例的导入
from time import time #导入time模块,用于测算一些步骤的时间消耗
import matplotlib.pyplot as plt
import numpy as np
import scipy as sp
#导入稀疏字典学习所需要的函数
from sklearn.decomposition import MiniBatchDictionaryLearning
from sklearn.feature_extraction.image import extract_patches_2d
from sklearn.feature_extraction.image import reconstruct_from_patches_2d
from sklearn.utils.testing import SkipTest #异常抛出函数
#检测SciPy版本高低
from sklearn.utils.fixes import sp_version
if sp_version < (0, 12):
raise SkipTest("Skipping because SciPy version earlier than 0.12.0 and "
"thus does not include the scipy.misc.face() image.")
try: #尝试打开样本测试用例
from scipy import misc
face = misc.face(gray=True)
except AttributeError:
# Old versions of scipy have face in the top level package
face = sp.face(gray=True)
MiniBatchDictionaryLearning :MiniBatch是字典学习的一种方法,专门应用于大数据情况下字典学习。
extract_patches_2d:碎片提取函数,调用该函数将一张图片切割为一个一个的patch。如果一张图片相当于一篇文章的话,那么该函数的目标就是把文章中的每个句子都找到,这样才方便提取蕴藏在每个句子中的字。图片和patch的关系如下图所示,整张头像的照片是个图片,通过对图片的分割可以将图片分割为一个一个的小块,也就是一个个Pitch。
reconstruct_from_patches_2d:图片复原函数,它可以通过patch复原一整张图片。
Step2:通过测试样例计算字典D
# Convert from uint8 representation with values between 0 and 255 to a floating point representation with values between 0 and 1.
face = face / 255.0
# downsample for higher speed
face = face[::2, ::2] + face[1::2, ::2] + face[::2, 1::2] + face[1::2, 1::2]
face = face / 4.0
height, width = face.shape
# Distort the right half of the image
print(‘Distorting image...‘)
distorted = face.copy()
#对照片的右半部分加上噪声,之所以左半部分不加是因为教材想要产生一个对比的效果
distorted[:, width // 2:] += 0.075 * np.random.randn(height, width // 2)
# Extract all reference patches from the left half of the image
print(‘Extracting reference patches...‘)
t0 = time()
patch_size = (7, 7)
data = extract_patches_2d(distorted[:, :width // 2], patch_size)
data = data.reshape(data.shape[0], -1)
data -= np.mean(data, axis=0)
data /= np.std(data, axis=0)
print(‘done in %.2fs.‘ % (time() - t0))
print(‘Learning the dictionary...‘)
t0 = time()
#初始化MiniBatchDictionaryLearning类,并按照初始参数初始化类的属性
dico = MiniBatchDictionaryLearning(n_components=100, alpha=1, n_iter=500)
#调用fit方法对传入的样本集data进行字典提取,components_返回该类fit方法的运算结果,也就是我们想要的字典D
D = dico.fit(data).components_
dt = time() - t0
print(‘done in %.2fs.‘ % dt)
#画出字典D
plt.figure(figsize=(4.2, 4))
for i, comp in enumerate(D[:100]):
plt.subplot(10, 10, i + 1)
plt.imshow(comp.reshape(patch_size), cmap=plt.cm.gray_r,
interpolation=‘nearest‘)
plt.xticks(())
plt.yticks(())
plt.suptitle(‘Dictionary learned from face patches\n‘ +
‘Train time %.1fs on %d patches‘ % (dt, len(data)),
fontsize=16)
plt.subplots_adjust(0.08, 0.02, 0.92, 0.85, 0.08, 0.23)#left, right, bottom, top, wspace, hspace
运行程序,输出结果如下:
Step3:画出标准图像和真正的噪声,方便同之后字典学习学到的噪声相比较
def show_with_diff(image, reference, title):
"""Helper function to display denoising"""
plt.figure(figsize=(5, 3.3))
plt.subplot(1, 2, 1)
plt.title(‘Image‘)
plt.imshow(image, vmin=0, vmax=1, cmap=plt.cm.gray,
interpolation=‘nearest‘)
plt.xticks(())
plt.yticks(())
plt.subplot(1, 2, 2)
difference = image - reference
plt.title(‘Difference (norm: %.2f)‘ % np.sqrt(np.sum(difference ** 2)))
plt.imshow(difference, vmin=-0.5, vmax=0.5, cmap=plt.cm.PuOr,
interpolation=‘nearest‘)
plt.xticks(())
plt.yticks(())
plt.suptitle(title, size=16)
plt.subplots_adjust(0.02, 0.02, 0.98, 0.79, 0.02, 0.2)
show_with_diff(distorted, face, ‘Distorted image‘)
程序输出如下图所示:
Step4:测试不同的字典学习方法和参数对字典学习的影响
print(‘Extracting noisy patches... ‘)
t0 = time()
#提取照片中被污染过的右半部进行字典学习
data = extract_patches_2d(distorted[:, width // 2:], patch_size)
data = data.reshape(data.shape[0], -1)
intercept = np.mean(data, axis=0)
data -= intercept
print(‘done in %.2fs.‘ % (time() - t0))
#四种不同的字典表示策略
transform_algorithms = [
(‘Orthogonal Matching Pursuit\n1 atom‘, ‘omp‘,
{‘transform_n_nonzero_coefs‘: 1}),
(‘Orthogonal Matching Pursuit\n2 atoms‘, ‘omp‘,
{‘transform_n_nonzero_coefs‘: 2}),
(‘Least-angle regression\n5 atoms‘, ‘lars‘,
{‘transform_n_nonzero_coefs‘: 5}),
(‘Thresholding\n alpha=0.1‘, ‘threshold‘, {‘transform_alpha‘: .1})]
reconstructions = {}
for title, transform_algorithm, kwargs in transform_algorithms:
print(title + ‘...‘)
reconstructions[title] = face.copy()
t0 = time()
#通过set_params对第二阶段的参数进行设置
dico.set_params(transform_algorithm=transform_algorithm, **kwargs)
#transform根据set_params对设完参数的模型进行字典表示,表示图片的稀疏表示结果放在code中
code = dico.transform(data)
#code矩阵乘D得到复原后的矩阵patches
patches = np.dot(code, D)
patches += intercept
patches = patches.reshape(len(data), *patch_size)
if transform_algorithm == ‘threshold‘:
patches -= patches.min()
patches /= patches.max()
#通过reconstruct_from_patches_2d函数将patches重新拼接回图片
reconstructions[title][:, width // 2:] = reconstruct_from_patches_2d(
patches, (height, width // 2))
dt = time() - t0
print(‘done in %.2fs.‘ % dt)
show_with_diff(reconstructions[title], face,
title + ‘ (time: %.1fs)‘ % dt)
plt.show()
该程序输出为四中不同转换算法下的降噪效果:
Deep Architecture
受限玻尔兹曼机(RBM)
RBM简介
受限玻尔兹曼机(Restricted Boltzmann Machine,RBM)是一种可用随机神经网络(stochastic neural network)来解释的概率图模型(probabilistic graphical model)。RBM是Smolensky于1986年在波尔兹曼机(Boltzmann Machine,BM)基础上提出的,所谓“随机”是指网络中的神经元是随机神经元,输出状态只有两种(未激活和激活),状态的具体取值根据概率统计法则来决定。RBM理论是Hinton在2006年提出基于RBM的(Deep Belief Network)模型,大量学者开始研究RBM的理论及其应用。
RBM的网络结构
RBM包含两个层,可见层(visible layer)和隐藏层(hidden layer)。神经元之间的连接具有如下特点:层内无连接,层间全连接,显然RBM对应的图是一个二分图。一般来说,可见层单元用来描述观察数据的一个方面或一个特征,而隐藏层单元的意义一般来说并不明确,可以看作特征提取层。而当隐层单元比可见层单元数少时,我们也可以把这个看作是一个降维的过程。RBM和BM的不同之处在于,BM允许层内神经元之间有连接,而RBM则要求层内神经元之间没有连接,因此RBM的性质:当给定可见层神经元的状态时,各隐藏层神经元的激活条件独立;反之当给定隐藏层神经元的状态是,可见层神经元的激活也条件独立。
如图给出了一个RBM网络结构示意图。其中:分别表示可见层和隐藏层中包含神经元的数目,下标v,h代表visible和hidden;表示可见层的状态向量;表示隐藏层的状态向量;表示可见层的偏置向量;表示隐藏层的偏置向量;表示隐藏层和可见层之间的权值矩阵,表示隐藏层中第i个神经元与可见层中第j个神经元之间的连接权重。记中的参数,可将其视为把W,a,b中的所有分量拼接起来得到的长向量。
能量函数和概率分布
RBM模型是基于能量的模型,需要为其定义一个能量函数,并利用能量函数引入一系列相关的概率分布函数。对于一组给定的状态,可定义能量函数:
,
其矩阵向量形式
利用能量函数给出状态的联合概率分布
其中,称作归一化因子,也称作配分函数(Partition Function)。
对于实际问题,我们最关心的是观测数据v的概率分布,对应于的边缘分布,也称作似然函数(likelihood function):
。
类似地,我们同样可以得到。对于的计算包含项,其计算复杂度非常高,无法直接计算,需要一些数学推导来简化计算量。
对数似然函数
给定训练样本,RBM的训练意味着调整参数,从而拟合给定的训练样本,使得参数条件下对应RBM表示的概率分布尽可能符合训练数据。
假定训练样本集合为,其中为训练样本的数目,,
它们是独立同分布的,则训练RBM的目标就是最大化如下似然:
,
一般通过对数转化为连加的形式,其等价形式:
。
简洁起见,将简记为。
梯度计算
最大化$L_S$常用的数值方法是梯度上升法(Gradient Ascent),通过迭代的方法进行逼近,迭代形式:,其中表示学习速率。其关键就是计算梯度(关于各个参数的偏导数,,)。
一般采用MCMC采样来估计,但由于常规的MCMC需要经过许多步的状态转移才能保证采集到的样本符合目标分布。若我们以训练样本作为起点,就可以仅需要很少次的状态转移抵达RBM的分布。Hinton教授2002年基于这个上想法发明了对比散度(Contrastive Divergence,CD)算法,目前已经成为训练RBM的标准算法。
深度信念网络(DBN)
DBN简介
深度信念网络是一个概率生成模型,与传统的判别模型的神经网络相对,生成模型是建立一个观察数据和标签之间的联合分布,对和 都做了评估,而判别模型仅仅而已评估了后者,也就是。
DBNs由多个限制玻尔兹曼机(RBM)层组成,一个典型的网络结构如下图所示。这些网络被“限制”为一个可视层和一个隐层,层间存在连接,但层内的单元间不存在连接。隐层单元被训练去捕捉在可视层表现出来的高阶数据的相关性。
DBN训练过程
经典的DBN网络结构 是由若干层 RBM 和一层 BP 组成的一种深层神经网络,结构如下图所示。
DBN 在训练模型的过程中主要分为两步:
- 分别训练每一层 RBM 网络,确保特征向量映射到不同特征空间时,都尽可能多地保留特征信息。训练的方法为逐层无监督的方法。下图中有一个简单的例子。首先把数据向量x和第一层隐藏层作为一个RBM, 训练出这个RBM的参数(连接和的权重, 和各个节点的偏置等等), 然后固定这个RBM的参数, 把视作可见向量, 把视作隐藏向量, 训练第二个RBM, 得到其参数, 然后固定这些参数, 训练和构成的RBM。
- 在 DBN 的最后一层设置 BP 网络,接收 RBM 的输出特征向量作为它的输入特征向量,有监督地训练实体关系分类器。而且每一层 RBM 网络只能确保自身层内的 权值对该层特征向量映射达到最优,并不是对整个 DBN 的特征向量映射达到最优,所以反向传播网络还将错误信息自顶向下传播至每一层 RBM,微调整个 DBN 网络.RBM 网络训练模型的过程可以看作对一个深层 BP 网络权值参数的初始化,使DBN 克服了 BP 网络因随机初始化权值参数而容易陷入局部最优和训练时间长的缺点。
上述训练模型中第一步在深度学习的术语叫做预训练,第二步叫做微调。最上面有监督学习的那一层,根据具体的应用领域可以换成任何分类器模型,而不必是BP网络。
自动编码器(AutoEncoder)
AutoEncoder简介
AutoEncoder(自动编码器) 是前馈神经网络(Feedforward Neural Network)的一种,曾经主要用于数据的降维或者特征的抽取,而现在也被扩展用于生成模型中。与其他 Feedforward NN 不同的是,其他 Feedforward NN 关注的是输出层和错误率,而 AutoEncoder 关注的是隐藏层。
AutoEncoder结构
AutoEncoder 是多层神经网络,其中输入层和输出层表示相同的含义,具有相同的节点数。AutoEncode学习的是一个输入输出相同的“恒等函数”。不过输入和输出相同,使得这个网络的输出没有任何意义。
AutoEncoder的意义在于学习的(通常是节点数更少的)中间coder层(最中间的那一层),这一层是输入向量的良好表示。这个过程起到了“降维”的作用。当AutoEncoder只有一个隐含层的时候,其原理相当于主成分分析(PCA),当AutoEncoder有多个隐含层的时候,每两层之间可以用RBM来pre-training,最后由BP来调整最终权值。网络权重更新公式很容易用求偏导数的方法推导出来,算法是梯度下降法。
上图为自动编码器的一般结构图。
从上面的图中,我们能够看到两个部分,第一个部分是编码器(Encoder),第二个部分是解码器(Decoder),编码器和解码器都可以是任意的模型,通常我们使用神经网络模型作为编码器和解码器。输入的数据经过神经网络降维到一个编码(code),接着又通过另外一个神经网络去解码得到一个与输入原数据一模一样的生成数据,然后通过去比较这两个数据,最小化他们之间的差异来训练这个网络中编码器和解码器的参数。当这个过程训练完之后,我们可以拿出这个解码器,随机传入一个编码(code),希望通过解码器能够生成一个和原数据差不多的数据,又或者可以拿出里面的编码器,传入一个新的数据,希望通过编码器将数据压缩。
参考资料
- https://zhuanlan.zhihu.com/p/26015351
- http://www.cnblogs.com/hdu-zsk/p/5954658.html
- https://blog.csdn.net/u010367506/article/details/23453849
- https://blog.csdn.net/bbbeoy/article/details/72910467
- http://blog.sina.com.cn/s/blog_837f83580102v7bm.html
- https://blog.csdn.net/Alicehzj/article/details/78713914
- 机器学习-周志华
- https://www.jianshu.com/p/ab697790090f
- https://www.cnblogs.com/weihuchao/p/6874683.html
- https://blog.csdn.net/mmc2015/article/details/42459753
- https://blog.csdn.net/xiaozhouchou/article/details/51866685
- https://blog.csdn.net/tCDPYh6sA3/article/details/61191617
- https://blog.csdn.net/shenziheng1/article/details/53547401
- https://blog.csdn.net/shenziheng1/article/details/53637907
- https://blog.csdn.net/m0_37407756/article/details/68059453
- https://blog.csdn.net/maxiemei/article/details/23846871
- http://www.mamicode.com/info-detail-1568956.html
- https://blog.csdn.net/baidu_38060633/article/details/70338345
- https://blog.csdn.net/cht5600/article/details/52355566
- https://zhuanlan.zhihu.com/p/27792859
- Efficient_and_Robust_Automated_Machine_Learning