第七课.简单的图像分类(一)

图像分类基础

图像分类应该属于计算机视觉的基本任务,在计算机视觉中,广泛使用卷积神经网络捕捉特征;CNN在第六课中已经进行过描述,但在这里,还是再复习一下;

卷积神经网络

parameters:
in_channels (int) – 输入张量的通道数
out_channels (int) – 输出张量的通道数
kernel_size (int or tuple) – kernel的尺寸
stride (int or tuple, optional) – 卷积的步长,默认为1
padding (int or tuple, optional) – Zero-padding added to both sides of the input.Default:0

shape:
Input:[N,C_in,H_in,W_in]
Output:[N,C_out,H_out,W_out]

其中, H H H W W W的计算满足:
H o u t = H i n + 2 p a d d i n g [ 0 ] − k e r n e l [ 0 ] s t r i d e [ 0 ] + 1 H_{out}=\frac{H_{in}+2padding[0]-kernel[0]}{stride[0]}+1 Hout=stride[0]Hin+2padding[0]kernel[0]+1
W o u t = W i n + 2 p a d d i n g [ 1 ] − k e r n e l [ 1 ] s t r i d e [ 1 ] + 1 W_{out}=\frac{W_{in}+2padding[1]-kernel[1]}{stride[1]}+1 Wout=stride[1]Win+2padding[1]kernel[1]+1
一个卷积层包含多个filter,每个filter的kernel数量等于输入张量的通道数,输出张量的通道数量就是filter的个数,一个filter中的各个kernel是不同的;
可以理解为不同的filter提取不同的局部特征,filter内的kernel不同是为了获取输入张量上不同通道的局部信息,最后加和得到该filter对应的局部特征相似度;
卷积的过程如下:
fig1
输入张量(红色)中kernel_size大小的区域被卷积后成为输出张量(蓝色)的一个向量,容易看出,卷积层有5个filter,每个filter有3个kernel;
通过上图容易发现,卷积神经网络其实相当于不同的全连接网络组成一组滤波器(全连接网络不附带激活函数),作用在输入张量的局部特征上,这些局部特征计算得到的张量再组合成为新的张量:
fig2
上图中, i n t ( h e i g h t ) × i n t ( w i d t h ) int(height)\times int(width) int(height)×int(width)就是上一层输入张量(蓝色)的局部特征个数, i n t ( d e p t h ) int(depth) int(depth)就是全连接网络的个数,每个全连接网络的输出为1个值(最后一层只有一个神经元);

Pooling layer

计算机视觉任务中,Pooling layer一般就是Pool2d,用于对局部特征求最大或者求平均,比如这是一个MaxPool2d:
fig3

容易发现,Pool2d和Conv2d很相似,只是不改变通道数,因为pooling操作的对象是通道维度后的张量;
H H H W W W的计算同样满足:
H o u t = H i n + 2 p a d d i n g [ 0 ] − k e r n e l [ 0 ] s t r i d e [ 0 ] + 1 H_{out}=\frac{H_{in}+2padding[0]-kernel[0]}{stride[0]}+1 Hout=stride[0]Hin+2padding[0]kernel[0]+1
W o u t = W i n + 2 p a d d i n g [ 1 ] − k e r n e l [ 1 ] s t r i d e [ 1 ] + 1 W_{out}=\frac{W_{in}+2padding[1]-kernel[1]}{stride[1]}+1 Wout=stride[1]Win+2padding[1]kernel[1]+1

BatchNormalization

在计算机视觉任务中,常常会用到BatchNormalization,即对一个batch的数据进行标准化;
首先要对一个batch的数据求平均和方差,均值和方差按批次的维度(轴)计算;
假设现在获得一个mini-batch的张量 ( m , ∗ ) (m,*) (m,)为:
{ x 1 , x 2 , . . . , x m } \left \{ x_{1},x_{2},...,x_{m} \right \} {x1,x2,...,xm}
*代表任意的size,比如 ( c h a n n e l , h e i g h t , w i d t h ) (channel,height,width) (channel,height,width),这个batch共 m m m个张量, x i x_{i} xi就是一个张量 ( c h a n n e l , h e i g h t , w i d t h ) (channel,height,width) (channel,height,width)简写为 ( c , h , w ) (c,h,w) (c,h,w)
计算均值有:
μ = 1 m ∑ i = 1 m x i \mu =\frac{1}{m}\sum_{i=1}^{m}x_{i} μ=m1i=1mxi
均值 μ \mu μ的形状为 ( c , h , w ) (c,h,w) (c,h,w)
方差(variance)为:
σ 2 = 1 m ∑ i = 1 m ( x i − μ ) 2 \sigma ^{2}=\frac{1}{m}\sum_{i=1}^{m}(x_{i}-\mu )^{2} σ2=m1i=1m(xiμ)2
方差 σ 2 \sigma^{2} σ2的形状为 ( c , h , w ) (c,h,w) (c,h,w)
现在可以对数据进行标准化为:
x i ^ = x i − μ σ 2 + ε \widehat{x_{i}}=\frac{x_{i}-\mu }{\sqrt{\sigma ^{2}+\varepsilon }} xi =σ2+ε xiμ
注意到 ε \varepsilon ε,这是一个很小的数,一般取1e-5,目的是为了防止 σ = 0 \sigma=0 σ=0时除法出错;标准化后的数据被收缩到以0为中心,以1为标准差的正态分布上,这是有利于训练的,因为当数据分布变得简单与相似,模型的参数也就不需要很复杂;所以BatchNormalization一定程度上可以避免过拟合;
每当网络使用到一层BatchNormalization时,可能会需要学习两个参数 γ , β \gamma ,\beta γ,β,这两个参数是两个向量(每个向量有channle个元素),得到张量为:
{ y 1 , y 2 , . . . , y m } \left \{ y_{1},y_{2},...,y_{m} \right \} {y1,y2,...,ym}
即:
{ y i = B N γ , β ( x i ) = γ x i ^ + β } \left \{ y_{i}=BN_{\gamma ,\beta}(x_{i})=\gamma \widehat{x_{i}}+\beta \right \} {yi=BNγ,β(xi)=γxi +β}
理论上可以直接使用标准化后的张量 x i ^ \widehat{x_{i}} xi 作为 y i y_{i} yi,但进行适当的特征缩放与平移后,张量的值会变得有利于网络的计算;

BatchNormalization与归一化

BatchNormalization与归一化是两个不同的操作,回顾BatchNormalization,它会先沿着batch轴方向计算均值 ( c , h , w ) (c,h,w) (c,h,w)和方差 ( c , h , w ) (c,h,w) (c,h,w),再利用均值和方差对这个batch的数据进行标准化,最后对这组数据进行尺度缩放与平移(基于两个向量 γ , β \gamma ,\beta γ,β);
两个待学习参数的向量展开表示为:
γ = { γ 1 , γ 2 , . . . , γ c } \gamma=\left \{\gamma _{1},\gamma_{2},...,\gamma_{c} \right \} γ={γ1,γ2,...,γc}
β = { β 1 , β 2 , . . . , β c } \beta=\left \{\beta _{1},\beta_{2},...,\beta_{c} \right \} β={β1,β2,...,βc}
归一化一般是指不同特征之间分别进行尺度缩放,比如数据有2个特征 f 1 f_{1} f1:数值在0到2000,和 f 2 f_{2} f2 :数值在0到5,为了便于计算,会使用 f 1 / 2000 f_{1}/2000 f1/2000 f 2 / 5 f_{2}/5 f2/5作为新的两个特征(两个特征值都在0到1之间)

torch.nn.BatchNorm2d

在pytorch中,一般会使用BatchNorm2d去完成计算机视觉的模型,BatchNorm2d常用到两个参数:

torch.nn.BatchNorm2d(num_features, eps=1e-05)

num_features 输入张量的通道数
eps 为了除法的数值稳定(避免直接除以0方差出错),默认1e-5

注意,训练时,均值和方差是沿着batch轴计算的,但在测试时,均值和方差会沿着整个训练数据集得出,然后再标准化;所以,BatchNorm2d还有两个对象,一个保存不同通道的均值,一个保存不同通道的方差;
为了节省显存,不管是训练还是测试,均值和方差都会从理论上的 ( c , h , w ) (c,h,w) (c,h,w)再平均到 ( c ) (c) (c),即每个通道的二维均值张量和二维方差张量会计算平均,变成一个通道只有一个均值和方差:

import torch
import torch.nn as nn

# 3通道
bn=nn.BatchNorm2d(num_features=3)
print(bn.state_dict())

x=torch.randn(2,3,4,4)
y=bn(x)
print(bn.state_dict())
"""
OrderedDict([('weight', tensor([0.1341, 0.3598, 0.5994])), ('bias', tensor([0., 0., 0.])), ('running_mean', tensor([0., 0., 0.])), ('running_var', tensor([1., 1., 1.]))])
OrderedDict([('weight', tensor([0.1341, 0.3598, 0.5994])), ('bias', tensor([0., 0., 0.])), ('running_mean', tensor([ 0.0171, -0.0172,  0.0395])), ('running_var', tensor([0.9901, 1.0059, 1.0156]))])
"""

MNIST图像分类

MNIST数据集包含一组 60000 个示例的训练集和 10000 个示例的测试集。它是NIST提供的较大集的子集。数字图像已做了大小规范化,以固定大小的图像居中。
对于想要尝试实际数据学习技术和模式识别方法,同时在预处理和格式化上花费最少时间的人,它是一个很好的数据集:MNIST地址

加载MNIST并进行标准化

首先导入pytorch:

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

USE_CUDA=torch.cuda.is_available()
BATCH_SIZE=32

device=torch.device("cuda" if USE_CUDA else "cpu")

在之前的自然语言处理任务中,常会用到torchtext处理数据;类似的,在计算机视觉任务里,也必不可少的出现了为pytorch定制的数据处理工具torchvision:

# 适用于pytorch处理CV数据的库:torchvision
import torchvision
# transforms用于对图像提供基本处理
from torchvision import datasets,transforms

获取数据集并加载训练数据:

# 加载数据集mnist
mnistdata=datasets.MNIST("./DataSet",
               train=True, #train为True,返回的数据为processed/training.pt
                           #mnist的训练数据共6万张图片
               download=True,#如果路径下没有,就从官网下载
              )
mnistdata
"""
Dataset MNIST
    Number of datapoints: 60000
    Split: train
    Root Location: ./DataSet
    Transforms (if any): None
    Target Transforms (if any): None
"""

# mnistdata[i]有两个对象,第一个代表图片本身,第二个代表图片所属类别
mnistdata[0]
#(<PIL.Image.Image image mode=L size=28x28 at 0x7EFE825DD198>, 5)

fig4
需要注意,现在的mnist像素值在0到255,我也可以计算整个训练集"所有像素"的均值和方差(仅为1个值,不要和BatchNormalization中的计算混淆):

# 标准化输入图片前,需要知道均值和方差
import numpy as np

# data为图片组成的列表
data=[np.array(d[0]).reshape(1,28,28) for d in mnistdata]

print(len(data))
print(np.mean(data))
print(np.std(data))

#需要注意,现在的mnist像素值在0到255
"""
60000
33.318421449829934
78.56748998339798
"""

如果在加载同时,使用transforms有:

# 实验需要transform对图片做处理,所以重新加载数据集(导入同时进行处理)
mnistdata=datasets.MNIST("./DataSet",
               train=True, #train为True,返回的数据为processed/training.pt
                           #mnist的训练数据共6万张图片
               download=False,#即使没有数据集也不下载
                         
               # Compose可以将不同的transforms操作组合起来
               transform=transforms.Compose([
                   transforms.ToTensor(),#将图像转为张量
               ])
              )

print(mnistdata[0][0])
"""
容易看出,使用了transforms后,数据自动归一化了,即要想让值正确被标准化,就不能使用之前得到的均值和标准差:
33.318421449829934
78.56748998339798
而应该使用归一化后的均值和标准差进行标准化
"""

容易看出,使用了transforms后,数据自动归一化了,即要想让值正确被标准化,就不能使用之前得到的均值和标准差:
33.318421449829934
78.56748998339798


注意这里可以用"整个数据集所有像素"的均值和方差进行标准化是因为图像是单通道的


而应该使用归一化后的均值和标准差进行标准化:

#查看归一化后的均值和方差
data=[d[0].data.cpu().numpy() for d in mnistdata]

print(len(data))
print(np.mean(data))
print(np.std(data))
"""
60000
0.13066062
0.30810776
"""

重新加载进行标准化处理的数据集:

# 重新加载进行标准化处理的数据集
mnistdata=datasets.MNIST("./DataSet",
               train=True, #train为True,返回的数据为processed/training.pt
                           #mnist的训练数据共6万张图片
               download=False,#即使没有数据集也不下载
                         
               # Compose可以将不同的transforms操作组合起来
               transform=transforms.Compose([
                   transforms.ToTensor(),#将图像转为张量
                   # 标准化 Normalize(mean, std, inplace=False)
                   # Given mean: ``(M1,...,Mn)`` and std: ``(S1,..,Sn)`` for ``n`` channels
                   transforms.Normalize((0.1307,),(0.3081,))
               ])
              )

print(mnistdata[0][0].size())

#验证一下标准化的结果
data=[d[0].data.cpu().numpy() for d in mnistdata]

print(len(data))
print(np.mean(data))
print(np.std(data))

"""
torch.Size([1, 28, 28])
60000
-0.00012829367
1.0000249
"""

模型定义

模型定义如下:

"""
Conv2d
parameters:
in_channels (int) – 输入张量的通道数
out_channels (int) – 输出张量的通道数
kernel_size (int or tuple) – kernel的尺寸
stride (int or tuple, optional) – 卷积的步长,默认为1
padding (int or tuple, optional) – Zero-padding added to both sides of the input.Default:0

shape:
Input:[N,C_in,H_in,W_in]
Output:[N,C_out,H_out,W_out]

out[i]=(in[i]+2*p[i]-kernel[i])/stride[i]+1
"""

#定义模型
class CNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1=nn.Conv2d(1,20,5,1)
        self.conv2=nn.Conv2d(20,50,5,1)
        
        self.fc1=nn.Linear(4*4*50,500)
        self.fc2=nn.Linear(500,10)
        
    def forward(self,x):
        # x: [batch_size,1,28,28]
        x=F.relu(self.conv1(x)) # [batch_size,20,28-5+1=24,24] 
        x=F.max_pool2d(x,kernel_size=2,stride=2)   # [batch_size,20,(24-2)/2+1=12,12]
        
        x=F.relu(self.conv2(x)) #[batch_size,50,12-5+1=8,8]
        x=F.max_pool2d(x,kernel_size=2,stride=2) #[batch_size,50,(8-2)/2+1=4,4]
        
        x=x.view(-1,4*4*50) # flatten [batch_size,4*4*50]
        
        x=F.relu(self.fc1(x)) # [batch_size,500]
        
        x=self.fc2(x) # [batch_size,10]
        
        # log_softmax不改变shape
        return F.log_softmax(x,dim=1) #在列轴方向操作 [batch_size,10] 

log_softmax回顾pytorch记事本,log_softmax的输入输出张量shape相同,其本质就是对每个元素都计算log_softmax:
L o g S o f t m a x ( x i ) = l o g ( e x p ( x i ) ∑ j e x p ( x j ) ) LogSoftmax(x_{i})=log(\frac{exp(x_{i})}{\sum_{j}exp(x_{j})}) LogSoftmax(xi)=log(jexp(xj)exp(xi))

使用dataloader

使用dataloader构造train_dataloader:

#使用dataloader构造train_dataloader
train_dataloader=torch.utils.data.DataLoader(mnistdata,
                                             batch_size=BATCH_SIZE,
                                            shuffle=True, #每个epoch打乱一次
                                            num_workers=1,
                                            pin_memory=True, # 提前把值布置到GPU上,某种程度上可以加速训练
                                            )

同理,构造test_dataloader:

# 同理,构造test_dataloader
test_dataloader=torch.utils.data.DataLoader(
    datasets.MNIST("./DataSet",
               train=False, #从test.pt获取数据
               download=False,                      
               transform=transforms.Compose([
                   transforms.ToTensor(),
                   transforms.Normalize((0.1307,),(0.3081,))])
              ),
    batch_size=BATCH_SIZE,
    shuffle=True,
    num_workers=1,
    pin_memory=True,
)

训练与测试

实例化模型,并选择优化方法为结合动量更新的SGD:

LEARNING_RATE=1e-2
MOMENTUM=0.5

model=CNN().to(device)

optimizer=optim.SGD(model.parameters(),lr=LEARNING_RATE,momentum=MOMENTUM)

损失函数使用NLL loss,同样回顾pytorch记事本,定义训练与测试函数:

def train(model,device,train_dataloader,optimizer,epoch):
    model.train()
    for i,(data,target) in enumerate(train_dataloader):
        # target从0到9
        data,target=data.to(device),target.to(device)
        
        pred=model.forward(data) # [batch_size,10],而且每个元素已经是log(softmax)过
        
        # NLLloss :负对数似然损失
        loss=F.nll_loss(pred,target,reduction='mean')
        
        loss.backward()
        
        optimizer.step()
        
        model.zero_grad()
        
        if i%100==0:
            print("train epoch:{},iter:{},loss:{}".format(epoch,i,loss.item()))
            
            
def test(model,device,test_dataloader):
    total_loss=0.0
    correct=0.0
    model.eval()
    with torch.no_grad():
        for i,(data,target) in enumerate(test_dataloader):
            # target的元素值从0到9
            # data为[batch_size,1,28,28] target [batch_size]
            data,target=data.to(device),target.to(device)
            pred=model.forward(data) # [batch_size,10],而且每个元素已经是log(softmax)过
            # NLLloss :负对数似然损失
            loss=F.nll_loss(pred,target,reduction='sum')
            total_loss+=loss.item()
            
            #索引到最大值的位置
            pred=pred.argmax(dim=1) # [batch_size]
            
            # 比较统计出分类正确的个数
            # x.view_as(other) 把x的size view至other的size 
            # x.eq(y)->Tensor x与y逐个元素比较,相等为1,不等为0,所以返回张量的size和x或y的size一样
            correct+=pred.eq(target.view_as(pred)).sum().item()
            
    # test_dataloader.dataset有10000个数据
    total_loss/=len(test_dataloader.dataset)
    correct/=len(test_dataloader.dataset)
    
    print("test loss:{},accuracy:{}%".format(total_loss,correct*100))
    model.train()

训练并保存模型:

NUM_EPOCHS=2

for epoch in range(NUM_EPOCHS):
    train(model,device,train_dataloader,optimizer,epoch)
    test(model,device,test_dataloader)
    
torch.save(model.state_dict(),"mnistcnn.pth")

训练过程为:

train epoch:0,iter:0,loss:2.3189268112182617
train epoch:0,iter:100,loss:0.6662697792053223
train epoch:0,iter:200,loss:0.2979623079299927
train epoch:0,iter:300,loss:0.09481921792030334
train epoch:0,iter:400,loss:0.30337268114089966
train epoch:0,iter:500,loss:0.18740123510360718
train epoch:0,iter:600,loss:0.11033137142658234
train epoch:0,iter:700,loss:0.07353109121322632
train epoch:0,iter:800,loss:0.033268675208091736
train epoch:0,iter:900,loss:0.08645057678222656
train epoch:0,iter:1000,loss:0.11677625775337219
train epoch:0,iter:1100,loss:0.1111474335193634
train epoch:0,iter:1200,loss:0.2825027108192444
train epoch:0,iter:1300,loss:0.028150692582130432
train epoch:0,iter:1400,loss:0.00713057816028595
train epoch:0,iter:1500,loss:0.038987040519714355
train epoch:0,iter:1600,loss:0.048560887575149536
train epoch:0,iter:1700,loss:0.009530067443847656
train epoch:0,iter:1800,loss:0.04192616045475006
test loss:0.059446940588951114,accuracy:98.17%
train epoch:1,iter:0,loss:0.0873970091342926
train epoch:1,iter:100,loss:0.008814126253128052
train epoch:1,iter:200,loss:0.035313352942466736
train epoch:1,iter:300,loss:0.026842668652534485
train epoch:1,iter:400,loss:0.029370427131652832
train epoch:1,iter:500,loss:0.013188257813453674
train epoch:1,iter:600,loss:0.019715994596481323
train epoch:1,iter:700,loss:0.05217146873474121
train epoch:1,iter:800,loss:0.08963392674922943
train epoch:1,iter:900,loss:0.062028124928474426
train epoch:1,iter:1000,loss:0.05624012649059296
train epoch:1,iter:1100,loss:0.04770314693450928
train epoch:1,iter:1200,loss:0.016552984714508057
train epoch:1,iter:1300,loss:0.08976559340953827
train epoch:1,iter:1400,loss:0.04011422395706177
train epoch:1,iter:1500,loss:0.0032268762588500977
train epoch:1,iter:1600,loss:0.03296753764152527
train epoch:1,iter:1700,loss:0.005710721015930176
train epoch:1,iter:1800,loss:0.06353166699409485
test loss:0.04207099027633667,accuracy:98.68%

FashionMNIST图像分类

随着研究的深入,即使是纯正的算法实验,mnist已经不能满足难度,所以出现了fashion mnist:数据集地址
fig5

初步认识FashionMNIST

由于torchvision下的datasets提供了fashionMNIST的处理方法,所以只需要把之前的MNIST实验数据集改成FashionMNIST即可:

# 把MNIST改成FashionMNIST就行
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

# 适用于pytorch处理CV数据的库:torchvision
import torchvision
# transforms用于对图像提供基本处理
from torchvision import datasets,transforms

torchvision.__version__

# 加载数据集mnist
fashionmnistdata=datasets.FashionMNIST("./DataSet",
               train=True, #train为True,返回的数据为processed/training.pt
                           #mnist的训练数据共6万张图片
               download=True,#如果路径下没有,就从官网下载
              )

fashionmnistdata
"""
Dataset FashionMNIST
    Number of datapoints: 60000
    Split: train
    Root Location: ./DataSet
    Transforms (if any): None
    Target Transforms (if any): None
"""

fashionmnistdata[9][0]

看到一只高跟鞋:
fig6

完成图像分类任务

根据MNIST分类的处理流程,可以复用代码去处理FashionMNIST:

# 把MNIST改成FashionMNIST就行
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np

# 适用于pytorch处理CV数据的库:torchvision
import torchvision
# transforms用于对图像提供基本处理
from torchvision import datasets,transforms

USE_CUDA=torch.cuda.is_available()
BATCH_SIZE=32
device=torch.device("cuda" if USE_CUDA else "cpu")


# 实验需要transform对图片做处理,所以重新加载数据集(导入同时进行处理)
mnistdata=datasets.FashionMNIST("./DataSet",
               train=True, #train为True,返回的数据为processed/training.pt
                           #mnist的训练数据共6万张图片
               download=False,#即使没有数据集也不下载
                         
               # Compose可以将不同的transforms操作组合起来
               transform=transforms.Compose([
                   transforms.ToTensor(),#将图像转为张量
               ])
              )

#查看归一化后的均值和方差
data=[d[0].data.cpu().numpy() for d in mnistdata]

print(len(data))
print(np.mean(data))
print(np.std(data))
"""
60000
0.2860402
0.3530239
"""

# 重新加载进行标准化处理的数据集
mnistdata=datasets.FashionMNIST("./DataSet",
               train=True, #train为True,返回的数据为processed/training.pt
                           #mnist的训练数据共6万张图片
               download=False,#即使没有数据集也不下载
                         
               # Compose可以将不同的transforms操作组合起来
               transform=transforms.Compose([
                   transforms.ToTensor(),#将图像转为张量
                   # 标准化 Normalize(mean, std, inplace=False)
                   # Given mean: ``(M1,...,Mn)`` and std: ``(S1,..,Sn)`` for ``n`` channels
                   transforms.Normalize((0.2860,),(0.3530,))
               ])
              )

print(mnistdata[0][0].size())
#验证一下标准化的结果
data=[d[0].data.cpu().numpy() for d in mnistdata]
print(len(data))
print(np.mean(data))
print(np.std(data))
"""
torch.Size([1, 28, 28])
60000
0.00011496393
1.0000685
"""


#定义模型
class CNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1=nn.Conv2d(1,20,5,1)
        self.conv2=nn.Conv2d(20,50,5,1)
        
        self.fc1=nn.Linear(4*4*50,500)
        self.fc2=nn.Linear(500,10)
        
    def forward(self,x):
        # x: [batch_size,1,28,28]
        x=F.relu(self.conv1(x)) # [batch_size,20,28-5+1=24,24] 
        x=F.max_pool2d(x,kernel_size=2,stride=2)   # [batch_size,20,(24-2)/2+1=12,12]
        
        x=F.relu(self.conv2(x)) #[batch_size,50,12-5+1=8,8]
        x=F.max_pool2d(x,kernel_size=2,stride=2) #[batch_size,50,(8-2)/2+1=4,4]
        
        x=x.view(-1,4*4*50) # flatten [batch_size,4*4*50]
        
        x=F.relu(self.fc1(x)) # [batch_size,500]
        
        x=self.fc2(x) # [batch_size,10]
        
        # log_softmax不改变shape
        return F.log_softmax(x,dim=1) #在列轴方向操作 [batch_size,10] 
    
    
USE_CUDA=torch.cuda.is_available()
BATCH_SIZE=32
device=torch.device("cuda" if USE_CUDA else "cpu")

#使用dataloader构造train_dataloader
train_dataloader=torch.utils.data.DataLoader(mnistdata,
                                             batch_size=BATCH_SIZE,
                                            shuffle=True, #每个epoch打乱一次
                                            num_workers=1,
                                            pin_memory=True, # 提前把值布置到GPU上,某种程度上可以加速训练
                                            )
# 同理,构造test_dataloader
test_dataloader=torch.utils.data.DataLoader(
    datasets.FashionMNIST("./DataSet",
               train=False, #从test.pt获取数据
               download=False,                      
               transform=transforms.Compose([
                   transforms.ToTensor(),
                   transforms.Normalize((0.2860,),(0.3530,))])
              ),
    batch_size=BATCH_SIZE,
    shuffle=True,
    num_workers=1,
    pin_memory=True,
)


LEARNING_RATE=1e-2
MOMENTUM=0.5
model=CNN().to(device)
optimizer=optim.SGD(model.parameters(),lr=LEARNING_RATE,momentum=MOMENTUM)


def train(model,device,train_dataloader,optimizer,epoch):
    model.train()
    for i,(data,target) in enumerate(train_dataloader):
        # target从0到9
        data,target=data.to(device),target.to(device)       
        pred=model.forward(data) # [batch_size,10],而且每个元素已经是log(softmax)过       
        # NLLloss :负对数似然损失
        loss=F.nll_loss(pred,target,reduction='mean')       
        loss.backward()       
        optimizer.step()       
        model.zero_grad()
        if i%100==0:
            print("train epoch:{},iter:{},loss:{}".format(epoch,i,loss.item()))
              
def test(model,device,test_dataloader):
    total_loss=0.0
    correct=0.0
    model.eval()
    with torch.no_grad():
        for i,(data,target) in enumerate(test_dataloader):
            # target的元素值从0到9
            # data为[batch_size,1,28,28] target [batch_size]
            data,target=data.to(device),target.to(device)
            pred=model.forward(data) # [batch_size,10],而且每个元素已经是log(softmax)过
            # NLLloss :负对数似然损失
            loss=F.nll_loss(pred,target,reduction='sum')
            total_loss+=loss.item()
            #索引到最大值的位置
            pred=pred.argmax(dim=1) # [batch_size]
            # 比较统计出分类正确的个数
            # x.view_as(other) 把x的size view至other的size 
            # x.eq(y)->Tensor x与y逐个元素比较,相等为1,不等为0,所以返回张量的size和x或y的size一样
            correct+=pred.eq(target.view_as(pred)).sum().item()            
    # test_dataloader.dataset有10000个数据
    total_loss/=len(test_dataloader.dataset)
    correct/=len(test_dataloader.dataset)    
    print("test loss:{},accuracy:{}%".format(total_loss,correct*100))
    model.train()
    
NUM_EPOCHS=2
for epoch in range(NUM_EPOCHS):
    train(model,device,train_dataloader,optimizer,epoch)
    test(model,device,test_dataloader)
torch.save(model.state_dict(),"fashionmnistcnn.pth")

明显看出,同样的模型,同样的输入数据标准化方法,FashionMNIST的效果不如MNIST上的效果好,这也反映了FashionMNIST确实是一个训练快速且考验模型的优秀数据集:

train epoch:0,iter:0,loss:2.300595998764038
train epoch:0,iter:100,loss:0.9157668352127075
train epoch:0,iter:200,loss:0.9919059872627258
train epoch:0,iter:300,loss:0.4668219983577728
train epoch:0,iter:400,loss:0.6859469413757324
train epoch:0,iter:500,loss:0.5600563287734985
train epoch:0,iter:600,loss:0.4927268624305725
train epoch:0,iter:700,loss:0.31422850489616394
train epoch:0,iter:800,loss:0.6181213855743408
train epoch:0,iter:900,loss:0.5355464220046997
train epoch:0,iter:1000,loss:0.42539727687835693
train epoch:0,iter:1100,loss:0.6514754891395569
train epoch:0,iter:1200,loss:0.3070918619632721
train epoch:0,iter:1300,loss:0.5045261383056641
train epoch:0,iter:1400,loss:0.4136778712272644
train epoch:0,iter:1500,loss:0.4964996576309204
train epoch:0,iter:1600,loss:0.4898112714290619
train epoch:0,iter:1700,loss:0.4621049463748932
train epoch:0,iter:1800,loss:0.2887163460254669
test loss:0.4504015432357788,accuracy:83.8%
train epoch:1,iter:0,loss:0.65262371301651
train epoch:1,iter:100,loss:0.6052740216255188
train epoch:1,iter:200,loss:0.420048326253891
train epoch:1,iter:300,loss:0.3331279158592224
train epoch:1,iter:400,loss:0.5645015239715576
train epoch:1,iter:500,loss:0.5631495714187622
train epoch:1,iter:600,loss:0.39666175842285156
train epoch:1,iter:700,loss:0.40362614393234253
train epoch:1,iter:800,loss:0.3553037941455841
train epoch:1,iter:900,loss:0.4407092034816742
train epoch:1,iter:1000,loss:0.28769898414611816
train epoch:1,iter:1100,loss:0.3469170033931732
train epoch:1,iter:1200,loss:0.34690746665000916
train epoch:1,iter:1300,loss:0.27406278252601624
train epoch:1,iter:1400,loss:0.6393352746963501
train epoch:1,iter:1500,loss:0.29725462198257446
train epoch:1,iter:1600,loss:0.18399234116077423
train epoch:1,iter:1700,loss:0.3601799011230469
train epoch:1,iter:1800,loss:0.49094414710998535
test loss:0.37649406254291534,accuracy:86.38%
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值