# 手写数字识别
import torch
import torchvision #是Pytorch的一个图形库,服务于pytorch深度学习的框架,构建计算机视觉模型,做常见的图形变换
import torch.nn as nn
#显示进度条库
from tqdm import tqdm
import torch.optim as optim
import torch.nn.functional as F
import torchvision.datasets as dsets
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
device = "cuda:0" if torch.cuda.is_available() else "cpu"
#定义一些训练需要用到的超参数
image_size=28 #图片的总尺寸28*28
num_classes=10 #标签的种类数
num_epochs=20 #训练的总循环周期
batch_size = 64 #一个训练批次的大小,64张图片
#这个函数包括了两个操作:将图片转换为张量,以及将图片进行归一化处理
transform = torchvision.transforms.Compose([torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(mean = [0.5],std = [0.5])])
#加载数据集
train_dataset= dsets.MNIST(root='./data', #文件存储路径
train=True, #提取训练集
transform=transforms.ToTensor(), #将图像转化为张量
download=True )#找不到文件时候,自动下载
#加载测试集
test_dataset= dsets.MNIST(root='./data',train=False
,transform=transforms.ToTensor())
#训练集的加载器,自动将数据切分成批,顺序随机打乱
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=True)#shuffle=True意思是顺序随机打乱
#首先,定义下标数组indices,它相当于对所有的test_dataset中的数据编码
#然后,定义下表indices_val表示校验集数据的下表。indices_test表示测试集的下标
indices=range(len(test_dataset))
indices_val=indices[:5000]
indices_test=indices[5000:]
#根据下标构造的两个数据集的SubsetRandomSampler采样器,他会对下标进行采样
sampler_val=torch.utils.data.sampler.SubsetRandomSampler(indices_val)
sampler_test=torch.utils.data.sampler.SubsetRandomSampler(indices_test)
#根据两个采样器定义加载器
# 注意将sampler_val和sampler_test分别赋值给了validation_loader和test_loader
validation_loader=torch.utils.data.DataLoader(dataset=test_dataset,batch_size=batch_size,shuffle=False,sampler=sampler_val)
test_loader=torch.utils.data.DataLoader(dataset=test_dataset,batch_size=batch_size,shuffle=False,sampler=sampler_test)
# #随便从数据集读取一张图片,并且绘制出来
#
# idx=100
# muteimg=train_dataset[idx][0].numpy()
#
# plt.imshow(muteimg[0,...])
# plt.show()
# print('标签是:',train_dataset[idx][1])
#构建网络
#构造一个Conv类,重写__ini__函数和forwa函数,__init__函数是构造函数,每当调用Conv类的时候具体化一个实例,都会调用构造函数,forward函数在进行正向神经网络时候被自动调用,且构造计算图
#再写一个retrieve_features函数,可以提取网络中各个卷积层的权重
#定义卷积神经网络:4和8为人为指定的两个卷积层的厚度
depth=[4,8]
class ConvNet(nn.Module):
def __init__(self):
super(ConvNet,self).__init__()
#定义一个卷积层,输入通道为1,输出通道为4,窗口大小为5,padding为2
self.conv1=nn.Conv2d(1,4,5,padding=2)
#定义一个池化层,一个窗口为2*2的池化运算
self.pool1=nn.MaxPool2d(2,2)
#第二层卷积,输入通道为depth[0],输出通道为depth[1],窗口为5,padding为2
self.conv2=nn.Conv2d(depth[0],depth[1],5,padding=2)
#一个线性连接层,输入尺寸为后一层立方体的线性平铺,输出层512个节点
self.fc1=nn.Linear(image_size//4*image_size//4*depth[1],512) #除以4是因为经过了一个池化层2*2,大小变为原来的1/4
self.fc2=nn.Linear(512,num_classes) #最后一层线性分类单元,输入为512,输出为要分类的类别数,也就是10(0-9)
def forward(self,x): #该函数完成神经网络的前向运算,在这里把各个组件进行实际的拼装
#x的size
x=self.conv1(x) # 第一层卷积
x=F.relu(x) # 激活函数用Relu,防止过拟合
# 第二层池化层,将图像缩小
x=self.pool1(x)
#第三层卷积层
x=self.conv2(x) #窗口为5,输入输出通道为4,8
#第四层池化
x=self.pool1(x)
#此时x的size(batch_size,depth[1],image_width/4,image_height/4
#将三维向量打成一个一维向量 view函数可以将一个tensor按指定方式重新排布
x=x.view(-1,image_size//4*image_size//4*depth[1])
x=F.relu(self.fc1(x)) #第五层为全连接层,使用relu激活函数
#x的size (batch_size,512)
#以默认0.5的概率对这一层进行dropout的操作,防止过拟合?
x=F.dropout(x,training=self.training)
x=self.fc2(x)#全连接
#x的尺寸:(batch_size,num_classes)
#输出曾为log_softmax,
x=F.log_softmax(x,dim=1)
return x
def retrieve_dfeatures(self,x):
#该函数用于提取卷积神经网络的特征图,返回feature_map1
#feature_map2为前两层卷积层的特征图
feature_map1=F.relu(self.conv1(x)) #完成第一层卷积
x=self.pool1(feature_map1) # 完成第一层池化
#第二层卷积,两层特征图都存储到了 1 2 中
feature_map2=F.relu(self.conv2(x))
return (feature_map1,feature_map2)
#运行模型
net=ConvNet() # 新建一个卷积神经网络的实例,此时构造函数init会自动执行
# net.to(device)
criterion=nn.CrossEntropyLoss() # loss函数的定义,交叉熵
optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
#定义优化器,普通的随机梯度下降法
record=[] # 记录准确率等数值的容器
weights=[] # 每若干步记录一次卷积核
#开始循环训练
def rightness(output, target):
preds = output.data.max(dim=1, keepdim=True)[1]
return preds.eq(target.data.view_as(preds)).cpu().sum(), len(target)
for epoch in range(num_epochs):
train_rights=[] # 记录训练集上准确率的容器
for batch_idx,(data,target) in enumerate(train_loader): #对容器中的没一个批次进行循环
data,target=data.clone().requires_grad_(True),target.clone().detach()
#给网络模型做标记,标记着模型在训练集上训练
#这种区分主要是为了打开/关闭net的training标志,从而决定是否运行dropout
net.train()
output=net(data) #神经网络完成一次前馈啥的计算过程,得到预测输出output
loss=criterion(output,target) #将output与标签target比较,计算loss
optimizer.zero_grad() #清空梯度
loss.backward()
optimizer.step() #一步随机梯度下降算法
right=rightness(output,target) #计算准确率所需的数值,返回数值为(正确数,总样本数)
train_rights.append(right)
if batch_idx %100 ==0:
net.eval() #给网络模型做标记,标志着模型在训练集上训练
val_rights=[] # 将校验集上的准确率的容器
# 开始在校验集上做循环 计算校验集上的准确率
for(data,target) in validation_loader:
data,target=data.clone().requires_grad_(True),target.clone().detach()
#完成一次前馈计算过程,得到目前训练的模型net在校验集上的表现
output=net(data)
#计算准确率所需的数值,返回数值为(正确样例数,总样本数)
right=rightness(output,target)
val_rights.append(right)
train_r=(sum([tup[0] for tup in train_rights]),sum([tup[1] for tup in train_rights]))
val_r=(sum([tup[0] for tup in val_rights]),sum([tup[1] for tup in val_rights]))
print('训练周期:{}[{}/{}({:.0f}%)]\t,Loss:{:.6f}\t,训练准确率:{:.2f}%\t,校验准确率:{:.2f}%'.format(epoch,batch_idx*len(data),len(train_loader.dataset)
,100.*batch_idx/len(train_loader),loss.data,
100.*train_r[0]/train_r[1],
100.*val_r[0]/val_r[1]
))
record.append((100-100.*train_r[0]/train_r[1],100-100.*val_r[0]/val_r[1]))
weights.append([net.conv1.weight.data.clone(),net.conv1.bias.data.clone(),
net.conv2.weight.data.clone(),net.conv2.bias.data.clone()])
#在测试集上分批运行并计算总的准确率
net.eval() #标志着模型为运行阶段
vals=[] # 记录准确率所用的列表
#测试
for data,target in test_loader:
data,target=data.clone().requires_grad_(True),target.clone().detach()
output=net(data)
val=rightness(output,target)
vals.append(val)
#计算准确率
plt.figure(figsize=(10,7))
plt.plot(record)
plt.xlabel('Steps')
plt.ylabel('Error rate')
手写数字识别
最新推荐文章于 2024-03-21 18:12:22 发布