根据迪哥视频的pytorch Mnist数据集分类(DNN)网络
Mnist数据集分类任务
-
另外关于测试集的问题,迪哥写的过于繁琐,提供的网址用requirest下载不了,直接暴力搜索用网盘下载到文件夹
-
直接手打一个data_path,然后用gzip打开
-
gzip.open()不能接受字符串路径,需要先通过path转化后在解压
-
with gzip.open((data_path).as_posix(), “rb”) as f:
- ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding=“latin-1”) #这里变量最后一个_必须要加
#加载必要的库
from pathlib import Path
import requests
import pickle
import gzip
%matplotlib inline
# 创建一个存储mnist数据集的路径
data_path = Path.cwd() / 'data'/ 'MNIST'/ 'mnist.pkl.gz'
print(data_path)
# gzip.open()不能接受字符串路径,需要先通过path转化后在解压
# 由于用requests抓取网络文件太慢,直接网盘下载mnist,省略了抓取步骤
with gzip.open((data_path).as_posix(), "rb") as f:
((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1") #这里变量最后一个_必须要加
print("ok")
# 用imshow可以直接显示28×28组成的数字
from matplotlib import pyplot
import numpy as np
pyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)
# 构建模型
import torch
# 用一个map(),将所有数据迭代为矩阵格式
x_train, y_train, x_valid, y_valid = map(
torch.tensor, (x_train, y_train, x_valid, y_valid)
)
print(x_train, y_train)
print(x_train.shape)
print(x_train.min(), y_train.max())
import torch.nn.functional as F
bs = 64
xb = x_train[0:bs]
yb = y_train[0:bs]
weights = torch.randn([784, 10], dtype=torch.float, requires_grad=True)
bias = torch.zeros(10, requires_grad=True)
# 损失值计算采用交叉墒,注意此处声明该函数不用预先设参数,可以后面调用是再设
loss_func = F.cross_entropy
# 定义向前传播计算函数
def model(xb):
return xb.mm(weights) + bias
print(loss_func(model(xb), yb)) 在这里插入代码片
创建一个model来简化代码
- 必须继承nn.Module且在其构造函数中需调用nn.Module的构造函数
- 无需写反向传播函数,nn.Module能够利用autograd自动实现方向传播
- Module中的可学习参数可以通过named_parameters()或者parameters()返回迭代器
from torch import nn
class Mnist_NN(nn.Module): # 创建一个模型类,父类为nn.Module
def __init__(self):
# 在构造函数中就要明确每一层
super().__init__() # 声明继承父类的构造函数
self.hidden1 = nn.Linear(784, 128) # nn.Linear用来设置全连接层
self.hidden2 = nn.Linear(128, 64)
self.out = nn.Linear(64, 10)
def forward(self, x):
x = F.relu(self.hidden1(x))
x = F.relu(self.hidden2(x))
x = self.out(x)
return x
net = Mnist_NN()
print(net)
# 可以打印我们定义好名字的权重和偏置项
for name, parameter in net.named_parameters():
print(name, parameter, parameter.size()) # 分别打印参数名,参数,和维度
使用TensorDataset和DataLoader来简化
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
train_ds = TensorDataset(x_train, y_train) #Dataset对tensor进行打包
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True) #用DataLoad读取数据shuffle表示每次batch都要打乱数据
valid_ds = TensorDataset(x_valid, y_valid)
valid_dl = DataLoader(valid_ds, batch_size=bs * 2)
# 将数据都封装好随时可以调用
def get_data(train_ds, valid_ds, bs):
return (
DataLoader(train_ds, batch_size=bs, shuffle=True),
DataLoader(valid_ds, batch_size=bs * 2)
)
from torch import optim # 导入优化器模块
'''
创建一个返回模型和优化器的方法,虽然不理解有什么用?
不能直接调用模型和优化器?
'''
def get_model():
model = Mnist_NN()
return model, optim.SGD(model.parameters(), lr=0.001) # 采用SGD优化器,优化对象为model的权重参数
def loss_batch(model, loss_func, xb, yb, opt=None): # 计算损失
loss = loss_func(model(xb), yb) # 调用之前定义的交叉墒获得损失值
if opt is not None:
loss.backward() # 根据损失值求导反向传播
opt.step() # 表示用opt优化器来更新权重参数
opt.zero_grad() # 每次更新梯度清零
return loss.item(), len(xb) # loss数据类型是Variable,loss.item()直接获取对应的python数据类型,同时获取对应数据个数
- 一般在训练模型时加上model.train(),这样会正常使用Batch Normalization和Dropout
- 测试的时候一般选择model.eval(),这样就不会使用Batch Normalization和Dropout
import numpy as np
# 将所有过程都放到一个函数中进行计算
def fit(steps, model, loss_func, opt, train_dl, valid_dl):
for step in range(steps):
model.train() # 训练模型时加上model.train(),作用是启用Batch Normalization和Dropout
for xb, yb in train_dl:
loss_batch(model, loss_func, xb, yb, opt)
model.eval() # 测试的时候选择model.eval()
with torch.no_grad(): # 在该模块下,所有计算得出的tensor的requires_grad都自动设置为False,因为只在验证集上求损失值,不用求导
losses, nums = zip(
*[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl]
)
val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)
print('当前step:' + str(step), '验证集损失:' + str(val_loss))
train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
model, opt = get_model()
fit(25, model, loss_func, opt, train_dl, valid_dl)
运行结果如下
当前step:0 验证集损失:2.2804175106048583
当前step:1 验证集损失:2.247503145980835
当前step:2 验证集损失:2.201047773361206
当前step:3 验证集损失:2.132051001358032
当前step:4 验证集损失:2.0252137477874754
当前step:5 验证集损失:1.8659272445678712
当前step:6 验证集损失:1.6515238025665284
当前step:7 验证集损失:1.4080945442199706
当前step:8 验证集损失:1.1849663320541381
当前step:9 验证集损失:1.0117872383117676
当前step:10 验证集损失:0.8840345878601075
当前step:11 验证集损失:0.7874993204116821
当前step:12 验证集损失:0.7118284031867981
当前step:13 验证集损失:0.6517571626663208
当前step:14 验证集损失:0.6032021932601929
当前step:15 验证集损失:0.5631895722389221
当前step:16 验证集损失:0.530268014717102
当前step:17 验证集损失:0.5030230499267578
当前step:18 验证集损失:0.4805735528945923
当前step:19 验证集损失:0.4615746584415436
当前step:20 验证集损失:0.4459013089179993
当前step:21 验证集损失:0.4309808259963989
当前step:22 验证集损失:0.4189828608989716
当前step:23 验证集损失:0.40864138970375063
当前step:24 验证集损失:0.3988726670503616
总结一下
- 不断调试完成模型
- 之前报错TypeError: unsupported operand type(s) for /: ‘builtin_function_or_method’ and ‘int’
- 因为loss.item没加()
- 补充下fit参数含义
- steps:要循环多少次,每个批次都是随机抽取
- model:选用的哪个模型进行训练,这里是采用get_model然后调用Mnist_NN这个类
- loss_func:计算损失函数的方法,这里采用定义好的F.cross_entropy
- opt:反向传播优化器,这里用的SGD
- train_dl:训练集
- valid_dl:验证集
- 总体嵌套函数较多,不容易理解,注意这里的loss_func和opt都是作为loss_batch中的参数起作用
- loss_batch还包含了所有求导反向传播,更新参数及梯度清零操作
- 不确定的地方,猜测 loss_batch中利用的model()函数和fit中获取的model不是同一个
- 前一个model是定义的向前传播计算,只包括wx + b,后面的model是一个类实例,包含所有w层和relu层,不明白前一个model函数在loss_batch中的作用