(基于PyTorch)神经网络手写数字识别代码&解释

依旧使用了数据集MNIST中的部分数据。

1、PyTorch基本知识

import torch
#简单的PyTorch张量
x=torch.tensor(3.5)
print(x)
#变量x的类型是PyTorch张量

 

tensor(3.5000)输出值的含义是,该变量的值是3.5000,同时它被包装在一个PyTorch张量中。

#简单的张量计算
y=x+3
print(y)
#生成的y也是一个PyTorch张量

 

x=torch.tensor(3.5,requires_grad=True)
#通过x的参数requires_grad=True告诉PyTorch我们希望得到一个关于x的梯度
print(x)

y=(x-1)*(x-2)*(x-3)
print(y)

#计算梯度
y.backward()
#x=3.5时的梯度
x.grad

#创建包含x y和z的计算图
x=torch.tensor(3.5,requires_grad=True)
y=x*x
z=2*y+3
#计算梯度
z.backward()
#当x=3.5时的梯度
x.grad

#创建包含x y和z的计算图
a=torch.tensor(2.0,requires_grad=True)
b=torch.tensor(1.0,requires_grad=True)

x=2*a+3*b
y=5*a*a+3*b*b*b
z=2*x+3*y

#计算梯度
z.backward()

#当a=2.0时的梯度
a.grad

#导入pandas库,用于读取csv文件
import pandas

#使用pandas将训练数据读取到一个DataFrame中
df=pandas.read_csv('mnist_train.csv',header=None)

#使用head()函数查看一个较大的DataFrame的前几行
df.head()

#使用info()函数查看DataFrame的概况
df.info()

 

 以上结果告诉我们,该DataFrame有200行,这对应200幅训练图像,同时,每行有785个值。

#导入pandas库用于读取csv文件
import pandas
#导入matplotlib用于绘图
import matplotlib.pyplot as plt

#从DataFrame读取数据
row=0
#row=0表示第一行
data=df.iloc[row]
#选择数据集的第一行并赋值给变量data

#第一个值是标签
label=data[0]

#图像是余下的784个值
img=data[1:].values.reshape(28,28)
#选取剩余的784个值,将他们重新映射成为一个28x28的正方形数组,并赋值给变量img
plt.title("label="+str(label))
#在标题显示之前提取的标签
plt.imshow(img,interpolation='none',cmap='Blues')
#pyplot无须平滑像素以及调色板的颜色为蓝色
plt.show()

 2、全部代码

2.1 导入包 

import numpy
import torch
import torch.nn as nn
#将torch.nn模块作为nn导入,是一种常见的命名方式
from torch.utils.data import Dataset
#将数据载入torch.utils.data.Dataset
import pandas
#导入pandas库用于读取csv文件
import matplotlib.pyplot as plt
#导入matplotlib用于绘图

 2.2 神经网络类

class Classifier(nn.Module):
    #Classifier类继承了nn.Module
    def __init__(self):
        #__init__(self)是一个特殊的函数,当我们从一个类最终创建对象时需要调用它,它也被称为构造函数
        #初始化PyTorch父类,调用父类构造函数
        super().__init__()
        
        #定义神经网络层
        self.model=nn.Sequential(
            #nn.Sequential()提供了一个神经模块的列表
            nn.Linear(784,200),
            #nn.Linear(784,200)是一个从784个节点到200个节点的全连接映射。这个模块包含节点之间链接的权重,在训练时会被更新
            
            #nn.Sigmoid(),
            #nn.Sigmoid()将S型逻辑激活函数应用于前一个模块的输出,也就是本例中200个节点的输出
            nn.LeakyReLU(0.02),
            #改进:将激活函数改为LeakReLU(0.02),其中0.02是函数左半边的梯度
            
            nn.LayerNorm(200),
            #标准化,减少神经网络中参数和信号的取值范围,以及将均值转换为0
            #在网络信号输出最终层之前使用nn.LayerNorm(200)将它们标准化
            
            nn.Linear(200,10),
            #nn.Linear(200,10)是将200个节点映射到10个节点的全连接映射,它包含中间隐藏层与输出层10个节点之间链接的权重
            
            nn.Sigmoid()
            #nn.Sigmoid()再将S型逻辑激活函数应用于10个节点的输出。其结果就是网络的最终输出
            #nn.LeakyReLU(0.02)
            #改进:将激活函数改为LeakReLU(0.02),其中0.02是函数左半边的梯度
        )
        #nn.Linear:当数值从输入端传递到输出端时,该模块对它们应用了Ax+B形式的线性函数。这里,A为链接权重,B为偏差。这两个参数都会在训练时被更新。它们也被称为可学习参数。
        
        #创建损失函数
        #self.loss_function=nn.MSELoss()
        #均方误差:先计算每个输出节点的实际输出和预期输出之差的平方,再计算平均值,即torch.nn.MSELoss()
        #适合情况:回归任务,输入值设计为连续范围的值,如,一个预测温度的网络会输出0~100°C的任何值
        
        self.loss_function=nn.BCELoss()
        #改进:对图像进行分类,分类任务(判断true/false或者0/1),常用二元交叉熵损失,它同时惩罚置信度高的错误输出和置信值低的正确输出,即nn.BCELoss()
        #但是BCE只能处理0~1的值,而Leaky ReLU有可能输出范围之外的值,则,可以在最终层之后保留一个S型函数,但是在隐藏层之后使用LeakyReLU
        
        #创建优化器,使用简单的梯度下降
        #self.optimiser=torch.optim.SGD(self.parameters(),lr=0.01)
        #随机梯度下降(SGD),将学习率设置为0.01
        #在上面的代码中,我们通过self.parameters()访问所有可学习参数,并将所有可学习参数都传递给SGD优化器
        self.optimiser=torch.optim.Adam(self.parameters())
        #随机梯度下降的缺点:会陷入损失函数的局部最小值;对所有可学习的参数都使用单一的学习率
        #改进:Adam,利用动量的概念,减小陷入局部最小值的可能性,想象一下:一个沉重的球如何利用动量滚过一个小坑;对每个可学习参数使用单独的学习率,这些学习率随着每个参数在训练期间的变化而变化
        
        #可视化训练,跟踪训练的一种方法是监控损失
        #记录训练进展的计数器和列表
        self.counter=0
        self.progress=[]
        
        pass
    
    def forward(self,inputs):
        #通过forward()方法向网络传递信息
        #直接运行模型
        return self.model(inputs)
        #将输入传递给self.model(),它由nn.Sequential()定义,模型的输出直接返回给forward()的主调函数
        
    def train(self,inputs,targets):
        #train()既需要网络的输出值,也需要预期的目标值,这样才可以与实际输出进行比较,并计算损失值
        #计算网络的输出值
        outputs=self.forward(inputs)
        #计算损失值
        loss=self.loss_function(outputs,targets)
        
        #使用损失来更新网络的链接权重
        #梯度归零,反向传播,并更新权重
        self.optimiser.zero_grad()
        #optimiser.zero_grad()将计算图中的梯度全部归零
        loss.backward()
        #loss.backward()从loss函数中计算网络中的梯度
        #每次训练网络之前,需要将梯度归零,否则,每次loss.backward()计算出来的梯度会累积
        self.optimiser.step()
        #optimiser.step()使用这些梯度来更新网络的可学习参数
        
        #每隔10个训练样本增加一次计数器的值,并将损失值添加进列表的末尾
        self.counter+=1
        if(self.counter%10==0):
            self.progress.append(loss.item())
            #item()函数是为了方便展开一个单值张量,获取里面的数字
            pass
        if(self.counter%1000==0):
            #每1000次训练后打印计数器的值,这样可以了解训练进展的快慢
            print("counter=",self.counter)
            pass
        
    def plot_progress(self):
        #将损失值绘制成图
        df=pandas.DataFrame(self.progress,columns=['loss'])
        #将损失值列表progress转换为一个pandas DataFrame,便于绘图
        df.plot(ylim=(0,1.0),figsize=(16,8),alpha=0.1,marker='.',grid=True,yticks=(0,0.25,0.5))
        #使用plot()函数的选项,设计图的设计和风格
        pass

2.3 数据集类

class MnistDataset(Dataset):
    def __init__(self,csv_file):
        self.data_df=pandas.read_csv(csv_file,header=None)
        #csv_file被读入一个名为data_df的pandas DataFrame中
        pass
    
    def __len__(self):
        #__len__()返回数据集中的项目总数
        return len(self.data_df)
        #通过len(mnist_dataset)获取数据集的大小
    
    def __getitem__(self,index):
        #__getitem__()通过所以返回数据集中的第n项,例如使用mnist_dataset[3]访问第4项
        #目标图像(标签)
        label=self.data_df.iloc[index,0]
        #第index项中提取标签label
        target=torch.zeros((10))
        #创建维度为10的张量变量target来表示神经网络的预期输出
        target[label]=1.0
        #除了与标签相对应的项是1之外,其他值皆为0,这种表示方法叫做独热编码
        #比如,标签0所对应的张量是[1,0,0,0,0,0,0,0,0,0],标签4所对应的张量是[0,0,0,0,1,0,0,0,0,0]
        
        #图像数据,取值范围是0~255,标准化为0~1
        image_values=torch.FloatTensor(self.data_df.iloc[index,1:].values)/255.0
        
        #返回标签、图像数据张量以及目标张量
        return label,image_values,target
    
    def plot_image(self,index):
        #绘图
        arr=self.data_df.iloc[index,1:].values.reshape(28,28)
        plt.title("label="+str(self.data_df.iloc[index,0]))
        plt.imshow(arr,interpolation='none',cmap='Blues')
    pass

2.4 使用训练数据训练神经网络

mnist_dataset=MnistDataset('mnist_train.csv')
#导入训练数据mnist_train.csv
mnist_dataset.plot_image(9)
#绘制第10幅图像,第10幅图像的索引是9,因为第一幅图像的索引是0

mnist_dataset[9]

%%time
#%%time可以用来记录本单元格运行所需时间
#创建神经网络
C=Classifier()

#在MNIST数据集训练神经网络
epochs=3

for i in range(epochs):
    print('training epoch',i+1,"of",epochs)
    for label,image_data_tensor,target_tensor in mnist_dataset:
        C.train(image_data_tensor,target_tensor)
    pass
pass

#绘制分类器损失值
C.plot_progress()
#损失值得下降意味着网络分类图像能力越来越好
#生成的图每次都会有点区别,因为神经网络本质是一个随机过程
#可以看出,改进之后的网络,损失值下降的速率要高于原始网络(如果我们考虑噪声在图中的密度,而不考虑高度,则损失值得噪声也比较少)

 2.5 测试数据

#加载MNIST测试数据
mnist_test_dataset=MnistDataset('mnist_test.csv')

#挑选一幅图像,选择索引为19的第20幅图像
record=19

#绘制图像和标签
mnist_test_dataset.plot_image(record)

#接下来看看神经网络如何判断这副图像
image_data=mnist_test_dataset[record][1]

#调用训练后的神经网络
output=C.forward(image_data)

#绘制输出张量
pandas.DataFrame(output.detach().numpy()).plot(kind='bar',legend=False,ylim=(0,1))
#输出被转换成一个简单的numpy数组,再被包装成一个DataFrame,以便绘制柱形图
#10条柱形分别对应10个神经网络输出节点的值,最大值对应4,也就是说我们的网络认为图像是4

#测试用训练数据训练后的网络

score=0
items=0

for label,image_data_tensor,target_tensor in mnist_test_dataset:
    answer=C.forward(image_data_tensor).detach().numpy()
    if(answer.argmax()==label):
        #answer.argmax()语句的作用是输出张量answer中最大值的索引。如果第一个值是最大的,则argmax是0
        score+=1
        pass
    items+=1
    
    pass
print(score,items,score/items)

 准确率96%,感觉挺好~

-------------------------------------------------------------------------------------------------------------

本文使用到的数据集如下:

链接:百度网盘 请输入提取码
提取码:g9qq

  • 8
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值