首页 > 其他分享 >[附CIFAR10炼丹记前编] CS231N assignment 2#5 _ pytorch 学习笔记 & 解析

[附CIFAR10炼丹记前编] CS231N assignment 2#5 _ pytorch 学习笔记 & 解析

时间:2023-04-15 10:35:10浏览次数:52  
标签:CS231N nn CIFAR10 assignment self torch 32 model size

pytorch

环境搭建

课程给你的环境当中, 可以直接用pytorch, 当时其默认是没有给你安装显卡支持的. 如果你只用CPU来操作, 那其实没什么问题, 但我的电脑有N卡, 就不能调用. 

考虑到我已有pytorch环境(大致方法就是确认pytorch版本和对应的cuda版本安装cuda,再按照官网即可,建议自己搜索), 所以需要安装jupyter. 但是默认情况下如果一个个安装比如这样

pip install jupyter==1.0.0
pip install ipython==7.4.0

pip会默认给你安装依赖导致版本异常. 所以我还是在原本的requirements.txt做了裁剪pip安装,实现了自己的pytorch环境的工作.

加载数据

pytorch可以帮我们获取数据集.这是我以前笔记的内容:

import torch
from torch.utils.data import DataLoader
import torchvision
testSet = torchvision.datasets.CIFAR10(root="./train",train=False,transform=torchvision.transforms.ToTensor(),download=True)
mydataloader = DataLoader(testSet, batch_size=16,shuffle=True)

作业的区别大抵就是: dataloader的batch_size不大一样, 随后Transform也多了一层. 

transform = T.Compose([
                T.ToTensor(),
                T.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
            ])

这就是图像预处理,我们曾经的图像一直是减去了平均灰度图像, 而现在被直接写死了, 并额外做了个标准化.

低抽象度的实现

我们在B站搜到的教程大多数都是直接调用API搭建神经网络, 被封装的很好. 但我们应当如同之前那样, 从操作矩阵开始. 不过尽管如此, 也会比之前略简单, 因为只要我们开启了选项x.requires_grad == True, pytorch就能够帮我们完成梯度的记录, 后续只需要简单的代码就可以完成梯度下降了.

pytorch的矩阵被称为(更准确说是被封装为)张量(tensor), 其实就是换了个名字, 你可以理解区别就是它可以被放在GPU运算. 首先是flatten函数.

import torch # 核心module

def flatten(x):
    N = x.shape[0] # read in N, C, H, W
    return x.view(N, -1)  # "flatten" the C * H * W values into a single vector per image

def test_flatten():
    x = torch.arange(12).view(2, 1, 3, 2)
    print('Before flattening: ', x)
    print('After flattening: ', flatten(x))

这个操作和numpy几乎是一摸一样,此外Numpy的很多其他操作也都支持. 但是视图的意义在于, x.view本质上和x指代的是同一个数组, 只是呈现形式不同, 如果我们把x.view赋给了其他元素(比如xv = x.view), 那么x元素修改,xv会同步修改. 然而奇妙的是, 我们对xv可以当作是一个真正reshape后的矩阵来使用,这也方便了全连接层.

import torch.nn.functional as F  # 要用到函数就要做这个
dtype = torch.float32  # 单精度. 不用双精度原因是节约计算量, 且专业卡以外的geforce N卡普遍大幅削弱了双精度算力

def two_layer_fc(x, params):
    x = flatten(x) # 展平成N个向量
    w1, w2 = params 

    x = F.relu(x.mm(w1)) # mm矩阵乘法,relu函数
    x = x.mm(w2) # mm乘法
    return x
    

def two_layer_fc_test():
    hidden_layer_size = 42
    x = torch.zeros((64, 50), dtype=dtype)  # 生成矩阵,语法和numpy一致
    w1 = torch.zeros((50, hidden_layer_size), dtype=dtype)
    w2 = torch.zeros((hidden_layer_size, 10), dtype=dtype)
    scores = two_layer_fc(x, [w1, w2])
    print(scores.size())  

two_layer_fc_test()

 与之类似, 我们通过内置的函数实现卷积网络推演 (请注意: 这里要求是conv=>relu=>conv=>relu=>fc)

    out1 = F.conv2d(x, conv_w1, bias=conv_b1, stride=1, padding=(2,2)) # 注意参数
    relu1 = F.relu(out1)
    out2 = F.conv2d(relu1, conv_w2, bias=conv_b2, stride=1, padding=(1,1))
    relu2 = F.relu(out2)
    scores = torch.mm(flatten(relu2), fc_w) + fc_b

权重的初始化

之前我们都是直接用weight scale超参数. 但是我们需要一个经验的初始值, 也就是所谓的"Kaiming normalization":

如果权重为二阶矩阵(FC), 则令n=矩阵的shape[0]
如果权重为>三阶矩阵(CNN..), 则令n=shape[1]之后所有shaape相乘
随后令W ~ N(0,√(2/N))

当然, 不同的激活函数对应的初始化也不一样, 比如说这个就是针对relu的. 而假若我们用的是tanh这样的激活函数, 则使用xaiver初始化.

验证准确性

包括在后面高度抽象化的使用中, 我们是不希望pytorch计算梯度的. 所以加上了no_grad手动要求不计算梯度.

def check_accuracy_part2(loader, model_fn, params):
    num_correct, num_samples = 0, 0
    with torch.no_grad():
        for x, y in loader: # x是图像,y是label
            x = x.to(device=device, dtype=dtype)  # x.to表示将这个tensoy移动到某个设备中演算
            y = y.to(device=device, dtype=torch.int64) # 转化成int, 因为结果只可能是整数,防止精度损失导致判断异常
            scores = model_fn(x, params)
            _, preds = scores.max(1) # #取最大值
            num_correct += (preds == y).sum() # 计算准确度
            num_samples += preds.size(0)
        acc = float(num_correct) / num_samples
        print('Got %d / %d correct (%.2f%%)' % (num_correct, num_samples, 100 * acc))

而在手动计算梯度的时候, 我们先通过自带的函数计算梯度, 随后反演的时候不允许梯度计算.

for t, (x, y) in enumerate(loader_train):
        x = x.to(device=device, dtype=dtype)
        y = y.to(device=device, dtype=torch.long)
        scores = model_fn(x, params) # 推演
        loss = F.cross_entropy(scores, y) # 计算误差,softmax
        loss.backward() # 计算梯度
        with torch.no_grad():
            for w in params:
                w -= learning_rate * w.grad # 调用w.grad得到梯度值
                w.grad.zero_() # 令梯度为0,避免影响下一次计算

训练网络

因为前面很多地方已经封装好了, 我们要做i的就是根据备注初始化参数即可. 按照指示, 得到了大约47%的准确度.

conv_w1 = random_weight((32,3,5,5))
conv_b1 = zero_weight((32,))
conv_w2 = random_weight((16,32,3,3))
conv_b2 = zero_weight((16,))
fc_w = random_weight((16 * 32 * 32,10))
fc_b = zero_weight(10)

更抽象 - 继承网络基类

从这开始, 如果你入门过pytorch, 就会开始熟悉起来了. 在更抽象的过程中, 我们不需要手动去操作张量. 我们定义网络的内, 其中的成员就是网络的各层. 这个网络需要继承nn.Module这个网络基类. 其中有两个核心函数:

  • 构造函数: 定义各个层
  • forward函数: 层推演

从现在开始, 我们就是API caller了, 仅仅体验一番是绝对不可能记住的! pytorch是工具, 我们就要学会去查阅技术文档.  torch.nn — PyTorch 1.8.0 documentation   我感觉pytorch的文档写的还是不错的, 很规范而整洁, 用好几乎能替代google了(

import torch.nn as nn # 这两个包是方便我们操作封装的
import torch.optim as optim

class TwoLayerFC(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super().__init__() # 务必初始化超类
        self.fc1 = nn.Linear(input_size, hidden_size)
        nn.init.kaiming_normal_(self.fc1.weight) # 对weight初始化
        self.fc2 = nn.Linear(hidden_size, num_classes)
        nn.init.kaiming_normal_(self.fc2.weight)
    
    def forward(self, x):
        x = flatten(x) 
        scores = self.fc2(F.relu(self.fc1(x))) # 直接用self.层(tensor)就能完成操作
        return scores

下面是卷积网络:

def __init__(self, in_channel, channel_1, channel_2, num_classes):
        super().__init__()
        # 务必记住生成图像尺寸的公式
        #  H' = 1 + (H + 2 * pad - HH) / stride
        #  W' = 1 + (W + 2 * pad - WW) / stride
        self.conv1 = nn.Conv2d(in_channels=in_channel,out_channels=channel_1,kernel_size=5,padding=2, bias=True)
        nn.init.kaiming_normal_(self.conv1.weight)
        nn.init.constant_(self.conv1.bias,0) # 设置权重的初始值为0
        self.relu = F.relu
        self.conv2 = nn.Conv2d(in_channels=channel_1,out_channels=channel_2,kernel_size=3,padding=1,bias=True)
        nn.init.kaiming_normal_(self.conv2.weight)
        nn.init.constant_(self.conv2.bias,0)
        self.fc = nn.Linear(channel_2 * 32 * 32, num_classes)


    def forward(self, x):
        scores = None
        x = self.relu(self.conv1(x))
        x = self.relu(self.conv2(x))
        scores = self.fc(flatten(x))
        return scores

下面的就没什么特别的了, 固定的步骤, 我做一下总结:

初始化(loss,optimizer等),设置GPU(model,loss,img,label) => 设置为训练模式 => 清零梯度 => model(x) => 计算梯度,反演 => 梯度下降

    model = model.to(device=device)  # move the model parameters to CPU/GPU
    for e in range(epochs):
        for t, (x, y) in enumerate(loader_train):
            model.train()  # put model to training mode
            x = x.to(device=device, dtype=dtype)
            y = y.to(device=device, dtype=torch.long)
            scores = model(x)
            loss = F.cross_entropy(scores, y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            if t % print_every == 0:
                print('Iteration %d, loss = %.4f' % (t, loss.item()))
                check_accuracy_part34(loader_val, model)
                print()

 作业只要求我们完成初始化

model = ThreeLayerConvNet(3, channel_1, channel_2, 10)
optimizer = torch.optim.SGD(model.parameters(),lr=learning_rate)

在封装之后准确率达到了52%.

sequential

这个更加简单, 我们甚至不需要写一个类了, 只需要指定各个层是什么就可以了.

model = nn.Sequential(
    Flatten(),
    nn.Linear(3 * 32 * 32, hidden_layer_size),
    nn.ReLU(),
    nn.Linear(hidden_layer_size, 10),
)
optimizer = optim.SGD(model.parameters(), lr=learning_rate, # SGD可以指定动量作为升级版
                     momentum=0.9, nesterov=True)
model = nn.Sequential(
    nn.Conv2d(3,channel_1,kernel_size=5,padding=2),
    nn.ReLU(),
    nn.Conv2d(channel_1,channel_2,kernel_size=3,padding=1),
    nn.ReLU(),
    nn.Flatten(),
    nn.Linear(channel_2*32*32,10)
)
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate,
                     momentum=0.9, nesterov=True)

 有了改进的梯度下降方法帮助,准确率达到了58%.

开放式作业 - 设计神经网络 (CIFAR10炼丹记前编)

真理 ------

我当时看的那个教程就是采用了cifar作为目标. 在那个时候, 我参照了这样的网络模型

对应的代码如下:

model = torch.nn.Sequential( 
            torch.nn.Conv2d(in_channels=3,out_channels=32,kernel_size=5,stride=1,padding=2),
            torch.nn.MaxPool2d(2), 
            torch.nn.Conv2d(in_channels=32,out_channels=32,kernel_size=5,stride=1,padding=2),
            torch.nn.MaxPool2d(2),
            torch.nn.Conv2d(in_channels=32, out_channels=64, kernel_size=5, stride=1, padding=2),
            torch.nn.MaxPool2d(2),
            torch.nn.Flatten(),
            torch.nn.Linear(1024,64),
            torch.nn.Linear(64,10)
        )

很遗憾, 当时我仅仅做到了67%的准确度. (这个图里面是tensorboard)

但是其他博客各路大佬做到了76%. 我就开始思考, 究竟问题在何处? 

首先我先单纯把我之前的代码转移到这个作业上, 确认按照作业的参数统一标准. 得到准确度为66.96%, 果真如此.

我发现了那时试验的最大硬伤: 没有激活函数,且下降方式是SGD. 所以我加上了ReLU, 同时更换下降为adam, lr=1e-3. 这样准确度提升到了71.8%. 值得注意的是, 我在pycharm自己的代码试验只能跑到70%左右,可能是自己跑的epoch不够多,毕竟loss波动不等于没有下降趋势.

随后参照博客(3条消息) cs231n assignment2 PyTorch_一叶知秋Autumn的博客-CSDN博客 , 将输出通道数3=>32=>64=>128(原本是3=>32=>32=>64), 进一步提升到73.1%. 再增加无法看到任何效果.

我们还没用到batch normalize和dropout, 纠结了这么久没想到用课程之前学的确实还是有点失败的. 我在每层conv/relu间加入了正则化, 并在全连接层中间加入了0.5的dropout. 嗯,很好, 我们将准确度又拉高了2.9%. 这一步可能是我所有操作中最有效的了. 在此基础上加上l2正则化(我的reg=0.01)之后效果会急剧下降, 因为已经有了dropout防止过拟合, 二者作用互补, 有时不应当一起使用.

随后疯狂对着上面的网络调参, 包括将卷积核大小从5=>3(+1.8%,原因不知), 使用leakyrelu(无用), 增大全连接层隐含层并在全连接层内补上relu(没用). 准确度达到78%.

考虑我的显卡还不错, 所以增大了batch_size(=>256)和增大了epoch数量(50), 期望在时间相近下取得较好效果. 具体实验参照了这个博客如何用Keras从头开始训练一个在CIFAR10上准确率达到89%的模型 - 知乎 (zhihu.com) 当然这就有点不讲武德了, 纯粹就是试试看时间拉长后的效果如何. 准确度达到79.6%. 预计如果训练足够长时间, 准确度能够达到80%-81%.

 但是结合别人的结果, 这也让我感觉, 或许再怎么调参上限就是如此了. 所以留给我们的就是几个方向: 数据增强/改变训练方式, 更高阶的调节手段, 更换网络架构. 

上面的实验算是将之前课程的全部内容过了一遍. 我后面会专门写一个博客记录一下, 因为我自知这个是需要系统学习的, 看看学了assignment3之后会不会有新知. 不说了,要准备期中考试了(

标签:CS231N,nn,CIFAR10,assignment,self,torch,32,model,size
From: https://www.cnblogs.com/360MEMZ/p/17318545.html

相关文章

  • COSC 2408 Assignment 2
    ProgrammingProject1COSC2408Assignment2AssessmentTypeThisisagroupassignment.Youneedtohaveformedyourcanvasgroupbeforesubmittingthisassignment.Submiton-lineviaCanvas→Assignments→Assignment2.Marksawardedformeetingrequirements......
  • [深入推导]CS231N assignment 2#4 _ 卷积神经网络 学习笔记 & 解析
    卷积神经网络基本算法实现卷积神经网络应该算是图像处理中绝对的主流了,关于算法得基本思想我在之前也学的比较懂了,这点如果不了解网上有很多教程.不过我并没有用代码亲自实现它.我们首先确定怎么编写.前面搞全连接网络总是会想着怎么去简化运算,现在我们接触了新的网络,......
  • CS231N assignment 2#3 _ dropout 学习笔记 & 解析
    dropout定义&作用&基本实现如课程所说,dropout最大的意义在于防止过拟合.我们还记得,dropout在网络架构上介于激活函数之后,下一层输入之前.想法很简单,就是将隐含层的某些数据屏蔽掉,直接从以输入到下一层,概率为p. 需要注意的是,dropout是仅针对训练而言的,测试......
  • CS231N assignment 2 _ normalization 学习笔记 & 解析
    预警:本次内容不算多,但数学推导较复杂Normalization归一化的意义之前内部的权重没有做过标准化.实际上如果能标准化,可以提升训练效果,甚至可以提升精度(虽然不大).设立专门的batch/layernormalization层的意义在于:梯度更加规范对于学习率(可以更高),初始化权重等......
  • CS231N assignment 2 _ 全连接神经网络 学习笔记 & 解析
    本章内容较多预警Intro我们写过一个两层的神经网络,但是梯度是在loss内计算的,因此对网络的架构相关的修改难免比较困难.为此,我们需要规范化网络设计,设计一系列函数.,后面我们还会封装一个类,这也是最希望的方式了.环境搭建又到了工科生最上头(bushi的搭环境环节.......
  • CS231N assignment 1 _ 图像特征 学习笔记 & 解析
    利用图像特征提升准确度我们之前仅仅是逐个像素的计算,而忽视和图像在HSV空间的像素集中度和图像纹理特征.根据别人的博客,准确率进一步从54%提升到58%.下面进行解析.HOG/颜色直方图代码解读本次的作业没有要求我们自己实现,但是我们还是应该读懂代码. 在前面,我们一直......
  • CS231N assignment 1 _ softmax 学习笔记 & 解析
    [注意:考虑到这个和SVM重复很多,所以会一笔带过/省略一些]softmax和SVM只是线性分类器分类结果的评判不同,完全依靠打分最大来评判结果,误差就是希望结果尽可能接近正确分类值远大于其他值.我们将打分结果按照指数权重正则化为和为1的向量:而这个值希望尽可能接近1,也就是-l......
  • [博客入坑]CS231N assignment 1 _ KNN 知识 & 详细解析
    从零开始的全新博客我之前一直在语雀上更新文章,但是一会不更发现居然需要VIP才能发博客了:不过考虑到自己确实有一会没写博客了,之前对神经网络在课上学过,也鼓捣过pytorch,但是深感自己没有系统学习过.第一次接触这种公开课,希望也能有种从零开始的感觉,让自己面对这......
  • 深度学习—ResNet_CIFAR100代码
        1'''2参考资料:PyTorch官方文档3'''45#导入所需的包6importtorch7importwandb8importtorch.nnasnn9fromtorchvisionimporttransforms10fromtorchvision.datasetsimportCIFAR10011fromtorch......
  • 深度学习—AlexNet_CIFAR100代码
        1#导入所需的包2importtorch3#importwandb4importtorch.nnasnn5fromtorchvisionimporttransforms6fromtorch.utils.dataimportDataLoader7fromtorchvision.datasetsimportCIFAR10089#使用Compose容器组合定义图......