神经网络主要是处理分类问题,比如垃圾邮件识别:现在有一封电子邮件,把其中的所有词汇提取出来,放到机器里,机器判断这封邮件是否垃圾邮件。这种能自动对输入的东西进行分类的机器,就叫做分类器(classifier)。
分类器的输入是一个数值向量,叫做特征向量。比如在垃圾邮件识别例子中,用0,1分别代表字典中的单词在邮件中是否出现,这样把邮件转化为以0和1组成的向量。分类器的输出是一个数值,比如判断为垃圾邮件输出1、整除邮件输出0。
分类器的目标就是让正确分类的比例尽可能高。在垃圾邮件识别的例子中,我们需要先收集一批邮件,并人工标注每一封邮件是否垃圾邮件,根据分类器的判断结果与人工标注结果之间的差异调整分类器参数,训练好的分类器就可以判断新邮件是否垃圾邮件了。
如下图,假设现在有两类数据分别代表猫、狗,区分这两类数据最简单的方法就是在中间画一条直线把数据分成两类,新的向量只要在直线下方的就是猫,直线上方的是狗,神经元就是把特征空间一切两半,认为两半分别属两个类。
在上一篇文章里已经讲过,这个就是逻辑回归, hθ(x(i))=g(θTx(i)),g(z)=11+e−z h θ ( x ( i ) ) = g ( θ T x ( i ) ) , g ( z ) = 1 1 + e − z ,一个神经元其实就是逻辑回归分类器,如下图。
神经元(逻辑回归)虽然简单好用,但是有个缺点,就是只能切一刀,如果遇到复杂的情况就处理不了了。如下图就无法用一条直线把两类分开。
这时候你会想,如果能过多切几刀就好了,没错!这种复杂情况的分类方法就是通过多层神经网络,每切一刀其实就是使用一个神经元,只要你切足够多刀,理论上你可以表示很复杂的函数分布。下面就是神经网络的表示图,每一个神经元的输出作为下一层神经元的输入。
由于篇幅所限,此处不展开介绍反向传播算法及其数学推导、正则化、梯度检测,后续将单独写一篇文章来介绍。
我们继续选择手写数字识别问题作为例子建立神经网络,并检验其预测的准确率。每一条数据是长度为400的向量。
因为输入的是长度400的向量,输出是0-9的数字分类结果,因此输入层神经元数量是400,输出层神经元数量是10,隐藏层神经元的数量我们选择40个,Hidden Layer与Input Layer之间的联系定义为权重。Θ1(包含了bias unit),Output Layer与Hidden Layer定义为Θ2(包含了bias unit),则Θ1是形状为(25,401)的矩阵,Θ2是形状为(10,62)的矩阵。在训练之前,我们给Θ1,Θ2分别初始化一个很小的初始值。
#神经网络框架为400*25*10
input_layer_size = 400
hidden_layer_size = 25
output_layer_size = 10
n_training_samples = X_train.shape[0]
#随机初始化Thetas
def genRandThetas():
epsilon_init = 0.12
theta1_shape = (hidden_layer_size, input_layer_size+1)
theta2_shape = (output_layer_size, hidden_layer_size+1)
rand_thetas = [np.random.rand( *theta1_shape ) * 2 * epsilon_init - epsilon_init,
np.random.rand( *theta2_shape ) * 2 * epsilon_init - epsilon_init]
return rand_thetas
第一步我们已经建立了如下的三层神经网络如下图:
如上图, θ(l)ij θ i j ( l ) 代表第l层第i个神经元指向第l+1层第j个神经元的权重,x是特征值, a(l) a ( l ) 是第l层的输出。下面我们来从第一层开始计算神经元的输出值(忽略bias unit),知道计算出h(x)。
第一层各神经元的输出就是各特征值,即:
a(1)=x a ( 1 ) = x
第二层各个神经元的输出如下,p为第一层神经元的个数400,q为第二层神经元的个数40:
a(2)1=g(θ(1)11a(1)1+θ(1)21a(1)2+...+θ(1)p1a(1)p),p=400 a 1 ( 2 ) = g ( θ 11 ( 1 ) a 1 ( 1 ) + θ 21 ( 1 ) a 2 ( 1 ) + . . . + θ p 1 ( 1 ) a p ( 1 ) ) , p = 400
a(2)2=g(θ(1)12a(1)1+θ(1)22a(1)2+...+θ(1)p2a(1)p),p=400 a 2 ( 2 ) = g ( θ 12 ( 1 ) a 1 ( 1 ) + θ 22 ( 1 ) a 2 ( 1 ) + . . . + θ p 2 ( 1 ) a p ( 1 ) ) , p = 400
…
a(2)q=g(θ(1)1qa(1)1+θ(1)2qa(1)2+...+θ(1)pqa(1)p),p=400,q=40 a q ( 2 ) = g ( θ 1 q ( 1 ) a 1 ( 1 ) + θ 2 q ( 1 ) a 2 ( 1 ) + . . . + θ p q ( 1 ) a p ( 1 ) ) , p = 400 , q = 40
写成向量化形式为: a(2)=g(Θ(1).T⋅a(1)) a ( 2 ) = g ( Θ ( 1 ) . T · a ( 1 ) )
同理,第三层各神经元的输出向量化为:
a(3)=g(Θ(2).T⋅a(2)) a ( 3 ) = g ( Θ ( 2 ) . T · a ( 2 ) )
def propagateForward(row,Thetas):
feature = row
zs_as_per_layer = [] #储存各层神经网络的z值和a值
for i in range(len(Thetas)):
theta = Thetas[i]
z = np.dot(feature,theta.T).reshape(theta.shape[0],1)
a = expit(z)
zs_as_per_layer.append((z, a))
if i == len(Thetas)-1:
return np.array(zs_as_per_layer)
a = np.insert(a,0,1) #Add bias unit
feature = a.T
损失函数Cost function是预测值与真实值之间的偏差的总和,我们回忆一下逻辑回归中的损失函数: J(θ)=1m∑mi=1[−y(i)log(hθ(x(i)))−(1−y(i))log(1−hθ(x(i)))] J ( θ ) = 1 m ∑ i = 1 m [ − y ( i ) l o g ( h θ ( x ( i ) ) ) − ( 1 − y ( i ) ) l o g ( 1 − h θ ( x ( i ) ) ) ]
神经网络的损失函数也是类似的,神经网络的输出层有K个神经元(逻辑回归),因此损失函数为:
J(θ)=1m∑mi=1∑Kk=1[−y(i)klog(hθ(x(i)))k−(1−y(i)k)log(1−hθ(x(i)))k] J ( θ ) = 1 m ∑ i = 1 m ∑ k = 1 K [ − y k ( i ) l o g ( h θ ( x ( i ) ) ) k − ( 1 − y k ( i ) ) l o g ( 1 − h θ ( x ( i ) ) ) k ]
def computeCost(myThetas,myX,myy):
myThetas = reshapeParams(myThetas)
myX = reshapeX(myX)
m = n_training_samples
total_cost = 0.
for i in range(m):
myrow = myX[i]
myhs = propagateForward(myrow,myThetas)[-1][1]
tmpy = np.zeros((10,1))
tmpy[myy[i]-1] = 1
mycost = -tmpy.T.dot(np.log(myhs))-(1-tmpy.T).dot(np.log(1-myhs))
total_cost += mycost
total_cost = float(total_cost)/m
return total_cost
使用梯度下降或者其他高级最优化算法,总是要求出C关于θ的导数,即 ∂C∂θ ∂ C ∂ θ 。使用前向传播求导计算量非常大,而反向传播算法求梯度可以大大减少计算的次数。
由于反向传播算法比较复杂,此处就直接使用了,原理及数学推导后续会单独写一篇,有兴趣的同学可以期待一下哦
首先记损失函数C关于l层的第j个元素的偏导为: δlj=∂C∂zlj δ j l = ∂ C ∂ z j l
δL=∇aC⨀σ′(zL) δ L = ∇ a C ⨀ σ ′ ( z L )
δl=((wl+1)Tδl+1)⨀σ′(zl) δ l = ( ( w l + 1 ) T δ l + 1 ) ⨀ σ ′ ( z l )
∂C∂blj=δlj ∂ C ∂ b j l = δ j l
∂C∂wjkl=al−1kδlj ∂ C ∂ w j k l = a k l − 1 δ j l
根据反向传播四大公式,即可求出梯度
def backPropagate(myThetas,myX,myy):
myThetas = reshapeParams(myThetas)
m = n_training_samples
myX = reshapeX(myX)
Delta1 = np.zeros((hidden_layer_size,input_layer_size+1))
Delta2 = np.zeros((output_layer_size,hidden_layer_size+1))
for i in range(m):
myrow = myX[i]
a1 = myrow.reshape((input_layer_size+1,1))
temp = propagateForward(myrow,myThetas)
z2 = temp[0][0]
a2 = temp[0][1]
z3 = temp[1][0]
a3 = temp[1][1]
tmpy = np.zeros((10,1))
tmpy[myy[i]-1] = 1
delta3 = a3 - tmpy
delta2 = myThetas[1].T[1:,:].dot(delta3)*sigmoidGradient(z2)
a2 = np.insert(a2,0,1,axis=0)
Delta1 += delta2.dot(a1.T)
Delta2 += delta3.dot(a2.T)
D1 = Delta1/float(m)
D2 = Delta2/float(m)
return flattenParams([D1, D2]).flatten()
根据损失函数以及梯度,使用最优化算法如梯度下降等,即可求解Θ,得到预测函数h(x)。
def trainNN():
randomThetas_unrolled = flattenParams(genRandThetas())
result = scipy.optimize.fmin_cg(computeCost,x0=randomThetas_unrolled,
fprime=backPropagate,args(X_train,y_train),
maxiter=50,disp=True,full_output=True)
return reshapeParams(result[0])
使用测试集得到我们的神经网络预测准确度为93.9%,对比我们上一期的逻辑回归的预测准确率89.1%,有了明显的提高,神经网络的强大可见一斑。