手写数字识别分成三大部分:收集数据集、搭建网络、训练网络。
收集数据集:加载数据集(由MNIST提供手写数字识别所需图像数据)
网络搭建:卷积层、池化层、全连接层、前向传播
训练网络:计算模型输出、计算损失函数、利用定义好的优化器在反向传播过程中计算梯度并更新参数,由于网络特性会自动将梯度叠加所以在完成参数更新之后要进行清除梯度的操作
流程思路:
网络搭建应用LeNet网络模型
网络介绍:
LeNet5诞生于1994年,是最早的卷积神经网络之一,并且推动了深度学习领域的发展。自从1988年开始,在多年的研究和许多次成功的迭代后,这项由Yann LeCun完成的开拓性成果被命名为LeNet5。
LeNet-5是Yann LeCun等人在多次研究后提出的最终卷积神经网络结构,一般LeNet即指代LeNet-5。
LeNet-5包含七层,不包括输入,每一层都包含可训练参数(权重),当时使用的输入数据是32*32像素的图像。下面逐层介绍LeNet-5的结构,并且,卷积层将用Cx表示,子采样层则被标记为Sx,完全连接层被标记为Fx,其中x是层索引。
层C1是具有六个55的卷积核的卷积层(convolution),特征映射的大小为2828,这样可以防止输入图像的信息掉出卷积核边界。C1包含156个可训练参数和122304个连接。
层S2是输出6个大小为1414的特征图的子采样层(subsampling/pooling)。每个特征地图中的每个单元连接到C1中的对应特征地图中的22个邻域。S2中单位的四个输入相加,然后乘以可训练系数(权重),然后加到可训练偏差(bias)。结果通过S形函数传递。由于2*2个感受域不重叠,因此S2中的特征图只有C1中的特征图的一半行数和列数。S2层有12个可训练参数和5880个连接。
层C3是具有16个5-5的卷积核的卷积层。前六个C3特征图的输入是S2中的三个特征图的每个连续子集,接下来的六个特征图的输入则来自四个连续子集的输入,接下来的三个特征图的输入来自不连续的四个子集。最后,最后一个特征图的输入来自S2所有特征图。C3层有1516个可训练参数和156 000个连接。
层S4是与S2类似,大小为22,输出为16个55的特征图。S4层有32个可训练参数和2000个连接。
层C5是具有120个大小为55的卷积核的卷积层。每个单元连接到S4的所有16个特征图上的55邻域。这里,因为S4的特征图大小也是55,所以C5的输出大小是11。因此S4和C5之间是完全连接的。C5被标记为卷积层,而不是完全连接的层,是因为如果LeNet-5输入变得更大而其结构保持不变,则其输出大小会大于1*1,即不是完全连接的层了。C5层有48120个可训练连接。
F6层完全连接到C5,输出84张特征图。它有10164个可训练参数。这里84与输出层的设计有关。
网络搭建除了可以使用LeNet外,还可以使用 AlexNet, VGG, GoogLeNet和ResNet,由于篇幅较长这里暂不介绍。
相关知识储备:
Tensorbroad打开方式:在终端输入tensorboard --logdir=logs_train#自己SummaryWriter类中给的名字
如果默认6006端口过于拥挤可以在上述代码段后加这句--port=6007换一个端口
路径中的双斜杠是因为单斜杠会被解析成转义字符
目录:
一、paddlepaddle实现手写数字识别
二、pytorch实现手写数字识别
paddlepaddle实现手写数字识别:
导入相关库:
import os import random from PIL import Image from paddle.vision.transforms import ToTensor from paddle.vision.datasets import MNIST import paddle import numpy as np from paddle.nn import Conv2D, MaxPool2D, Linear # 组网 import paddle.nn.functional as F
一、收集数据集:
# 定义数据读取器 # shuffle=True:将序列的所有元素随机排列(default:False) train_loader = paddle.io.DataLoader(MNIST(mode='train', transform=ToTensor()), batch_size=10, shuffle=True) valid_loader = paddle.io.DataLoader(MNIST(mode='test', transform=ToTensor()), batch_size=10)
二、搭建网络:
# 定义 LeNet 网络结构 # 调用dropout防止过拟合 # 继承父类并改写父类初始化方法 class LeNet(paddle.nn.Layer): def __init__(self, num_classes=1): super(LeNet, self).__init__() # 创建卷积和池化层 # 创建第1个卷积层 self.conv1 = Conv2D(in_channels=1, out_channels=6, kernel_size=5) self.max_pool1 = MaxPool2D(kernel_size=2, stride=2) # 尺寸的逻辑:池化层未改变通道数;当前通道数为6 # 创建第2个卷积层 self.conv2 = Conv2D(in_channels=6, out_channels=16, kernel_size=5) self.max_pool2 = MaxPool2D(kernel_size=2, stride=2) # 创建第3个卷积层 self.conv3 = Conv2D(in_channels=16, out_channels=120, kernel_size=4) # 尺寸的逻辑:输入层将数据拉平[B,C,H,W] -> [B,C*H*W] # 输入size是[28,28],经过三次卷积和两次池化之后,C*H*W等于120 self.fc1 = Linear(in_features=120, out_features=64) # 创建全连接层,第一个全连接层的输出神经元个数为64, 第二个全连接层输出神经元个数为分类标签的类别数 self.fc2 = Linear(in_features=64, out_features=num_classes) # 网络的前向计算过程 def forward(self, x): x = self.conv1(x) # 每个卷积层使用Sigmoid激活函数,后面跟着一个2x2的池化 x = F.sigmoid(x) x = self.max_pool1(x) x = F.sigmoid(x) x = self.conv2(x) x = self.max_pool2(x) x = self.conv3(x) # 尺寸的逻辑:输入层将数据拉平[B,C,H,W] -> [B,C*H*W] x = paddle.reshape(x, [x.shape[0], -1]) x = self.fc1(x) x = F.sigmoid(x) x = self.fc2(x) return x
三、训练网络:
# 定义训练过程 def train(model, opt, train_loader, valid_loader): # 使用GPU的代码 # 开启0号GPU训练 # use_gpu = True #默认使用第一号GPU进行运算,如果没有就会换成CPU进行运算 # paddle.device.set_device('gpu:0') if use_gpu else paddle.device.set_device('cpu') print('start training ... ') model.train() for epoch in range(EPOCH_NUM): for batch_id, data in enumerate(train_loader()): img = data[0] label = data[1] # 计算模型输出 logits = model(img) # 计算损失函数 loss_func = paddle.nn.CrossEntropyLoss(reduction='none') loss = loss_func(logits, label) avg_loss = paddle.mean(loss) if batch_id % 2000 == 0: print("epoch: {}, batch_id: {}, loss is: {:.4f}".format(epoch, batch_id, float(avg_loss.numpy()))) avg_loss.backward() opt.step() opt.clear_grad() model.eval() accuracies = [] losses = [] for batch_id, data in enumerate(valid_loader()): img = data[0] label = data[1] # 计算模型输出 logits = model(img) pred = F.softmax(logits) # 计算损失函数 loss_func = paddle.nn.CrossEntropyLoss(reduction='none') loss = loss_func(logits, label) acc = paddle.metric.accuracy(pred, label) accuracies.append(acc.numpy()) losses.append(loss.numpy()) print("[validation] accuracy/loss: {:.4f}/{:.4f}".format(np.mean(accuracies), np.mean(losses))) model.train() # 保存模型参数 paddle.save(model.state_dict(), 'mnist.pdparams')
四、测试网络:
def load_image(img_path): # 读取图片,并转换为灰度图 im = Image.open(img_path).convert('L') im = im.resize((28, 28), Image.ANTIALIAS) im = np.array(im).reshape(1,1, 28, 28).astype(np.float32) # 图像归一化 im = im / 255 return im def predict_process(model): params_file_path = './mnist.pdparams' img_path = 'C:/Users/jingm/Pictures/temp/2.jpg' # 加载模型参数 param_dict = paddle.load(params_file_path) model.load_dict(param_dict) # 加载数据 model.eval() tensor_img = load_image(img_path) # 模型返回10个分类标签对应的概率 results = model(paddle.to_tensor(tensor_img)) #print(results)#可以查看结果 # 取概率最大的标签作为预测输出 label = np.argsort(results.numpy()) print('本次预测数字:', label[0][-1])
五、主函数:
# 创建模型 model = LeNet(num_classes=10) # 设置迭代轮数 EPOCH_NUM = 5 #四种优化算法的设置方案,可以逐一尝试效果 # 设置优化器为Momentum,学习率为0.001 opt = paddle.optimizer.Momentum(learning_rate=0.001, momentum=0.9, parameters=model.parameters()) # opt = paddle.optimizer.SGD(learning_rate=0.01, parameters=model.parameters()) # opt = paddle.optimizer.Adagrad(learning_rate=0.01, parameters=model.parameters()) # opt = paddle.optimizer.Adam(learning_rate=0.01, parameters=model.parameters()) # SGD: 随机梯度下降算法,每次训练少量数据,抽样偏差导致的参数收敛过程中震荡。 # Momentum: 引入物理“动量”的概念,累积速度,减少震荡,使参数更新的方向更稳定。 # 每个批次的数据含有抽样误差,导致梯度更新的方向波动较大。如果我们引入物理动量的概念,给梯度下降的过程加入一定的“惯性”累积,就可以减少更新路径上的震荡,即每次更新的梯度由“历史多次梯度的累积方向”和“当次梯度”加权相加得到。历史多次梯度的累积方向往往是从全局视角更正确的方向,这与“惯性”的物理概念很像,也是为何其起名为“Momentum”的原因。类似不同品牌和材质的篮球有一定的重量差别,街头篮球队中的投手(擅长中远距离投篮)喜欢稍重篮球的比例较高。一个很重要的原因是,重的篮球惯性大,更不容易受到手势的小幅变形或风吹的影响。 # AdaGrad: 根据不同参数距离最优解的远近,动态调整学习率。学习率逐渐下降,依据各参数变化大小调整学习率。 # 通过调整学习率的实验可以发现:当某个参数的现值距离最优解较远时(表现为梯度的绝对值较大),我们期望参数更新的步长大一些,以便更快收敛到最优解。当某个参数的现值距离最优解较近时(表现为梯度的绝对值较小),我们期望参数的更新步长小一些,以便更精细的逼近最优解。类似于打高尔夫球,专业运动员第一杆开球时,通常会大力打一个远球,让球尽量落在洞口附近。当第二杆面对离洞口较近的球时,他会更轻柔而细致的推杆,避免将球打飞。与此类似,参数更新的步长应该随着优化过程逐渐减少,减少的程度与当前梯度的大小有关。根据这个思想编写的优化算法称为“AdaGrad”,Ada是Adaptive的缩写,表示“适应环境而变化”的意思。RMSProp是在AdaGrad基础上的改进,学习率随着梯度变化而适应,解决AdaGrad学习率急剧下降的问题。 # Adam: 由于动量和自适应学习率两个优化思路是正交的,因此可以将两个思路结合起来,这就是当前广泛应用的算法。 # 启动训练过程 train(model, opt, train_loader, valid_loader) predict_process(model)
pytorch实现手写数字识别:
导入相关库:
import torch import torchvision from torch import nn from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter #组网 import torch.nn.functional as F from torch.nn import Conv2d,MaxPool2d,Linear,Dropout
一、收集数据集
#构造SummaryWriter类 writer=SummaryWriter("logs_train") # 准备数据集 torchvision.transforms.ToTensor()可将图像数据转换为tensor格式 #加载训练集 train_data=torchvision.datasets.MNIST(root = "../data", train = True, transform = torchvision.transforms.ToTensor(), download = True) #加载测试集(当train为False表示加载测试集) test_data=torchvision.datasets.MNIST(root = "../data", train = False, transform = torchvision.transforms.ToTensor(), download = True) # 长度 train_data_size=len(train_data) test_data_size=len(test_data) # 如果train_data_Size=10,训练数据集的长度为:10 print("训练数据集的长度为:{}".format(train_data_size)) print("测试数据集的长度为{}".format(test_data_size)) # 利用DataLoader来加载数据集 train_dataloader=DataLoader(train_data, batch_size = 16) test_dataloader=DataLoader(test_data, batch_size = 16)
二、搭建网络
class Lenet(nn.Module): def __init__(self, num_classes=1): super(Lenet,self).__init__() # 创建卷积和池化层 # 创建第1个卷积层 self.conv1=Conv2d(in_channels = 1, out_channels = 6, kernel_size = 5) self.max_pool1=MaxPool2d(kernel_size = 2, stride = 2) # 尺寸的逻辑:池化层未改变通道数;当前通道数为6 # 创建第2个卷积层 self.conv2=Conv2d(in_channels = 6, out_channels = 16, kernel_size = 5) self.max_pool2=MaxPool2d(kernel_size = 2, stride = 2) # 创建第3个卷积层 self.conv3=Conv2d(in_channels = 16, out_channels = 120, kernel_size = 4) # 尺寸的逻辑:输入层将数据拉平[B,C,H,W] -> [B,C*H*W] # 输入size是[28,28],经过三次卷积和两次池化之后,C*H*W等于120 self.fc1=Linear(in_features = 120, out_features = 64) self.drop=Dropout(0.5) # 创建全连接层,第一个全连接层的输出神经元个数为64, 第二个全连接层输出神经元个数为分类标签的类别数 self.fc2=Linear(in_features = 64, out_features = num_classes) # 网络的前向计算过程 def forward(self, x): x=self.conv1(x) # 每个卷积层使用Sigmoid激活函数,后面跟着一个2x2的池化 x=torch.relu(x)#sigmoid x=self.max_pool1(x) x=torch.relu(x) x=self.conv2(x) x=self.max_pool2(x) x=self.conv3(x) # 尺寸的逻辑:输入层将数据拉平[B,C,H,W] -> [B,C*H*W] x=torch.reshape(x, [x.shape[0], -1]) x=self.fc1(x) x=torch.relu(x) x=self.drop(x) x=self.fc2(x) return x
三、训练网络
# 创建网络模型 lenet=Lenet(10) # 损失函数 loss_fn=nn.CrossEntropyLoss() # 优化器 # learning_rate=0.01 # 1e-2 learning_rate=1e-2 optimizer=torch.optim.SGD(lenet.parameters(), lr = learning_rate) # 设置训练网络的一些参数 # 记录训练的次数 total_train_step=0 # 记录测试的次数 total_test_step=0 #训练的轮数 epoch=10 for i in range(epoch): print("------第{}轮训练开始--------".format(i+1)) #训练步骤开始 lenet.train() for data in train_dataloader: imgs,targets=data outputs=lenet(imgs) # print(data.shape) loss=loss_fn(outputs,targets) #优化器优化模型 optimizer.zero_grad() loss.backward() optimizer.step() total_train_step=total_train_step+1 if total_train_step%100==0: print("训练次数:{},Loss:{}".format(total_train_step,loss.item())) writer.add_scalar("train_loss",loss.item(),total_train_step) #测试步骤开始 lenet.eval() total_test_loss=0 total_accuracy=0 with torch.no_grad(): for data in test_dataloader: imgs,targets=data outputs=lenet(imgs) loss=loss_fn(outputs,targets) total_test_loss=total_test_loss+loss.item() accuracy=(outputs.argmax(1)==targets).sum() total_accuracy=total_accuracy+accuracy print("整体测试集上的Loss{}".format(total_test_loss)) print("整体测试集上的正确率:{}".format(total_accuracy/test_data_size)) writer.add_scalar("test_loss",total_test_loss,total_train_step) writer.add_scalar("test_accuracy",total_accuracy/test_data_size,total_test_step) total_train_step+=1 torch.save(lenet,"lenet_{}.pth".format(i)) # torch.save(lenet.state_dict(),"lenet_{}.pth".format(i)) print("模型已保存") writer.close()
四、网络预测
image_path="C:\\Users\\jingm\\Pictures\\temp\\0.jpg" image=Image.open(image_path) # print(image) transform=torchvision.transforms.Compose([torchvision.transforms.Resize((28,28)),torchvision.transforms.ToTensor()]) image=transform(image) print(image.shape) model=torch.load("lenet_9.pth",map_location = torch.device('cpu')) print(model) image=torch.reshape(image,(3,1,28,28)) model.eval() with torch.no_grad(): output=model(image) print(output) print(output.argmax(1))
版权声明:
文章参考paddle文章飞桨PaddlePaddle-源于产业实践的开源深度学习平台
以及b站up主小土堆PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】_哔哩哔哩_bilibili
及CSDN博主「BBlue-Sky」原创文章
原文链接:LeNet由来及意义_BBlue-Sky的博客-CSDN博客_lenet简介
及CSDN博主「DU_YULIN」原创文章