深度学习实战 —— 花卉识别

介绍

数据集

  数据集来自牛津制作的102 Flower 数据集,见:Oxford 102 Flower Dataset —— Download and Processing。【我用的是 kaggle 里面的数据

Resnet 介绍

   原文link:Deep Residual Learning for Image Recognition
   核心:**resudial connection(shortcut connection)**我是这样理解的:我们直接把浅层网络的输出加到深层网络的输出上去。这样既没有增加参数量,也没有增加计算的复杂度,还能降低梯度消失的可能(输出最差就等于输入)。
  从数学角度:传统的模型由于梯度反向传播(其实就是链式法则),容易出现梯度消失(Gradient Descending)的问题(一个小于1的数的n次方趋近0)。梯度没了,模型就没法更新了。而 Resnet 后面多了一项,所以层数加很多,梯度也不至于消失。

Resnet 核心结构如下:
在这里插入图片描述
之前的模型:
在这里插入图片描述
Resnet:
Resnet
Resnet 内部结构:
在这里插入图片描述

代码

  用到的数据:train(训练集), valid(验证集),cat_to_name.json(花卉类别的映射)。

导入模块

import matplotlib.pyplot as plt 
import numpy as np
import torch
import torch.optim as optim
from torch import nn
from torchvision import transforms, models, datasets
import time
import copy
import json
import os

数据读取和处理

# 读取数据
train_dir = './train/'
val_dir = './valid/'

# 用jason文件,给花的类别序号和名称做一个映射。
with open(os.path.join('flower_jupyter','./cat_to_name.json'), 'r') as f:
    cat_to_name = json.load(f)

# 预处理 -- 数据增强
data_transform = {
    'train':
        transforms.Compose([  # 按顺序操作
            transforms.Resize([96, 96]),  # 把所有图片转换成相同大小 resize (H,W)-->(256,256)
            transforms.RandomRotation(45),  # 随机旋转(-45° -- 45°),数据增强
            transforms.RandomCrop(64),  # 随机裁剪[64,128,256,224]都是比较常见的
            transforms.RandomHorizontalFlip(p=0.5),
            transforms.RandomVerticalFlip(p=0.5),
            transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),
            # transforms.RandomGrayscale(p=0.005)# 转成RRR 或 GGG 或BBB
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 那人家大数据集的均值,标准差。分别表示R G B
        ]),
    'valid':  # train 怎么预处理,一般 valid 就怎么做。
        transforms.Compose([
            transforms.Resize([64, 64]),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 跟训练数据一样
        ]),
}

batch_size = 128

创建数据集

""" 创建datasets key-val 键是'train'和'valid' """
base_dir = 'flower_jupyter'
image_datasets = {x: datasets.ImageFolder(os.path.join(base_dir,x), data_transform[x]) for x in ['train', 'valid']}
# 是将数据集(image_datasets)中的数据批量加载,并且可以进行打乱、多线程加载等操作
# shuffle=True表示在每个 epoch 开始时随机打乱数据。这有助于模型的训练,因为打乱数据可以防止模型对数据的顺序产生依赖。
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in
               ['train', 'valid']}
# dataset_sizes = {x: len(image_datasets) for x in ['train','val']} # 算准确率的
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}  # 算准确率的
class_name = image_datasets['train'].classes  # 实际索引位置的类别

训练模型

思路:加载预训练模型(resnet)-- 修改模型结构(如替换最后的全连接层以适应新的类别数) – 冻结部分层 – 模型微调 – 训练&评估

# 加载models中提供的模型,直接用训练好的权重做初始变量
model_name = 'resnet'  # 还可以选 'alexnet', 'vgg', 'squeezenet', 'densenet', 'inception'等
feature_extract = True
model_fit = models.resnet18() 
# print(model_fit) 可以打印看一下
# 将一些层冻住(定义为false),使其不自动更新
def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            param.requires_grad = False # 反向传播时不算梯度
def initialize_model(model_name, num_classes, feature_extract, use_pretrained=True):
    # 选择适合的模型,不同的模型初始化参数不同
    if model_name == "resnet":
        """
        Resnet18
        """
        # 1. 加载 训练
        model_ft = models.resnet18(pretrained=use_pretrained)# 预训练模型
        # 2. 冻住,参数不更新
        set_parameter_requires_grad(model_ft, feature_extract)

        # 全连接
        # 3. 全连接层输入特征
        num_ftrs = model_ft.fc.in_features # number of features

        # 4. 更改全连接层,设置输出102
        model_ft.fc = nn.Linear(num_ftrs, 102) # True
        input_size = 64 # 输入大小
        
    return model_ft, input_size
# 是否用 GPU 训练
train_on_gpu = torch.cuda.is_available()
if not train_on_gpu:
    print("CUDA is not available. Training on CPU...")
else:
    print("CUDA is availavle. Training on CPU")

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# 设置模型名字、输出分类数
model_ft, input_size = initialize_model(model_name, 102, feature_extract, use_pretrained=True)

# GPU OR CPU 计算
model_ft = model_ft.to(device)
# 模型保存, 名字叫best.pt。模型保存下来,之后还能用(主要是保存:graph, weight)
filename = 'best.pt'
# 是否训练所有层
params_to_update = model_ft.parameters()
# 打印出需要训练的层
print("Params to learn:")
if feature_extract:
    params_to_update = []
    for name, param in model_ft.named_parameters():
        if param.requires_grad == True:
            params_to_update.append(param)
            print("\t", name)
else:
    for name, param in model_ft.named_parameters():
        if param.requires_grad == True:
            print("\t", name)
# Optimization: 初始化Adam优化器
optimizer_ft = optim.Adam(params_to_update, lr=1e-2)  # params_to_update 为要更新的参数(FC层),进Adam去优化
# learning rate Decay(另一种是 warming up,先深后降)
# 策略:每10 个epoch衰减一次,衰减一次变为原来的1/10
scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=10, gamma=0.1)
# 交叉熵损失函数
# 在训练过程中,通常会使用 criterion 来计算模型输出和真实标签之间的损失值,然后通过反向传播更新模型参数
criterion = nn.CrossEntropyLoss()

训练

# 定义训练函数
# is_inception:要不要用其他的网络
def train_model(model, dataloaders, criterion, optimizer, num_epochs=10,filename='best.pt'):
    since = time.time()
    # 保存最好的准确率
    best_acc = 0
    """
    checkpoint = torch.load(filename)
    best_acc = checkpoint['best_acc']
    model.load_state_dict(checkpoint['state_dict'])
    optimizer.load_state_dict(checkpoint['optimizer'])
    model.class_to_idx = checkpoint['mapping']
    """
    # 指定用GPU还是CPU
    model.to(device)
    # 训练时候显示的loss,指标等
    val_acc_history = []
    train_acc_history = []
    train_losses = []
    valid_losses = []
    LRs = [optimizer.param_groups[0]['lr']]
    # 最好的一次存下来
    best_model_wts = copy.deepcopy(model.state_dict())

    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        # 训练和验证
        for phase in ['train', 'valid']:
            if phase == 'train':
                model.train()  # 训练
            else:
                model.eval()  # 验证

            running_loss = 0.0
            running_corrects = 0

            # 把数据都取个遍
            for inputs, labels in dataloaders[phase]:
                # 下面是将inputs,labels传到GPU
                inputs = inputs.to(device)
                labels = labels.to(device)

                # 清零
                optimizer.zero_grad()
                # 只有训练的时候计算和更新梯度
                with torch.set_grad_enabled(phase == 'train'):

                    outputs = model(inputs)
                    loss = criterion(outputs, labels)

                    # 概率最大的返回preds
                    _, preds = torch.max(outputs, 1)

                    # 训练阶段更新权重
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                # 计算损失
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)

            # 打印操作
            epoch_loss = running_loss / len(dataloaders[phase].dataset)
            epoch_acc = running_corrects.double() / len(dataloaders[phase].dataset)

            time_elapsed = time.time() - since  # 一个 epoch 用的时间
            print('Time elapsed {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))

            # 得到最好那次的模型
            if phase == 'valid' and epoch_acc > best_acc:
                best_acc = epoch_acc
                # 模型保存
                best_model_wts = copy.deepcopy(model.state_dict())
                state = {
                    # tate_dict变量存放训练过程中需要学习的weight和bias
                    'state_dict': model.state_dict(),
                    'best_acc': best_acc,
                    'optimizer': optimizer.state_dict(),
                }
                torch.save(state, filename)
            if phase == 'valid':  # 存结果
                val_acc_history.append(epoch_acc)
                valid_losses.append(epoch_loss)
            if phase == 'train':
                train_acc_history.append(epoch_acc)
                train_losses.append(epoch_loss)

        print('Optimizer learning rate : {:.7f}'.format(optimizer.param_groups[0]['lr']))
        LRs.append(optimizer.param_groups[0]['lr'])
        print()
        scheduler.step()  # 学习率衰减(scheduler 是上面定义的学习器

    time_elapsed = time.time() - since  # 总花的时间
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    # 保存训练完后用最好的一次当做模型最终的结果
    model.load_state_dict(best_model_wts)
    return model, val_acc_history, train_acc_history, valid_losses, train_losses, LRs

# 若太慢,把epoch调低
# 验证与训练若差距大,可能是过拟合
model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs = train_model(model_ft, dataloaders,criterion, optimizer_ft,num_epochs=20)
# 继续训练
for param in model_ft.parameters():
    param.requires_grad = True

# 继续训练所有参数,lr 调小
optimizer = optim.Adam(model_fit.parameters(), lr=1e-3)
scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)
criterion = nn.CrossEntropyLoss()
# criterion = nn.NLLLoss()

# 加载之前训练好的 weight
# 并在原有的模型基础上继续训练
# 下面保存的是刚刚训练效果较好的路径
checkpoint = torch.load(filename)
best_acc = checkpoint['best_acc']

# 修复 state_dict 的键名称
state_dict = checkpoint['state_dict']
fixed_state_dict = {k.replace('fc.0', 'fc'): v for k, v in state_dict.items()}

# 加载修复后的 state_dict
model_ft.load_state_dict(fixed_state_dict)

# 打印模型结构和 state_dict 的键,以便调试
print(model_ft)
print(fixed_state_dict.keys())

# model_ft.load_state_dict(checkpoint['state_dict'])

# 再跑一下
model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs = train_model(model_ft, dataloaders,
                                                                                            criterion, optimizer_ft,
                                                                                            num_epochs=20)

# 加载训练好的模型
model_ft, input_size = initialize_model(model_name, 102, feature_extract, use_pretrained=True)


model_ft = model_ft.to(device) 

# 保存文件的名字
filename = 'best.py'

# 加载模型
checkpoint = torch.load(filename)
best_acc = checkpoint['best_acc']
model_ft.load_state_dict(checkpoint['state_dict'])

# 测试数据预处理

# 得到一个batch的测试数据
dataiter = iter(dataloaders['valid'])
# images, labels = dataiter.next() 这个版本不对
images, labels = next(dataiter)

model_ft.eval()

if train_on_gpu:
    # 前向传播跑一次会得到output
    output = model_ft(images.cuda())
else:
    output = model_ft(images)
print(output.shape)

# 计算得到最大概率
_, preds_tensor = torch.max(output, 1)

# tensor -- numpy 转换
preds = np.squeeze(preds_tensor.numpy()) if not train_on_gpu else np.squeeze(
    preds_tensor.cpu().numpy())  # 将秩为1的数组转为 1 维张量
print(preds)

# 展示预测结果
def im_convert(tensor):
    image = tensor.to("cpu").clone().detach()
    image = image.numpy().squeeze()
    image = image.transpose(1, 2, 0)
    image = image * np.array((0.229, 0.224, 0.225)) + np.array((0.485, 0.456, 0.406))
    image = image.clip(0, 1)
    return image

fig = plt.figure(figsize=(20, 20))
columns = 4
rows = 2

print(cat_to_name.keys())
print("len_cat_to_name",len(cat_to_name.keys()))
print(preds)
print(labels)
for idx in range(columns * rows):
    ax = fig.add_subplot(rows, columns, idx + 1, xticks=[], yticks=[])
    plt.imshow(im_convert(images[idx]))

    pred_class = cat_to_name[str(preds[idx])]
    label_class = cat_to_name[str(labels[idx].item())]
    ax.set_title("{} ({})".format(pred_class, label_class),
                 color=("green" if pred_class == label_class else "red"))
plt.show()

准确率 50%左右吧。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值