本案例使用 jupyter notebook 实现

数据集来源

https://www.kaggle.com/biaiscience/dogs-vs-cats

查看数据集

数据集共分为test和train两个文件夹,test文件夹里面的图片没有标签,因此我们仅使用train文件夹内的图片,部分图片如下,可以看到图片的标签为文件名的前三个字母,猫为cat,狗为dog。

猫狗分类机器学习 猫狗分类pytorch_猫狗分类机器学习

导入头文件

import matplotlib.pyplot as plt
import torch
import torch.nn as nn
from torch.optim import Adam
from torchvision import transforms
from torchvision import models
from torchvision.io import read_image
from torch.nn import functional as F
from torch.utils.data import Dataset, DataLoader
import numpy as np
from sklearn.model_selection import StratifiedShuffleSplit
import os
os.environ['TORCH_HOME']='./download_model'  # 修改Pytorch模型默认下载位置

加载数据

  • 获取train目录下的文件路径名
# 获取图片路径
train_path = './train/train/'

image_file_path = np.array([train_path + i for i in os.listdir(train_path)])

# 根据文件名获取图片对应的标签
labels = np.array([
    0 if name.split('/')[-1].startswith('cat') else 1
    for name in image_file_path
])

print(len(image_file_path),len(labels))
25000 25000
  • 我们可以查看一下获取到的结果形式
# 查看结果
image_file_path[:10], labels[:10]
(array(['../input/dogs-vs-cats/train/train/cat.12461.jpg',
    '../input/dogs-vs-cats/train/train/dog.3443.jpg',
    '../input/dogs-vs-cats/train/train/dog.7971.jpg',
    '../input/dogs-vs-cats/train/train/dog.10728.jpg',
    '../input/dogs-vs-cats/train/train/dog.1942.jpg',
    '../input/dogs-vs-cats/train/train/dog.375.jpg',
    '../input/dogs-vs-cats/train/train/cat.10176.jpg',
    '../input/dogs-vs-cats/train/train/cat.8194.jpg',
    '../input/dogs-vs-cats/train/train/dog.3259.jpg',
    '../input/dogs-vs-cats/train/train/cat.3498.jpg'], dtype='<U47'),
array([0, 1, 1, 1, 1, 1, 0, 0, 1, 0]))
  • 使用 StratifiedShuffleSplit 进行随机分层抽样,将25000张图片按 8:1:1 的比例分为训练集、验证集和测试集,且每个数据集中猫和狗的占比均相同
# 分层抽样

# 将数据集按照 8:2 的比例分为训练集和其他数据集
train_val_sss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=2021)

for train_index, val_index in train_val_sss.split(image_file_path, labels):
    train_path, val_path = image_file_path[train_index],image_file_path[val_index]
    train_labels, val_labels = labels[train_index],labels[val_index]
    

# 将其他数据集按照 1:1 的比例分为验证集和测试集
val_test_sss = StratifiedShuffleSplit(n_splits=1, test_size=0.5, random_state=2021)

for val_index, test_index in val_test_sss.split(val_path, val_labels):
    val_path, test_path = val_path[val_index],val_path[test_index]
    val_labels, test_labels = val_labels[val_index],val_labels[test_index]
    
# 最终我们得到 8:1:1 的训练集、验证集和测试集
# 每个数据集中猫和狗所占比例相同
  • 查看分层抽样结果
# 由于猫的标签是0,狗的标签是1,因此我们可以用.sum()函数获取到狗的个数
print(len(train_path),len(train_labels),train_labels.sum())
print(len(val_path),len(val_labels),val_labels.sum())
print(len(test_path),len(test_labels),test_labels.sum())
20000 20000 10000
2500 2500 1250
2500 2500 1250
  • 定义Pytorch数据加载类
# 定义Dataset加载方式
class MyData(Dataset):
    def __init__(self, filepath, labels=None, transform=None):
        self.filepath = filepath
        self.labels = labels
        self.transform = transform

    def __getitem__(self, index):
        image = read_image(self.filepath[index]) # 读取后的图片维度是[channl, height, width]
        image = image.to(torch.float32) / 255. # 转换为float32类型,除以255进行归一化
        if self.transform is not None:
            image = self.transform(image)
        if self.labels is not None:
            return image, self.labels[index]
        return image

    def __len__(self):
        return self.filepath.shape[0]
  • 定义一些指标
image_size = [224, 224]  # 图片大小
batch_size = 64 # 批大小
  • 定义图像增强器
transform = transforms.Compose([
    transforms.RandomHorizontalFlip(),	# 随机水平翻转
    transforms.RandomRotation(30),	# 随机旋转
    transforms.Resize([256, 256]),	# 设置图片大小
    transforms.RandomCrop(image_size),	# 将图片随机裁剪为所需图片大小
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 使用ImageNet的标准化参数
])
  • 获取数据集对象
ds1 = MyData(train_path, train_labels, transform)
ds2 = MyData(val_path, val_labels, transform)
ds3 = MyData(test_path, test_labels, transform)

train_ds = DataLoader(ds1, batch_size=batch_size, shuffle=True)
val_ds = DataLoader(ds2, batch_size=batch_size, shuffle=True)
test_ds = DataLoader(ds3, batch_size=batch_size, shuffle=True)
  • 针对单张图片,查看图像增强的效果
# 对一张图片查看增强效果

image = read_image(train_path[0])
image = image.to(torch.float32) / 255.	# 读取单张图片,并进行归一化
image = image.unsqueeze(0)	# 为单张图片加入batch维度

plt.figure(figsize=(12,12))
mean = np.array([0.485, 0.456, 0.406]) # 图像增强所用的标准化参数
std = np.array([0.229, 0.224, 0.225])
for i in range(25):
    plt.subplot(5,5,i+1)
    img = transform(image)
    img = img[0,:,:,:].data.numpy().transpose([1,2,0]) # 将图片从[channel,height,width]变为[height,width,channel]
    img = std * img + mean # 反标准化
    img = np.clip(img, 0, 1) # 将像素值限制在0~1之间
    plt.imshow(img)
    plt.axis('off')
plt.show()

猫狗分类机器学习 猫狗分类pytorch_2d_02

  • 查看训练集一个批次的图片
# 查看训练集一个batch的图片
for step, (bx, by) in enumerate(train_ds):
    if step > 0:
        break
    plt.figure(figsize=(16,16))
    for i in range(len(by)):
        plt.subplot(8,8,i+1)
        image = bx[i,:,:,:].data.numpy().transpose([1,2,0])
        image = std * image + mean # 反标准化
        image = np.clip(image, 0, 1)
        plt.imshow(image)
        plt.axis('off')
        plt.title('cat' if by[i].data.numpy()==0 else 'dog')
    plt.show()

猫狗分类机器学习 猫狗分类pytorch_ide_03

构建模型

  • 对预训练的ResNet50模型进行部分修改,将顶层的1000类输出替换为2类,并冻结参数。
    因为前面我们使用os将Pytorch的目录重定向到download_model,因此不要忘了在你当前文件夹下创建download_model文件夹,然后ResNet50将会下载到这个文件夹内,而不是默认下载到C盘。
model = models.resnet50(pretrained=True)  # 获取预训练的ResNet50架构
in_features = model.fc.in_features  # 获取顶层输出层的输入维度
model.fc = nn.Linear(in_features, 2)  # 将输出层替换为自己需要的输出层,输入维度为2
model.add_module('softmax', nn.Softmax(dim=-1)) # 添加softmax层

# 将除了顶层以为,其他层的参数冻结
for name, m in model.named_parameters():
    if name.split('.')[0] != 'fc':
        m.requires_grad_(False)
  • 打印模型
print(model)
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): Bottleneck(
  (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (downsample): Sequential(
    (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
    (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
)
(1): Bottleneck(
  (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
(2): Bottleneck(
  (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
)
(layer2): Sequential(
(0): Bottleneck(
  (conv1): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (downsample): Sequential(
    (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
    (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
)
(1): Bottleneck(
  (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
(2): Bottleneck(
  (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
(3): Bottleneck(
  (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
)
(layer3): Sequential(
(0): Bottleneck(
  (conv1): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (downsample): Sequential(
    (0): Conv2d(512, 1024, kernel_size=(1, 1), stride=(2, 2), bias=False)
    (1): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
)
(1): Bottleneck(
  (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
(2): Bottleneck(
  (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
(3): Bottleneck(
  (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
(4): Bottleneck(
  (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
(5): Bottleneck(
  (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
)
(layer4): Sequential(
(0): Bottleneck(
  (conv1): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (downsample): Sequential(
    (0): Conv2d(1024, 2048, kernel_size=(1, 1), stride=(2, 2), bias=False)
    (1): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
)
(1): Bottleneck(
  (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
(2): Bottleneck(
  (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
)
)
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=2048, out_features=2, bias=True)
(softmax): Softmax(dim=-1)
)
  • 将模型迁移到GPU上,以提高训练速度
# 如果有GPU则使用GPU,否则使用CPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
  • 定义优化器和损失函数
optimizer = Adam(model.parameters(), lr=0.0001)
loss_fun = F.cross_entropy

训练模型

  • 定义训练函数
def train(model, epoch, train_ds):
    model.train() # 开启训练模式
    total_num = len(train_ds.dataset) # 训练集总个数
    train_loss = 0	# 训练损失
    correct_num = 0 # 分类正确的个数

    for image, label in train_ds:
        image = image.to(device)	# 将图片和标签都迁移到GPU上进行加速
        label = label.to(device)
        label = label.to(torch.long) # 将标签从int32类型转化为long类型,否则计算损失会报错

        output = model(image) # 获取模型输出
        loss = loss_fun(output, label) # 计算交叉熵损失
        train_loss += loss.item() * label.size(0) # 累计总训练损失
        optimizer.zero_grad()	# 清除优化器上一次的梯度
        loss.backward()	# 进行反向传播
        optimizer.step()	# 更新优化器

        predict = torch.argmax(output, dim=-1)	# 获取预测类别
        correct_num += label.eq(predict).sum() # 统计预测正确的个数

    train_loss = train_loss / total_num	# 计算一个轮次的训练损失
    train_acc = correct_num / total_num	# 计算一个轮次的训练准确率
    # 打印相关信息
    print('epoch: {} --> train_loss: {:.6f} - train_acc: {:.6f} - '.format(
        epoch, train_loss, train_acc), end='')
  • 定义测试函数
def evaluate(model, eval_ds, mode='val'):
    model.eval() # 开启测试模式

    total_num = len(eval_ds.dataset)
    eval_loss = 0
    correct_num = 0

    for image, label in eval_ds:
        image = image.to(device)
        label = label.to(device)
        label = label.to(torch.long)
        
        output = model(image)
        loss = loss_fun(output, label)
        eval_loss += loss.item() * label.size(0)

        predict = torch.argmax(output, dim=-1)
        correct_num += label.eq(predict).sum()
    
    eval_loss = eval_loss / total_num
    eval_acc = correct_num / total_num
    
    print('{}_loss: {:.6f} - {}_acc: {:.6f}'.format(
        mode, eval_loss, mode, eval_acc))
  • 开始训练,训练20个epoch
for epoch in range(20):
    train(model, epoch, train_ds)
    evaluate(model, val_ds)
epoch: 0 --> train_loss: 0.263510 - train_acc: 0.933600 - val_loss: 0.127010 - val_acc: 0.972800
epoch: 1 --> train_loss: 0.121579 - train_acc: 0.967150 - val_loss: 0.088527 - val_acc: 0.975200
epoch: 2 --> train_loss: 0.097462 - train_acc: 0.969750 - val_loss: 0.074590 - val_acc: 0.977600
epoch: 3 --> train_loss: 0.086457 - train_acc: 0.970450 - val_loss: 0.067147 - val_acc: 0.977200
epoch: 4 --> train_loss: 0.078661 - train_acc: 0.972100 - val_loss: 0.062467 - val_acc: 0.978800
epoch: 5 --> train_loss: 0.076047 - train_acc: 0.972200 - val_loss: 0.056072 - val_acc: 0.980800
epoch: 6 --> train_loss: 0.071828 - train_acc: 0.973850 - val_loss: 0.054756 - val_acc: 0.977600
epoch: 7 --> train_loss: 0.068942 - train_acc: 0.976200 - val_loss: 0.054501 - val_acc: 0.981200
epoch: 8 --> train_loss: 0.068446 - train_acc: 0.974250 - val_loss: 0.048335 - val_acc: 0.984000
epoch: 9 --> train_loss: 0.068512 - train_acc: 0.973300 - val_loss: 0.049108 - val_acc: 0.979600
epoch: 10 --> train_loss: 0.063924 - train_acc: 0.977150 - val_loss: 0.052838 - val_acc: 0.980800
epoch: 11 --> train_loss: 0.060832 - train_acc: 0.977450 - val_loss: 0.046959 - val_acc: 0.980400
epoch: 12 --> train_loss: 0.064698 - train_acc: 0.975300 - val_loss: 0.046457 - val_acc: 0.979600
epoch: 13 --> train_loss: 0.060457 - train_acc: 0.976800 - val_loss: 0.044242 - val_acc: 0.981600
epoch: 14 --> train_loss: 0.061942 - train_acc: 0.976750 - val_loss: 0.045544 - val_acc: 0.983600
epoch: 15 --> train_loss: 0.060018 - train_acc: 0.976900 - val_loss: 0.044339 - val_acc: 0.984000
epoch: 16 --> train_loss: 0.057508 - train_acc: 0.977150 - val_loss: 0.045551 - val_acc: 0.981600
epoch: 17 --> train_loss: 0.056687 - train_acc: 0.978300 - val_loss: 0.045825 - val_acc: 0.980000
epoch: 18 --> train_loss: 0.058531 - train_acc: 0.977450 - val_loss: 0.047936 - val_acc: 0.979200
epoch: 19 --> train_loss: 0.057430 - train_acc: 0.978200 - val_loss: 0.046414 - val_acc: 0.982000
  • 在测试集上测试模型
# 测试模型
evaluate(model, test_ds, mode='test')
test_loss: 0.041599 - test_acc: 0.982400
  • 解冻参数层,将学习率降低,进行微调
# 解冻特征提取层
for name, m in model.named_parameters():
    if name.split('.')[0] != 'fc':
        m.requires_grad_(True)
        
# 使用一个学习率更小的优化器
optimizer = Adam(model.parameters(), lr=0.00001)
  • 进行微调训练3个epoch
for epoch in range(3):
    train(model, epoch, train_ds)
    evaluate(model, val_ds)
epoch: 0 --> train_loss: 0.042424 - train_acc: 0.983500 - val_loss: 0.021933 - val_acc: 0.991600
epoch: 1 --> train_loss: 0.024783 - train_acc: 0.991050 - val_loss: 0.016949 - val_acc: 0.993200
epoch: 2 --> train_loss: 0.018476 - train_acc: 0.993450 - val_loss: 0.021186 - val_acc: 0.992400
  • 保存模型
torch.save(model,'model.pkl')