pytorch简介
在前两篇文章中,我们分别介绍了神经网络的背后运行原理(神经网络原理概述-Quiet (jl-sky.github.io)),并手工制作了一个网络模型(MNIST手写数字识别(手工网络模型)-Quiet (jl-sky.github.io)),体会了手动构建神经网络后,构建大型网络的复杂程度可想而知。其中,最烦琐的部分莫过于通过微积分计算反向传播误差(back-propagated error)和网络权重(weight)之间的关系。每当网络结构需要改变时,我们很可能需要重新计算一次。
现在,我们使用PyTorch来取代大部分的底层工作,从而可以专注于网络的设计。
PyTorch最强大且最便利的功能之一是,无论我们设想的网络是什么样子的,它都能替我们进行所有的微积分计算。即使设计改变了,PyTorch也会自动更新微积分计算,无须我们亲自动手计算梯度(gradient)。
PyTorch使用一种独特的变量存储数字,我们称它为PyTorch张量
# 简单PyTorch张量
x = torch.tensor(3.5)
print(x)
在上面的代码中,我们创建了一个变量x,它的类型是PyTorch张量,初始值为3.5。张量类型的数据可以满足日常的加减乘除数据计算,如:
# 简单的张量计算
y = x + 3
print(y)
但它更为强大的功能是,它可以进行自动求导运算。
pytorch的自动求导机制
定义一个张量数据,并通过requires_grad=True告诉PyTorch我们希望得到一个关于x的梯度。
# PyTorch张量
x = torch.tensor(3.5, requires_grad=True)
print(x)
用x再次创建一个张量数据:
# y以x的函数表示
y = (x-1) * (x-2) * (x-3)
print(y)
PyTorch张量记录了自己是由哪个张量计算而得,以及如何计算的。在这里,PyTorch记录了y由x定义。
除此以外,一个PyTorch张量可以包含除原始数值之外的附加信息,比如梯度值。关于它所依赖的其他张量的信息,以及这种依赖的数学表达式。
我们知道,训练神经网络的计算需要使用微积分计算出误差梯度。也就是说,输出误差改变的速率随着网络链接权重的改变而改变。神经网络的输出由链接权重计算得出。输出依赖于权重,就像y依赖于x。
下面,我们来看看PyTorch如何计算y随x变化的速率。
我们计算当x = 3.5时y的梯度,即dy/dx。
要计算y的梯度,PyTorch需要知道它依赖于哪个张量以及依赖关系的数学表达式。之后便能计算出dy/dx。
# 计算梯度
y.backward()
上面的代码完成所有这些步骤。通过观察y,PyTorch发现它来自(x−1) (x−2) * (x−3),并自动算出梯度dy/dx = 3x2 −12x + 11。同时,这行代码也计算出梯度的数值,并与x的实际值一同存储在张量x里。因为x是3.5,所以梯度是3*(3.5*3.5)−12(3.5)+ 11 = 5.75。
此时,张量x里梯度的数值为
# x = 3.5时的梯度
x.grad
接下来,我们再来看一个多元微分的例子,体会pytorch自动求导运算的强大。
我们定义一个多元复合函数
z=2x+3y
y=5a^2^+3b^2^x=2a+3b
其链式法则微分图如下:
接下来我们使用torch定义这个关系式
# 创建包含x、y和z的计算图
a = torch.tensor(2.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)
x = 2*a + 3*b
y = 5*a*a + 3*b*b*b
z = 2*x + 3*y
接着,我们使用torch进行梯度计算并查询张量a的值
# 计算梯度
z.backward()
# 当a = 2.0时的梯度
a.grad
可以看到,对于复杂的关系式,pytorch的求导机制对于简化我们网络模型的工作量之重要!
接下来,我们借助torch重新构建神经网络,并仍旧借助MNIST数据集对网络进行测试。
模型构建
模型定义
self.model=nn.Sequential(
nn.Linear(784,200),#定义一个784个节点到200个节点的全相联映射,该模块包含节点之间的连接权重,训练时可以被更新
nn.Sigmoid(),#定义激活函数(sigmod),作为上一层(200个节点的隐藏层)的网络输出
nn.Linear(200,10),#定义一个200个节点到10个节点的全相联映射
nn.Sigmoid()
)
定义损失函数
# 定义损失函数
self.lossFunction=nn.MSELoss()#使用均方误差作为目标损失函数进行最小化迭代
# self.lossFunction=nn.BCELoss()#二元交叉熵损失
创建优化器更新权重
#创建优化器更新权重,通过self.model.parameters()传递模型参数进行优化更新
self.optimiser=torch.optim.SGD(self.model.parameters(),lr)#随机梯度下降
# self.optimiser=torch.optim.Adam(self.model.parameters())
前向传播
# pytorch通过forward函数向网络传递信息
def forward(self,inputs):
return self.model(inputs)
模型训练
def train(self,inputs,targets):
outputs=self.forward(inputs)#计算网络输出值
loss=self.lossFunction(outputs,targets)#计算损失(误差)
# 梯度归零,反向传播,更新权重
self.optimiser.zero_grad()#每次训练前需要将图计算网络梯度归零,否则loss.backward()计算出的梯度会累积
loss.backward()#从loss函数中计算梯度
self.optimiser.step()#使用梯度更新学习参数
return
损失图显示
为了可视化结果,我们定义一个变量来记录训练情况
# 网络训练进展可视化(损失图记录),记录每次迭代更新的损失情况
self.count = 0
self.congress = []
在训练过程中,记录损失情况
# 记录每次训练的损失情况
self.count += 1
if (self.count % 10 == 0):
# print(self.count,'>>>',loss.item())
# 每10个训练样本就记录一次损失值(误差),item()函数展开单值张量,获取里边数字
self.congress.append(loss.item())
if self.count%10000==0:
print(self.count,'>>>\n',self.congress)
损失图显示
def plotProgerss(self):#绘制损失图
df=pd.DataFrame(self.congress,columns=['loss'])
# y轴范围,图像大小,透明度,标记点,是否有网格,y轴刻度值(步长0.25)
df.plot(ylim=(0,1.0),figsize=(16,8),alpha=0.1,marker='.',grid=True,yticks=(0,0.25,0.5))
plt.show()
return
模型构建完整代码
# -*- codeing=utf-8 -*-
# @Author:姜磊
# 人间烟火气,最抚凡人心
import torch
import torch.nn as nn#设置分类器
import pandas as pd
import matplotlib.pyplot as plt
# 优化方法:激活函数改进,损失函数改进,权重更新方法改进,数据标准化(可提高收敛速度),正则化
# 构建网络分类器
class classiFier(nn.Module):#继承nn.Module类
def __init__(self,lr=0.01):
super().__init__()#继承父类构造函数
# 定义网络模型
self.model=nn.Sequential(
nn.Linear(784,200),#定义一个784个节点到200个节点的全相联映射,该模块包含节点之间的连接权重,训练时可以被更新
# nn.Sigmoid(),#定义激活函数(sigmod),作为上一层(200个节点的隐藏层)的网络输出
nn.LeakyReLU(0.02),#定义激活函数(线性整流函数,0.02为该函数左侧梯度)
nn.LayerNorm(200),#数据标准化
nn.Linear(200,10),#定义一个200个节点到10个节点的全相联映射
# nn.Sigmoid()
nn.LeakyReLU(0.02), # 定义激活函数(线性整流函数,0.02为该函数左侧梯度)
)
# 定义损失函数
self.lossFunction=nn.MSELoss()#使用均方误差作为目标损失函数进行最小化迭代
# self.lossFunction=nn.BCELoss()#二元交叉熵损失
#创建优化器更新权重,通过self.model.parameters()传递模型参数进行优化更新
self.optimiser=torch.optim.SGD(self.model.parameters(),lr)#随机梯度下降
# self.optimiser=torch.optim.Adam(self.model.parameters())
# 网络训练进展可视化(损失图记录),记录每次迭代更新的损失情况
self.count = 0
self.congress = []
# pytorch通过forward函数向网络传递信息
def forward(self,inputs):
return self.model(inputs)
def train(self,inputs,targets):
outputs=self.forward(inputs)#计算网络输出值
loss=self.lossFunction(outputs,targets)#计算损失(误差)
# 梯度归零,反向传播,更新权重
self.optimiser.zero_grad()#每次训练前需要将图计算网络梯度归零,否则loss.backward()计算出的梯度会累积
loss.backward()#从loss函数中计算梯度
self.optimiser.step()#使用梯度更新学习参数
# 记录每次训练的损失情况
self.count += 1
if (self.count % 10 == 0):
# print(self.count,'>>>',loss.item())
# 每10个训练样本就记录一次损失值(误差),item()函数展开单值张量,获取里边数字
self.congress.append(loss.item())
if self.count%10000==0:
print(self.count,'>>>\n',self.congress)
return
def plotProgerss(self):#绘制损失图
df=pd.DataFrame(self.congress,columns=['loss'])
# y轴范围,图像大小,透明度,标记点,是否有网格,y轴刻度值(步长0.25)
df.plot(ylim=(0,1.0),figsize=(16,8),alpha=0.1,marker='.',grid=True,yticks=(0,0.25,0.5))
plt.show()
return
MNIST数据集制作
导入数据集
def __init__(self,filePath):
self.dataDf=pd.read_csv(filePath,header=None)
return
返回数据集记录总数
# 重写__len__方法,当len函数作用于实例后的类对象时被调用
def __len__(self):#获取文件长度
return len(self.dataDf)
获取数据集某个记录
# 重写__getitem__函数,可通过索引值返回实例化对象的数据,即将实例化对象作为可迭代数据结构进行使用
def __getitem__(self, index):#获取某一行数据集信息(label、手写数字像素值、目标向量)
label=self.dataDf.iloc[index,0]#手势数字实值
# 定义训练数据张量
imgValue=torch.FloatTensor(self.dataDf.iloc[index,1:].values)/255.0
# 定义目标张量
target=torch.zeros((10))#创建一个10维的张量
target[label]=1.0
return label,imgValue,target
注:使用torch时,要将数据全部转换为张量变量,本例中,训练数据以及目标数据全部转换为张量
定义一个绘图函数
def plotImg(self,index):#绘图
imgData=self.dataDf.iloc[index,1:].values.reshape(28,28)
plt.imshow(imgData,cmap='Blues',interpolation=None)
pylab.show()
return
数据集制作完整代码
class MnistDataset():
def __init__(self,filePath):
self.dataDf=pd.read_csv(filePath,header=None)
return
# 重写魔法方法,可将实例化后的对象作为一种特殊的数据结构进行使用
# 重写__len__方法,当len函数作用于实例后的类对象时被调用
def __len__(self):#获取文件长度
return len(self.dataDf)
# 重写__getitem__函数,可通过索引值返回实例化对象的数据,即将实例化对象作为可迭代数据结构进行使用
def __getitem__(self, index):#获取某一行数据集信息(label、手写数字像素值、目标向量)
label=self.dataDf.iloc[index,0]#手势数字实值
# 定义训练数据张量
imgValue=torch.FloatTensor(self.dataDf.iloc[index,1:].values)/255.0
# 定义目标张量
target=torch.zeros((10))#创建一个10维的张量
target[label]=1.0
return label,imgValue,target
def plotImg(self,index):#绘图
imgData=self.dataDf.iloc[index,1:].values.reshape(28,28)
plt.imshow(imgData,cmap='Blues',interpolation=None)
pylab.show()
return
MNIST训练
# -*- codeing=utf-8 -*-
# @Author:姜磊
# 人间烟火气,最抚凡人心
import pandas as pd
from ImgShow import MnistDataset
import matplotlib.pyplot as plt
# from torch.utils.data import Dataset
from ClassFier import classiFier
# mnist手写数字识别测试网络
class MnistTorch():
def __init__(self,trainFilePath,testFilePath):
# 分类器实例化 参数:学习率默认0.01
# self.c = classiFier(lr=0.1)
self.c = classiFier()
# 导入训练集
self.trainDataSet = MnistDataset(trainFilePath)
# 导入测试集
self.testDataSet=MnistDataset(testFilePath)
return
# 训练 参数:训练次数
def mnistTorchTrain(self,epoch):
# 数据集的训练次数
for i in range(epoch):
for label, imgValue, target in self.trainDataSet:
# print(imgValue)
# print(target)
self.c.train(imgValue, target)
return
def mnistErrorPlot(self):
# 每10个训练样本后的误差可视化显示
self.c.plotProgerss()
return
# 对数据集中的某一行数据进行测试
def mnistTorchTest(self,record):
imgData=self.testDataSet[record][1]#原始数字大小
TorchTestOut=self.c.forward(imgData)#网络输出
self.testDataSet.plotImg(record)#展示原始手绘数字图像
# 将输出结果转换为numpy数组并包装为DataFrame数据后绘制成柱状图
pd.DataFrame(TorchTestOut.detach().numpy()).plot(kind='bar',legend=False,ylim=(0,1))
plt.show()
def testScore(self):
score=0#测试成功的数据条数
item=0
for label,imgData,target in self.testDataSet:
testAnswer=self.c.forward(imgData).detach().numpy()#训练答案
if(label==testAnswer.argmax()):
score+=1
item+=1
# 精确率
accuary=score/item
return score,item,accuary
trainFilePath = '../data/mnist_train.csv'
testFilePath = '../data/mnist_test.csv'
mt=MnistTorch(trainFilePath,testFilePath)
# 训练,参数:训练次数
mt.mnistTorchTrain(2)
# 对测试集某一条数据记录进行测试
# mt.mnistTorchTest(12)
# 训练进度(损失图)展示
# mt.mnistErrorPlot()
# 网络精确率,训练成功的数据条数,数据总数,精确率
score,item,accuary=mt.testScore()
print(score,'-',item,'-',accuary)