MNIST:手写数字
50000张训练图片
10000张测试图片
图像大小:28x28
10个类别(0-9)
理论
数据读取和预处理
CUDA:显卡驱动,有了CUDA显卡才能进行复杂运算
数据读取 DataLoader 和 图像预处理 transforms 详解(这篇值得反复阅读)
深度学习 | 三个概念:Epoch, Batch, Iteration
使用 transforms.Compose() 将图像变换方法整合在一起
torchvision.transforms 对有限的图片数据进行各种变换,如缩小或者放大图片的大小、对图片进行水平或者垂直翻转等,这些都是数据增强的方法。
使用sklearn实现对数据集划分,从而进行交叉验证
可视化工具包
以进度条形式可视化迭代器运行的包:tqdm
对 softmax 回归的感性理解:
线性回归模型多输入,单输出,而 softmax 多输入,多输出。输出结果为输入的向量所属类别,由于类别有多个,所以多输出。输出概率最大的为所属类别。但由于直接输出的概率值 总和不一定为1,且可能有负值,所以对多个输出结果进行 softmax 方法的计算,从而使得输出结果为 总和为1,且均为正值的概率值。
实现
# 导入库
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from sklearn.model_selection._split import KFold
# 定义超参数
BATCH_SIZE = 128 # 每批处理的数据量
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 用CPU还是GPU训练
EPOCHS = 10 # 定义总训练次数
k_split_value = 5 # 定义 5 折交叉验证
# 定义图像处理方法
tranform = transforms.Compose([
transforms.ToTensor(), # 将图片转换成Tensor
transforms.Normalize((0.1307,), (0.3081,)) # 进行数据归一化处理
])
# 下载、加载数据集
from torch.utils.data import DataLoader
train_data = datasets.MNIST(root="./MNIST",
train=True,
transform=tranform,
download=False) # 注,如果已经下载了一遍,也需要用该命令设置data的路径
test_data = datasets.MNIST(root="./MNIST",
train=False,
transform=tranform,
download=False)
# 将测试集和训练集合并,以便后续对数据集进行分割
dataFold = torch.utils.data.ConcatDataset([train_data, test_data])
# 构建网络模型,使用 AlexNet
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
# 由于MNIST为28x28, 而最初AlexNet的输入图片是227x227的。所以网络层数和参数需要调节
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1) # AlexCONV1(3,96, k=11,s=4,p=0)
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) # AlexPool1(k=3, s=2)
self.relu1 = nn.ReLU()
# self.conv2 = nn.Conv2d(96, 256, kernel_size=5,stride=1,padding=2)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1) # AlexCONV2(96, 256,k=5,s=1,p=2)
self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2) # AlexPool2(k=3,s=2)
self.relu2 = nn.ReLU()
self.conv3 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1) # AlexCONV3(256,384,k=3,s=1,p=1)
# self.conv4 = nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1)
self.conv4 = nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1) # AlexCONV4(384, 384, k=3,s=1,p=1)
self.conv5 = nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1) # AlexCONV5(384, 256, k=3, s=1,p=1)
self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2) # AlexPool3(k=3,s=2)
self.relu3 = nn.ReLU()
self.fc6 = nn.Linear(256 * 3 * 3, 1024) # AlexFC6(256*6*6, 4096)
self.fc7 = nn.Linear(1024, 512) # AlexFC6(4096,4096)
self.fc8 = nn.Linear(512, 10) # AlexFC6(4096,1000)
self.dropout = nn.Dropout(p=0.5)
def forward(self, x):
x = self.conv1(x)
x = self.pool1(x)
x = self.relu1(x)
x = self.conv2(x)
x = self.pool2(x)
x = self.relu2(x)
x = self.conv3(x)
x = self.conv4(x)
x = self.conv5(x)
x = self.pool3(x)
x = self.relu3(x)
x = x.view(-1, 256 * 3 * 3) # Alex: x = x.view(-1, 256*6*6)
x = self.fc6(x)
x = self.dropout(x)
x = F.relu(x)
x = self.fc7(x)
x = self.dropout(x)
x = F.relu(x)
x = self.fc8(x)
return x
# 定义优化器
model = AlexNet().to(DEVICE)
optimizer = optim.Adam(model.parameters()) # 使用 Adam 优化器
# 定义训练方法
def train_model(model, device, train_loader, optimizer, epoch):
model.train() # PyTorch 提供的训练方法
for batch_index, (data, label) in enumerate(train_loader):
# 部署到DEVICE
data, label = data.to(device), label.to(device)
# 梯度初始化为0
optimizer.zero_grad()
# 训练后的结果
output = model(data)
# 计算损失(针对多分类任务交叉熵,二分类用sigmoid)
loss = F.cross_entropy(output, label)
# 找到最大概率的下标
pred = output.argmax(dim=1)
# 反向传播Backpropagation
loss.backward()
# 参数的优化
optimizer.step()
if batch_index % 3000 == 0:
print("Train Epoch : {} \t Loss : {:.6f}".format(epoch, loss.item()))
# 定义测试方法
def test_model(model, device, test_loader):
# 模型验证
model.eval()
# 统计正确率
correct = 0.0
# 测试损失
test_loss = 0.0
with torch.no_grad(): # 不计算梯度,不反向传播
for data, label in test_loader:
data, label = data.to(device), label.to(device)
# 测试数据
output = model(data)
# 计算测试损失
test_loss += F.cross_entropy(output, label).item()
# 找到概率值最大的下标
pred = output.argmax(dim=1)
# 累计正确率
correct += pred.eq(label.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
print("Test —— Average loss : {:.4f}, Accuracy : {:.3f}\n".format(test_loss,
100.0 * correct / len(test_loader.dataset)))
# 对数据进行 K 折交叉划分并且调用前面的方法训练模型
def KFold_and_Train(k_split_value):
counter = 1 # 自定义一个交叉验证计数器
kf = KFold(n_splits=k_split_value, shuffle=True, random_state=0) # 定义K折交叉验证的方法
for train_index, test_index in kf.split(dataFold): # 使用kf方法将dataFold分成测试集和验证集
print(f"第{counter}次交叉验证")
counter += 1 # 计数器自增1
# get train, val
train_fold = torch.utils.data.dataset.Subset(dataFold, train_index)
test_fold = torch.utils.data.dataset.Subset(dataFold, test_index)
# package type of DataLoader
train_loader = torch.utils.data.DataLoader(dataset=train_fold, batch_size=BATCH_SIZE, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_fold, batch_size=BATCH_SIZE, shuffle=True)
# training and test
for epoch in range(EPOCHS):
train_model(model, DEVICE, train_loader, optimizer, epoch)
test_model(model, DEVICE, test_loader)
# 调用函数
KFold_and_Train(k_split_value)