在前面的文章中,我们介绍了为线性回归、二分类、三分类神经网络的优化选取适当的损失函数。我们知道,当损失函数的值越小,代表神经网络预测值与真实值之间的差异越小,模型效果越好。对于是凸函数的损失函数,我们可以对权重向量求导,再令其导数等于0,来求出使损失函数最小化的值。遗憾的是,在绝大多数问题中,损失函数都不是一个简单的凸函数,无法通过数学推导来求得的最优解。对于这样的问题,我们通常采用数值最优化的方法(如:梯度下降法、牛顿法等),通过迭代的方式,逐步逼近局部最优解。本文我们将介绍如何在神经网络中使用梯度下降法。
梯度下降法
在优化神经网络之前,我们先简单介绍一下什么是梯度下降法。梯度下降法可以说是机器学习中最常用的数值最优化方法了,它从一个随机的起点出发,沿着梯度(损失函数在当前点的微分)的反方向移动一小段距离,到达一个新的点,再次计算梯度并移动,通过不断迭代,最终到达函数的最低点(这个最低点不一定是函数全局的最低点)。
梯度下降的迭代公式为:
其中:
- 上标 代表第 次迭代, 代表在第 次迭代时的 的取值;
- 为待优化函数/损失函数;
- 为 在 处的梯度;
- 为自行设定的步长,也叫学习率。
当 时停止迭代( 为自行设定的大于0的阈值,如:),取 为最终解。
PyTorch中的AutoGrad模块
在Pytorch中,张量本身可以支持微分运算。
import torch
import numpy as np
# 用 requires_grad=True 构建可微分张量
x = torch.tensor(1., requires_grad=True)
x
tensor(1., requires_grad=True)
# 构建函数关系
y = x**2
y
tensor(1., grad_fn=<PowBackward0>)
此时张量y具有属性grad_fn=<PowBackward0>
,我们可以通过y.grad_fn
查看该属性。grad_fn
属性存储了可微分张量y在计算过程中的函数关系(即 y=x**2)。由于y是由可微分张量x计算而来,所以y本身也是一个可微分张量,可通过y.requires_grad
查看。
y.requires_grad
True
我们可以尝试由y构建的张量z是否也具有同样的性质。
z = y + 30
print(z)
print(z.grad_fn)
print(z.requires_grad)
tensor(31., grad_fn=<AddBackward0>)
<AddBackward0 object at 0x000001ADD091B6A0>
True
可以看出,如果初始张量是可微的,在计算过程中,由其构建的新张量都是可微的,并且新张量会保存与前一步的函数关系。
张量的计算图
根据张量间的函数关系,可以绘制张量计算图。在上述例子中,张量的计算图如下:
- 张量计算图是用于记录可微分张量之间计算关系的图;
- 由节点和有向边构成,其中节点表示张量,边表示函数计算关系,方向表示实际运算方向;
- 本质上是有向无环图。
张量的节点可以分为三类:
- 叶节点:初始输入的可微分张量;上例中的x;
- 输出节点:最后计算得出的张量;上例中的z;
- 中间节点:除了叶节点和输出节点之外的节点;上例中的y。
在一张计算图中,可以有多个叶节点和中间节点,但通常只有一个输出节点,若有多个输出结果,我们也会保存在一个张量中。
Pytorch中的计算图是动态计算图,会根据可微分张量的计算过程自动生成,并且伴随新张量或运算的加入不断更新,使运算更灵活高效。
反向传播
我们可以使用torch.autograd.grad()
来计算可微张量的导数。
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
z = y + 30
# y 对 x 求导
print(torch.autograd.grad(y, x, retain_graph=True))
# z 对 y 求导
print(torch.autograd.grad(z, y, retain_graph=True))
# z 对 x 求导
print(torch.autograd.grad(z, x))
(tensor(2.),)
(tensor(1.),)
(tensor(2.),)
我么也可以使用反向传播来进行计算导数/梯度。反向传播,可以简单理解为,在此前记录的函数关系基础上,反向传播函数关系,进而求得叶节点的导数值。我们还是用上面x,y,z的计算关系来作为例子。
首先,对于某一个可微分张量的导数值(梯度值),存储在grad属性中:
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
z = y + 30
x.grad
x.grad属性是空值,不会返回任何结果,我们虽然已经构建了x、y、z三者之间的函数关系,x也有具体取值,但要计算x点导数,还需要进行具体的求导运算,也就是执行所谓的反向传播。
我们执行反向传播:
z.backward()
反向传播结束后,即可查看叶节点的导数值。
x.grad
tensor(2.)
注意:
- 在默认情况下,在一张计算图上执行
backward()
或者autograd.grad()
,只能计算一次,再次调用将报错,除非将函数参数retain_graph
设置为True。
在中间节点上也可以进行反向传播:
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
z = y + 30
y.backward()
x.grad
tensor(2.)
默认情况下,在反向传播过程中,中间节点并不会保存梯度;若想保存中间节点的梯度,我们可以使用retain_grad()方法:
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
y.retain_grad()
z = y**2
z.backward()
print(y.grad)
print(x.grad)
tensor(2.)
tensor(4.)
在一些特殊情况下,我们不希望可微张量从创建到运算结果输出都被记录,此时就可以使用一些方法来阻止部分运算被记录。
-
with torch.no_grad()
方法:with相当于是一个上下文管理器,with torch.no_grad()内部代码都“屏蔽”了计算图的追踪记录;
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
with torch.no_grad():
z = y**2
# z 只是一个普通张量
z
tensor(1.)
.detach()
方法:创建一个不可导的相同张量;
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
y1 = y.detach()
z = y1**2
print(y)
print(y1)
print(z)
tensor(1., grad_fn=<PowBackward0>)
tensor(1.)
tensor(1.)
如果需要识别在一个计算图中某张量是否是叶节点,可以使用is_leaf
属性查看对应张量是否是叶节点。
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
z = y**2
print(x.is_leaf)
print(y.is_leaf)
print(z.is_leaf)
True
False
False
注意:is_leaf
方法也有容易混淆的地方,对于任何一个新创建的张量,无论是否可导、是否加入计算图,都是可以是叶节点,这些节点距离真正的叶节点,只差一个requires_grad属性调整。
神经网络中的反向传播
在之前的文章中我们介绍过,神经网络的正向传播是指神经网络从输入层出发,依据设定好的函数运算关系,从左往右逐层计算,得到输出层结果的过程。而神经网络的反向传播,恰好相反,是从输出结果出发,依据函数关系及链式法则,从右向左求解梯度的过程。在PyTorch中,这一过程由.backward()
自动完成。
回顾我们在之前文章中构建神经网络正向传播的例子。
我们有500条数据,20个特征,标签3分类。我们要实现一个三层神经网络,架构是:第一层有13个神经元,第二层有8个神经元,第三层是输出层。第一层的激活函数是ReLU,第二层的激活函数是sigmoid。它的正向传播的代码如下:
import torch
import torch.nn as nn
torch.manual_seed(523)
X = 100*torch.randn((500,20), dtype=torch.float32)
y = torch.randint(0, 3, (500,), dtype=torch.float32)
class Model(nn.Module):
def __init__(self, in_feature=10, out_feature=2):
super(Model, self).__init__()
self.linear1 = nn.Linear(in_feature, 13)
self.linear2 = nn.Linear(13, 8)
self.output = nn.Linear(8, out_feature)
def forward(self, X):
sigma1 = torch.relu(self.linear1(X))
sigma2 = torch.sigmoid(self.linear2(sigma1))
z_hat = self.output(sigma2)
return z_hat
# 在forward函数中,我们将z_hat作为输出结果,
# 是因为我们将使用nn中的CrossEntroyLoss作为损失函数,
# 而z_hat是CrossEntroyLoss的输入参数
由于该神经网络为三分类网络,我们用CrossEntropyLoss作为其损失函数:
input_ = X.shape[1]
output_ = len(y.unique())
net = Model(input_, output_)
z_hat = net.forward(X)
criterion = nn.CrossEntropyLoss()
loss = criterion(z_hat, y.long())
该神经网络的计算图如下:
对损失函数进行反向传播:
loss.backward()
查看返回的梯度的shape(与权重矩阵的shape是一致的):
# 神经网络第一层权重的梯度的shape
net.linear1.weight.grad.shape
torch.Size([13, 20])
我们注意到,在上述过程中,我们并没有对权重矩阵设置 requires_grad=Ture,那是因为我们的权重矩阵是由继承的nn.Module类自动生成的,在生成过程中,已经默认将requires_grad设置为True了。
在神经网络中使用随机梯度下降法
当我们从反向传播中获得了梯度后,就可以对权重的值进行更新。回顾本文开头介绍的梯度下降法的公式:
我们继续沿用上一小节中的例子来进行代码实现。
# 设定超参数
lr = 0.01
- lr 代表 learning rate 学习率(即步长,梯度下降公式中的)通常为一个很小的数,如:0.001,0.01,0.05,0.1。
# 对net中第一层权重矩阵进行第一次迭代
net.linear1.weight.data -= lr * net.linear1.weight.grad
print(net.linear1.weight.data[0:5])
代码打印结果如下:
tensor([[ 0.0870, 0.1616, -0.2232, 0.0182, -0.0080, -0.2116, -0.0446, 0.1539,
0.1337, 0.1365, 0.0592, -0.1800, -0.1057, -0.2055, 0.0790, 0.1459,
-0.1940, 0.1789, -0.1130, 0.0987],
[ 0.0939, 0.1414, -0.1520, -0.1762, -0.0556, 0.0523, 0.0731, 0.0059,
0.1907, 0.0755, 0.1626, 0.1898, 0.1082, 0.1420, -0.1051, 0.0681,
-0.1620, 0.0750, 0.0382, -0.0955],
[-0.0734, -0.1051, -0.2168, 0.1326, -0.1887, -0.1151, 0.2064, 0.0848,
0.0797, -0.1815, -0.1496, -0.2139, -0.0666, 0.1644, -0.2142, -0.1242,
-0.0604, -0.0372, -0.1209, 0.1619],
[ 0.1495, 0.0230, -0.1951, -0.1492, -0.0597, -0.1927, 0.0299, 0.1652,
-0.1647, 0.2015, -0.1960, 0.1051, 0.1920, -0.1612, 0.1436, -0.1860,
0.0525, -0.1239, 0.1356, 0.0672],
[ 0.1308, -0.0689, 0.1470, -0.1147, -0.0099, 0.1324, -0.1758, 0.0781,
-0.1289, 0.1064, 0.0458, 0.1904, 0.1662, -0.0075, 0.1079, -0.0063,
0.0656, -0.0992, 0.1638, -0.1795]])
完成迭代之后,我们将重复下面的步骤,直至梯度下降算法收敛(找到权重的最优解):
正向传播(获得loss)---> 反向传播(获得梯度)---> 更新权重 ---> 正向传播(获得loss)---> ...
# 再次进行正向传播,计算loss
z_hat = net.forward(X)
loss = criterion(z_hat, y.long())
# 再次进行反向传播
loss.backward()
# 对net中第一层权重矩阵进行第二次迭代
net.linear1.weight.data -= lr * net.linear1.weight.grad
print(net.linear1.weight.data[0:5])
代码打印结果如下:
tensor([[ 0.0867, 0.1617, -0.2232, 0.0184, -0.0083, -0.2116, -0.0448, 0.1544,
0.1336, 0.1365, 0.0588, -0.1803, -0.1056, -0.2057, 0.0792, 0.1459,
-0.1940, 0.1787, -0.1131, 0.0985],
[ 0.0938, 0.1411, -0.1523, -0.1763, -0.0555, 0.0524, 0.0731, 0.0061,
0.1906, 0.0756, 0.1626, 0.1898, 0.1081, 0.1420, -0.1049, 0.0683,
-0.1619, 0.0752, 0.0381, -0.0953],
[-0.0732, -0.1050, -0.2168, 0.1325, -0.1887, -0.1153, 0.2064, 0.0847,
0.0796, -0.1816, -0.1495, -0.2137, -0.0666, 0.1643, -0.2142, -0.1241,
-0.0606, -0.0374, -0.1211, 0.1621],
[ 0.1494, 0.0229, -0.1951, -0.1491, -0.0596, -0.1923, 0.0301, 0.1652,
-0.1646, 0.2017, -0.1957, 0.1049, 0.1918, -0.1614, 0.1437, -0.1860,
0.0526, -0.1240, 0.1358, 0.0673],
[ 0.1309, -0.0688, 0.1467, -0.1152, -0.0099, 0.1327, -0.1755, 0.0781,
-0.1292, 0.1060, 0.0463, 0.1905, 0.1660, -0.0076, 0.1079, -0.0061,
0.0655, -0.0989, 0.1638, -0.1795]])
对比两次迭代后的结果我们发现,每一次迭代更新后,权重矩阵将发生微小的变化,使损失函数沿着减小的方向迈出了一小步。
使用动量法(Momentum)改进前进方向
每次迭代时,都会产生一个梯度,权重矩阵沿着梯度的反方向前进,为了提高迭代的效率,我们希望能够实现:
- 当本次迭代和上次迭代的方向相近时,加大前进的步伐;
- 当本次迭代和上次迭代的方向有很大差异时,综合考虑两次的方向。
动量法可以帮助我们实现上述目标。假设第次迭代的前进方向为,则:
从上述公式不难看出,第 次迭代前进的方向,实际上是第 次迭代前进方向: 与本轮求得的梯度反方向:()的加权平均和,权重分别为 和 。前进方向 被称为动量,参数 被称为动量参数。
下面我们将介绍使用torch.optim
库来实现上述更新权重矩阵的过程。
import torch
import torch.nn as nn
import torch.optim as optim # 导入torch.optim库
torch.manual_seed(523)
# 原始数据准备
X = 100*torch.randn((500,20), dtype=torch.float32)
y = torch.randint(0, 3, (500,), dtype=torch.float32)
# 模型所用超参数准备
lr = 0.01 # 学习率
gamma = 0.9 # 动量参数
# 神经网络架构
class Model(nn.Module):
def __init__(self, in_feature=10, out_feature=2):
super(Model, self).__init__()
self.linear1 = nn.Linear(in_feature, 13)
self.linear2 = nn.Linear(13, 8)
self.output = nn.Linear(8, out_feature)
def forward(self, X):
sigma1 = torch.relu(self.linear1(X))
sigma2 = torch.sigmoid(self.linear2(sigma1))
z_hat = self.output(sigma2)
return z_hat
# 实例化神经网络
torch.manual_seed(523)
input_ = X.shape[1]
output_ = len(y.unique())
net = Model(input_, output_)
# 实例化损失函数、优化算法
criterion = nn.CrossEntropyLoss()
opt = optim.SGD(net.parameters() # 待优化的参数为神经网络net中的所有参数
, lr=lr # 学习率
, momentum=gamma # 动量参数
)
进行一次迭代循环:
opt.zero_grad() # 清除梯度
z_hat = net.forward(X) # 正向传播
loss = criterion(z_hat, y.long()) # 损失函数值
loss.backward() # 反向传播
opt.step() # 更新权重
print(loss)
print(net.linear1.weight.data[0])
tensor(1.2273, grad_fn=<NllLossBackward>)
tensor([ 0.0688, 0.0787, 0.0049, -0.0480, -0.1132, 0.1122, 0.2166, -0.0108,
0.1118, -0.1780, -0.1765, 0.1041, -0.1062, -0.1410, 0.0300, 0.0522,
-0.1015, -0.0356, 0.1306, 0.1921])
重复一迭代循环:
opt.zero_grad() # 清除梯度
z_hat = net.forward(X) # 正向传播
loss = criterion(z_hat, y.long()) # 损失函数值
loss.backward() # 反向传播
opt.step() # 更新权重
print(loss)
print(net.linear1.weight.data[0])
tensor(1.2231, grad_fn=<NllLossBackward>)
tensor([ 0.0689, 0.0791, 0.0049, -0.0478, -0.1131, 0.1122, 0.2162, -0.0110,
0.1117, -0.1776, -0.1762, 0.1042, -0.1063, -0.1412, 0.0301, 0.0521,
-0.1015, -0.0357, 0.1304, 0.1921])
随机梯度下降法/小批量随机梯度下降法
我们可以注意到,在梯度下降法中,每次进行迭代时,我们都在计算中使用了全部的样本,这样做可能会造成两个问题:1)运算效率偏低,尤其在样本量巨大的情况下;2)优化的结果过于拟合当前的样本。针对这样的情况,我们可以使用随机梯度下降法或者小批量随机梯度下降法。
- 随机梯度下降法:每次进行迭代时,随机抽取一个样本用于计算损失函数的值;
- 小批量随机梯度下降法:每次进行迭代时,随机抽取一小部分样本用于计算损失函数的值。
以上两种方法都是梯度下降法的变种,因此,从广义上讲,梯度下降法包含了其原始形态以及由它衍生的变种。这两种方法的核心思想都是用局部(小部分样本)模拟整体(全部样本),这样做的好处是能够使算法更容易跳脱出局部最小值(从而更有可能找到全局最小值);缺点就是不如原始的梯度下降法稳定。
在用梯度下降法优化神经网络时,一般使用的小批量梯度下降法。
小批量梯度下降法中有两个比较重要的概念:
batch size:单个批量 batch 含有的样本数被称为 batch size;
epoch:是衡量训练数据被使用次数的单位;一个epoch表示优化算法将全部训练数据都使用了一次。
在数据预处理模块torch.utils
中,TensorDataset
和DataLoader
是对数据进行预处理的重要的类。TensorDataset:用来打包数据的类(如:打包特征和标签);它可以将第一个维度数量一样的tensor打包在一起。
import torch
from torch.utils.data import TensorDataset
a = torch.randn((500, 3, 4, 5))
b = torch.randn((500, 1))
查看打包的第一个数据:
TensorDataset(a,b)[0]
代码打印结果如下:
(tensor([[[-0.0233, 1.9141, -0.5995, 1.9380, 0.1375],
[-0.1492, -0.8560, -1.5663, 1.0305, 0.3425],
[ 0.1487, -0.3927, -0.1159, -0.3164, 0.0164],
[-0.6294, -0.4785, -1.4078, 1.6561, -0.1408]],
[[-1.3591, -0.1150, -0.8489, -0.4108, -0.7836],
[-2.9301, -0.4060, -0.5938, -0.2844, 1.3479],
[ 2.6921, -0.3981, 0.2316, 1.0572, 0.4549],
[-1.9132, 0.6705, 0.4639, 0.4736, 0.6568]],
[[-0.4895, -0.7288, 0.0756, 1.8666, 1.8532],
[ 0.5564, 1.1624, 0.2139, -0.2616, 0.0708],
[ 0.6012, -1.8113, 1.0708, 0.3703, 0.8664],
[ 0.3937, 0.9258, 0.6254, 1.1905, 0.3784]]]),
tensor([-0.5008]))
查看打包的前两个数据:
TensorDataset(a,b)[0:2]
代码打印结果如下:
(tensor([[[[-0.0233, 1.9141, -0.5995, 1.9380, 0.1375],
[-0.1492, -0.8560, -1.5663, 1.0305, 0.3425],
[ 0.1487, -0.3927, -0.1159, -0.3164, 0.0164],
[-0.6294, -0.4785, -1.4078, 1.6561, -0.1408]],
[[-1.3591, -0.1150, -0.8489, -0.4108, -0.7836],
[-2.9301, -0.4060, -0.5938, -0.2844, 1.3479],
[ 2.6921, -0.3981, 0.2316, 1.0572, 0.4549],
[-1.9132, 0.6705, 0.4639, 0.4736, 0.6568]],
[[-0.4895, -0.7288, 0.0756, 1.8666, 1.8532],
[ 0.5564, 1.1624, 0.2139, -0.2616, 0.0708],
[ 0.6012, -1.8113, 1.0708, 0.3703, 0.8664],
[ 0.3937, 0.9258, 0.6254, 1.1905, 0.3784]]],
[[[-0.5772, -1.1265, 0.1254, 0.3652, 1.7364],
[ 0.3899, 0.2242, -0.3251, 0.6372, -0.4175],
[ 1.0690, -0.2528, 1.2352, 0.9929, -0.6703],
[-1.3898, 1.2658, -0.3822, -1.2853, 1.2490]],
[[-0.3043, -1.9394, 0.2151, 0.9892, -1.0453],
[-0.6024, 1.1334, -1.4179, -1.7300, 0.0180],
[-0.8412, -1.6207, 2.5445, 1.0061, -1.2067],
[ 0.1509, -0.0163, 0.0898, -1.0971, 0.9862]],
...
[-1.1061, -0.0206, 0.8473, 0.5660, -1.4148],
[-1.0360, 1.2338, -1.8632, -2.7693, -2.2408],
[-0.1658, -0.3251, -0.5832, 1.8602, 0.9598]]]]),
tensor([[-0.5008],
[-0.0585]]))
- DataLoader:用来切割小批量数据的类;可以接受任意数组,张量作为输入,并把它们一次性转换为神经网络可以接入的tensor;
import torch
from torch.utils.data import DataLoader
import numpy as np
DataLoader(np.random.randn(5,2))
for i in DataLoader(np.random.randn(5,2)):
print(i)
# 数据类型是自动读取的,无法在DataLoader里面修改
tensor([[-1.2635, -0.6255]], dtype=torch.float64)
tensor([[-1.5648, 0.4153]], dtype=torch.float64)
tensor([[0.8743, 0.5387]], dtype=torch.float64)
tensor([[ 0.9333, -1.4642]], dtype=torch.float64)
tensor([[1.7805, 0.6314]], dtype=torch.float64)
#如果在输入时直接设置好类型,则可以使用设置的类型
for i in DataLoader(torch.randn((5,2), dtype=torch.float32)):
print(i.dtype)
torch.float32
torch.float32
torch.float32
torch.float32
torch.float32
DataLoader中比较重要的参数有:
- batch_size:单个批量的样本量有多少;
- shuffle:分批之前是否将样本打乱;
- drop_last:是否舍弃最后一个batch;
data = DataLoader(torch.randn(500,2)
, batch_size=120
, shuffle=True
, drop_last=False
)
for i in data:
print(i.shape)
torch.Size([120, 2])
torch.Size([120, 2])
torch.Size([120, 2])
torch.Size([120, 2])
torch.Size([20, 2])
对于小批量梯度下降而言,我们一般这样使用 TensorDataset 和 DataLoader。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
torch.manual_seed(526)
X = torch.randn((50000,20), dtype=torch.float32)
y = torch.randint(0, 3, (50000,), dtype=torch.float32)
# 神经网络架构
class Model(nn.Module):
def __init__(self, in_feature=10, out_feature=2):
super(Model, self).__init__()
self.linear1 = nn.Linear(in_feature, 13)
self.linear2 = nn.Linear(13, 8)
self.output = nn.Linear(8, out_feature)
def forward(self, X):
sigma1 = torch.relu(self.linear1(X))
sigma2 = torch.sigmoid(self.linear2(sigma1))
z_hat = self.output(sigma2)
return z_hat
# 实例化神经网络
torch.manual_seed(526)
input_ = X.shape[1]
output_ = len(y.unique())
net = Model(input_, output_)
# 设定优化算法超参数
lr = 0.01
gamma = 0.9
# 实例化损失函数、优化算法
criterion = nn.CrossEntropyLoss()
opt = optim.SGD(net.parameters() # 待优化的参数为神经网络net中的所有参数
, lr=lr # 学习率
, momentum=gamma # 动量参数
)
# 对数据进行预处理
epochs = 2
batch_size = 4000
data = TensorDataset(X, y)
batchdata = DataLoader(data, batch_size=batch_size, shuffle=True)
#可以使用.datasets查看数据集相关的属性
print(len(batchdata.dataset)) #总共有多少数据
50000
print(batchdata.dataset[0:5]) # 查看其中前五个样本(是前五个样本,而不是前五个batch)
代码打印结果如下:
(tensor([[ 8.8439e-01, -1.3363e-01, 6.6058e-01, 9.2611e-01, 7.8543e-01,
-5.5079e-01, -2.7012e-01, -5.5204e-01, 1.2068e+00, 1.4919e+00,
9.6343e-01, 8.7922e-01, 1.5607e+00, 4.8819e-01, -1.1414e-01,
6.1098e-02, 3.0282e-01, 1.0431e+00, -9.7493e-01, -1.0644e+00],
[-5.7288e-01, 2.6316e+00, 3.0401e-01, 7.4866e-01, 3.1152e-01,
-1.8385e+00, -2.2854e-01, 5.2794e-01, 1.7308e+00, -3.5668e-01,
-8.9991e-01, -1.8500e-01, -2.6706e-01, 1.8283e+00, -2.8022e-01,
-1.8416e-01, 6.4034e-01, -1.5039e+00, 3.2260e-01, 5.0082e-01],
[-1.0566e+00, -2.6430e-01, 3.2868e+00, 9.8606e-01, -8.6114e-01,
2.5253e+00, 1.6629e+00, 2.3904e-01, -7.2164e-01, -5.8973e-01,
-7.1223e-01, -6.8196e-01, -1.3684e-01, 1.3941e+00, -2.0936e+00,
8.1910e-01, -9.5350e-01, 1.3128e-03, -9.2216e-01, -1.1640e-01],
[-4.5827e-01, 1.1861e+00, 9.8771e-01, 6.4958e-01, -1.0657e+00,
5.7455e-01, -2.0034e-01, 1.2669e+00, 1.0582e+00, 2.5852e+00,
1.4684e+00, 1.6047e+00, 1.7991e+00, -7.6422e-01, 8.1970e-01,
1.3128e+00, -2.3513e-01, 8.0393e-01, -4.1873e-01, 4.4923e-01],
[ 1.2122e+00, -1.0183e+00, 1.4373e+00, 9.6765e-01, 5.5025e-01,
-8.8258e-01, -3.0236e-01, 1.2925e+00, 1.1455e+00, 1.0128e+00,
-7.7963e-01, -6.5754e-01, -1.4936e-01, 4.8650e-01, -8.1125e-02,
1.3128e+00, 3.0601e-01, 1.9770e+00, -1.0962e+00, -1.0372e+00]]), tensor([2., 1., 0., 2., 0.]))
也可以分别查看样本的特征张量和标签:
print(batchdata.dataset[0][0]) #查看第一个样本的特征张量
tensor([ 0.8844, -0.1336, 0.6606, 0.9261, 0.7854, -0.5508, -0.2701, -0.5520,
1.2068, 1.4919, 0.9634, 0.8792, 1.5607, 0.4882, -0.1141, 0.0611,
0.3028, 1.0431, -0.9749, -1.0644])
print(batchdata.dataset[0][1]) #查看第一个样本的标签
tensor(2.)
#属性batch_size,查看现在的batch_size是多少
batchdata.batch_size
4000
我们在迭代时,常常这样使用:
# 迭代循环
for i_epoch in range(epochs):
for i_batch, (xb, yb) in enumerate(batchdata):
opt.zero_grad()
z_hat = net.forward(xb)
loss = criterion(z_hat, yb.long())
loss.backward()
opt.step()
print("epoch: {0}; batch: {1}; loss: {2}".format(i_epoch, i_batch, loss))
epoch: 0; batch: 0; loss: 1.1020163297653198
epoch: 0; batch: 1; loss: 1.1016513109207153
epoch: 0; batch: 2; loss: 1.1053823232650757
epoch: 0; batch: 3; loss: 1.1038187742233276
epoch: 0; batch: 4; loss: 1.101757526397705
epoch: 0; batch: 5; loss: 1.100555419921875
epoch: 0; batch: 6; loss: 1.1004079580307007
epoch: 0; batch: 7; loss: 1.1001160144805908
epoch: 0; batch: 8; loss: 1.1007158756256104
epoch: 0; batch: 9; loss: 1.1017191410064697
epoch: 0; batch: 10; loss: 1.09959077835083
epoch: 0; batch: 11; loss: 1.0989680290222168
epoch: 0; batch: 12; loss: 1.1008515357971191
epoch: 1; batch: 0; loss: 1.0995385646820068
epoch: 1; batch: 1; loss: 1.0994977951049805
epoch: 1; batch: 2; loss: 1.0994902849197388
epoch: 1; batch: 3; loss: 1.0982191562652588
epoch: 1; batch: 4; loss: 1.0990889072418213
epoch: 1; batch: 5; loss: 1.099034070968628
epoch: 1; batch: 6; loss: 1.0993760824203491
epoch: 1; batch: 7; loss: 1.0985561609268188
epoch: 1; batch: 8; loss: 1.0986825227737427
epoch: 1; batch: 9; loss: 1.0986409187316895
epoch: 1; batch: 10; loss: 1.098567008972168
epoch: 1; batch: 11; loss: 1.0989420413970947
epoch: 1; batch: 12; loss: 1.0988399982452393
我们看到,在多次迭代中,损失函数虽有波动,但总体呈现下降的趋势,神经网络在迭代中逐步被优化。
在下一篇文章中,我们将把小批量梯度下降法应用到 FashionMNIST 数据集的训练中,敬请期待!