深度学习的核心就是各种不同的神经网络模型(CNN、RNN、GCN、GNN等)的学习和训练过程。这些神经网络模型的共同点都是一个“黑盒子”,通过一定的学习算法将大量数据交给模型训练,不断缩小模型预测值与真实值之间的误差,最终将精度达到一定阈值的模型应用到实际场景当中。所以,整个深度学习可以概括为:一个 大数据整理-模型搭建-网络训练-数据预测 的过程。
那么看完深度学习的本质之后,我们继续来探究神经网络的本质。由上述可以看到神经网络模型构建的整个过程中,最重要的就是神经网络的学习/训练阶段,那么什么样的算法才能不断缩小预测值与真实值之间的误差,使神经网络起作用的呢?没错,这个基础算法就是BP反向传播算法,它主要包括两部分:正向传播FP(求损失/误差) + 反向传播BP(根据误差更新参数)
在我们普遍的认知中,任何两个有联系的事务/现象都能用一种关系表示,在数学中,这个关系就被表述为函数f(x),比如一次函数、二次函数等等。但是自然界中的很多的现象/场景是不能直接用简单的数学函数去表示的,比如人的身高体重跟他寿命的关系等。但是,我们又想客观、可量化的去表述这个关系模型怎么办呢?近年来一些学者已经论证,任何复杂的关系都可以通过足够复杂的数学函数来表示!
为了提高关系模型的可拓展性,我们不可能每出现一个关系就去研究一个专门的复杂函数表示,而是要以简单函数为基础进行一定程度的复杂化。已知最简单的函数就是一次函数y=wx+b了,那么怎么将这个简单函数复杂化呢?那就是多项式函数嵌套!于是结合这些,就提出了神经网络模型模仿生物学的神经网络结构去实现复杂嵌套函数表述任何的复杂关系模型。
- X1~Xn:表示该隐含层神经计算单元的输入,用于表示简单一次函数y=wx+b中的x
- Wij:表示每个输入连接上的权重,用于表示简单一次函数y=wx+b中的w
- 神经元:一个神经元表示一次多项式函数求和,它主要包括两部分。第一部分是求和计算,该部分用于将所有输入的简单一次函数累加起来(信息聚合);第二部分是激活函数,该部分用于将聚合后的信息结果进行激活,常用的激活函数有relu、sigmod等。
- 为什么要用激活函数:我们说过,神经网络的核心就是构造足够复杂的函数去表达任何复杂关系。如果不使用激活函数,那么每次神经元计算都是一些简单一次函数的求和,简化合并常量后最终仍可以写成 y = Wx + B的形式,这仍然是一个简单的一次函数,并没有复杂化!但是当我们在每一次神经元计算后使用激活函数的话,计算结果就不能进行简单的合并了!从而达到复杂化目标函数的目的,使得模型对关系的逼近结果更好!
- Oj:该计算单元的输出结果,然后继续参与下一个神经元的嵌套计算
首先我们要明确神经网络训练的目的:确定神经网络中的权重参数Wij和bi。相当于在使用数学函数之前,我们要先确定函数中的未知数,比如y=kx+b中的k和b。只要每一层所有的w和b确定了,那么神经网络模型就确定了,我们就可以使用这个网络模型进行预测了。那么怎么进行神经网络参数的确定呢?我们先来看一下神经网络某一层的正向传递计算过程:
通过层层不断的正向传递,我们最终就能得到该组输入在这组神经网络参数下的预测输出,然后接下来通过比较预测输出与真实输出之间的误差损失,通过梯度下降算法我们就可以去不断优化网络参数确定最优的w和b,从而达到最理想的状态!
首先,导数是一个标量,它表示了函数上某一点在该处的变化率大小。在多元函数中,函数上某一点沿函数都有无数个变化方向。方向导数就是在函数定义域的内点对某一方向求导得到的导数,比如偏导数x和偏导数y其实就是在x轴和y轴方向的方向导数!那么,函数内点沿任意方向的方向导数定义和计算公式如下所示:
梯度是一个向量,它表示某一函数在某点处变化最快的方向,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模,方向导数)。梯度的推导过程如下:
在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一,另一种常用的方法是最小二乘法。由上述分析可知,梯度是某函数上某点处上升变化最快的方向,一般对于函数上的无约束优化问题,我们需要找到函数的最优最小值,那么通过朝着梯度的负方向(下降最快的方向)不断迭代,我们就能不断接近函数的最小值,从而求得最优。梯度下降算法的迭代公式如下:
其中,是我们的优化目标参数,是我们的学习率,即梯度下降时走的步长。一个比较形象化的例子是:比如我们在一座大山上的某处位置,由于我们不知道怎么下山,于是决定走一步算一步,也就是在每走到一个位置的时候,求解当前位置的梯度,沿着梯度的负方向,也就是当前最陡峭的位置向下走一步,然后继续求解当前位置梯度,向这一步所在位置沿着最陡峭最易下山的位置走一步。这样一步步的走下去,一直走到觉得我们已经到了山脚。当然这样走下去,有可能我们不能走到山脚,而是到了某一个局部的山峰低处。从上面的解释可以看出,梯度下降不一定能够找到全局的最优解,有可能是一个局部最优解。当然,如果损失函数是凸函数,梯度下降法得到的解就一定是全局最优解。
梯度下降优化可能带来的问题有:陷入局部最优、陷入鞍点等。在实际应用中也可以通过一些优化的梯度下降去一定程度上解决这些问题,比如添加冲量、Adam算法等(具体可以自己了解)。接下来我们分析一些常见的不同类型的梯度下降算法。
我们的目的是要误差函数尽可能的小,即求解最优weights使误差函数尽可能小。首先,我们随机初始化weigths(初始值不同,梯度下降的初始点也就不一样,可能到达的局部最优解也不同),然后不断反复的更新weights使得误差函数减小,直到满足要求时停止。这里更新算法我们选择梯度下降算法,利用初始化的weights并且反复更新weights。然后对所有数据点进行上述损失函数的偏导(累和),再最小化损失函数。
可以看出,批量梯度下降每次学习都使用整个训练集,因此其优点在于每次更新都会朝着正确的方向进行,最后能够保证收敛于极值点。但是其缺点在于每次学习时间过长,并且如果训练集很大以至于需要消耗大量的内存,并且全量梯度下降不能进行在线模型参数更新。并且BGD不能跳出局部最优。
随机梯度下降法其实和批量梯度下降法原理类似,区别在与求梯度时没有用所有的m个样本的数据进行优化,而是仅仅随机选取一个样本来求梯度和优化参数。SGD的优点是数据量小,更新速度快、参数方差波动大,适用于在线学习,有可能跳出局部最优到达全局最优。但是,SGD伴随的一个问题是噪音较BGD要多,使得SGD并不是每次迭代都向着整体最优化方向前进。
我们从上面两种梯度下降法可以看出,其各自均有优缺点,那么能不能在两种方法的性能之间取得一个折衷呢?即,算法的训练过程比较快,而且也要保证最终参数训练的准确率,而这正是小批量梯度下降法(Mini-batch Gradient Descent,简称MBGD)的初衷。我们采用x个数据组成的子样本来迭代,1
在正向传播得到预测结果后,我们需要尽可能缩小预测值与真实值之间的误差,使得我们的网络模型尽可能地接近真实水平。于是,我们就有了优化目标,在已知优化目标函数和梯度下降算法基础之上,我们就可以去优化网络参数了,其推导过程如下:
根据推导可知:每一个参数w的梯度下降迭代求解过程,根据链式法则,都需要其后向直接连接的所有路径上的求得的优化参数和权重来计算,以此不断的向前迭代,就能求出所有网络参数w和b的一次迭代优化结果。所以在网络损失优化过程中,我们需要进行网络损失误差的反向传播,其目的就是进行网络参数优化!这样的不断地迭代正向传播求预测+反向传播来优化的过程就组成了神经网络的学习/训练过程!
PyTorch是一个开源的Python深度学习框架,基于Torch。Pytorch主要提供两个高级功能:1、具有强大的GPU加速的张量计算(如NumPy)。2、包含自动求导系统的深度神经网络。简而言之,Pytorch对深度学习中的常见操作,包括自动求导、梯度下降等进行了封装和加速,使得我们只需要关注网络模型的搭建,而不用关注其中的计算细节。Pytorch搭建网络模型的过程其实就是构建动态计算图的过程。
Tensor(张量)是Pytorch的基本数据结构,类似于NumPy的ndarray,但还可以在GPU上使用来加速计算
import torch
import numpy as np
# (1)张量初始化
# 直接生成张量
data = [[1,2,3],[4,5,6]]
x_data = torch.tensor(data) #直接构造
x_data = torch.IntTensor(data) #IntTensor指定类型
print(x_data)
# 通过numpy生成张量(二者共享内存)
np_array = np.ones((5,4))
tensor_array = torch.tensor(np_array,dtype=torch.int) #直接构造,指定类型
tensor_array = torch.IntTensor(np_array) #IntTensor指定Int类型
tensor_array = torch.FloatTensor(np_array) #FloatTensor指定Float类型
tensor_array = torch.from_numpy(np_array) #from_numpy转化为Tensor,默认float类型
print(tensor_array)
# 通过指定数据维度来生成张量
rand_tensor = torch.rand((5,3))
zero_tensor = torch.zeros((5,3))
one_tensor = torch.ones((5,3))
empty_tensor = torch.empty((5,3))
print(zero_tensor)
# (2)张量属性
x_data = torch.rand((5,3))
print(x_data.dtype) #数据类型
print(x_data.shape) #维度大小
print(x_data.size()) #维度大小
print(x_data.device) #运行存储设备
# (3)张量运算
x = torch.rand((5,4))
y = torch.rand((5,4))
print(x+y) #加法一
print(torch.add(x,y)) #加法二
print(x.add(y)) #加法二
# 加法三:给定一个输出张量作为参数
result = torch.empty((5,4))
torch.add(x,y,out=result)
print(result)
加法四:加法:原位/原地操作(in-place) 注意任何一个in-place改变张量的操作后面都固定一个 _
x.add_(y)
print(x)
# GPU加速
# 判断当前环境GPU是否可用, 然后将tensor导入GPU内运行
if torch.cuda.is_available():
tensor = tensor.to('cuda')
# 矩阵乘法(前者列 = 后者行)
x = torch.tensor([[1,2],[0,1],[3,4]]) #3 x 2
y = torch.tensor([[1,2,3],[0,1,1]]) # 2 x 3
print(torch.matmul(x,y))
print(x@y)
# (4)张量的索引、切片、赋值:类似于Numpy
tensor = torch.ones(4, 4)
tensor[:,1] = 0 # 将第1列(从0开始)的数据全部赋值为0
print(tensor)
tensor[1:,1:3] = torch.tensor([[1,2],[3,4],[5,6]],dtype=torch.int)
print(tensor)
# (5)张量操作
# 形状变换 view
x = torch.randn(3, 4)
y = x.view(12)
z = x.view(-1, 6) # -1 表示该维度自动计算
print(x.size(), y.size(), z.size())
# 获取数值 item
x = torch.randn(1)
print(x)
print(x.item())
# 2.自动求导机制
# (1)创建张量计算图:设置requires_grad 为 True,那么pytorch将会追踪记录对于该张量的所有操作,构建计算图(默认为False)
a = torch.tensor([2.,3.],requires_grad=True)
b = torch.tensor([6.,4.],requires_grad=True)
# (2)构建计算另一个张量Q(生成计算图) 注意:Only Tensors of floating point and complex dtype can require gradients
Q = 3*a**2 - b**2
# (3)计算属性grad_fn:张量的grad_fn属性记录了创建每个张量的Function计算(用户手动创建的为None)
print(Q.grad_fn)
# (4)梯度下降:通过调用.backward()时,Autograd 将计算参数这些梯度并将其存储在各个张量的.grad属性中。
# 注意:
# -gradient 参数:我们需要在Q.backward()中显式传递gradient参数,因为它是向量。 gradient是与Q形状相同的张量,该参数是形状匹配的张量。它表示Q相对于本身的梯度,即 1。
# -gradient 参数:如果 Tensor 是一个标量(即它包含一个元素的数据),则不需要为 backward() 指定任何参数
Q.backward(torch.tensor([1.,1.]))
# # (5)记录累加(沉积)每个参数的梯度:grad
print(a.grad) # a.grad = 6a
print(b.grad) # b.grad = -2b
# 3.简单练习:线性模型 y = w*x
x_data = torch.tensor([1.0,2.0,3.0])
y_data = torch.tensor([2.0,4.0,6.0])
# w参数赋初值
w = torch.Tensor([1.0])
w.requires_grad = True # w以及他所参与的运算建立计算图,记录梯度
def forward(x):
return x*w #前馈计算:x自动转为Tensor,与w建立计算图
def loss(x,y):
y_pred = forward(x)
return sum((y_pred - y)**2) #计算损失和:建立计算图
for epoch in range(100):
# 计算损失和
l = loss(x_data,y_data)
# 反向传播,计算梯度(通过建立好的计算图)
#注意:每次backward后,计算图都会自动清空(梯度数据不会),等待下次重建
l.backward()
#梯度更新
# 注意:data属性是Tensor类型,但是data不参与计算图(require_grad=False)
# 可以通过data来计算/更新一些不需要建立图的数值,防止不知名的计算图占用不必要的内存/带来计算更新错误
w.data = w.data - 0.01*w.grad.data
#梯度归零(tensor计算出来的梯度会一直累加,必须显示清零)
w.grad.data.zero_()
#打印中间损失过程,item求tensor的标量(注意tensor必须是单个数字才行)
print("progress:",epoch,l.item())
print("after training: w=",w.data)
# 4.神经网络模型 实现线性模型
x_data = torch.tensor([[1.0],[2.0],[3.0]]) # 3*1 一维输入
y_data = torch.tensor([[2.0],[4.0],[6.0]]) # 3*1 一维输出
# (1)继承torch.nn.Module 神经网络模型:Module自动提供了backward和建立计算图
class LinearModel(torch.nn.Module):
#初始化方法:初始化网络模型结构
def __init__(self):
#初始化父类(固定)
super(LinearModel,self).__init__()
# Linear对象(input dim,output dim):线性层 ,其内部包含w,b,可以计算y=w*x+b运算单元
# 注意:只要知道了input,output的维度,w,b的维度自然就确定了,Linear会自动初始化
self.linear = torch.nn.Linear(1,1)
#前馈计算(overwrite)
def forward(self,x):
y_pred = self.linear(x) #计算:y = x*w + b
return y_pred
model = LinearModel() #实例化网络模型
#构造损失计算器MSELoss(继承自Module,也会自动建立计算图)
criterion = torch.nn.MSELoss(size_average=False) #不求损失和平均
#构造随机梯度下降优化器:w = w - l*w` ,提供不同的优化策略(Adam,Adgrad等)
optimizer = torch.optim.SGD(model.parameters(),lr=0.01)
for epoch in range(1000):
#前馈计算
#y_pred = mode(x_data)自动调用call forward()
y_pred = model(x_data)
#损失计算
loss = criterion(y_pred,y_data)
print("progress: ",epoch,loss.item())
#梯度清零(防止累加)
optimizer.zero_grad()
#梯度下降,反向传播,计算梯度
loss.backward()
#参数更新
optimizer.step()
#输出中间层参数(weight、bias -〉Tensor)
print("w = ",model.linear.weight.item())
print("b = ",model.linear.bias.item())
#测试数据
x_test = torch.tensor([[4.0]])
y_test = model(x_test)
print("y_pred = ",y_test.item())
import torch
import numpy as np
from torch.utils.data import DataLoader,Dataset
import torch.nn.functional as F
# 1.全连接神经网络(以糖尿病数据集为例)
# (1)导入数据集
# 使用float32的原因: N卡的GPU只支持float32的浮点数(不支持double)
data = np.loadtxt("D:\datasets\diabetes.csv.gz",delimiter=",",dtype=np.float32)
x_data = torch.from_numpy(data[:,:-1])
y_data = torch.from_numpy(data[:,[-1]])
# (2)建立模型
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
# 第一层神经网络:含有六个神经元,将八维输入降到6维 (n*8) dot (8*6) => (n*6)
self.linear1 = torch.nn.Linear(8,6)
# 第二层神经网络:含有四个神经元,将六维输入降到4维 (n*6) dot (6*4) => (n*4)
self.linear2 = torch.nn.Linear(6,4)
# 第三层神经网络:含有1个神经元,将4维输入降到1维 (n*4) dot (4*1) => (n*1)
self.linear3 = torch.nn.Linear(4,1)
# 激活函数:Sigmod ,使得神经网络变得复杂,摆脱线性
# 注意:
# torch.nn.Sigmoid:一个类,作为神经网络中的一层,参与建立计算图
# torch.nn.function.sigmod:一个函数,sigmod计算函数
self.sigmod = torch.nn.Sigmoid()
def forward(self,x):
x = self.sigmod(self.linear1(x))
x = self.sigmod(self.linear2(x))
x = self.sigmod(self.linear3(x))
#返回值 表示概率(logistic回归)
return x
model = Model()
# (3)损失器和优化器
# BCELoss:二分类交叉熵损失:loss = -1/n * Sum(ylogy` + (1-y)log(1-y`))
# -信息熵:信息量,表示一个系统信息量的大小,系统分布的混乱程度。 Entory = -Sum(p(x)*log(p(x)))
# -交叉熵:衡量真实分布p(x)于非真实分布q(x)的接近程度,CrossEntory = -Sum(p(x)*log(q(x)))
criterion = torch.nn.BCELoss(size_average=True)
optimizer = torch.optim.SGD(model.parameters(),lr=0.1)
# # (4)训练模型
for epoch in range(1000):
#前馈
#全部数据作为一个batch训练
y_pred = model(x_data)
loss = criterion(y_pred,y_data)
print(epoch,loss.item())
# 反馈
optimizer.zero_grad()
loss.backward()
# 更新
optimizer.step()
# 2.使用数据集加载
# dataLoder:数据加载器--帮我们划分batch、选择数据、打乱数据等等
# dataset:数据集(抽象类)--帮我们创建自己的数据集,用于dataloder处理
# class CustomDataset(Dataset):#需要继承data.Dataset
# def __init__(self):
# # TODO
# # 1. Initialize file path or list of file names.
# # (1)如果用来读取小规模结构化数据,可以通过init一次性加载数据集(存数据)
# # (2)如果用来读取大规模数据、图片、音频流等,需要每次读取每次加载,防止爆内存(存文件名、位置)
# pass
# def __getitem__(self, index):
# # TODO
# # 1. ‘Read one data’ from file (e.g. using numpy.fromfile, PIL.Image.open).
# # 2. Preprocess the data (e.g. torchvision.Transform).
# # 3. Return a data pair (e.g. image and label).
# #这里需要注意的是,第一步:read one data,是一个data
# #返回一个样本simple及其标签Label(字典)
# pass
# def __len__(self):
# # You should change 0 to the total size of your dataset.
# # 获取数据集长度
# return 0
class DiabetesDataset(Dataset):
def __init__(self,filePath):
data = np.loadtxt(filePath,delimiter=",",dtype=np.float32)
self.len = data.shape[0]
self.x_data = torch.from_numpy(data[:,:-1])
self.y_data = torch.from_numpy(data[:,[-1]])
def __getitem__(self, item):
return self.x_data[item],self.y_data[item]
def __len__(self):
return self.len
dataset = DiabetesDataset("D:\datasets\diabetes.csv.gz")
#dataLoader:
# dataset:加载数据集
# batch_size:每个mini-batch的大小(样本数目)
# shuffle:是否打乱数据
# num_workers:载入数据时参与处理的进程数目
train_loader = DataLoader(dataset=dataset,batch_size=32,shuffle=True,num_workers=2)
for epoch in range(100):
#每次取一个batch(trainLoader帮我们封装好了)
for index,data in enumerate(train_loader,0):
inputs,labels = data
y_pred = model(inputs)
loss = criterion(y_pred,labels)
print(epoch,index,loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 3.多分类 全连接网络
a = torch.tensor([[0.2,0.3,0.7,1.3],[0.5,0.1,1.1,-0.2],[2.1,0.8,1.3,0.43]])
labels = torch.tensor([[3],[3],[0]])
x1 = F.softmax(a.data,dim=1)
print(x1)
_,predict = torch.max(x1,dim=1) #返回tuple
print(predict)
print(predict==labels)
import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim
'''
torchvision工具包:
1.datasets:包含很多数据集
2.transforms:包含对PIL Image图像处理的变换
3.models:包含很多定义好的经典网络模型(比如AlexNext、VGGNet等)
'''
#定义全连接神经网络
class Net(torch.nn.Module):
def __init__(self):
super(Net,self).__init__()
self.liner1 = torch.nn.Linear(784,512)
self.liner2 = torch.nn.Linear(512,256)
self.liner3 = torch.nn.Linear(256, 128)
self.liner4 = torch.nn.Linear(128, 64)
self.liner5 = torch.nn.Linear(64, 10)
def forward(self,x):
x = x.view(-1,784)
#使用relu函数作为隐层的激活函数,防止深层网络中sigmod的求导梯度消失
x = F.relu(self.liner1(x)) #第一次传播并激活 N * 512
x = F.relu(self.liner2(x)) # 第2次传播并激活 N * 256
x = F.relu(self.liner3(x)) # 第3次传播并激活 N * 128
x = F.relu(self.liner4(x)) # 第4次传播并激活 N * 64
return self.liner5(x) # 第5次传播:不做激活,交给CrossEntory做概率
#训练网络(训练多次,每次训练全部,一次训练中为mini_batch)
def trainNet(epoch):
running_loss = 0.0
for batch_idx,data in enumerate(train_loader,0):
inputs,target = data #获取数据,标签
#print(inputs.size())
optimizer.zero_grad() #清空梯度(梯度计算出来时累加的)
outputs = model.forward(inputs) #前馈计算
loss = criterion(outputs,target) #计算交叉熵损失误差
loss.backward() #反馈计算梯度
optimizer.step() #梯度更新优化
running_loss += loss.item() #记录累加一次batch的误差
if batch_idx % 300 ==299:
print('[%d %5d] loss: %.3f' % (epoch+1,batch_idx+1,running_loss/300))
running_loss = 0.0
#测试网络(以minni_batch的方式进行)
def test():
correct = 0
total = 0
with torch.no_grad():#声明包含部分不计算梯度
for data in test_loader:
test_input,labels = data
# print(labels) 数据集的labels都是一维的 ,tensor([x,y,z,...])
test_output = model.forward(test_input)
#计算概率最大值的下标, max返回 (max_value,max_index)
_,predicted = torch.max(F.softmax(test_output.data,dim=1),dim=1)
total += labels.size(0)
correct += (predicted==labels).sum().item()
print('Accuracy on test set: %d %%' % (100*correct/total))
#指定一次批量训练的数据大小
batch_size = 64
'''
torchvision.transforms:设置数据转换组合器transform 操作的Pipline ,专门对PIL.Image进行变换
1.将图像 或 shape为(H,W,C)的numpy.ndarray 转换为Tensor矩阵,每个元素是像素大小:转换成形状为[C,H,W],取值范围是[0,1.0]的torch.FloadTensor
注意:原始图像读入都是 (高度,宽度,通道数),即(H,W,C) => 转化为深度学习习惯的 (C,H,W)
2.将矩阵标准化,符合正态分布 x = (x - μ)/σ:给定均值(R,G,B) 方差(R,G,B),将会把Tensor正则化。
3.可直接通过()使用,转换图片数据集 data = transform(imgDatas)
'''
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,),(0.3081,))
])
'''
torchvision.datasets中包含了以下数据集:
1.MNIST
2.CIFAR10 and CIFAR100
3.COCO(用于图像标注和目标检测)
4.等等
torchvision.datasets中还包含了图像加载方法ImageFolder:
'''
#读取训练集合 60000 * 1 * 28 * 28 (N * C * W * H)
# root:数据集本地存放目录
# train:是否是取训练集
# download:是否下载到本地
# transform:操作Pipline
train_dataset = datasets.MNIST(root='../dataset/mnist/',train=True,download=True,transform=transform)
#训练数据加载器 64 * 1 * 28 * 28
train_loader = DataLoader(train_dataset,shuffle=True,batch_size=batch_size)
#读取测试集合
test_dataset = datasets.MNIST(root='../dataset/mnist/',train=False,download=True,transform=transform)
#测试数据加载器
test_loader = DataLoader(test_dataset,shuffle=False,batch_size=batch_size)
#将 二维图像拉直 -> 64 * 784(784个特征) ,才能使用全连接神经网络
model = Net() #定义网络
criterion = torch.nn.CrossEntropyLoss() #定义损失函数交叉熵 = softmax + log(用于多分类 概率分布损失)
optimizer = optim.SGD(model.parameters(),lr=0.01,momentum=0.5) #定义随机梯度下降优化器对模型参数(w+b)进行优化
if __name__ == '__main__':
for epoch in range(10):
trainNet(epoch)
test()
#头文件导入
from sklearn import datasets
from sklearn.model_selection import train_test_split
#导入pytorch搭建神经网络
import torch
from torch.utils.data import DataLoader
from torch.utils.data import TensorDataset
import torch.nn.functional as F
import torch.optim as optim
import matplotlib as mpl
import matplotlib.pyplot as plt
#定义全连接BP神经网络
class Net(torch.nn.Module):
#初始化网络结构
def __init__(self,cnt):
super(Net,self).__init__()
#记录每轮训练的损失、准确率用于画图
self.neuralCnt = cnt
self.lossList = []
self.accuracyList = []
#搭建网络层
self.liner1 = torch.nn.Linear(4,cnt)
self.liner2 = torch.nn.Linear(cnt,cnt)
self.liner3 = torch.nn.Linear(cnt, 3)
#定义前向传播过程
def forward(self,x):
#使用relu函数作为隐含层的激活函数,防止深层网络中sigmod的求导梯度消失
x = F.relu(self.liner1(x)) #第一次传播并激活 N * cnt
x = F.relu(self.liner2(x)) # 第2次传播并激活 N * cnt
return self.liner3(x)
#平均损失
def getMeanLoss(self):
sum = 0.0
for val in self.lossList:
sum = sum + val
return sum/len(self.lossList)
#平均准确率
def getMeanAccuracy(self):
sum = 0.0
for val in self.accuracyList:
sum = sum + val
return sum / len(self.accuracyList)
#定义网络的具体计算过程+画图
class NetClassifier(object):
def __init__(self):
self.netList = []
def dataPrepare(self):
# 导入鸢尾花数据集
dataset = datasets.load_iris()
data_x = dataset['data'] # (150,4)
data_y = dataset['target'] # (150,1)
#数据标准归一化处理(Max-Min法)
stand_x = (data_x-data_x.min(axis=0))/(data_x.max(axis=0) - data_x.min(axis=0))
# 划分训练集和测试集
train_x, test_x, train_y, test_y = train_test_split(stand_x, data_y, test_size=0.2, shuffle=True)#shuffle打乱顺序
# 数据格式转换为Tensor,便于pytorch使用
train_X = torch.FloatTensor(train_x)
test_X = torch.FloatTensor(test_x)
train_Y = torch.LongTensor(train_y)
test_Y = torch.LongTensor(test_y)
# 设置数据集模型参数,数据加载器loader
self.batch_size = 10;#批处理
self.train_dataset = TensorDataset(train_X, train_Y) # TensorDataset()可以对tensor进行打包合并
self.train_loader = DataLoader(self.train_dataset, batch_size=self.batch_size, shuffle=True) # 训练数据加载器
self.test_dataset = TensorDataset(test_X, test_Y)
self.test_loader = DataLoader(self.test_dataset, batch_size=self.batch_size, shuffle=False) # 测试数据加载器
def dataProcess(self):
#对不同神经元个数的网络进行训练
for cnt in range(8,14):
model = Net(cnt)#构造cnt个隐含神经元的网络
criterion = torch.nn.CrossEntropyLoss() # 误差计算器:交叉熵损失函数
optimizer = optim.SGD(model.parameters(), lr=0.01) # 随机梯度下降优化器对模型参数(w+b)进行优化
#进行1000轮训练
for epoch in range(1000):
#训练模型
self.trainNet(epoch,model,criterion,optimizer)
#测试模型
self.testNet(model)
#输出平均损失 + 平均准确率
modelLoss = model.getMeanLoss()
modelAccuracy = model.getMeanAccuracy()
print('[ Neural-Cnt %d ] : Mean-Loss is %.3f\n Mean-Accuracy on test set: %.3f %%' % (cnt, modelLoss, modelAccuracy))
#保存训练网络
self.netList.append(model)
if cnt==8:
self.minLoss = modelLoss
self.maxAccuracy = modelAccuracy
self.lastModel = model
elif modelAccuracy > self.maxAccuracy or (modelAccuracy==self.maxAccuracy and modelLoss < self.minLoss) :
self.minLoss = modelLoss
self.maxAccuracy = modelAccuracy
self.lastModel = model
# 模型保存
#torch.save(model.state_dict(), "bp-nn.pkl") # 保存网络参数
#画图
self.plotLine()
# 训练网络(训练多次,每次训练全部,一次训练中为mini_batch)
def trainNet(self,epoch,model,criterion,optimizer):
running_loss = 0.0
for batch_idx, data in enumerate(self.train_loader, 0):
inputs, target = data # 获取数据,标签
# print(inputs.size())
optimizer.zero_grad() # 清空梯度(梯度计算出来时累加的)
outputs = model.forward(inputs) # 前馈计算
loss = criterion(outputs, target) # 计算交叉熵损失误差
loss.backward() # 反馈计算梯度
optimizer.step() # 梯度更新优化
running_loss += loss.item() # 记录累加一次batch的误差
model.lossList.append(running_loss)#保存每轮的损失
# 测试网络训练效果
def testNet(self,model):
correct = 0
total = 0
#不用计算梯度优化参数
with torch.no_grad():
for data in self.test_loader:
test_input, labels = data
test_output = model.forward(test_input)
#使用softmax计算百分比概率预测分类
_, predicted = torch.max(F.softmax(test_output.data, dim=1), dim=1)
total += labels.size(0)
#判断正确分类个数
correct += (predicted == labels).sum().item()
model.accuracyList.append(100.0 * correct / total)
#绘制变化图像
def plotLine(self):
x = range(1000)
#画第一个多网络损失变化复合图
plt.figure(1)
plt.title('Loss Trend')
for model in self.netList:
plt.plot(x, model.lossList, label=('Neural'+str(model.neuralCnt)))
plt.legend()
plt.xlabel('epoch')
plt.ylabel('loss')
# 画第二个多网络准确率变化复合图
plt.figure(2)
plt.title('Accuracy Trend')
for model in self.netList:
plt.plot(x, model.accuracyList, label=('Neural' + str(model.neuralCnt)))
plt.legend()
plt.xlabel('epoch')
plt.ylabel('accuracy')
# 画第三个最优网络损失变化图
plt.figure(3)
plt.title('Neural-Cnt' + str(self.lastModel.neuralCnt) + ' Loss Trend')
plt.plot(x, self.lastModel.lossList)
plt.legend()
plt.xlabel('epoch')
plt.ylabel('loss')
# 画第四个最优网络准确率变化图
plt.figure(4)
plt.title('Neural-Cnt' + str(self.lastModel.neuralCnt) + ' Accuracy Trend')
plt.plot(x, self.lastModel.accuracyList)
plt.legend()
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()
if __name__ == "__main__":
classifier = NetClassifier()
classifier.dataPrepare()
classifier.dataProcess()