赞
踩
以MNIST手写数据集划分训练集和验证集,将自己手写拍照上传的数字图片作为测试样本,训练卷积神经网络分类器识别0-9的手写数字。
MNIST手写数字数据集包含60000张用于训练的手写数字图片和10000张用于测试的手写数字图片。所有的图片具有相同的尺寸(28x28 pixels),数字均位于图片中央。
数据集链接地址如下:
原训练集各类别分布和示例样本如下图所示:
图 1 训练集图片示例 图 2 训练集样本分布
在本实验中,原数据集中训练集被划分为训练集和验证集,其中验证集占比0.2,训练集占比0.8。原数据集的测试集作为测试集使用,用于评估模型性能。
在上述的分布图中不难发现,原数据集中的训练集基本上是类别均衡的;为了不破坏原数据集的类别均衡性,在划分训练集和验证集时,采用分类别抽样(stratified sampling)划分的方法。
图 3 TensorBoard makegrid看到的输入图片
算法概述:
模型架构:resnet18修改了第一个卷积层使其接受 BW 格式图片;修改了fc层使其输出10个类别的概率分布。未使用预先训练的权重。
数据处理:图片转换为 Tensor;按照 imagenet 图片第一个通道的均值和方差标准化处理。
权重更新:采用带动量的随机梯度下降法,初始学习率为 1e-3,动量参数为0.9;学习率每7个epoch缩小为0.1倍
损失计算:softmax + crossentropy loss
其他设置:batch_size=128, epoch_num = 24
使用说明: 训练模块设置了五个命令行参数,用户可自行决定数据文件夹位置,日志存放位置,模型保存位置,模型接受图片大小,训练集验证集的划分比例。
第三方包安装:
Pytorch
Tensorboard
Sk-learn
Numpy
Matplotlib
Seaborn(代码中没用上)
导入库
# import necessary packages import torch import torch.nn as nn import torch.optim as optim from torch.optim import lr_scheduler import matplotlib.pyplot as plt import numpy as np from torch.utils.data import Dataset, DataLoader, TensorDataset, Subset import torchvision from torchvision import transforms,utils,models import argparse import seaborn as sns from collections import Counter import time import copy from torch.utils.tensorboard import SummaryWriter from sklearn.metrics import confusion_matrix, accuracy_score
实验结果:
在测试集上的准确率为:0.9863999485969543 Confusion matrix如下所示:
可以看出模型最易混淆的为数字4和数字9
从上述训练集的损失和准确率变化可以看出模型在训练集上存在一定的过拟合现象,后续实验可以通过加dropout或者其他正则化手段来避免太大程度的过拟合。
训练过程如下,在下述epoch中选择validset上准确率最高的epoch对应的模型权重作为最终模型。
Epoch 0/24 ---------- train Loss: 0.2946 Acc: 0.9147 val Loss: 0.0904 Acc: 0.9732 Epoch 1/24 ---------- train Loss: 0.0623 Acc: 0.9824 val Loss: 0.0672 Acc: 0.9799 Epoch 2/24 ---------- train Loss: 0.0322 Acc: 0.9916 val Loss: 0.0611 Acc: 0.9828 Epoch 3/24 ---------- train Loss: 0.0165 Acc: 0.9967 val Loss: 0.0589 Acc: 0.9812 Epoch 4/24 ---------- train Loss: 0.0092 Acc: 0.9990 val Loss: 0.0566 Acc: 0.9830 Epoch 5/24 ---------- train Loss: 0.0063 Acc: 0.9995 val Loss: 0.0561 Acc: 0.9830 Epoch 6/24 ---------- train Loss: 0.0043 Acc: 0.9997 val Loss: 0.0563 Acc: 0.9838 Epoch 7/24 ---------- train Loss: 0.0033 Acc: 0.9999 val Loss: 0.0565 Acc: 0.9829 Epoch 8/24 ---------- train Loss: 0.0032 Acc: 0.9999 val Loss: 0.0558 Acc: 0.9835 Epoch 9/24 ---------- train Loss: 0.0029 Acc: 0.9999 val Loss: 0.0554 Acc: 0.9836 Epoch 10/24 ---------- train Loss: 0.0028 Acc: 1.0000 val Loss: 0.0556 Acc: 0.9836 Epoch 11/24 ---------- train Loss: 0.0028 Acc: 1.0000 val Loss: 0.0554 Acc: 0.9838 Epoch 12/24 ---------- train Loss: 0.0026 Acc: 1.0000 val Loss: 0.0555 Acc: 0.9833 Epoch 13/24 ---------- train Loss: 0.0026 Acc: 1.0000 val Loss: 0.0556 Acc: 0.9834 Epoch 14/24 ---------- train Loss: 0.0027 Acc: 1.0000 val Loss: 0.0554 Acc: 0.9835 Epoch 15/24 ---------- train Loss: 0.0025 Acc: 1.0000 val Loss: 0.0553 Acc: 0.9838 Epoch 16/24 ---------- train Loss: 0.0026 Acc: 1.0000 val Loss: 0.0554 Acc: 0.9840 Epoch 17/24 ---------- train Loss: 0.0027 Acc: 1.0000 val Loss: 0.0560 Acc: 0.9835 Epoch 18/24 ---------- train Loss: 0.0025 Acc: 1.0000 val Loss: 0.0550 Acc: 0.9842 Epoch 19/24 ---------- train Loss: 0.0026 Acc: 0.9999 val Loss: 0.0556 Acc: 0.9831 Epoch 20/24 ---------- train Loss: 0.0025 Acc: 1.0000 val Loss: 0.0556 Acc: 0.9841 Epoch 21/24 ---------- train Loss: 0.0025 Acc: 1.0000 val Loss: 0.0559 Acc: 0.9837 Epoch 22/24 ---------- train Loss: 0.0026 Acc: 1.0000 val Loss: 0.0554 Acc: 0.9834 Epoch 23/24 ---------- train Loss: 0.0025 Acc: 1.0000 val Loss: 0.0564 Acc: 0.9835 Epoch 24/24 ---------- train Loss: 0.0024 Acc: 1.0000 val Loss: 0.0562 Acc: 0.9833 Training complete in 7m 36s Best val Acc: 0.984250 total test accuracy:0.9863999485969543 over 10000 test samples
上传10张自己拍摄的手写数字照片,转换为灰度格式,resize到28x28的像素,检测模型输出,代码如下:
def predict_for_customized_data(model,dataPath,device):
model.eval()
for i in range(10):
img1 = Image.open(dataPath/(str(i)+'.JPG')).convert('L')
sized_img1 = transforms.Resize((28,28))(img1)
tensor1 = transforms.ToTensor()(sized_img1)
tensor1 = torch.unsqueeze(tensor1,0)
assert tensor1.shape == (1,1,28,28)
inputs = tensor1.to(device)
outputs = model(inputs)
print(outputs)
_, prediction = torch.max(outputs.data, 1)
print(prediction)
最终输出如下:
tensor([[ 0.0627, 3.3247, -1.5043, -2.4198, -0.0387, 0.9379, -0.0958, 1.3305, -0.1327, -2.4302]], device='cuda:0', grad_fn=<AddmmBackward>) tensor([1], device='cuda:0') tensor([[-0.0849, 3.3900, -1.5465, -2.3464, 0.1393, 0.9960, -0.1491, 1.2198, -0.0301, -2.4716]], device='cuda:0', grad_fn=<AddmmBackward>) tensor([1], device='cuda:0') tensor([[ 0.0470, 3.1663, -1.4129, -2.3464, 0.0404, 0.8320, -0.0912, 1.2537, 0.0387, -2.3751]], device='cuda:0', grad_fn=<AddmmBackward>) tensor([1], device='cuda:0') tensor([[ 0.0604, 3.3914, -1.4023, -2.2675, 0.0237, 0.8554, -0.2231, 1.2724, -0.1125, -2.4638]], device='cuda:0', grad_fn=<AddmmBackward>) tensor([1], device='cuda:0') tensor([[ 1.4152e-01, 3.2076e+00, -1.4329e+00, -2.4117e+00, -1.3021e-01, 8.3629e-01, 1.1261e-04, 1.3240e+00, -4.8039e-02, -2.4092e+00]], device='cuda:0', grad_fn=<AddmmBackward>) tensor([1], device='cuda:0') tensor([[ 0.0408, 3.3281, -1.4669, -2.3937, 0.0214, 0.9613, -0.1727, 1.3236, -0.1440, -2.4152]], device='cuda:0', grad_fn=<AddmmBackward>) tensor([1], device='cuda:0') tensor([[ 0.0352, 3.3936, -1.5284, -2.4327, -0.0555, 0.9108, -0.0615, 1.2695, -0.1061, -2.4949]], device='cuda:0', grad_fn=<AddmmBackward>) tensor([1], device='cuda:0') tensor([[ 1.6809e-02, 3.3713e+00, -1.5663e+00, -2.3387e+00, -1.7898e-03, 9.6868e-01, -1.6990e-01, 1.3084e+00, -1.1342e-01, -2.4470e+00]], device='cuda:0', grad_fn=<AddmmBackward>) tensor([1], device='cuda:0') tensor([[-0.1321, 3.6067, -1.6129, -2.4619, -0.0661, 1.0501, -0.1779, 1.4918, -0.2713, -2.4176]], device='cuda:0', grad_fn=<AddmmBackward>) tensor([1], device='cuda:0') tensor([[-0.1155, 3.4807, -1.5790, -2.3580, 0.0713, 1.0390, -0.1681, 1.3022, -0.1777, -2.4345]], device='cuda:0', grad_fn=<AddmmBackward>) tensor([1], device='cuda:0')
模型将手写数字全部预测为 1,检查输入数据后发现这是因为手写的数字为笔画的地方暗而背景亮。训练集MNIST中的数据却是笔画的地方亮而背景暗,且背景为一致的黑色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QKkkLUTJ-1664194390831)(https://www.writebug.com/myres/static/uploads/2022/9/26/97dc60160dbcc418962c71cd4d76bdcf.writebug)]
图 9 转为灰度图放缩后的图片
因此如果希望模型能够预测手写数字图片,我们需要对手写数字图片做一些处理。
下图是用像素值最大值255减去整张图片翻转明度后,对于小于60的值置 0的结果,可以看到,由于拍摄照片时光线的因素,数字2的上半部分和下半部分对应的像素亮度不一致。
这样的处理方法得到模型的预测值为
tensor([1], device='cuda:0')
tensor([1], device='cuda:0')
tensor([6], device='cuda:0')
tensor([0], device='cuda:0')
tensor([6], device='cuda:0')
tensor([6], device='cuda:0')
tensor([6], device='cuda:0')
tensor([6], device='cuda:0')
tensor([1], device='cuda:0')
tensor([6], device='cuda:0')
下图是将输入图片二值化,对于低于128的值置0,其余值置255的结果,可以看到,手写数字4的许多笔画都被遗漏掉了。这样处理之后模型的预测结果为
tensor([5], device='cuda:0')
tensor([5], device='cuda:0')
tensor([1], device='cuda:0')
tensor([2], device='cuda:0')
tensor([5], device='cuda:0')
tensor([5], device='cuda:0')
tensor([1], device='cuda:0')
tensor([1], device='cuda:0')
tensor([1], device='cuda:0')
tensor([1], device='cuda:0')
解决的方法还可以调用 opencv 的库函数来提高原图对比度,锐化图片之后再进行二值化等,但这样就并不是专注在神经网络模型上了。
对于修改神经网络模型来获得更好的泛化性能使之能够适应自己拍照上传的手写数字这个问题,也许可以在数据集生成时加入一定的数据增强的手段,比如让原数据像素值上下浮动一定范围等。
本模型在最初训练的时候尝试了随机裁剪和水平翻转等数据增强手段,但手写数字数据是一个较为依赖像素顺序,图片方向的数据(比如6和9的上下翻转或者旋转就会破坏原数据携带的信息)。这样做的结果反而不好(模型测试集准确率在83%左右)。
综合来看,模型在同分布的MNIST测试集上表现良好。
s6aunC-1664194390832)]
下图是将输入图片二值化,对于低于128的值置0,其余值置255的结果,可以看到,手写数字4的许多笔画都被遗漏掉了。这样处理之后模型的预测结果为
tensor([5], device='cuda:0')
tensor([5], device='cuda:0')
tensor([1], device='cuda:0')
tensor([2], device='cuda:0')
tensor([5], device='cuda:0')
tensor([5], device='cuda:0')
tensor([1], device='cuda:0')
tensor([1], device='cuda:0')
tensor([1], device='cuda:0')
tensor([1], device='cuda:0')
解决的方法还可以调用 opencv 的库函数来提高原图对比度,锐化图片之后再进行二值化等,但这样就并不是专注在神经网络模型上了。
对于修改神经网络模型来获得更好的泛化性能使之能够适应自己拍照上传的手写数字这个问题,也许可以在数据集生成时加入一定的数据增强的手段,比如让原数据像素值上下浮动一定范围等。
本模型在最初训练的时候尝试了随机裁剪和水平翻转等数据增强手段,但手写数字数据是一个较为依赖像素顺序,图片方向的数据(比如6和9的上下翻转或者旋转就会破坏原数据携带的信息)。这样做的结果反而不好(模型测试集准确率在83%左右)。
综合来看,模型在同分布的MNIST测试集上表现良好。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。