第一次作业:深度学习基础

第一周:深度学习及pytorch基础

视频学习

由于之前在跟随老师完成本科毕业设计的时候,已经对相关视频进行过学习,所以就不再单独写这一部分的笔记了,可以直接查看之前的学习笔记:链接

代码练习

螺旋数据分类

(贴一些感觉比较关键的代码部分)

# 得到一组随机数
seed = 12345
random.seed(seed)
torch.manual_seed(seed)

N = 1000  # 每类样本的数量
D = 2  # 每个样本的特征维度
C = 3  # 样本的类别
H = 100  # 神经网络里隐层单元的数量

X = torch.zeros(N * C, D).to(device) # X:样本总数*样本的维度特征(2)
Y = torch.zeros(N * C, dtype=torch.long).to(device) # Y:样本总数
# 将这些随机数据构成螺旋形
for c in range(C):
    index = 0
    t = torch.linspace(0, 1, N)
    inner_var = torch.linspace( (2*math.pi/C)*c, (2*math.pi/C)*(2+c), N) + torch.randn(N) * 0.2
    # 将当前的数据分布可视化
    # 每个样本的(x,y)坐标(即维度特征D)都保存在 X 里
    # Y 里存储的是样本的类别,分别为 [0, 1, 2],图中用不同颜色表示
    for ix in range(N * c, N * (c + 1)):
        X[ix] = t[index] * torch.FloatTensor((math.sin(inner_var[index]), math.cos(inner_var[index])))
        Y[ix] = c
        index += 1

线性模型

learning_rate = 1e-3
lambda_l2 = 1e-5

# nn 包用来创建线性模型
# 每一个线性模型都包含 weight 和 bias
# 没有激活函数,直接将输入层与隐含层连接,然后隐含层与输出层相连得到输出
model = nn.Sequential(
    nn.Linear(D, H),
    nn.Linear(H, C)
)

model.to(device) # 把模型放到GPU上

# nn 包含多种不同的损失函数,这里使用的是交叉熵(cross entropy loss)损失函数
criterion = torch.nn.CrossEntropyLoss()

# 这里使用 optim 包进行随机梯度下降(stochastic gradient descent)优化
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=lambda_l2)

# 开始训练
for t in range(1000):
    # 把数据输入模型,得到预测结果
    y_pred = model(X)
    # 计算损失和准确率
    loss = criterion(y_pred, Y) # Y中包含真实结果值
    score, predicted = torch.max(y_pred, 1)
    acc = (Y == predicted).sum().float() / len(Y)
    print('[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f' % (t, loss.item(), acc))
    display.clear_output(wait=True)

    # 反向传播前把梯度置 0 
    # pytorch默认会对梯度进行累加,不过这里每次训练都是相对独立的,不需要上一次的梯度数据
    optimizer.zero_grad()
    # 反向传播优化,计算当前梯度 
    loss.backward()
    # 更新全部参数
    optimizer.step()
    
# 最后的训练结果保存在y_pred(样本总数*样本的类别数)中,在每一个样本(行)中,包含了属于每一类别(列)的可能性,其中最大的就是最终得到的预测值。
# 由于是一个简单的线性模型,输入输出之间只是简单的线性组合,所以效果较差,可以看到预测结果

两层神经网络结构

model = nn.Sequential(
    nn.Linear(D, H),
    nn.ReLU(),
    nn.Linear(H, C)
)
# 使用了激活函数,引入非线性函数,由于不是简单的线性组合,通过这种方式,神经网络几乎可以逼近任何函数

回归分析

代码给出了使用ReLu激活和Tanh激活的两种模型,而最终得到的结果有很大的不同,其主要原因感觉是因为ReLu激活函数不能很好的处理负数部分,将负数部分直接抛弃,从而导致出现了这样较为曲折的情况。

进阶练习

数据处理部分

这部分为 Kaggle 于 2013 年举办的猫狗大战竞赛,使用在 ImageNet 上预训练的 VGG 网络进行测试。因为原网络的分类结果是1000类,所以这里进行迁移学习,对原网络进行 fine-tune (即固定前面若干层,作为特征提取器,只重新训练最后两层)。

仔细研读AI研习社猫狗大战赛题的要求:https://god.yanxishe.com/41 (目前比赛已经结束,但仍可做为练习赛每天提交测试结果)

下载比赛的测试集(包含2000张图片),利用fine-tune的VGG模型进行测试,按照比赛规定的格式输出,上传结果评测(练习赛每天仅可评测5次)。我已进行测试,VGG模型训练 1 个 epoch 的准确率约为 96.1 %。

在这一部分,老师已经给出了主要的代码框架,所以只需要针对猫狗大战的具体要求进行部分修改即可。

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


# 判断是否存在GPU设备,记得打开colab的gpu,不然默认使用cpu跑,特别慢
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('Using gpu: %s ' % torch.cuda.is_available())

下载AI研习社的数据集

! wget https://static.leiphone.com/cat_dog.rar
# 解压rar压缩包,需要安装rarfile库(pip install rarfile)
import rarfile
path = "cat_dog.rar"
path2 = "/content/"

rf = rarfile.RarFile(path) #待解压文件
rf.extractall(path2) #解压指定文件路径

由于下载的该数据集的文件存储结构与pytorch的规范格式不太一样,所以先对数据集进行一些整理操作。

mkdir cat_dog/val/Dog
mkdir cat_dog/val/Cat
mkdir cat_dog/train/Cat
mkdir cat_dog/train/Dog
mkdir cat_dog/test/test
mv cat_dog/val/dog* cat_dog/val/Dog/
mv cat_dog/val/cat* cat_dog/val/Cat/
mv cat_dog/train/cat* cat_dog/train/Cat/
mv cat_dog/train/dog* cat_dog/train/Dog/
mv cat_dog/test/*.jpg cat_dog/test/test

最终得到的文件结构如下:

normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])

vgg_format_train = transforms.Compose([
                transforms.RandomRotation(30),# 随机旋转
                transforms.CenterCrop(224),
                transforms.ToTensor(),
                normalize,
                
            ])

vgg_format = transforms.Compose([
                transforms.CenterCrop(224),
                transforms.ToTensor(),
                normalize,
            ])

data_dir = './cat_dog/'

# 利用ImageFolder进行分类文件夹加载
# train_sets = torchvision.datasets.ImageFolder('./cat_dog/train',vgg_format)
# 两种加载数据集的方法
dsets = {x: datasets.ImageFolder(os.path.join(data_dir, x), vgg_format)
         for x in ['train', 'val']}

tsets = {y: datasets.ImageFolder(os.path.join(data_dir, y), vgg_format)
        for y in ['test']}

# tset_sizes = {y: len(tsets[y]) for y in ['test']}
# print('tset_sizes', tset_sizes) =====> tset_sizes {'test': 2000} 两千张测试集

# dset_sizes = {x: len(dsets[x]) for x in ['train', 'val']}
# dset_classes = dsets['train'].classes
# print(dsets['train'].classes) =====> ['Cat', 'Dog']
# print(dsets['train'].class_to_idx) =====> {'Cat': 0, 'Dog': 1}
# print('dset_sizes: ', dset_sizes) =====> dset_sizes:  {'train': 20000, 'val': 2000}

loader_train = torch.utils.data.DataLoader(dsets['train'], batch_size=64, shuffle=True, num_workers=6)
loader_valid = torch.utils.data.DataLoader(dsets['val'], batch_size=5, shuffle=False, num_workers=6)

loader_test = torch.utils.data.DataLoader(tsets['test'],batch_size=5,shuffle=False,num_workers=6)

'''
valid 数据一共有2000张图,每个batch是5张,因此,下面进行遍历一共会输出到 400
同时,把第一个 batch 保存到 inputs_try, labels_try,分别查看
'''
count = 1
for data in loader_valid:
    # print(count, end='
')
    if count == 1:
        inputs_try,labels_try = data
    count +=1

# 显示图片的小程序

def imshow(inp, title=None):
#   Imshow for Tensor.
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = np.clip(std * inp + mean, 0,1)
    plt.imshow(inp)
    if title is not None:
        plt.title(title)
    plt.pause(0.001)  # pause a bit so that plots are updated

# 显示 labels_try 的5张图片,即valid里第一个batch的5张图片
out = torchvision.utils.make_grid(inputs_try)
imshow(out, title=[dset_classes[x] for x in labels_try])

创建VGG Model

torchvision中集成了很多在 ImageNet (120万张训练数据) 上预训练好的通用的CNN模型,可以直接下载使用。

在本课程中,我们直接使用预训练好的 VGG 模型。同时,为了展示 VGG 模型对本数据的预测结果,还下载了 ImageNet 1000 个类的 JSON 文件。

!wget https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json
model_vgg = models.vgg16(pretrained=True)

# 得到 ImageNet 1000 个试别类别
with open('./imagenet_class_index.json') as f:
    class_dict = json.load(f)
dic_imagenet = [class_dict[str(i)][1] for i in range(len(class_dict))]

inputs_try , labels_try = inputs_try.to(device), labels_try.to(device)
model_vgg = model_vgg.to(device)

outputs_try = model_vgg(inputs_try)

# print(outputs_try.shape) =====》 torch.Size([5, 1000])

'''
可以看到结果为5行(batch大小),1000列(种类)的数据,每一列代表对每一种目标识别的结果。
但是也可以观察到,结果非常奇葩,有负数,有正数,
为了将VGG网络输出的结果转化为对每一类的预测概率,我们把结果输入到 Softmax 函数
'''
m_softm = nn.Softmax(dim=1)
probs = m_softm(outputs_try)
vals_try,pred_try = torch.max(probs,dim=1)

print([dic_imagenet[i] for i in pred_try.data])
imshow(torchvision.utils.make_grid(inputs_try.data.cpu()), 
       title=[dset_classes[x] for x in labels_try.data.cpu()])
# 能够看出,VGG模型是十分强大的,再识别出猫的基础上,能够对猫的种类也进行识别分类

VGG模型修改

在本次实验中,并不需要VGG原有的1000类的类别识别,所以可以对VGG最后的全连接层进行修改,把最后的 nn.Linear 层由1000类,替换为2类。同时保留前面层网络的参数权重,即将反向传播的过程中,不再更新前面层的权重,仅更新最后一层。

# print(model_vgg) =====> in_features=4096, out_features=1000

model_vgg_new = model_vgg;

for param in model_vgg_new.parameters():
    param.requires_grad = False
model_vgg_new.classifier._modules['6'] = nn.Linear(4096, 2)
model_vgg_new.classifier._modules['7'] = torch.nn.LogSoftmax(dim = 1)

model_vgg_new = model_vgg_new.to(device)

# print(model_vgg_new.classifier) =====> in_features=4096, out_features=2

训练并测试网络

'''
第一步:创建损失函数和优化器

损失函数 NLLLoss() 的 输入 是一个对数概率向量和一个目标标签. 
它不会为我们计算对数概率,适合最后一层是log_softmax()的网络. 
'''
criterion = nn.NLLLoss()
# criterion = nn.CrossEntropyLoss()
# 这里的CrossEntropyLoss(交叉熵损失函数)通常用于多分类问题,也可以用作本次实验的二分类,不过感觉实验结果要比NLLLoss(负对数似然损失函数)差

# 学习率
lr = 0.001

# 随机梯度下降
# optimizer_vgg = torch.optim.SGD(model_vgg_new.classifier[6].parameters(),lr = lr)
# optimizer_vgg = torch.optim.ASGD(model_vgg_new.classifier[6].parameters(),lr = lr)
optimizer_vgg = torch.optim.Adam(model_vgg_new.classifier[6].parameters(),lr = lr)
# 这里按照老师的提示,尝试了Adam,是能明显看出准确率的提升的
'''
第二步:训练模型
'''

def train_model(model,dataloader,size,epochs=1,optimizer=None):
    model.train()
    
    for epoch in range(epochs):
        running_loss = 0.0
        running_corrects = 0
        count = 0
        for inputs,classes in dataloader:
            inputs = inputs.to(device)
            classes = classes.to(device)
            outputs = model(inputs)
            loss = criterion(outputs,classes)           
            optimizer = optimizer
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            _,preds = torch.max(outputs.data,1)
            # statistics
            running_loss += loss.data.item()
            running_corrects += torch.sum(preds == classes.data)
            count += len(inputs)
            print('Training: No. ', count, ' process ... total: ', size)
        epoch_loss = running_loss / size
        epoch_acc = running_corrects.data.item() / size
        print('Loss: {:.4f} Acc: {:.4f}'.format(
                     epoch_loss, epoch_acc))
        
# 训练网络(train数据集)
train_model(model_vgg_new,loader_train_trans,size=dset_sizes['train'], epochs=1, 
          optimizer=optimizer_vgg)

def test_model(model,dataloader,size):
    model.eval()
    predictions = np.zeros(size)
    all_classes = np.zeros(size)
    all_proba = np.zeros((size,2))
    i = 0
    running_loss = 0.0
    running_corrects = 0
    for inputs,classes in dataloader:
        inputs = inputs.to(device)
        classes = classes.to(device)
        outputs = model(inputs)
        loss = criterion(outputs,classes)           
        _,preds = torch.max(outputs.data,1)
        # statistics
        running_loss += loss.data.item()
        running_corrects += torch.sum(preds == classes.data)
        predictions[i:i+len(classes)] = preds.to('cpu').numpy()
        all_classes[i:i+len(classes)] = classes.to('cpu').numpy()
        all_proba[i:i+len(classes),:] = outputs.data.to('cpu').numpy()
        i += len(classes)
        print('Testing: No. ', i, ' process ... total: ', size)        
    epoch_loss = running_loss / size
    epoch_acc = running_corrects.data.item() / size
    print('Loss: {:.4f} Acc: {:.4f}'.format(
                     epoch_loss, epoch_acc))
    return predictions, all_proba, all_classes

# 测试网络(valid)
predictions, all_proba, all_classes test_model(model_vgg_new,loader_valid,size=dset_sizes['val'])

实验结果处理

由于研习社要求提交的结果是csv文件,并进行了排序,所以对于实验结果需要进行一定的操作。

import re

# 得到测试图片编号
string = tsets['test'].imgs[0][0]
num = re.sub("D", "", string)

result = []
# 将测试数据集(test)输入到网络中,得到识别结果
for item,lable in loader_test:
  item = item.to(device)
  ll =  model_vgg_new(item)
  _,pre = torch.max(ll.data,1)

  result += pre

# 结果排序
result_end =list()

cc = 0

for item in result:
  string = tsets['test'].imgs[cc][0]
  num = re.sub("D", "", string)
  result_end.append((num,item.tolist()))
  cc += 1

result_sort = sorted(result_end,key=lambda x:int(x[0]))

# 写入文件
import csv
 
f = open('out_file.csv','w')
writer = csv.writer(f)
for i in result_sort:
    writer.writerow(i)
f.close()

总结

通过这一周的学习,更多的是对于代码的掌握,以及一些知识的回顾。对于VGG网络的优化处理并没有想出很好的优化方法,仅通过修改优化器(SGD->Adam)提升了一些准确率,在其他方面并没有什么太大的改动,可能是相关内容接触不多的原因,所以在接下里的学习中,也会更加注重多看多学,能够学以致用。

原文地址:https://www.cnblogs.com/hyzs1220/p/13369801.html