1 网络搭建
1.1 导入数据
参考 上一篇博客 pytorch 基础 网络搭建(二),导入数据。参考 知乎
import torch
import torchvision
from torchvision import transforms, datasets
train = datasets.MNIST('', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor()
]))
test = datasets.MNIST('', train=False, download=True,
transform=transforms.Compose([
transforms.ToTensor()
]))
trainset = torch.utils.data.DataLoader(train, batch_size=10, shuffle=True)
testset = torch.utils.data.DataLoader(test, batch_size=10, shuffle=False)
1.2 搭建网络
步骤如下:
1、导入库。
import torch.nn as nn
import torch.nn.functional as F
torch.nn
库 让我们可以访问一些有用的神经网络事物,例如各种神经网络层类型(诸如常规全连接层、卷积层(用于图像)、循环层等)。目前,我们只讨论了全连接层,所以我们现在只使用它们。
torch.nn.functional
库 专门为我们提供了一些我们可能不想自己编写的方便函数的访问权限。比如激活函数,优化器,分类器等。这里我们将使用 relu 作为神经元的激活函数。
2、搭建网络。
class Net(nn.Module):
def __init__(self):
# use super() to inherit the parent class functionality
super().__init__()
self.fc1 = nn.Linear(28 * 28, 64)
self.fc2 = nn.Linear(64, 64)
self.fc3 = nn.Linear(64, 64)
self.fc4 = nn.Linear(64, 10)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = F.relu(self.fc3(x))
x = self.fc4(x)
return F.log_softmax(x, dim=1)
net = Net()
print(net)
为了制作我们的模型,我们将创建一个类。我们称这个类为
net
,这个net
继承自nn.Module
类。初始化后,便可以开始编写神经网络了,这里我们就是随便创建了 4 层,并称它们为:
fc1, fc2, fc3, fc4
(这里的 fc 是 fully connected 的意思,就是全连接层)。
nn.Linear
的作用是创建一个线性连接,即:y=wx+b
并为每一次的输入和输出神经元数量赋值。第一层的
28*28
为图片的输入量(训练集中所有图片大小都是28*28
的,这里我们将其扁平化了,实际上应该是1*784
),64
为输出层的数量。此后的每一层输入数都等于上一层的输出数。最后一层的输出数量为10
,这是因为我们一种有10
个类别。设置好网络的层数后,还需要设置网络的连接方式,或者说是前向传播方式 (Forward propagation)。这里我们选择的前向传播方式很简单,如函数
forward()
所示,层层递进。每层之间还包含一个relu
激活函数F.relu()
。 这些激活函数使我们的数据保持在 0 和 1 之间。最后,对于输出层,我们将使用
softmax
。Softmax
适用于多类问题,其中每一件事只能是一类或另一类。这意味着输出本身是一个置信度分数,加起来为 1。
输出结果为:
Net(
(fc1): Linear(in_features=784, out_features=64, bias=True)
(fc2): Linear(in_features=64, out_features=64, bias=True)
(fc3): Linear(in_features=64, out_features=64, bias=True)
(fc4): Linear(in_features=64, out_features=10, bias=True)
)
3、 随机创建一个图像,并输入网络。
让我们创建一个随机图像:
X = torch.randn((28,28))
所以这就像我们的图像,一个 28x28 的张量(数组),值范围从 0 到 1。我们的神经网络希望它被展平,但是这样:
X = X.view(-1,28*28)
但是为什么前面是 -1 呢?
神经网络的任何输入和输出都应该是一组特征集。即使我们打算只传递一组特性,仍然必须将其作为特性“列表”传递。
在我们的例子中,我们真的只想要一个 1x784,我们可以这么说,但你更经常会在这些整形中使用 -1。为什么? -1 表示“任何大小”。所以它可能是 1, 12, 92, 15295...等等。这是使该位可变的一种方便方法。在这种情况下,可变部分是我们将通过多少“样本”。
即使我们只想预测一个输入,它也需要是一个输入列表,而输出将是一个输出列表。不是一个真正的列表,它是一个张量。
X = torch.randn((28,28))
X = X.view(-1,28*28)
output = net(X)
print(output)
输出结果为:
tensor([[-2.3506, -2.2138, -2.4579, -2.3719, -2.4202, -2.3615, -2.2921, -2.1629,
-2.2845, -2.1592]], grad_fn=<LogSoftmaxBackward>)
目前来说我们这个数据得到的输出对我们来说毫无价值。我们想要的是迭代我们的数据集以及进行反向传播,这就是我们将在下一个教程中介绍的内容。
2 网络训练(反向传播)
参考之前的系列文章,已完成的代码如下:
import torch.nn as nn
import torch.nn.functional as F
# 1、导入数据集的库
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
# 2、加载数据集,root 指的是存放数据集的路径,ToTensor()完成数据类型强制转换,
train_data = datasets.MNIST(root="mnist", train=True, transform=ToTensor(), download=False)
test_data = datasets.MNIST(root="mnist", train=False, transform=ToTensor(), download=False)
# 3、将数据分组,喂给模型训练,并随机打乱数据(shuffle=True)
trainset = torch.utils.data.DataLoader(train_data, batch_size=10, shuffle=True)
testset = torch.utils.data.DataLoader(test_data, batch_size=10, shuffle=False)
# 4、定义网络
class Net(nn.Module):
def __init__(self):
# use super() to inherit the parent class functionality
super().__init__()
self.fc1 = nn.Linear(28 * 28, 64)
self.fc2 = nn.Linear(64, 64)
self.fc3 = nn.Linear(64, 64)
self.fc4 = nn.Linear(64, 10)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = F.relu(self.fc3(x))
x = self.fc4(x)
return F.log_softmax(x, dim=1)
net = Net()
print(net)
2.1 优化器 optimizer
对我们来说幸运的是, Pytorch 使用的“数据”实际上已经分批了,我们只需要迭代它。接下来,我们要计算损失并指定我们的优化器:
import torch.optim as optim
optimizer = optim.Adam(net.parameters(), lr=0.001)
2.2 损失函数(loss_function)
损失函数
loss_function
用于计算我们的分类与现实的之间的差距。作为人类,我们倾向于根据对或错来思考事物。但神经网络,则是已经一件事物的可能性来判断事情。就机器学习而言,为了使得模型准确,需要通过调整大量的参数以逐渐接近拟合。为此,我们使用损失loss
,这是衡量神经网络与目标输出的距离。损失计算的方法有很多种。一种比较流行的方法是使用均方误差。MSE=∑i=1n(yi−y^i)2N
由于我们的例子是一个分类问题,因此这里我们使用的损失函数是
nll_loss
接下来,我们需要一个优化器,这里我们使用的是
Adam
优化器,它是一种比较流行的优化器。Adam
可以自适应动量和梯度,这样可以避免梯度爆炸的问题。在将这个之前,我们先来了解一下学习率learning_rate
,这是一个可以调整的参数,它决定了模型学习的速度。过快会出现反复横跳,过慢训练时间就会很长。
通常来说,我们希望学习率先大后小,这样可以帮助我们的模型快速找到最优解附近,然后在该点位附近慢慢的找到最优点。
2.3 时期 (epoch)
现在我们可以迭代我们的数据。通常,将不止一次通过整个训练数据集。数据集的每一次完整传递都称为一个时期 (
epoch
)。一般来说,你可能会有 3 到 10 个epoch
,但这里没有硬性规定。时期太少,模型不会学习完所有的数据。时期太多,模型可能会过度拟合您的样本内数据(基本上记住样本内数据,但在样本外数据上表现不佳)。现在让我们使用 3 个
epoch
。所以我们将循环遍历epoch
,每个epoch
都会遍历我们的数据。就像是:
for epoch in range(3): # 3 full passes over the data
for data in trainset: # `data` is a batch of data
X, y = data # X is the batch of features, y is the batch of targets.
net.zero_grad() # sets gradients to 0 before loss calc. You will do this likely every step.
output = net(X.view(-1,784)) # pass in the reshaped batch (recall they are 28x28 atm)
loss = F.nll_loss(output, y) # calc and grab the loss value
loss.backward() # apply this loss backwards thru the network's parameters
optimizer.step() # attempt to optimize weights to account for loss/gradients
print(loss) # print loss. We hope loss (a measure of wrong-ness) declines!
tensor(0.0348, grad_fn=<NllLossBackward0>)
tensor(0.0033, grad_fn=<NllLossBackward0>)
tensor(0.1283, grad_fn=<NllLossBackward0>)
2.4 总结
在上面的代码中,有一行
net.zero_grad()
在每一批次数据通过之后,将梯度设置为零,下一批次的数据将在之前已经优化过的模型上,从新开始梯度下降。下面我们来总结一下在一个批次中,我们让神经网络做了什么:
- 从当前批次中获取特征 (X) 和标签 (y)
- 将梯度归零 (net.zero_grad)
- 通过网络传递数据
- 计算损失
- 调整网络中的权重以减少损失
3. 验证模型
当我们迭代时,我们会得到损失值,这是一个重要的指标,但我们关心的是准确性。那么,我们是怎么做的呢?为了测试这一点,我们需要做的就是迭代我们的测试集,通过将输出与目标值进行比较来测量正确性。
correct = 0
total = 0
with torch.no_grad():
for data in testset:
X, y = data
output = net(X.view(-1,784))
#print(output)
for idx, i in enumerate(output):
#print(torch.argmax(i), y[idx])
if torch.argmax(i) == y[idx]: # torch.argmax(i) 返回变量i的最大值的维度
correct += 1
total += 1
print("Accuracy: ", round(correct/total, 3))
Accuracy: 0.967
结果看起来还不错,准确率达到了 0.967。光有准确率还不够直观,下面我们将图片和结果打印出来,看一下结果是否正确:
import matplotlib.pyplot as plt
# randomly select a few images from the test set and plot them
plt.subplot(1,2,1)
plt.imshow(X[5].view(28,28))
plt.subplot(1,2,2)
plt.imshow(X[3].view(28,28))
plt.show()
print(torch.argmax(net(X[5].view(-1,784))[0]))
print(torch.argmax(net(X[3].view(-1,784))[0]))
其中X[3] 表示 一个batch里面的第四张图片,X[3].view(-1,784) 将图片强制转换为网络要求的输入形式,net(X[5].view(-1,784)) 将图片送入网络,得到输出列表,因为送入的图片只有一张,取列表的第一个结果net(X[5].view(-1,784))[0] ,这个结果包含了此图片在10个类别的得分,再采用torch.argmax得到得分最高的类别。下面的代码得到了类似的效果:
a_featureset = X[0]
reshaped_for_network = a_featureset.view(-1,784) # 784 b/c 28*28 image resolution.
output = net(reshaped_for_network) #output will be a list of network predictions.
first_pred = output[0]
print(first_pred)
tensor([-2.5393e+01, -1.7943e+01, -1.1810e+01, -1.5074e+01, -2.7140e+01,
-2.0390e+01, -3.6855e+01, -7.7486e-06, -2.1229e+01, -1.8540e+01],
grad_fn=<SelectBackward0>)
哪个指标值最大?我们使用 argmax
来找到这个:
biggest_index = torch.argmax(first_pred)
print(biggest_index)
tensor(7)