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


    第一周:深度学习及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)提升了一些准确率,在其他方面并没有什么太大的改动,可能是相关内容接触不多的原因,所以在接下里的学习中,也会更加注重多看多学,能够学以致用。

  • 相关阅读:
    n个元素进栈,有几种出栈方式
    The following IP can be used to access Google website
    一个未排序整数数组,有正负数,重新排列使负数排在正数前面,并且要求不改变原来的 相对顺序 比如: input: 1,7,-5,9,-12,15 ans: -5,-12,1,7,9,15 要求时间复杂度O(N),空间O(1) 。
    当今世界最受人们重视的十大经典算法
    指针
    变量作用域和生存期
    一篇文章搞清spark内存管理
    Spark的Shuffle是怎么回事
    一篇文章搞清spark任务如何执行
    scala这写的都是啥?一篇文章搞清柯里化
  • 原文地址:https://www.cnblogs.com/hyzs1220/p/13369801.html
Copyright © 2020-2023  润新知