神经网络反向传播

一、神经网络应用于分类问题:假设训练样本有m个,每个包含一组输入x和一组输出y,L表示神经网络层数, S l S_{l} Sl表示l层的神经元个数。神经网络分类问题有两种情况:
(1)二元分类:y只能是0或1,有且仅有一个输出单元。
(2)多类别分类:有K个不同的类,有K个输出单元,假设输出K维向量, y i = 1 y_{i}=1 yi=1表示分到第i类。
多类别分类正则化后的代价函数为: 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 ) ] + λ 2 m ∑ l = 1 L − 1 ∑ i = 1 S l ∑ j = 1 S l + 1 ( Θ j i ( l ) ) 2 J\left ( \theta \right )=-\frac{1}{m}[\sum_{i=1}^{m}\sum_{k=1}^{k}y_{k}^{\left ( i \right )}log\left ( h_{\theta }\left ( x^{\left ( i \right )} \right ) \right )_{k}+\left ( 1-y_{k}^{\left ( i \right )} \right )log\left ( 1-\left ( h_{\theta } \left ( x^{\left ( i \right )} \right )\right ) _{k}\right )]+\frac{\lambda }{2m}\sum_{l=1}^{L-1}\sum_{i=1}^{S_{l}}\sum_{j=1}^{S_{l+1}}\left ( \Theta_{ji}^{\left ( l \right )}\right )^{2} J(θ)=m1[i=1mk=1kyk(i)log(hθ(x(i)))k+(1yk(i))log(1(hθ(x(i)))k)]+2mλl=1L1i=1Slj=1Sl+1(Θji(l))2
其中 h θ ( x ) ∈ R K h_{\theta }\left ( x \right )\in R^{K} hθ(x)RK ( h θ ( x ) ) i = i t h o u t p u t \left ( h_{\theta }\left ( x \right )\right )_{i}=i^{th}output (hθ(x))i=ithoutput
正则化的那一项是排除了每一层 θ 0 \theta _{0} θ0后,每一层的θ矩阵的和。最里层的j循环循环所有的行(由 S l + 1 S_{l+1} Sl+1层的激活单元数决定),循环i则循环所有的列(由该层即 S l S_{l} Sl层的激活单元数决定)。
执行梯度下降法或高级优化算法时,需要为变量θ选取一些初始值,对逻辑回归来说可以初始化所有参数为0,但是神经网络不可行,会导致第二层的所有激活单元都是相同的值。可以使用随机初始化的思想,将权重随机初始化为一个接近0,范围在-ε到ε之间的数。
二、反向传播算法
采用该算法计算代价函数的偏导数 ∂ ∂ θ i j ( l ) J ( θ ) \frac{\partial }{\partial \theta _{ij}^{\left ( l \right )}}J\left ( \theta \right ) θij(l)J(θ),首先计算最后一层的误差,再一层一层反向求出各层的误差,直到倒数第二层。假设神经网络是如下的四层结构:
在这里插入图片描述
从最后一层的误差开始计算,用 δ j ( l ) \delta _{j}^{\left ( l \right )} δj(l)表示第l层的第j个激活单元的预测误差,则 δ ( 4 ) = a ( 4 ) − y \delta^{\left ( 4 \right )}=a^{\left ( 4 \right )}-y δ(4)=a(4)y,利用这个误差值来计算前一层的误差: δ ( 3 ) = ( θ ( 3 ) ) T δ ( 4 ) ∗ g ′ ( z ( 3 ) ) \delta^{\left ( 3 \right )}=\left ( \theta ^{\left ( 3 \right )} \right )^{T}\delta^{\left ( 4 \right )}*g^{'}\left ( z^{\left ( 3 \right )} \right ) δ(3)=(θ(3))Tδ(4)g(z(3))
于是假设λ=0,即不做任何正则化处理时: ∂ ∂ θ i j ( l ) J ( θ ) = a j ( l ) δ i l + 1 \frac{\partial }{\partial \theta _{ij}^{\left ( l \right )}}J\left ( \theta \right )=a_{j}^{\left ( l \right )}\delta _{i}^{l+1} θij(l)J(θ)=aj(l)δil+1
其中l表示目前计算的是第几层,j表示目前计算层中的激活单元的下标,i表示下一层中误差单元的下标。
当训练集是一个特征矩阵,误差单元也是一个矩阵,用 Δ i j ( l ) \Delta _{ij}^{\left ( l \right )} Δij(l)来表示这个误差矩阵,即第l层的第i个激活单元受到第j个参数影响而导致的误差。算法表示为:
在这里插入图片描述
三、梯度检验
对较为复杂的模型如神经网络使用梯度下降算法时,可能会存在一些不易察觉的错误,虽然代价看上去在不断减小,但最终的结果可能并不是最优解。因此采取一种叫做梯度的数值检验(Numerical Gradient Checking)方法,通过估计梯度值来检验计算的导数值是否正确。
方法是在代价函数上沿着切线的方向,选择两个非常近的点然后计算两个点的平均值用以估计梯度。即对于某个特定的θ,计算出在θ-ɛ处和θ+ɛ的代价值(ɛ是一个非常小的值,通常选取0.001),然后求两个代价的平均,用以估计在θ处的代价值。
在这里插入图片描述
四、神经网络的步骤
第一件事是选择网络结构,即决定选择多少层以及每层分别有多少个单元。其中第一层的单元数是训练集的特征数量,最后一层的单元数是结果的类的数量。隐藏层数大于1时,最好每个隐藏层的单元个数相同。通常情况下隐藏层的单元个数越多越好,每个隐藏层的单元数量还应该和特征数目匹配,可以和输入特征的数量相同或者是它的二倍或者三四倍。训练神经网络具体步骤如下:
1、参数的随机初始化
2、利用正向传播方法计算所有的hθ(x)
3、编写计算代价函数J的代码
4、利用反向传播方法计算所有偏导数
5、利用数值检验方法检验这些偏导数
6、使用优化算法来最小化代价函数
五、以吴恩达机器学习课程练习材料实现,训练神经网络进行多类别分类,背景是识别手写数字。在之前的文章中是直接利用训练好的参数构建神经网络进行分类,而现在任务包括利用反向传播算法来学习神经网络的参数。从零开始编写相关函数构建神经网络,代码实现可以参考:吴恩达机器学习作业Python实现(四):神经网络(反向传播)。不过尝试实现之后因为条件限制,在本人电脑上运行不起来,加上运行速度太慢了,就尝试使用pytorch框架实现。代码实现来源参考:Neural Networks Learning
原始训练数据集以matlab的数据存储格式.mat保存,数据中有5000个训练样本,其中每个训练样本是一个20像素×20像素灰度图像的数字,每个像素由一个浮点数表示,该浮点数表示该位置的灰度强度。每个20×20像素的网格被展开成一个400维的向量,得到一个5000×400矩阵X,每一行作为一个训练样本。训练集的第二部分是表示训练集标签的5000维向量y,“0”标记为“10”,而“1”到“9”按自然顺序标记为“1”到“9”。
相关函数实现代码如下:

import torch
import torch.nn as nn
import torch.utils.data as Data
from scipy.io import loadmat
import matplotlib.pyplot as plt

#定义神经网络模型
class NeuralNetwork(nn.Module): #继承nn.Module类
  def __init__(self):
    super(NeuralNetwork,self).__init__()  #调用父类Module的构造函数
    self.linear1=nn.Linear(400,25)  #设置隐藏层,定义输入输出维度
    self.sigmoid=nn.Sigmoid() #引入Sigmoid函数
    self.linear2=nn.Linear(25,10) #设置输出层

  def forward(self,X):  #前向传播函数
    X=self.linear1(X)
    X=self.sigmoid(X) 
    X=self.linear2(X) 
    out=self.sigmoid(X) 
    return out

#使用均匀分布初始化参数
def InitParameters(net,epsilon_init):
  for m in net.modules():
    if isinstance(m,nn.Linear):
      weight_shape=m.weight.data.shape
      bias_shape=m.bias.data.shape
      m.weight.data=torch.rand(weight_shape)*2*epsilon_init-epsilon_init  #随机初始化参数范围为[-epsilon_init,epsilon_init]
      m.bias.data=torch.rand(bias_shape)*2*epsilon_init-epsilon_init

#正则化
def add_regular_item(net,m,lm):
  for module in net.modules():
    if isinstance(module,nn.Linear):
      module.weight.grad.data.add_(lm/m*module.weight.data) #添加梯度正则化项λθ/m

#训练模型
def train(net,num_epochs,train_iter,loss,optim,lm,print_frequence):
  for epoch in range(num_epochs):
    n_batch,sum_loss,m=0,0.0,0
    for X,y in train_iter:
      optim.zero_grad() #把梯度置零,也就是把loss关于weight的导数变成0
      m=y.shape[0]
      y_hat=net(X)  #前向传播求出预测的值
      l=loss(y_hat,y.view(-1,y.shape[1])).sum() #计算损失
      l.backward()  #反向传播求梯度
      add_regular_item(net,m,lm)  #正则化
      optim.step()  #更新所有参数
      sum_loss+=l
      n_batch+=1
    if print_frequence>0:
      if (epoch+1)%print_frequence==0:
        print("epoch:%d,average loss:%f"%(epoch+1,sum_loss/n_batch))
        print("epoch:%d,train accuracy:%0.2f%%\n"%(epoch+1,evaluate(net,train_iter)))

#计算准确率
def evaluate(net,test_iter):
  sum_accurate,num_data=0.0,0
  for X,y in test_iter:
    y=y.view(-1,y.shape[1]) #获得实际值
    num_data+=y.shape[0] #总的样本数量
    y_hat=net(X)  #获得预测值
    sum_accurate+=(y_hat.argmax(1)==y.argmax(1)).sum()  #获得预测正确的数量
  return 100*sum_accurate.float()/num_data

数据加载及初始化并训练神经网络的代码如下:

train_file_data=loadmat('multi_class_cl.mat') #读取matlab格式的数据集
X=torch.tensor(train_file_data['X'],dtype=torch.float) #获得特征数据并生成相应的torch.FloatTensor
y=torch.tensor(train_file_data['y'],dtype=torch.long)-1  #获得分类数据并生成相应的torch.LongTensor,将类别数据从0开始分类,原本1-9是类别1-9,10是类别0,更改后0-8是类别1-9,9是类别0
y=torch.zeros(y.shape[0],max(y)+1).scatter_(dim=1,index=y,value=1)  #把y中每个类别转化为一个向量,对应的类别在向量对应位置上置为1
#print(X.shape)
#print(y.shape)

#定义必要的变量并训练网络
net=NeuralNetwork() #定义神经网络模型
batch_size,num_epochs,print_frequency,lr,lm=250,1200,50,0.5,0 #batch_size是更新内部模型参数之前要处理的样本数,num_epochs定义了学习算法在整个训练数据集中的工作次数,lm是正则化参数
loss=nn.BCELoss() #定义损失函数
InitParameters(net,0.12)  #初始化参数
optim=torch.optim.SGD(net.parameters(),lr)  #采用SGD优化方法构造优化器,lr是学习率
train_dataset=Data.TensorDataset(X,y) #加载数据
train_iter=Data.DataLoader(train_dataset,batch_size=batch_size,shuffle=True)  #数据加载器,把训练数据分成多个小组,每次抽出batch_size个样本,shuffle在每个epoch开始的时候,对数据进行重新打乱
train(net,num_epochs,train_iter,loss,optim,lm,print_frequency)  #训练模型

当batch_size=250,num_epochs=1200,lm=0时,在所有初始化参数不变的情况下,运行两次模型代码,发现准确率分别为96.58%、96.16%,可以看到参数的随机初始化是会影响结果的,但影响不大。
使用正则化,令lm=1,此时准确率为66.50%,可以看到正则化对模型预测效果影响挺大的。
lm=1时,控制batch_size=250,增加学习算法在整个训练数据集中的工作次数,令num_epochs=1500,此时准确率为67.50%;控制num_epochs=1200,增加更新内部模型参数之前要处理的样本数,令batch_size=300,此时准确率为74.26%。相比之下,batch_size对模型的影响似乎更大。
可视化隐藏层的代码如下:

#可视化隐藏层
def plot_hidden(weight):
  fig,ax_array = plt.subplots(5, 5, sharex=True, sharey=True, figsize=(6,6))  #把父图分成5*5个子图,设置图的大小,True设置x和y轴属性在所有子图中共享
  for r in range(5):
    for c in range(5):
      ax_array[r, c].matshow(weight[r * 5 + c].reshape(20, 20), cmap='gray_r')  #绘制数字图像,cmap设置绘制风格为白底黑字
      plt.xticks([])  #去除刻度,保证美观
      plt.yticks([])
  plt.show()

params = list(net.named_parameters()) #获得神经网络参数
plot_hidden(params[0][1].data)  #获得隐藏层参数并可视化

在这里插入图片描述

(结语个人日记:不知不觉是六月了噢,依旧无法返校,没有办法见到想见的人。不过开心的是这周导师终于确定下来了,而且是做选择之后一开始就想要的导师。上学期末发了一封邮件但是没有实质进展,疫情期间纠结着开学再联系老师,最后还是在某个冲动的一天整理了一天的简历,头铁给老师发过去了,至少再刷点存在感同时也让老师了解一下自己吖。没想到老师竟然同意了哈哈哈,意料之外惊喜之中。可能有时候真得不能纠结想太多,付出行动才能有机会推动事情的进展(手动捂脸))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值