torchvision是pytorch的一个图形库,它服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型。
以下是torchvision的构成:
这里只讲下面需要使用的部分:
下面我们介绍一下使用torchvision.datasets
中自带的Fashion-MNIST数据集。
这个数据集包含10种样本,按照标签从小到大的顺序对应的图像分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。训练集中每种样本6000张图片,所以训练集总共6w张图片,测试集中每种样本1000张图片,所以测试集总共1w张图片。每幅图片都是28×28的像素数组,每个像素的值为0~255之间的8位无符号整数(uint8),使用三维张量存储,第一维表示通道个数。由于为灰度图像,故通道数为1,即1×28×28。
修正了下面“REF”中的第一个参考博客中的部分错误内容。
import torch
import torchvision
import torchvision.transforms as transforms
import torch.utils.data as Data
import torch.optim as optim
import torch.nn as nn
import matplotlib.pyplot as plt
"""
torchvision.datasets中有很多自带的数据集。
root参数指明我下载的这些数据集要保存在哪个路径下;
train参数为True表示下载(保存)的是训练集,False表示测试集
download参数为True,从互联网下载数据集后将其放在root中。 如果数据集已经下载,则不是再次下载。
transform参数(可调用,可选):接受PIL图像并返回转换后版本的函数/转换。 例如,ToTensor()就是转换为张量形式并进行归一化
"""
fashion_mnist_train = torchvision.datasets.FashionMNIST(root='./', train=True, download=True, transform=transforms.ToTensor())
fashion_mnist_test = torchvision.datasets.FashionMNIST(root='./', train=False, download=True, transform=transforms.ToTensor())
在我执行torchvision.datasets.FashionMNIST(root='./', train=True, download=True, transform=transforms.ToTensor())
和torchvision.datasets.FashionMNIST(root='./', train=False, download=True, transform=transforms.ToTensor())
命令后,会自动构建如下的FashionMNIST文件夹及其内部文件:
“实现多层感知器的学习过程”文件夹是我自己创建的,MLP.py是存放代码的,".pt"类型的文件是pytorch类型的文件,是调用上面两条语句后自动生成的。
对比一下两条语句返回的对象与自动生成的.pt文件的区别,首先说明,返回的对象和.pt文件都是数据集,只是存储的格式不同。
① fashion_mnist_train 和 fashion_mnist_test 的第一维表示样本个数,即fashion_mnist_train[0]表示第一个样本,为一个元组,元组的第一维度是特征,第二维度是标签。这与DataSet类型的表示方式是一致的。float类型。
② 而.pt文件保存的一个元组,元组的第一维和第二维均为张量;第一维的张量是全部样本的特征信息,特征信息张量的第一维就是样本数;第二维的张量是全部样本的标签。int类型。
建议自己使用type、dtype等方式输出一下试试,自己观察一遍才能真正理解。
写下面代码时还未阅读到“REF”[1]博客,所以代码中的数据集是从test.pt和train.pt中获取的,而这两个文件是调用torchvision.datasets.FashionMNIST自动生成的。其实使用返回对象访问数据集更为简单,
feature, lable = mnist_train[0]
就可以分别获取到训练集的特征与标签。
# 加载数据
train_dataset = torch.load('./FashionMNIST/processed/training.pt') # 60000个样本
test_dataset = torch.load('./FashionMNIST/processed/test.pt') # 10000个样本
train_dataset 是个元组,两个元素均为张量,第一个张量是全部样本的特征张量,大小为60000×28×28;第二个张量是全部样本的标签张量,一维张量大小为60000。
# 由初始训练数据集格式转换为我们方便处理的格式,即分离特征与标签
x = train_dataset[0][:1000].unsqueeze(dim=1).float() # 训练集特征 28×28
y = train_dataset[1][:1000] # 训练集标签
train_dataset[0]
:特征
train_dataset[1]
:标签
[:1000]
:取前1000个样本,6w个样本CPU根本跑不出来
.unsqueeze(dim=1)
:因为要求conv2d是对(N, C, H, W)的四维张量进行操作的,而数据集是(N, H, W)的,即少一个单通道维度,该函数就是在第二维加一个维度将(N, H, W)变为(N, 1, H, W)
.float()
:将数据类型从int转换为float,如果不转换会出现”RuntimeError: expected scalar type Byte but found Float“的问题,因为model()中的输入需要是浮点才能计算梯度等信息,所以在传入全部数据集进行输出损失值时会报如上错误,但是由于DataLoader生成的batch自动将int转为了float,所以训练的时候并不会报错。
# 显示一下,看看对不对
# plt.imshow(x[0][0]) # 获取第一个样本的第一个通道
# plt.show() # 显示的是“靴子”
这部分对于我们来说已经太熟练了。
这里我们不再使用add_module,因为使用add_module需要加名字参数,太麻烦了,所以直接使用如下方式创建。
class LeNet(nn.Module) :
def __init__(self):
super(LeNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=25, kernel_size=(3, 3)),
nn.BatchNorm2d(num_features=25),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))
)
self.layer2 = nn.Sequential(
nn.Conv2d(in_channels=25, out_channels=50, kernel_size=(3, 3)),
nn.BatchNorm2d(num_features=50),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))
)
self.layer3 = nn.Sequential(
nn.Linear(in_features=50*5*5, out_features=1024),
nn.ReLU(inplace=True),
nn.Linear(in_features=1024, out_features=128),
nn.ReLU(inplace=True),
nn.Linear(in_features=128, out_features=10)
)
def forward(self, x) :
conv1 = self.layer1(x)
conv2 = self.layer2(conv1)
input = conv2.view(conv2.size(0), -1)
y_hat = self.layer3(input)
return y_hat
实例化模型
model = LeNet()
batch_size = 50
num_epochs = 100
num_examples = 60000
learning_rate = 0.01
# 用选取的样本进行训练
dataset = Data.TensorDataset(x, y)
# 实现小批量的迭代器
train_loader = Data.DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True)
# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
# 定义损失函数
loss = nn.CrossEntropyLoss()
训练速度很慢,因为使用的是CPU,所以也不必等到训练完,可以正常执行完一个epoch、理解过程即可。
for epoch in range(1, num_epochs+1) :
for step, (batch_x, batch_y) in enumerate(train_loader) :
optimizer.zero_grad()
batch_y_hat = model(batch_x)
loss_value = loss(batch_y_hat, batch_y)
loss_value.backward()
optimizer.step()
loss_value = loss(model(x), y)
print('Epoch %d, Loss %f' % (epoch, loss_value.item()))
训练完成后选取测试集进行测试,判断模型预测的标签与真实标签是否一致,计算准确率。
(上面训练模型只选取了1k个样本,因为我的GPU太小了,内存不够)
以上代码按照在本文的出现顺序复制执行即可。
想来想去还是觉得不行,需要用GPU跑跑试试,于是就去网上学习。
我将全部代码分成了三部分,LeNet_Network.py
存放定义的网络结构,LeNet_Train.py
存放模型训练的代码,LeNet_Test.py
存放模型测试的代码,TrainedModel
文件夹存放LeNet.pkl
文件,该文件保存训练好的网络模型参数,这使得每次使用模型去测试时不用去训练了,直接向模型中导入模型参数即可。
如果你在import自定义的模块时出现错误,而且感觉不应该出现这种低级的问题,那么不妨尝试一下在pycharm中右键这两个模块所在的文件夹,点击“Mark Directory as”,点击“Sources root”,OK了。
这部分没变。
import torch.nn as nn
"""
定义网络结构
"""
class LeNet(nn.Module) :
def __init__(self):
super(LeNet, self).__init__()
# 不使用add_module,因为使用add_module需要加名字参数
self.layer1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=25, kernel_size=(3, 3)),
nn.BatchNorm2d(num_features=25),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))
)
self.layer2 = nn.Sequential(
nn.Conv2d(in_channels=25, out_channels=50, kernel_size=(3, 3)),
nn.BatchNorm2d(num_features=50),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))
)
self.layer3 = nn.Sequential(
nn.Linear(in_features=50*5*5, out_features=1024),
nn.ReLU(inplace=True),
nn.Linear(in_features=1024, out_features=128),
nn.ReLU(inplace=True),
nn.Linear(in_features=128, out_features=10)
)
def forward(self, x) :
conv1 = self.layer1(x)
conv2 = self.layer2(conv1)
input = conv2.view(conv2.size(0), -1)
y_hat = self.layer3(input)
return y_hat
大部分代码都没变,多了几个.to(device)
语句和.cuda()
语句。
下面代码的“定义模型”部分,如果本设备存在可以使用的GPU,则device
为GPU,否则为CPU,.to(device)
将网络放在GPU上。
同样的,下面生成batch的迭代中,也要将生成的batch(数据)放在GPU上,所以使用.cuda()
。由于我们还要使用全部数据集来观察损失值,所以x.cuda()
和y.cuda()
也不可缺少。
这里使用save将模型参数保存在TrainedModel文件夹中的LeNet.pkl文件中。
模仿着写。
import torch
import torchvision
import torchvision.transforms as transforms
import torch.utils.data as Data
import torch.optim as optim
import torch.nn as nn
import matplotlib.pyplot as plt
from LeNet_Network import LeNet
fashion_mnist_train = torchvision.datasets.FashionMNIST(root='./', train=True, download=True, transform=transforms.ToTensor())
fashion_mnist_test = torchvision.datasets.FashionMNIST(root='./', train=False, download=True, transform=transforms.ToTensor())
train_dataset = torch.load('./FashionMNIST/processed/training.pt') # 60000个样本
x = train_dataset[0][:4000].unsqueeze(dim=1).float() # 训练集特征 28×28
y = train_dataset[1][:4000] # 训练集标签
# 显示一下,看看对不对
# plt.imshow(x[0][0]) # 获取第一个样本的第一个通道
# plt.show() # 显示的是“靴子”
"""
定义模型
(并讲模型放在GPU上)
"""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = LeNet().to(device)
"""
设置一些训练参数
"""
batch_size = 50
num_epochs = 10
num_examples = 60000 # 没用上
learning_rate = 0.01
# 用选取的样本进行训练
dataset = Data.TensorDataset(x, y)
# 实现小批量的迭代器
train_loader = Data.DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True)
# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
# 定义损失函数
loss = nn.CrossEntropyLoss()
"""
开始迭代训练
"""
for epoch in range(1, num_epochs+1) :
for step, (batch_x, batch_y) in enumerate(train_loader) :
# 放在GPU上
batch_x = batch_x.cuda()
batch_y = batch_y.cuda()
optimizer.zero_grad()
batch_y_hat = model(batch_x)
loss_value = loss(batch_y_hat, batch_y)
loss_value.backward()
optimizer.step()
loss_value = loss(model(x.cuda()), y.cuda())
print('Epoch %d, Loss %f' % (epoch, loss_value.item()))
torch.save(model.state_dict(),"./TrainedModel/LeNet.pkl") # 保存训练好的参数
定义一个空模型,即参数都是默认初始化的模型,使用 model.load_state_dict(torch.load("./TrainedModel/LeNet.pkl"))
语句将保存的模型参数导入空模型中。
测试部分我选用了全部的测试集,即1w个数据进行测试,每100个数据作为一组,即一个batch,计算一组正确率进行输出,总共100组。
torch.argmax(model(batch_x), dim=1)
是选取每组样本中预测值最大的索引作为标签(你可以试着输出一下model(batch_x)
,观察一下输出的结构,就方便理解了),argmax按照第二个维度选出最大索引。再转化为numpy,进行逐位对比判断是否相同,计算均值作为该组的准确率。
import torch
import numpy as np
import torch.utils.data as Data
from LeNet_Network import LeNet
if __name__ == '__main__' :
"""
获取数据,处理数据
"""
test_dataset = torch.load('./FashionMNIST/processed/test.pt') # 10000个样本
features = test_dataset[0].unsqueeze(dim=1).float()
labels = test_dataset[1]
"""
创建模型
"""
model = LeNet()
# print(torch.load("./TrainedModel/LeNet.pkl")) # OrderedDict形式
model.load_state_dict(torch.load("./TrainedModel/LeNet.pkl")) # 导入模型参数
loader = Data.DataLoader(dataset=Data.TensorDataset(features, labels), shuffle=True, batch_size=100)
with torch.no_grad(): # 不构建计算图
for step, (batch_x, batch_y) in enumerate(loader) :
acc = np.mean(torch.argmax(model(batch_x), dim=1).numpy() == batch_y.numpy())
print(f'Step {step+1}, Acc {acc}')
在使用pytorch时,并不是所有的操作都需要进行计算图的生成(计算过程的构建,以便梯度反向传播等操作)。而对于tensor的计算操作,默认是要进行计算图的构建的,在这种情况下,可以使用 with torch.no_grad():
,强制之后的内容不进行计算图构建。
[1] 【深度学习】Fashion-MNIST数据集简介 - CSDN博客
[2] torchvision的使用(transforms用法介绍) - CSDN博客
[3] Pytorch学习基础——LeNet从训练到测试 - CSDN博客
[4] 3.5 图像分类数据集(Fashion-MNIST) - Dive-into-DL-PyTorch
[5] pytorch中with torch.no_grad(): - CSDN博客
[6] PyTorch保存网络结构以及参数【 torch.save()、torch.load() 】- CSDN博客
[7] 1.LeNet5–手写数字识别(Pytorch) - 哔哩哔哩
[8] model.parameters()与model.state_dict() - 知乎