基于VGG16模型对猫狗分类任务进行迁移学习

import os
from torchvision import models
from torchvision import transforms, datasets
from torchvision.utils import make_grid
import torch
from torch.utils.data import DataLoader
from torch.autograd import Variable
import time
import matplotlib.pyplot as plt

use_gpu = torch.cuda.is_available()
print(use_gpu)

###################### 模型训练前的数据整理和准备 ######################
path = '../data/dogs_vs_cat'
def spilt_train_val(path, number):
    # 将train数据集分割一部分出来作为val数据集 以Kaggle中Dogs vs. Cats的数据集为例子
    train_path = os.path.join(path, 'train')
    val_path = os.path.join(path, 'val')
    if not os.path.exists(val_path):
        return
        os.mkdir(val_path)
    import random
    import shutil
    file_list = os.listdir(train_path)
    random.shuffle(file_list)# 将列表随机打乱
    print('Before', len(file_list))
    file_list = file_list[-number:]
    for img in file_list:
        img_train_path = os.path.join(train_path, img)
        img_val_path = os.path.join(val_path, img)
        shutil.copy(img_train_path, img_val_path)
        # 删除src文件
        os.remove(img_train_path)
    print('After', len(os.listdir(train_path)))
def creat_dogcat_mov(path=path, str_tvt='train'):
    train_path = os.path.join(path, str_tvt)
    file_list = os.listdir(train_path)

    dog = 'dog'
    cat = 'cat'
    dog_path = os.path.join(train_path, dog)
    cat_path = os.path.join(train_path, cat)
    if not os.path.exists(dog_path):
        os.mkdir(dog_path)
    if not os.path.exists(cat_path):
        os.mkdir(cat_path)
    import shutil
    for img in file_list:
        img_name = img.split('.')[0]
        if img_name == 'dog':
            img_train_path = os.path.join(train_path, img)
            shutil.copy(img_train_path, dog_path)
        if img_name == 'cat':
            img_train_path = os.path.join(train_path, img)
            shutil.copy(img_train_path, cat_path)
        os.remove(img_train_path)
# 只需调用1次
# spilt_train_val(path=path, number=5000)
path = '../data/dog_cat'

# 只需调用1次
# creat_dogcat_mov(path=path, str_tvt='train')
# creat_dogcat_mov(path=path, str_tvt='val')

###### @@@@@@数据目录组织结构,便于ImageFolder读取@@@@ ######
# ../data/dog_cat/train
# ../data/dog_cat/train/cat
# ../data/dog_cat/train/dog
# ../data/dog_cat/val
# ../data/dog_cat/val/cat
# ../data/dog_cat/val/dog
# ../data/dog_cat/test/test

# 输入的是一个224*224*3的图片(224*224位分辨率,3为RGB 3个通道)
# 定义转换操作运算
# 将图像RGB三个通道的像素值分别减去0.5再除以0.5,从而将所有像素值
# 固定到[-1.0, 1.0]范围内
transform = transforms.Compose([transforms.CenterCrop(224),# 对原始图片进行裁剪
                                    transforms.ToTensor(),# 转化成张量数据结构
                                    transforms.Normalize([0.5, 0.5, 0.5],
                                                         [0.5, 0.5, 0.5])])# 用均值和标准差对张量图像进行归一化
# 读取数据并转化成字典类型{'train': data1, 'val': data2}
data_img = {x: datasets.ImageFolder(root=os.path.join(path, x),
                                    transform=transform)
                                    for x in ['train', 'val']}
# 查看训练数据的类别信息
classes = data_img["train"].classes
classes_index = data_img["train"].class_to_idx
print(classes)
print(classes_index)

# DataLoader 加载数据
data_loader_imge = {x: DataLoader(data_img[x],
                               batch_size=4,
                               shuffle=True)
                    for x in ['train', 'val']}

# 预览训练数据集
x_train, y_train = next(iter(data_loader_imge['train']))
mean = [0.5, 0.5, 0.5]
std = [0.5, 0.5, 0.5]
img = make_grid(x_train)
img = img.numpy().transpose((1, 2, 0))
img = img*std + mean

print([classes[i] for i in y_train])
plt.imshow(img)
# plt.show()
###################### 以上  模型训练前的数据整理和准备 ######################

# 迁移学习

##################################### 迁移学习 #####################################
# 以分类任务的VGG16模型为例
###################### 迁移学习 第一步:下载预训练模型并查看其网络结构 ######################
# download the pretrained model
model = models.vgg16(pretrained=True)
print(model)
###################### 迁移学习 第二步:针对网络结构进行修改等操作 ######################
# 比如,对VGG16模型中最后的全连接层进行改写,使得最后输出的结果只有两个(只需要对猫狗进行分辨就可以了)
#     将最后的全连接层
#     (classifier): Sequential(
#         (0): Linear(in_features=25s=True)
#         (1): ReLU(inplace=True)
#         (2): Dropout(p=0.5, inplace=False)
#         (3): Linear(in_features=4096, out_features=4096, bias=True)
#         (4): ReLU(inplace=True)
#         (5): Dropout(p=0.5, inplace=False)
#         (6): Linear(in_features=4096, out_features=1000, bias=True) )
#     改写为:
#     (classifier): Sequential(
#         (0): Linear(in_features=25088, out_features=4096, bias=True)
#         (1): ReLU()
#         (2): Dropout(p=0.5, inplace=False)
#         (3): Linear(in_features=4096, out_features=4096, bias=True)
#         (4): ReLU()
#         (5): Dropout(p=0.5, inplace=False)
#         (6): Linear(in_features=4096, out_features=2, bias=True) )
# 第二步(1) 首先冻结参数,即使发生新的训练也不会进行参数的更新
# 目的是冻结参数,即使发生新的训练也不会进行参数的更新
for parma in model.parameters():
    parma.requires_grad = False
# 第二步(2) 对网络结构进行修改
# 对全连接层的最后一层进行了改写,torch.nn.Linear(4096, 2)使得最后输出的结果只有两个(只需要对猫狗进行分辨就可以了)。
model.classifier = torch.nn.Sequential(torch.nn.Linear(25088, 4096),
                                       torch.nn.ReLU(),
                                       torch.nn.Dropout(p=0.5),
                                       torch.nn.Linear(4096, 4096),
                                       torch.nn.ReLU(),
                                       torch.nn.Dropout(p=0.5),
                                       torch.nn.Linear(4096, 2))
# 对改写后的模型进行查看
print(model)

if use_gpu:
    model = model.cuda()
'''
###################### 迁移学习 第三步:进行对修改的模型进行训练(其实只对修改后的部分进行权重学习)######################
# 交叉熵 loss
cost = torch.nn.CrossEntropyLoss()
# 只对全连接层参数进行更新优化,loss计算依然使用交叉熵
optimizer = torch.optim.Adam(model.classifier.parameters())

# 进行训练 进行 n_epochs 次训练
n_epochs = 1
for epoch in range(n_epochs):
    since = time.time()
    print("Epoch{}/{}".format(epoch, n_epochs))
    print("-" * 10)
    for param in ["train", "val"]:
        if param == "train":
            model.train = True
        else:
            model.train = False

        running_loss = 0.0
        running_correct = 0
        batch = 0
        for data in data_loader_imge[param]:
            batch += 1
            X, y = data
            if use_gpu:
                X, y = Variable(X.cuda()), Variable(y.cuda())
            else:
                X, y = Variable(X), Variable(y)

            optimizer.zero_grad()
            y_pred = model(X)
            _, pred = torch.max(y_pred.data, 1)

            loss = cost(y_pred, y)
            if param == "train":
                loss.backward()
                optimizer.step()

            running_loss += loss.data
            running_correct += torch.sum(pred == y.data)
            if batch % 500 == 0 and param == "train":
                print("Batch {}, Train Loss:{:.4f}, Train ACC:{:.4f}".format(
                    batch, running_loss / (4 * batch), 100 * running_correct / (4 * batch)))

        epoch_loss = running_loss / len(data_img[param])
        epoch_correct = 100 * running_correct / len(data_img[param])

        print("{}  Loss:{:.4f},  Correct{:.4f}".format(param, epoch_loss, epoch_correct))
    now_time = time.time() - since
    print("Training time is:{:.0f}m {:.0f}s".format(now_time // 60, now_time % 60))
    
'''
###################### 迁移学习 第四步:对完成训练的修改的模型权重进行保存 ######################
# 保存网络中的参数, 速度快,占空间少
torch.save(model.state_dict(), "model_vgg16_finetune.pk")
# 读取网络中的参数
model.load_state_dict(torch.load("model_vgg16_finetune.pk"))
# 参数总量: pytorch自带方法,计算模型参数总量
total = sum([param.nelement() for param in model.parameters()])
print("Number of parameter: %.2fM" % (total / 1e6))

###################### 迁移学习 第五步:对完成训练的修改的模型进行测试验证 ######################
# 测试,固定BN和DropOut,不用再次参与计算,使其使用训练好的参数值
model.eval()
# 测试时,不再计算梯度提高运算效率
with torch.no_grad():
    batch_size = 16
    data_test_img = datasets.ImageFolder(root=os.path.join(path, 'test'),
                                         transform=transform)
    data_loader_test_img = torch.utils.data.DataLoader(dataset=data_test_img,
                                                       batch_size=batch_size)
    image, label = next(iter(data_loader_test_img))
    images = Variable(image.cuda())

    ###### 提取任意层特征 1 (batch_size = 1) ######
    '''
    # 模块 features模块  avgpool模块  classifier模块
    for name, module in model._modules.items():
        print(name)
        # features
        # avgpool
        # classifier
        if name == 'features':
            feature_img = module(images)
            print(feature_img.shape)
    # features 模块 第0层 的特征
    for name, module in model._modules['features']._modules.items():
        print(name)
        images = module(images)
        print(images.shape)
        if name == '0':
            break
    '''
    ###### 提取任意层特征 2 ######  推荐使用
    features = []
    def hook(module, input, output):
        # module:  model.features
        # input:   in forward function
        # output   self.features()
        print('module: ', module)
        print('input val: ', input)
        print('output val ', output)
        features.append(output.clone().detach())
    # features  avgpool  classifier
    # 替换 module-name 即可 ---> model.module-name.register_forward_hook
    handle = model.features.register_forward_hook(hook)
    model_out = model(images)
    features_out = features[0]
    print(features_out.size())
    handle.remove()
    ###### end 提取任意层特征 2 ######  推荐使用

    infer_time_start = time.time()
    y_pred = model(images)
    infer_time = time.time() - infer_time_start
    print('infer time: {0} ms'.format(infer_time*1000))

    # 每行 最大值索引
    _, pred = torch.max(y_pred.data, 1)
    print(pred)

    img = make_grid(image)# image shape (B x C x H x W)
    img = img.numpy().transpose(1, 2, 0)# H x W x C
    mean = [0.5, 0.5, 0.5]
    std = [0.5, 0.5, 0.5]
    img = img*std+mean
    print("Pred Label:", [classes[i] for i in pred])
    plt.imshow(img)
    # plt.show()

def print_hi(name):
    print(f'Hi, {name}')

if __name__ == '__main__':
    print_hi('transfer learning....')

 

posted @ 2020-10-27 14:23  土博姜山山  阅读(570)  评论(0编辑  收藏  举报