#主要名词解释
名词一览
张量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来减少每层的样本数,进一步减少参数数量,同时还可以提升模型的鲁棒性。
对于图像识别任务来说,卷积神经网络通过尽可能保留重要的参数,去掉大量不重要的参数,来达到更好的学习效果。