0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

优化神经网络

Last updated at Posted at 2020-05-22

#主要名词解释

名词一览

张量Tensor

后向传播
前向传播

均方误差函数
梯度下降法
链式求导
自动求导->求导数的物理意义??

线性变换、
激活函数、
线性激活函数、
非线性激活函数

卷积层、
全连接层、
池化层

过拟合

后向传播中的链式求导

批量 即Batch,是深度学习中的一个重要概念

在线学习和离线学习
递增算法
偏移/阈值 Bias
对于线性输出神经元,偏移项就是回归中的截距项

标准化数据:
重放缩(Rescaling)
规范化(Normalization)
标准化(Standardization)

torch.rand(x,y)
随即生成浮点数据在0-1区间的均匀分布

torch.randn(x,y)
随机生成的浮点数的取值满足均值为0,方差为1的正态分布

#初始化训练变量


import torch

#batch_n是在一个批次中输入数据的数量,值是100,这意味着我们在一个批次中输入100个数据 
batch_n = 100

#同时,每个数据包含的数据特征有input_data个,
#因为input_data的值是1000,所以每个数据的特征就是1000个,
#也就是一次输入是一个100行1000列的矩阵数组[100,1000]
input_data = 1000

#hidden_layer用于定义经过隐藏层后保留的数据特征的个数,这里有100个,
#因为我们的模型只考虑一层隐藏层,所以在代码中仅仅定义了一个隐藏层的参数;
#经过隐藏层过滤后,输出数据为[100,100]
hidden_layer = 100

#output_data是输出的数据,值是10,我们可以将输出的数据看作一个分类结果值得数量,
#个数10表示我们最后要得到10个分类结果值。
#最终的输出数据为[100,10]
output_data = 10

#一次模型的训练
#在得到输出结果之后计算损失并进行后向传播,
#这样一次模型的训练就完成了,
#然后训练这个流程就可以完成指定次数的训练,并达到优化模型参数的目的。
[100,1000] -> [100,100] -> [100,10]

#初始化权重


#[100,1000]输入层纬度
x = torch.randn(batch_n,input_data)

#[100,10]输出层纬度
y = torch.randn(batch_n,output_data)

#随即权重参数初始化(尽管这并不是一个好主意)
#[1000,100]输入层到隐藏层对应的权重参数
w1 = torch.randn(input_data,hidden_layer)
#[100,10]隐藏层到输出层对应的权重参数
w2 = torch.randn(hidden_layer,output_data)

#这里我们可能会好奇权重参数的维度是如何定义下来的,
#其实,只要我们把整个过程看作矩阵连续的乘法运算,就自然能够明白了,
x * w1 * w2 = y
[100,1000] * [1000,100] * [100,10] = [100,10]
#在代码中我们的真实值y也是通过随机的方式生成的,
#所以一开始在使用损失函数计算损失值时得到的结果会较大。

#定义训练次数和学习效率


#epoch_n的值为20,所以我们需要通过循环的方式让程序进行20次训练
epoch_n = 20

#在优化的过程中使用的学习效率learning_rate的值为1e-6,表示0.000001
learning_rate = 1e-6

#使用梯度下降的方法来优化神经网络的参数
for epoch in range(epoch_n):

    #前向传播得到的预测结果通过 y_pred来表示
    #在这个神经网络的前向传播中,通过两个连续的矩阵乘法计算出预测结果,
    #在计算的过程中还对矩阵乘积的结果使用clamp方法进行裁剪,
    #将小于零的值全部重新赋值于0,这就像加上了一个ReLU激活函数的功能。
    h1 = x.mm(w1)  # 100*1000
    h1 = h1.clamp(min=0)
    y_pred = h1.mm(w2)  # 100*10
    # print(y_pred)

    #简化写法
    #y_pred = x.mm(w1).clamp(min=0).mm(w2)
 
    #在得到了预测值后就可以使用预测值和真实值来计算误差值了。
    #我们用loss来表示误差值,对误差值的计算使用了均方误差函数。
    loss = (y_pred - y).pow(2).sum()
    print("Epoch:{} , Loss:{:.4f}".format(epoch, loss))
 
    #我们的代码实现使用的是每个节点的链式求导结果,
    #在通过计算之后,就能够得到每个权重参数对应的梯度分别是grad_w1和grad_w2
    gray_y_pred = 2 * (y_pred - y)
    gray_w2 = h1.t().mm(gray_y_pred)
 
    grad_h = gray_y_pred.clone()
    grad_h = grad_h.mm(w2.t())
    grad_h.clamp_(min=0)
    grad_w1 = x.t().mm(grad_h)
 
    #在得到参数的梯度值之后,按照之前定义好的学习速率对w1和w2的权重参数进行更新
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * gray_w2

自定义一个模型model


class Model(torch.nn.Module):
    def __init__(self):
        super(Model,self).__init__()
     
    def forward(self,input,w1,w2):
        x = torch.mm(input,w1)
        x = torch.clamp(x,min=0)
        x = torch.mm(x,w2)
        return x
     
    def backward(self):
        pass

model = Model()
for epoch in range(epoch_n):
 
    # y_pred = x.mm(w1).clamp(min= 0 ).mm(w2)
    y_pred = model(x, w1, w2)
    loss = (y_pred - y).pow(2).sum()
    print("Epoch:{} , Loss:{:.4f}".format(epoch, loss.data[0]))
 
    #自动梯度
    loss.backward()
    w1.data -= learning_rate * w1.grad.data
    w2.data -= learning_rate * w2.grad.data
 
    w1.grad.data.zero_()
    w2.grad.data.zero_()

#定义神经网络模型


#使用torch.nn中的线性层加激活函数配合torch.optim完成神经网络模型的搭建和模型参数的优化
#使用cnn类来自动生成和初始化对应维度的权重参数 w1,w2就可以不用随机生成了

models = torch.nn.Sequential(OrderedDict([
    ("Linel",torch.nn.Linear(input_data,hidden_layer)),
    ("ReLU1",torch.nn.ReLU()),
    ("Line2",torch.nn.Linear(hidden_layer,output_data))
])
)
#torch.nn包中已经定义好的均方误差函数类torch.nn.MSELoss来计算损失值
loss_fn = torch.nn.MSELoss()

#训练代码
for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred,y)
    if epoch%1000 == 0:
        print("Epoch:{},Loss:{:.4f}".format(epoch,loss.data[0]))
    models.zero_grad()
 
    loss.backward()
 
    for param in models.parameters():
        param.data -= param.grad.data*learning_rate

#优化模型


#在PyTorch的torch.optim包中提供了非常多的可实现参数自动优化的类,
#比如SGD、AdaGrad、RMSProp、Adam等,这些类都可以被直接调用,使用起来也非常方便。
optimzer = torch.optim.Adam(models.parameters(),lr = learning_rate)

#进行模型训练
for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred,y)
    print("Epoch:{}, Loss:{:.4f}".format(epoch, loss.data[0]))
    optimzer.zero_grad()
 
    loss.backward()
 
    #进行梯度更新
    optimzer.step()

#卷积神经网络模型搭建的代码


#模型搭建和参数优化
#在顺利完成数据装载后,我们可以开始编写卷积神经网络模型的搭建和参数优化的代码
#卷积层使用torch.nn.Conv2d类来搭建
#激活层使用torch.nn.ReLU 类方法来搭建
#池化层使用torch.nn.MaxPool2d类方法来搭建
#全连接层使用 torch.nn.Linear 类方法来搭建

#在结构上使用了两个卷积层 Conv2d
#一个最大池化层 MaxPool2d
#两个全连接层 Linear
class Model(torch.nn.Module):
    def __init__(self):
        super(Model,self).__init__()
        self.conv1 = torch.nn.Sequential(
            torch.nn.Conv2d(1,64,kernel_size=3,stride=1,padding=1),
            torch.nn.ReLU(),
            torch.nn.Conv2d(64,128,kernel_size=3,stride=1,padding=1),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(stride=2,kernel_size=2))
 
        self.dense = torch.nn.Sequential(# 我们通过继承torch.nn.Modeule来构造网络,因为手写数字
    # 识别比较简单,我们只是用了两个卷积层,一个最大池化层,两个全连接层。
    # 在向前传播过程中进行x.view(-1, 14 * 14 * 128)
    # 对参数实现扁平化。最后通过自己self.dense定义的全连接层进行最后的分类
            torch.nn.Linear(14*14*128,1024),
            torch.nn.ReLU(),
            torch.nn.Dropout(p = 0.5),
            torch.nn.Linear(1024,10)
        )
 
    # 我们通过继承torch.nn.Modeule来构造网络,因为手写数字
    # 识别比较简单,我们只是用了两个卷积层,一个最大池化层,两个全连接层。
    # 在向前传播过程中进行x.view(-1, 14 * 14 * 128)
    # 对参数实现扁平化。最后通过自己self.dense定义的全连接层进行最后的分类
    def forward(self, x):
        #进行卷积处理
        x = self.conv1(x)
        #对参数实现扁平化
        #如果不进行扁平化,则全连接层的实际输出的参数维度和其定义输入的维度将不匹配,程序将会报错
        x = x.view(-1,14*14*128)
        #全连接层进行最后的分类
        x = self.dense(x)
        return x

全连接网络 VS 卷积网络

全连接神经网络之所以不太适合图像识别任务,主要有以下几个方面的问题:

  • 参数数量太多 考虑一个输入10001000像素的图片(一百万像素,现在已经不能算大图了),输入层有10001000=100万节点。假设第一个隐藏层有100个节点(这个数量并不多),那么仅这一层就有(1000*1000+1)*100=1亿参数,这实在是太多了!我们看到图像只扩大一点,参数数量就会多很多,因此它的扩展性很差。
  • 没有利用像素之间的位置信息 对于图像识别任务来说,每个像素和其周围像素的联系是比较紧密的,和离得很远的像素的联系可能就很小了。如果一个神经元和上一层所有神经元相连,那么就相当于对于一个像素来说,把图像的所有像素都等同看待,这不符合前面的假设。当我们完成每个连接权重的学习之后,最终可能会发现,有大量的权重,它们的值都是很小的(也就是这些连接其实无关紧要)。努力学习大量并不重要的权重,这样的学习必将是非常低效的。
  • 网络层数限制 我们知道网络层数越多其表达能力越强,但是通过梯度下降方法训练深度全连接神经网络很困难,因为全连接神经网络的梯度很难传递超过3层。因此,我们不可能得到一个很深的全连接神经网络,也就限制了它的能力。

##那么,卷积神经网络又是怎样解决这个问题的呢?主要有三个思路:

  • 局部连接 这个是最容易想到的,每个神经元不再和上一层的所有神经元相连,而只和一小部分神经元相连。这样就减少了很多参数。
  • 权值共享 一组连接可以共享同一个权重,而不是每个连接有一个不同的权重,这样又减少了很多参数。
  • 下采样 可以使用Pooling来减少每层的样本数,进一步减少参数数量,同时还可以提升模型的鲁棒性。
    对于图像识别任务来说,卷积神经网络通过尽可能保留重要的参数,去掉大量不重要的参数,来达到更好的学习效果。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?