第七课目录
图像分类基础
图像分类应该属于计算机视觉的基本任务,在计算机视觉中,广泛使用卷积神经网络捕捉特征;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对应的局部特征相似度;
卷积的过程如下:

输入张量(红色)中kernel_size大小的区域被卷积后成为输出张量(蓝色)的一个向量,容易看出,卷积层有5个filter,每个filter有3个kernel;
通过上图容易发现,卷积神经网络其实相当于不同的全连接网络组成一组滤波器(全连接网络不附带激活函数),作用在输入张量的局部特征上,这些局部特征计算得到的张量再组合成为新的张量:

上图中,
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:

容易发现,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=1∑mxi
均值
μ
\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=1∑m(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)

需要注意,现在的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:数据集地址

初步认识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]
看到一只高跟鞋:

完成图像分类任务
根据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%
1901

被折叠的 条评论
为什么被折叠?



