深度学习实验三:多层感知机编程
本次实验练习使用torch.nn中的类设计一个多层感知机,并进行训练和测试。
同时练习使用Dataset和Dataloader辅助Mini-Batch随机梯度下降法对模型进行训练。
name = 'yyh'#填写你的姓名
sid = 'B02014152'#填写你的学号
print('姓名:%s, 学号:%s'%(name, sid))
姓名:yyh, 学号:B02014152
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
1. 准备数据集
本实验使用MNIST数据集,并用dataloader加载器,从数据集中采样出小批量样本。
from torchvision import datasets,transforms
data_path = '../data/'
mnist_train = datasets.MNIST(data_path,download=True,train = True,transform = transforms.ToTensor())
mnist_test = datasets.MNIST(data_path,download=True,train = False,transform = transforms.ToTensor())
#看一下mnist_train的内容
mnist_train
Dataset MNIST
Number of datapoints: 60000
Root location: ../data/
Split: Train
StandardTransform
Transform: ToTensor()
len(mnist_train)
60000
# 为mnist_train 构造一个迭代器
it_mnist_train = iter(mnist_train)
# 观察mnist_train中的元素
next(it_mnist_train)# 返回一个tuple,包含一张图像以及对应的label
(tensor([[[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0118, 0.0706, 0.0706, 0.0706,
0.4941, 0.5333, 0.6863, 0.1020, 0.6510, 1.0000, 0.9686, 0.4980,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.1176, 0.1412, 0.3686, 0.6039, 0.6667, 0.9922, 0.9922, 0.9922,
0.9922, 0.9922, 0.8824, 0.6745, 0.9922, 0.9490, 0.7647, 0.2510,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.1922,
0.9333, 0.9922, 0.9922, 0.9922, 0.9922, 0.9922, 0.9922, 0.9922,
0.9922, 0.9843, 0.3647, 0.3216, 0.3216, 0.2196, 0.1529, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0706,
0.8588, 0.9922, 0.9922, 0.9922, 0.9922, 0.9922, 0.7765, 0.7137,
0.9686, 0.9451, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.3137, 0.6118, 0.4196, 0.9922, 0.9922, 0.8039, 0.0431, 0.0000,
0.1686, 0.6039, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0549, 0.0039, 0.6039, 0.9922, 0.3529, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.5451, 0.9922, 0.7451, 0.0078, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0431, 0.7451, 0.9922, 0.2745, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.1373, 0.9451, 0.8824, 0.6275,
0.4235, 0.0039, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.3176, 0.9412, 0.9922,
0.9922, 0.4667, 0.0980, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.1765, 0.7294,
0.9922, 0.9922, 0.5882, 0.1059, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0627,
0.3647, 0.9882, 0.9922, 0.7333, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.9765, 0.9922, 0.9765, 0.2510, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.1804, 0.5098,
0.7176, 0.9922, 0.9922, 0.8118, 0.0078, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.1529, 0.5804, 0.8980, 0.9922,
0.9922, 0.9922, 0.9804, 0.7137, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0941, 0.4471, 0.8667, 0.9922, 0.9922, 0.9922,
0.9922, 0.7882, 0.3059, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0902, 0.2588, 0.8353, 0.9922, 0.9922, 0.9922, 0.9922, 0.7765,
0.3176, 0.0078, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0706, 0.6706,
0.8588, 0.9922, 0.9922, 0.9922, 0.9922, 0.7647, 0.3137, 0.0353,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.2157, 0.6745, 0.8863, 0.9922,
0.9922, 0.9922, 0.9922, 0.9569, 0.5216, 0.0431, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.5333, 0.9922, 0.9922, 0.9922,
0.8314, 0.5294, 0.5176, 0.0627, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000]]]),
5)
img,label = next(it_mnist_train)
#img是一个1*28*28的张量
img.shape
torch.Size([1, 28, 28])
plt.imshow(img[0],cmap = 'gray') # camp设置gary,即以灰度色彩通道显示
plt.axis('off') # 不显示坐标轴
plt.title('Label = %d'%(label))
plt.show()
#查看样本的取值范围
print('Max = ',img.max().item(),', Min=',img.min().item())
Max = 1.0 , Min= 0.0
下面为mnist_train构造一个加载器
train_loader = torch.utils.data.DataLoader(mnist_train, batch_size = 32, shuffle = True)
#用加载器加载一批样本
images,labels = next(iter(train_loader))
images.shape
torch.Size([32, 1, 28, 28])
labels.shape
torch.Size([32])
下面展示这一批样本及其对应的label
plt.figure(figsize = (16,9))
for k in range(labels.shape[0]):
plt.subplot(4,8,k+1)
plt.imshow(images[k].squeeze(),cmap = 'gray')
plt.axis('off')
plt.title('%d'%(labels[k]))
plt.show()
2. 设计MLP
从torch.nn.Module派生一个子类,表示一个MLP;
你设计的MLP至少包含两个隐层,输出层神经元数目为10,表示10个不同数字类别
注意,网络的输入是 n × 784 n\times784 n×784的二维张量,每一行表示一个样本。
#在下面添加代码,实现一个表示多层感知机的类MLP,用于识别MNIST手写体数字
class MLP(nn.Module):
def __init__(self, in_features, out_features):
super().__init__() # 首先调用父类的构造函数
# 定义对象属性
self.hid1 = nn.Linear(in_features = in_features, out_features=512, bias=True)
self.hid2 = nn.Linear(in_features = 512, out_features=256, bias=True)
self.hid3 = nn.Linear(in_features = 256, out_features=128, bias=True)
self.out = nn.Linear(in_features = 128, out_features=out_features, bias=True)
def forward(self, x):
z1 = self.hid1(x)
a1 = F.relu(z1)
z2 = self.hid2(a1)
a2 = F.relu(z2)
z3 = self.hid3(a2)
a3 = F.relu(z3)
z4 = self.out(a3)
a4 = F.softmax(z4, dim=1)
return a4
#测试MLP类
X = torch.rand((10,784),dtype = torch.float32)
net = MLP(784,10)
Y = net(X)
print(Y)
tensor([[0.0922, 0.1060, 0.1071, 0.0935, 0.0893, 0.1091, 0.0930, 0.1085, 0.1031,
0.0982],
[0.0916, 0.1062, 0.1078, 0.0947, 0.0909, 0.1100, 0.0929, 0.1079, 0.1019,
0.0961],
[0.0911, 0.1055, 0.1068, 0.0942, 0.0895, 0.1086, 0.0941, 0.1098, 0.1038,
0.0965],
[0.0917, 0.1078, 0.1058, 0.0945, 0.0920, 0.1084, 0.0918, 0.1078, 0.1031,
0.0972],
[0.0923, 0.1074, 0.1069, 0.0939, 0.0912, 0.1070, 0.0921, 0.1089, 0.1033,
0.0970],
[0.0914, 0.1048, 0.1070, 0.0940, 0.0902, 0.1082, 0.0936, 0.1099, 0.1034,
0.0974],
[0.0920, 0.1075, 0.1054, 0.0948, 0.0908, 0.1079, 0.0923, 0.1097, 0.1031,
0.0966],
[0.0912, 0.1068, 0.1047, 0.0960, 0.0902, 0.1088, 0.0937, 0.1090, 0.1028,
0.0967],
[0.0904, 0.1045, 0.1063, 0.0943, 0.0908, 0.1082, 0.0929, 0.1113, 0.1033,
0.0981],
[0.0912, 0.1057, 0.1078, 0.0947, 0.0896, 0.1089, 0.0937, 0.1088, 0.1030,
0.0965]], grad_fn=<SoftmaxBackward>)
nn.Module
的方法parameters()返回模型中的所有参数张量,named_parameters()返回模型中的参数名称以及对应的参数。请对上面定义的网络模型,调用这两个方法获取它的参数名称和参数大小(Tensor.size()函数)。
net.parameters()#得到一个生成器
<generator object Module.parameters at 0x000002EC553FAAC0>
#通过枚举该生成器可以获得每一个参数
next(net.parameters()) # 值都是随机初始化的
Parameter containing:
tensor([[ 0.0204, 0.0030, -0.0248, ..., -0.0017, -0.0351, 0.0142],
[-0.0070, -0.0173, -0.0080, ..., -0.0282, -0.0108, 0.0180],
[ 0.0185, -0.0228, 0.0084, ..., 0.0165, 0.0039, 0.0243],
...,
[ 0.0220, -0.0135, 0.0349, ..., 0.0077, 0.0071, 0.0046],
[ 0.0135, 0.0169, -0.0084, ..., 0.0199, 0.0217, -0.0070],
[ 0.0270, -0.0356, 0.0228, ..., -0.0059, -0.0012, -0.0098]],
requires_grad=True)
net.named_parameters()#得到一个生成器
<generator object Module.named_parameters at 0x000002EC553FA660>
#通过枚举该生成器可以获得每一个参数的名字和张量
param = next(net.parameters())
param
Parameter containing:
tensor([[ 0.0204, 0.0030, -0.0248, ..., -0.0017, -0.0351, 0.0142],
[-0.0070, -0.0173, -0.0080, ..., -0.0282, -0.0108, 0.0180],
[ 0.0185, -0.0228, 0.0084, ..., 0.0165, 0.0039, 0.0243],
...,
[ 0.0220, -0.0135, 0.0349, ..., 0.0077, 0.0071, 0.0046],
[ 0.0135, 0.0169, -0.0084, ..., 0.0199, 0.0217, -0.0070],
[ 0.0270, -0.0356, 0.0228, ..., -0.0059, -0.0012, -0.0098]],
requires_grad=True)
#param的类型是Parameter
type(param)
torch.nn.parameter.Parameter
#Parameter.data是真正存储权重的张量
param.data
tensor([[ 0.0204, 0.0030, -0.0248, ..., -0.0017, -0.0351, 0.0142],
[-0.0070, -0.0173, -0.0080, ..., -0.0282, -0.0108, 0.0180],
[ 0.0185, -0.0228, 0.0084, ..., 0.0165, 0.0039, 0.0243],
...,
[ 0.0220, -0.0135, 0.0349, ..., 0.0077, 0.0071, 0.0046],
[ 0.0135, 0.0169, -0.0084, ..., 0.0199, 0.0217, -0.0070],
[ 0.0270, -0.0356, 0.0228, ..., -0.0059, -0.0012, -0.0098]])
#使用net.parameters()获取和输出net中的所有参数张量的大小
#大约2行代码
for param in net.parameters():
print(param.shape)
torch.Size([128, 784])
torch.Size([128])
torch.Size([10, 128])
torch.Size([10])
#使用net.named_parameters()获得net中的所有参数的名字和大小
#大约2行代码
for name, param in net.named_parameters():
print(name, ":", param.shape)
hid1.weight : torch.Size([512, 784])
hid1.bias : torch.Size([512])
hid2.weight : torch.Size([256, 512])
hid2.bias : torch.Size([256])
hid3.weight : torch.Size([128, 256])
hid3.bias : torch.Size([128])
out.weight : torch.Size([10, 128])
out.bias : torch.Size([10])
nn.Module
有一个对象属性_parameters
,使用一个OrderedDict包含了所有参数。可以直接从这个属性获得模型的参数字典
#直接获取MLP中的参数属性_parameters
net._parameters
OrderedDict()
nn.Module
的成员发给发modules()
和named_modules()
分别返回模型的子模块以及命名子模块
net.modules()
<generator object Module.modules at 0x000002EC5B16AD60>
next(net.modules())
MLP(
(hid1): Linear(in_features=784, out_features=512, bias=True)
(hid2): Linear(in_features=512, out_features=256, bias=True)
(hid3): Linear(in_features=256, out_features=128, bias=True)
(out): Linear(in_features=128, out_features=10, bias=True)
)
net.named_modules()
<generator object Module.named_modules at 0x000002EC5B16AAC0>
next(net.named_modules())
('',
MLP(
(hid1): Linear(in_features=784, out_features=512, bias=True)
(hid2): Linear(in_features=512, out_features=256, bias=True)
(hid3): Linear(in_features=256, out_features=128, bias=True)
(out): Linear(in_features=128, out_features=10, bias=True)
))
#输出MLP中的子模块:
#大约2行代码
for module in net.modules():
print(module)
MLP(
(hid1): Linear(in_features=784, out_features=512, bias=True)
(hid2): Linear(in_features=512, out_features=256, bias=True)
(hid3): Linear(in_features=256, out_features=128, bias=True)
(out): Linear(in_features=128, out_features=10, bias=True)
)
Linear(in_features=784, out_features=512, bias=True)
Linear(in_features=512, out_features=256, bias=True)
Linear(in_features=256, out_features=128, bias=True)
Linear(in_features=128, out_features=10, bias=True)
#获取MLP中的命名子模块
#大约2行代码
for name, module in net.named_modules():
print(name, ":", module)
: MLP(
(hid1): Linear(in_features=784, out_features=512, bias=True)
(hid2): Linear(in_features=512, out_features=256, bias=True)
(hid3): Linear(in_features=256, out_features=128, bias=True)
(out): Linear(in_features=128, out_features=10, bias=True)
)
hid1 : Linear(in_features=784, out_features=512, bias=True)
hid2 : Linear(in_features=512, out_features=256, bias=True)
hid3 : Linear(in_features=256, out_features=128, bias=True)
out : Linear(in_features=128, out_features=10, bias=True)
也可通过nn.Module
的对象属性_modules
获得模型的所有子模块。
#直接通过属性_modules获取MLP中的子模块
net._modules
OrderedDict([('hid1', Linear(in_features=784, out_features=512, bias=True)),
('hid2', Linear(in_features=512, out_features=256, bias=True)),
('hid3', Linear(in_features=256, out_features=128, bias=True)),
('out', Linear(in_features=128, out_features=10, bias=True))])
net._modules
是一个有序字典,因此可以使用关键字访问字典的元素,关键字是各个子模块的名字。比如’fc1’,可以通过’fc1’这个关键字获取对应的子模块:net_modules['fc1']
.
#在下面输出net对象的第一个子模块的'weight'参数
#大约一行代码
net._modules['hid1'].weight
Parameter containing:
tensor([[ 0.0204, 0.0030, -0.0248, ..., -0.0017, -0.0351, 0.0142],
[-0.0070, -0.0173, -0.0080, ..., -0.0282, -0.0108, 0.0180],
[ 0.0185, -0.0228, 0.0084, ..., 0.0165, 0.0039, 0.0243],
...,
[ 0.0220, -0.0135, 0.0349, ..., 0.0077, 0.0071, 0.0046],
[ 0.0135, 0.0169, -0.0084, ..., 0.0199, 0.0217, -0.0070],
[ 0.0270, -0.0356, 0.0228, ..., -0.0059, -0.0012, -0.0098]],
requires_grad=True)
也可以直接用你在上面的类定义中的对象属性来访问模型中的子模块:比如,你在初始化函数中定义了一个线性层:self.lin = Linear(…)。你可以使用lin这个属性获取这个线性层。
Linear
等网络层都有一个权重参数weight,你可以直接使用net.lin.weight获取到这个参数。
#在下面输出net对象的第一层的权重参数
#大约一行代码
net.hid1.weight
Parameter containing:
tensor([[ 0.0204, 0.0030, -0.0248, ..., -0.0017, -0.0351, 0.0142],
[-0.0070, -0.0173, -0.0080, ..., -0.0282, -0.0108, 0.0180],
[ 0.0185, -0.0228, 0.0084, ..., 0.0165, 0.0039, 0.0243],
...,
[ 0.0220, -0.0135, 0.0349, ..., 0.0077, 0.0071, 0.0046],
[ 0.0135, 0.0169, -0.0084, ..., 0.0199, 0.0217, -0.0070],
[ 0.0270, -0.0356, 0.0228, ..., -0.0059, -0.0012, -0.0098]],
requires_grad=True)
3.训练模型
3.1 第一步,对数据做预处理
MNIST图像的像素取值范围是[0,1],先把值域改变为[-1,1]. 在PyTorch中,可以使用torchvision.transforms.Normalize类处理。
from torchvision.transforms import Normalize as normalize
#查看img的最大最小值:
img.min(),img.max()
(tensor(0.), tensor(1.))
#构造一个图像归一化对象normalizer,将图像像素值归一化到[-1,1]区间
#一行代码
normalizer = normalize(0.5,0.5)
#测试normalizer
normalized_img = normalizer(img)
normalized_img.min(),normalized_img.max()
(tensor(-1.), tensor(1.))
3.2 第二步,构造训练集,加入预处理
data_path = '../data/'
mnist_train = datasets.MNIST(data_path,download=False,train = True,transform = transforms.Compose([transforms.ToTensor(),normalizer]))
mnist_test = datasets.MNIST(data_path,download=False,train = False,transform = transforms.Compose([transforms.ToTensor(),normalizer]))
3.3 第三步,构造加载器
train_loader = torch.utils.data.DataLoader(mnist_train, batch_size = 32, shuffle = True) # 置乱
test_loader = torch.utils.data.DataLoader(mnist_test, batch_size = 32, shuffle = True)
3.4 第四步,训练模型
训练模型的过程包含两层循环,第一层是轮次(epoch)循环,每一次循环遍历一次整个训练样本集;第二层是小批次循环,每一次循环从加载器中加载一个小批次样本,用于训练模型,完成一次迭代。
一次迭代包含以下步骤:
- 梯度清零。调用优化器的zero_grad()函数
- 前向传播。调用模型对当前批次的样本做预测
- 计算损失。根据预测结果与真实label计算损失
- 后向传播。调用损失张量的backward()函数,更新模型权重的梯度
- 梯度下降。调用优化器的step()函数,更新权重参数。
import torch
a = torch.tensor([[1,2,3],[2,3,4]])
print(a.shape)
a = a.view([6])
print(a.shape)
torch.Size([2, 3])
torch.Size([6])
#请在下面定义一个训练模型的函数Train:
def Train(model, loader, epochs, lr = 0.1):
'''参数说明:
model:模型对象;
loader:样本加载器;
epochs:轮次;
lr:学习率
'''
epsilon = 1e-6#损失收敛条件
#首先调用模型的train函数,进入train模式
model.train()
#在下面定义优化器
optimizer = optim.SGD(params=net.parameters(),lr = lr)#请用一行代码实现
#在下面定义损失函数(交叉熵损失):
loss = F.cross_entropy#请用一行代码实现
loss0 = 0
for epoch in range(epochs):
for it,(imgs, labels) in enumerate(loader):
#1. zero_grads
#请用一行代码实现
optimizer.zero_grad()
#2. F.P.前向传播
#请用一行代码实现
logits = net(imgs.view([imgs.shape[0],784]))
#3. 计算损失
loss1 = loss(logits, labels)#请用一行代码实现
if(abs(loss1.item() - loss0)<epsilon):
break
loss0 = loss1.item()
if it%100==0:
print('epoch %d, iter %d, loss = %f\n'%(epoch,it,loss1.item()))
#4. 后向传播
#请用一行代码实现
loss1.backward()
#5. 梯度下降
#请用一行代码实现.
optimizer.step()
return model
#训练模型
model = MLP(784, 10)
#为了尽快训练模型,这里使用测试集训练10轮,你可以修改轮次和学习率
# for it,(imgs, labels) in enumerate(test_loader):
# print(imgs.shape)
# print(labels)
# break
model = Train(model, test_loader, 10)
epoch 0, iter 0, loss = 1.547397
epoch 0, iter 100, loss = 1.738499
epoch 0, iter 200, loss = 1.704441
epoch 0, iter 300, loss = 1.494424
epoch 1, iter 0, loss = 1.599131
epoch 1, iter 100, loss = 1.521236
epoch 1, iter 200, loss = 1.508781
epoch 1, iter 300, loss = 1.628269
epoch 2, iter 0, loss = 1.628033
epoch 2, iter 100, loss = 1.652629
epoch 2, iter 200, loss = 1.580010
epoch 2, iter 300, loss = 1.552869
epoch 3, iter 0, loss = 1.536373
epoch 3, iter 100, loss = 1.620058
epoch 3, iter 200, loss = 1.645423
epoch 3, iter 300, loss = 1.609594
epoch 4, iter 0, loss = 1.553720
epoch 4, iter 100, loss = 1.662021
epoch 4, iter 200, loss = 1.639694
epoch 4, iter 300, loss = 1.589251
epoch 5, iter 0, loss = 1.585000
epoch 5, iter 100, loss = 1.616509
epoch 5, iter 200, loss = 1.523335
epoch 5, iter 300, loss = 1.615527
epoch 6, iter 0, loss = 1.664444
epoch 6, iter 100, loss = 1.708724
epoch 6, iter 200, loss = 1.523149
epoch 6, iter 300, loss = 1.708055
epoch 7, iter 0, loss = 1.620650
epoch 7, iter 100, loss = 1.615701
epoch 7, iter 200, loss = 1.524182
epoch 7, iter 300, loss = 1.615786
epoch 8, iter 0, loss = 1.461354
epoch 8, iter 100, loss = 1.588786
epoch 8, iter 200, loss = 1.585996
epoch 8, iter 300, loss = 1.542477
epoch 9, iter 0, loss = 1.554955
epoch 9, iter 100, loss = 1.611661
epoch 9, iter 200, loss = 1.565567
epoch 9, iter 300, loss = 1.511465
3.5 第五步,测试模型
请编写一个用于评估模型分类准确率的函数Evaluate。使用给定的数据加载器,对模型的预测结果与真实label进行比对,计算出模型的准确率。
#编写模型测试过程
def Evaluate(model, loader):
'''参数说明:
model:待测试的模型
loader:数据加载器
返回值为模型的预测准确率
'''
#进入评估模式:
#一行代码
net.eval()
correct = 0#用于累加每一次循环中预测的正确样本数
counts = 0#用于累加参与测试的样本总数
#在下面编写代码,从loader中加载样本,使用model预测,然后统计预测正确的样本数量
for it,(imgs, labels) in enumerate(loader):
output = net(imgs.view(imgs.shape[0], 784))
pred = output.argmax(dim = 1)
# pred = torch.max(output.data, 1)[1]
counts += imgs.shape[0]
correct += (pred==labels).sum()
# pred = output.data.max(1, keepdim=True)[1] # 获取最大值的位置,[batch_size,1]
# correct += pred.eq(labels.data.view_as(pred)).sum()
# counts += imgs.shape[0]
#计算准确率
print(correct)
accuracy = correct / counts
return accuracy
#使用test_loader评估模型的准确率|
acc = Evaluate(model,test_loader)
print('Accuracy = %f'%(acc))
tensor(8859)
Accuracy = 0.885900
#展示预测结果实例
imgs,labels = next(iter(test_loader))
logits = net(imgs.view(imgs.size(0),-1))
yhat = logits.argmax(dim = 1)
print(yhat)
plt.figure(figsize = (16,10))
for i in range(imgs.size(0)):
plt.subplot(4,8,i+1)
plt.imshow(imgs[i].squeeze()/2+0.5,cmap = 'gray')
plt.axis('off')
plt.title('GT=%d, Pred = %d'%(labels[i],yhat[i]))
plt.show()
tensor([4, 3, 8, 7, 2, 7, 6, 2, 1, 7, 2, 7, 0, 3, 7, 9, 9, 9, 9, 3, 2, 0, 8, 4,
4, 0, 9, 0, 0, 9, 4, 2])
4. 可视化模型(选做)
4.1 展示第一层神经元权重
你可以把上面训练的模型的第一隐层权值向量可视化展示出来,看看这些神经元学到了什么特征?
使用上面第2节介绍的模型参数的有关内容,获取模型第一隐层的权重张量,并将每一个神经元的权重(784维向量)转换为28*28大小的矩阵,并归一化到(0,1)范围,便于展示。
使用matplotlit.pyplot.imshow函数展示权重图像。
可以只选取少量神经元的权重予以展示。
#在下面编写代码获取权重张量并展示出来
h1_w = net.hid1.weight
# 归一化
h1_w = (h1_w - h1_w.min())/(h1_w.max() - h1_w.min())
h1_w = h1_w.view([-1, 28, 28])
# 展示前16个神经元的权重图像
for i in range(16):
plt.subplot(2,8,i+1)
plt.imshow(h1_w[i].squeeze().detach().numpy(),cmap = 'gray')
plt.axis('off')
4.2 展示隐含层神经元对不同样本的响应
假设隐含层包含n个神经元,给定一个输入样本x,这n个神经元的输出构成了该样本的一个特征表达,即n维特征向量。
如果你的模型经过训练可以很好的区分不同数字,那么不同类别的样本的这些特征向量应该有很明显的差异。
假设你的模型第一个隐层在MLP类中对应的对象属性名为fc1,那么你可以用net.fc1(x)或者x的特征向量。
更高层的特征向量参考你编写的forward函数中的写法获取。
下面选取少量数字样本,输出你训练的模型中某一个隐含层得到的特征向量。
使用matplotlit.pyplot.plot方式把每一个特征向量绘制为一个曲线图。
#在下面编写代码
# 选取一个bitch
imgs,labels = next(iter(test_loader))
# 选取4个样本,并且输出三个隐层,以及最后一层输出层的相关特征向量
for i in range(4):
num = imgs[i*4].view([-1])
img = imgs[i*4].squeeze()
plt.subplot(4,5,5*i+1)
plt.imshow(img)
plt.subplot(4,5,5*i+2)
plt.plot(net.hid1(num).detach().numpy())
plt.subplot(4,5,5*i+3)
plt.plot(net.hid2(net.hid1(num)).detach().numpy())
plt.subplot(4,5,5*i+4)
plt.plot(net.hid3(net.hid2(net.hid1(num))).detach().numpy())
plt.subplot(4,5,5*i+5)
plt.plot(net.out(net.hid3(net.hid2(net.hid1(num)))).detach().numpy())