• 如果您觉得本站非常有看点，那么赶紧使用Ctrl+D 收藏吧

# 【小白学PyTorch】8 实战之MNIST小试牛刀

2周前 (09-09) 19次浏览

• 1 探索性数据分析
• 1.1 数据集基本信息
• 1.2 数据集可视化
• 1.3 类别是否均衡
• 2 训练与推理
• 2.1 构建dataset
• 2.2 构建模型类
• 2.3 训练模型
• 2.4 推理预测

## 1 探索性数据分析

### 1.1 数据集基本信息

``````import pandas as pd
# 读取训练集
n_train = len(train_df)
n_pixels = len(train_df.columns) - 1
n_class = len(set(train_df['label']))
print('Number of training samples: {0}'.format(n_train))
print('Number of training pixels: {0}'.format(n_pixels))
print('Number of classes: {0}'.format(n_class))

# 读取测试集
n_test = len(test_df)
n_pixels = len(test_df.columns)
print('Number of test samples: {0}'.format(n_test))
print('Number of test pixels: {0}'.format(n_pixels))
``````

### 1.2 数据集可视化

``````# 展示一些图片
import numpy as np
from torchvision.utils import make_grid
import torch
import matplotlib.pyplot as plt
random_sel = np.random.randint(len(train_df), size=8)
data = (train_df.iloc[random_sel,1:].values.reshape(-1,1,28,28)/255.)

grid = make_grid(torch.Tensor(data), nrow=8)
plt.rcParams['figure.figsize'] = (16, 2)
plt.imshow(grid.numpy().transpose((1,2,0)))
plt.axis('off')
plt.show()
print(*list(train_df.iloc[random_sel, 0].values), sep = ', ')
``````

### 1.3 类别是否均衡

``````# 检查类别是否不均衡
plt.figure(figsize=(8,5))
plt.bar(train_df['label'].value_counts().index, train_df['label'].value_counts())
plt.xticks(np.arange(n_class))
plt.xlabel('Class', fontsize=16)
plt.ylabel('Count', fontsize=16)
plt.grid('on', axis='y')
plt.show()
``````

## 2 训练与推理

### 2.1 构建dataset

``````import pandas as pd
n_train = len(train_df)
n_test = len(test_df)
n_pixels = len(train_df.columns) - 1
n_class = len(set(train_df['label']))
``````

``````import torch
from torchvision import transforms

class MNIST_data(Dataset):
def __init__(self, file_path,
transform=transforms.Compose([transforms.ToPILImage(), transforms.ToTensor(),
transforms.Normalize(mean=(0.5,), std=(0.5,))])
):
if len(df.columns) == n_pixels:
# test data
self.X = df.values.reshape((-1, 28, 28)).astype(np.uint8)[:, :, :, None]
self.y = None
else:
# training data
self.X = df.iloc[:, 1:].values.reshape((-1, 28, 28)).astype(np.uint8)[:, :, :, None]
self.y = torch.from_numpy(df.iloc[:, 0].values)
self.transform = transform

def __len__(self):
return len(self.X)

def __getitem__(self, idx):
if self.y is not None:
return self.transform(self.X[idx]), self.y[idx]
else:
return self.transform(self.X[idx])
``````

``````batch_size = 64

train_dataset = MNIST_data('./MNIST_csv/train.csv',
transform= transforms.Compose([
transforms.ToPILImage(),
transforms.RandomRotation(degrees=20),
transforms.ToTensor(),
transforms.Normalize(mean=(0.5,), std=(0.5,))]))
test_dataset = MNIST_data('./MNIST_csv/test.csv')

batch_size=batch_size, shuffle=True)
batch_size=batch_size, shuffle=False)
``````

• train_dataset中使用了随机旋转，因为这个函数是作用在PIL图片上的，所以需要将数据先转成PIL再进行旋转，然后转成Tensor做标准化，这里标准化就随便选取了0.5，有需要的可以做进一步的更改。
• 需要注意的是，转成PIL之前的数据是numpy的格式，所以数据应该是(Wtimes H times C)的形式，因为这里是单通道图像，所以数据的shape为：（72000，28，28，1）.（72000为样本数量）
• 像是旋转、缩放等图像增强方法在训练集中才会使用，这是增强模型训练难度的操作，让模型增加鲁棒性；在测试集中常规情况是不使用旋转、缩放这样的图像增强方法的。（训练阶段是让模型学到内容，测试阶段主要目的是提高预测的准确度，这句话感觉是废话。。。）

### 2.2 构建模型类

``````import torch.nn as nn
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()

self.features1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
self.features = nn.Sequential(
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2)
)

self.classifier = nn.Sequential(
nn.Dropout(p=0.5),
nn.Linear(64 * 7 * 7, 512),
nn.BatchNorm1d(512),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(512, 512),
nn.BatchNorm1d(512),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(512, 10),
)

for m in self.modules():
if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear):
nn.init.xavier_uniform_(m.weight)
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()

def forward(self, x):
x = self.features1(x)
x = self.features(x)
x = x.view(x.size(0), -1)
x = self.classifier(x)
return x
``````

``````import torch.optim as optim

device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = Net().to(device)
# model = torchvision.models.resnet50(pretrained=True).to(device)
criterion = nn.CrossEntropyLoss().to(device)
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)
print(model)
``````

``````Net(
(features1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(features): Sequential(
(0): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(1): ReLU(inplace=True)
(2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(4): ReLU(inplace=True)
(5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(7): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU(inplace=True)
(9): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(10): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(11): ReLU(inplace=True)
(12): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(classifier): Sequential(
(0): Dropout(p=0.5, inplace=False)
(1): Linear(in_features=3136, out_features=512, bias=True)
(2): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(3): ReLU(inplace=True)
(4): Dropout(p=0.5, inplace=False)
(5): Linear(in_features=512, out_features=512, bias=True)
(6): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(7): ReLU(inplace=True)
(8): Dropout(p=0.5, inplace=False)
(9): Linear(in_features=512, out_features=10, bias=True)
)
)
``````

### 2.3 训练模型

``````def train(epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
# 读入数据
data = data.to(device)
target = target.to(device)
# 计算模型预测结果和损失
output = model(data)
loss = criterion(output, target)

loss.backward() # 损失反向传播
optimizer.step()# 然后更新参数
if (batch_idx + 1) % 50 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]tLoss: {:.6f}'.format(
epoch, (batch_idx + 1) * len(data), len(train_loader.dataset),
100. * (batch_idx + 1) / len(train_loader), loss.item()))

exp_lr_scheduler.step()
``````

``````log = [] # 记录一下loss的变化情况
n_epochs = 2
for epoch in range(n_epochs):
train(epoch)

# 把log化成折线图
import matplotlib.pyplot as plt
plt.plot(log)
plt.show()
``````

``````train_dataset = MNIST_data('./MNIST_csv/train.csv',
transform= transforms.Compose([
transforms.ToPILImage(),
transforms.Grayscale(num_output_channels=3),
transforms.RandomRotation(degrees=20),
transforms.ToTensor(),
transforms.Normalize(mean=(0.5,), std=(0.5,))]))
test_dataset = MNIST_data('./MNIST_csv/test.csv',
transform=transforms.Compose([
transforms.ToPILImage(),
transforms.Grayscale(num_output_channels=3),
transforms.ToTensor(),
transforms.Normalize(mean=(0.5,), std=(0.5,))]))
``````

``````# self.features1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
self.features1 = nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1)
``````

### 2.4 推理预测

``````def prediciton(data_loader):
model.eval()
test_pred = torch.LongTensor()

data = data.to(device)
output = model(data)
pred = output.cpu().data.max(1, keepdim=True)[1]
test_pred = torch.cat((test_pred, pred), dim=0)
return test_pred

``````

``````from torchvision.utils import make_grid
random_sel = np.random.randint(len(test_df), size=8)
data = (test_df.iloc[random_sel,:].values.reshape(-1,1,28,28)/255.)

grid = make_grid(torch.Tensor(data), nrow=8)
plt.rcParams['figure.figsize'] = (16, 2)
plt.imshow(grid.numpy().transpose((1,2,0)))
plt.axis('off')
plt.show()
print(*list(test_pred[random_sel].numpy()), sep = ', ')
``````

OK了，恭喜你，完成了MNIST手写数字集的分类。