AlexNet-Pytorch自学笔记

目录

一、网络模型

1.1 torch.nn.Conv2d() 二维卷积

1.1.1参数

1.1.2 输入输出 

1.2 torch.nn.ReLU() 激活函数

1.3 torch.nn.MaxPool2d() 最大池化层

1.3.1参数

1.3.2 输入输出 

1.4 torch.nn.Flatten() & torch.nn.Dropout()

1.4.1 Flatten参数

1.4.2 Dropout参数

1.5 torch.nn.Linear() 线性全连接层

1.5.1 参数

1.6 torchsummary

二、数据集预处理 

2.1 拆分训练集和测试集

2.2 图片预处理

2.2.1 transforms

 2.2.2 转为数据集

三、训练和测试 



一、网络模型

(来自网络)

网络模型如上图所示 ,共包括5个卷积网络层+3个全连接层;输入的数据是(3,224,224)的图像数据,3是通道数(RGB),224×224为宽高。因为没用gpu,所以网络模型图中上下两部分的通道数是加在一起的,每层卷积网络的(in_channels,out_channels)分别是(3,96)->(96,256)->(356,384)->(384,384)->(384,256)。

构建网络的代码(注释了每层输入输出的维度,想看得直观的话可以用1.6里的torchsummary):

import torch
from torch import nn
class alexnet(nn.Module):
    def __init__(self) -> None:
        super(alexnet,self).__init__()        #都是标准写法
        self.nets = nn.Sequential(            #构建网络

            #第一层(batch_size,3,224,224)->(b,96,55,55) [227+2x2-(11-1)-1]/4+1=55
            nn.Conv2d(3,96,kernel_size=11,stride=4,padding=2),
            nn.ReLU(inplace=True),
            #第一个maxpooling (b,96,55,55)->(b,96,27,27) [55-(3-1)-1]/2+1=27
            nn.MaxPool2d(kernel_size=3,stride=2),

            #第二层(b,96,27,27)->(b,256,27,27)
            nn.Conv2d(96,256,5,padding=2),
            nn.ReLU(inplace=True),
            #第二个maxpooling (b,256,27,27)->(b,256,13,13)
            nn.MaxPool2d(3,2),

            #第三层(b,256,13,13)->(b,384,13,13)
            nn.Conv2d(256,384,3,padding=1),
            nn.ReLU(inplace=True),

            #第四层输出维度不变
            nn.Conv2d(384,384,3,padding=1),
            nn.ReLU(inplace=True),

            #第五层(b,384,13,13)->(b,256,13,13)
            nn.Conv2d(384,256,3,padding=1),
            nn.ReLU(inplace=True),
            #第三个maxpooling(b,256,13,13)->(b,256,6,6)
            nn.MaxPool2d(3,2),

            #展平(b,256,6,6)->(b,256*6*6)            
            nn.Flatten(),
            nn.Dropout(p=0.5),
    
            #第一个全连接层(b,9216)->(b,4096)
            nn.Linear(9216,4096),
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            #第二个FC (b,4096)->(b,2048)
            nn.Linear(4096,2048),
            nn.ReLU(inplace=True),
            #第三个FC,这里是2分类,就(b,2048)->(b,2)
            nn.Linear(2048,2)
        )
    def forward(self,input_x):
        x = self.nets(input_x)
        return x

1.1 torch.nn.Conv2d() 二维卷积

1.1.1参数

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)

以AlexNet第一层卷积层为例,                      

参数(=第一层网络的取值)类型解释
in_channels = 3int输入图像矩阵通道数,这里是三通道RGB
out_channel = 96int输出图像矩阵通道数,本层就是输入3通道输出96通道
 kernel_size = 11int or tuple卷积核的大小,用于和数据进行二维卷积的卷积核维度,如果是方阵可以直接输入维度int,不是就写作(w,h)

 stride=4 

(int or tuple, optional)步长,就是卷积一次后,往某个方向挪几步进行下一次卷积,默认stride=1
padding=2(int or tuple, optional)填补,在某一维度头尾各填补P个0,在本层中把227x227的图像填补到了231x231;输入(w,h)的话就是在左右两侧各补w列0,在上下各补h行0。默认padding=0
padding_mode

(string, optional)

填补方式,默认是补0,可选‘zeros’, ‘reflect’镜像(以边缘点为中心), ‘replicate’复制边缘点的值 or ‘circular’循环每行/列
dilation(int or tuple, optional)扩张,即卷积核里面点之间的间距,默认是1,以dilation=3为例,起点为(0,0),进行卷积核的点就是(0,0)(3,0)(6,0)(9,0)(3,3)(6,3)(9,3)....就是卷积核从田变成了㗊0.0
groups                (int, optional)分组,默认是1,把Nin个input features分为G组,每组内Nin/G个features与卷积核卷积输出Nout/G个output features,再把G个组的输出作为最终的输出
bias(bool, optional)偏差,默认True,在输出添加一个可学习的偏置量

                                                

1.1.2 输入输出 

torch.nn.Conv2d()要输入一个四维的张量input(N,C,H,W),对卷积神经网络的输入样本就是(batch_size, channels,height,weight),其中channels = 3, height = width =224

输出的张量维度与输入维度的关系是

 所以对(N,3,224,224)的输入数据,经过Conv2d(3,96,11,4,2),输出的Hout/Wout是

[224 + 2x2 - (11-1) - 1] / 4 + 1 = 55 

1.2 torch.nn.ReLU() 激活函数

激活函数ReLU(),函数比较简单,更详细地资料可以自查~

if input > 0:
    return input
else:
    return 0

1.3 torch.nn.MaxPool2d() 最大池化层

1.3.1参数

torch.nn.MaxPool2d(kernel_sizestride=Nonepadding=0dilation=1return_indices=False,

ceil_mode=False)

        其实和卷积层挺像的,只不过这里输出的是每个窗口范围内的最大值,其余的值全都抛弃。

        这样一来池化之后维度变小了, 冗余信息量、计算量、内存占用量云云的都降低了,而且也缓解了对数据特征位置的敏感性(我个人理解就是特征位置如果稍有变化,只要在池化时在同一个窗口里,最后都还是能输出特征的值)。                  

参数类型解释
 kernel_sizeint or tuple最大池化窗口的尺寸,int即为正方形,tuple就是(h,w)

 stride

(int or tuple, optional)步长,池化后挪动的步数,跟跟Conv2d基本一样,默认stride=1
padding(int or tuple, optional)填补,补0,跟Conv2d基本一样,默认padding=0
dilation(int or tuple, optional)扩张,同Conv2d,默认是1
return_indices               (bool, optional)返回最大位置索引,默认False
ceil_mode(bool, optional)启用向上取整,默认False,即默认向下取整

                               

1.3.2 输入输出 

同样类似Conv2d,torch.nn.MaxPool2d()输入一个四维的张量input(N,C,H,W),输出数据的维度计算的公式竟然是一模一样的...

1.4 torch.nn.Flatten() & torch.nn.Dropout()

1.4.1 Flatten参数

torch.nn.Flatten(start_dim=1end_dim=- 1)

        不需要输入参数,就默认把第一个之后的维度都拉平,也就是不动第一个维度,第二个维度的数量是其他维度乘积

1.4.2 Dropout参数

torch.nn.Dropout(p=0.5inplace=False)

        按概率p将元素随机置为0,用于防止过拟合。

1.5 torch.nn.Linear() 线性全连接层

全连接层的作用摘自其他文章:

全连接层的主要作用就是将前层(卷积、池化等层)计算得到的特征空间映射样本标记空间。简单的说就是将特征表示整合成一个值,其优点在于减少特征位置对于分类结果的影响,提高了整个网络的鲁棒性。

大概就是寻找一个映射,把卷积层中提取出的分布在各通道上的特征,全部映射到我们标记的分类,毕竟卷积网络输出的二维的特征图,最终还是要输出到一维上。

1.5.1 参数

torch.nn.Linear(in_featuresout_featuresbias=Truedevice=Nonedtype=None)

        全连接层就是输入的每一个神经元都参与到每个输出的计算,用torch.nn.Linear()的话就是按照y = xA^T + b的线性关系计算。参数也比较简单,输入数据的size,输出的size,bias为True就是加偏置量,也就是b。

        AlexNet里用了3个全连接层,最后一个全连接层输出的size就是分类问题类别的数量。至于为什么要用三层,查了很久大概的说法是层数少了没办法做到很好的把特征归纳分类,特别是第一层后面还跟着个非线性的ReLU(也许之后要看看论文才行

1.6 torchsummary

模型分析完啦,最后可以用torchsummary.summary来查看每层的输出尺寸和参数个数

summary(model, input_size: Any, batch_size: int = -1, device: str = "cuda") 

from torchsummary import summary

net = alexnet()
print("Input Shape: [-1, 3, 224, 224]")
print(summary(net,(3,224,224),-1,device='cpu'))

Input Shape: [-1, 3, 224, 224]
----------------------------------------------------------------------------------------------------------------
        Layer (type)               Output Shape         Param #        
================================================================       
            Conv2d-1           [-1, 96, 55, 55]          34,944        
              ReLU-2           [-1, 96, 55, 55]               0        
         MaxPool2d-3           [-1, 96, 27, 27]               0        
            Conv2d-4          [-1, 256, 27, 27]         614,656        
              ReLU-5          [-1, 256, 27, 27]               0        
            Conv2d-9          [-1, 384, 13, 13]       1,327,488        
             ReLU-10          [-1, 384, 13, 13]               0        
           Conv2d-11          [-1, 256, 13, 13]         884,992        
             ReLU-12          [-1, 256, 13, 13]               0        
        MaxPool2d-13            [-1, 256, 6, 6]               0        
          Flatten-14                 [-1, 9216]               0        
          Dropout-15                 [-1, 9216]               0        
           Linear-16                 [-1, 4096]      37,752,832        
             ReLU-17                 [-1, 4096]               0        
          Dropout-18                 [-1, 4096]               0               
           Linear-19                 [-1, 2048]       8,390,656        
             ReLU-20                 [-1, 2048]               0        
           Linear-21                    [-1, 2]           4,098
================================================================       
Total params: 49,894,786
Trainable params: 49,894,786
Non-trainable params: 0
----------------------------------------------------------------       
Input size (MB): 0.57
Forward/backward pass size (MB): 11.12
Params size (MB): 190.33
Estimated Total Size (MB): 202.03
----------------------------------------------------------------       

二、数据集预处理 

2.1 拆分训练集和测试集

        这里假设拿到的数据文件夹里面有2个子文件夹代表2个分类,里面有各自分类的数据图片。以我做的这个简单数据为例,文件夹名..\\RMB,里面两个子文件夹1和100分别装着1块钱和100块的RMB图像。

                    

 接下来按照8/2的比例把他们分成训练集和测试集,并保存在.\\RMB_split数据下面。

import os
import random
import shutil

ROOT = os.getcwd()+'\\RMB'  
for root,dirs,files in os.walk(ROOT):  #root根地址,dirs子文件夹,files子文件
    for d in dirs: 
        #d应是包含子文件夹名的list,按举例是1和100,即两个label
        d_full = os.path.join(ROOT,d)  
        img_names = os.listdir(d_full)
        img_num = len(img_names)
        random.shuffle(img_names)    #打乱顺序
        for index,img in enumerate(img_names):
            img_path = os.path.join(d_full,img) #原图片的路径
            label = d
            if index < img_num*0.8:  #80%训练集
                #RMB_split是拆分后的文件夹,里面包含train和test
                #train和test各自包含两个文件夹1和100
                path_split = os.getcwd()+'\\RMB_split\\train\\'+label
            else:
                path_split = os.getcwd()+'\\RMB_split\\test\\'+label
            os.makedirs(path_split,exist_ok=True)  #新建子文件夹
            shutil.copy(img_path,path_split)    #复制到新文件夹里

2.2 图片预处理

2.2.1 transforms

        仿照别人的做法= =,对训练集和测试集作变换:

from torchvision import transforms

data_transforms = {
    # transforms.Compose()里要用列表
    'train': transforms.Compose([
        #随机裁剪面积为原图scale范围内倍数的图片,宽高比为ratio,尺寸为size
        transforms.RandomResizedCrop(size=224,scale=(0.9,1),ratio=(1.9/1,2.1/1)),
        #随机灰化
        #transforms.RandomGrayscale(p=0.5),
        #随机水平翻转
        transforms.RandomHorizontalFlip(p=0.5),
        #将尺寸为(H,W,C)且取值为[0, 255]的PIL图片
        #或者数据类型为np.uint8的NumPy数组转换为
        #尺寸为(C,H,W)、数据类型为torch.float32、取值范围在[0.0, 1.0]之间的Tensor
        transforms.ToTensor(),
        #标准化
        transforms.Normalize(mean = [0.5,0.5,0.5],std = [0.5,0.5,0.5])
    ]),
    'test':transforms.Compose([
        #测试就简单处理啦,改变尺寸,标准化
        transforms.Resize((224,224)),
        transforms.ToTensor(),
        transforms.Normalize([0.5,0.5,0.5],[0.5,0.5,0.5])])
}

看看处理之后长什么样的:

                                     

 显示图片的用的代码是

from torchvision.transforms import ToPILImage
for data in train_data:
    img,label = data
    img1 = img[0]  #选一张看
    img1 = ToPILImage()(img1)
    img1.show()
    break  #不然会看完所有的训练集

 2.2.2 转为数据集

from torchvision.utils.data import DataLoader
from torchvision.datasets import ImageFolder

train_dataset = ImageFolder(root = './RMB_split/train',transform=data_transforms['train'])
train_data = DataLoader(dataset=train_dataset,batch_size=32,shuffle=True)
test_dataset = ImageFolder(root = './RMB_split/test',transform=data_transforms['test'])
test_data = DataLoader(dataset=test_dataset)

        ImageFolder对输入的root里的子文件夹进行读取,并进行transform的变换。对输入数据要求的格式是子文件夹名为分类,例如:

'root/cat/...',       'root/dog/...',      'root/jojo/...',     ......

        DataLoader就是把数据转化为iterator啦,batch_size就是对应网络输入数据的第0维N,一次训练将输入batch_size张图片,这里还用shuffle打乱了顺序。输出里面的每个元素包含变换后的img和label。

三、训练和测试 

        我把网络模型中的代码单独保存为MyAlexNet.py,在使用中需要调用。

        训练和测试的程序如下(基本上是参考这一篇中代码,稍作修改适合自己的数据 ):对于pytorch中nn.CrossEntropyLoss()与nn.BCELoss()的理解和使用_东城西阙的博客-CSDN博客在pytorch中nn.CrossEntropyLoss()为交叉熵损失函数,用于解决多分类问题,也可用于解决二分类问题。nn.BCELoss()为二元交叉熵损失函数,只能解决二分类问题。https://blog.csdn.net/qq_35605081/article/details/108368276

import torch
import torch.nn as nn
from MyAlexNet import alexnet

import torchvision
from torch.utils.data import DataLoader
from torchvision import transforms

#图片预处理部分
data_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(size=224,scale=(0.9,1),ratio=(1.9/1,2.1/1)),
        transforms.RandomHorizontalFlip(p=0.5),
        transforms.ToTensor(),
        transforms.Normalize(mean = [0.5,0.5,0.5],std = [0.5,0.5,0.5])
    ]),
    'test':transforms.Compose([
        transforms.Resize((224,224)),
        transforms.ToTensor(),
        transforms.Normalize([0.5,0.5,0.5],[0.5,0.5,0.5])])
        }

train_dataset = ImageFolder(root ='./RMB_split/train',transform=data_transforms['train'])
train_data = DataLoader(dataset=train_dataset,batch_size=32,shuffle=True)
test_dataset = ImageFolder(root = './RMB_split/test',transform=data_transforms['test'])
test_data = DataLoader(dataset=test_dataset)

#训练
def trainAlexNet(epoch=10):    
    model = alexnet()
    l_r = 0.0001
    optimizer = torch.optim.Adam(model.parameters(),lr=l_r)  
    lossfn = nn.CrossEntropyLoss()
    for i in range(epoch):
        train_loss = 0
        train_num = 0
        train_acc = 0.0
        model.train()  #训练
        train_bar = tqdm(train_dataset)   #用于显示进度条,train_bar还是个iterable
        for step, data in enumerate(train_bar):
            img,target = data
            optimizer.zero_grad()     #梯度清零
            out = model(img)   #out维度(batch_size,classes)
            lossv = lossfn(out,target)   #损失函数
            out = torch.argmax(out,1)    #输出最大的index
            lossv.backward()    #反向传播
            optimizer.step()    #梯度下降

            train_loss += abs(lossv.item())*img.size(0)
            acc = torch.sum(out == target)  #一次训练中,判断结果正确个数
            train_acc += acc
            train_num += img.size(0)
        print("epoch:{} , train-Loss:{},train-accuracy:{}".format(i+1, 
               train_loss/train_num , train_acc/train_num))        
    torch.save(model,'AlexNetForRMB.m')

#测试
def testAlexNet(test_data):
    model = torch.load('AlexNetForRMB.m')
    test_loss = 0
    test_acc = 0.0
    test_num = 0
    lossfn = torch.nn.CrossEntropyLoss()
    model.eval() #测试
    with torch.no_grad():   #清除梯度
        test_bar = tqdm(testset)
        for _,data in enumerate(test_bar):
            img,label = data
            out = model(img)
            lossv = lossfn(out,label)
            test_loss += abs(lossv.item())*img.size(0)
            rs = torch.argmax(out,dim = 1)  #哪一个值最大,他的序号对应的就是类别
            acc = torch.sum(rs == label)
            test_acc += acc
            test_num += img.size(0)
        #test_loss_all.append(test_loss/test_num)
        #test_acc_all.append(test_acc.double().item()/test_num)
        print('test-loss:{:.6f}, test-accuracy: 
        {:.4%}'.format(test_loss/test_num,test_acc/test_num))

trainAlexNet(epoch=10)
model_test(test_data)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值