搭建卷积神经网络实现手写数字10分类
1.思路流程
1.导入minest数据集
2.对数据进行预处理
3.构建卷积神经网络模型
4.训练模型,评估模型
5.用模型进行训练预测
一.导入minest数据集
MNIST--->raw--->test-->(0,1,2...) 10个文件夹
MNIST--->raw--->train-->(0,1,2...) 10个文件夹
共60000张图片.可自己去网上下载
二.对数据进行预处理
----读取图片,将图片先转为张量。
img=cv2.imread(path)
----将图片进行归一化,即将像素值标准化到0-1之间
img_tensor=util.transforms_train(img)
----裁剪,翻转等,实现数据增强。
数据增强:通过对原始图像进行旋转、翻转等操作,可以增加数据的多样性。这有助于模型学习到更具泛化性的特征,减少对特定方向或位置的依赖,从而提高模型的鲁棒性和准确性
transforms_train=transforms.Compose([
# transforms.CenterCrop(10),
# transforms.PILToTensor(),
transforms.ToTensor(),#归一化,转tensor
transforms.Resize((28,28)),
transforms.RandomVerticalFlip()
])
ps:为什么要归一化
-
消除量纲影响:不同图像的像素值范围可能差异很大。归一化可以将像素值范围统一到一个特定的区间,例如 [0, 1] 或 [-1, 1],消除不同图像之间因像素值范围差异带来的影响,使模型更关注图像的特征和结构,而不是像素值的绝对大小。
-
提高训练稳定性:有助于优化算法的收敛性和稳定性。如果像素值范围较大且分布不均匀,可能导致梯度计算不稳定,从而影响模型的训练效率和效果。
-
缓解过拟合:一定程度上可以减少数据中的噪声和异常值对模型的影响,降低模型对某些特定像素值的过度依赖,从而提高模型的泛化能力,减少过拟合的风险。
三.构建卷积神经网络模型
常见卷积神经网络(CNN),主要由卷积,池化,全连接组成。卷积核在输入图像上滑动,通过卷积运算提取局部特征。卷积核在整个图像上重复使用,大大减少了模型的参数数量,降低了计算复杂度,同时也增强了模型对平移不变性的鲁棒性。池化层对特征进行压缩,提取主要特征,减少噪声和冗余信息。
x=torch.randn(2,3,28,28)
用x表示初始图形的信息。为了简单理解,简单表述。其中
2--->两张图片
3--->图片的通道数是3个,即 RGB
28,28--->图片的宽高是28px 28px
采用以上的神经网络conv为卷积操作,maxpool为池化。Linear为全连接。relu为激活函数。
进入全连接层时需要将展平。torch.Size([2, 16, 5, 5])--->torch.Size([2, 400])
x=torch.flatten(x,1)
因为全连接是只进行的线性的变化。所以要把每张图片的维数参数降为1。
使用print(summary(net, x))可查看网络的层次结构。其中-1就表示自己算,是多少张图片就是多少
输入的的是x=torch.randn(2,3,28,28),最终输出的是(2,10)
四.训练模型,评估模型
需要初始化之前的数据和网络,然后选择合适的优化器和损失函数,学习率和加载图片的批次去训练模型。使用loss_avg和accurary来评估模型的性能。对于pytorch来说优化器可以实现自动梯度清0,自动更新参数。我们需要主要的是就实现其中的维度的转化。loss越小越接近真实值。其中计算精度的方法使用one-hot编码。其中0表示[0,0,0,0,0,0,0,0,0,0],1表示[0,1,0,0,0,0,0,0,0,0],2表示[0,0,1,0,0,0,0,0,0,0].。。。其他依次类推。我们把用网络得出的参数,类似[0.1,0.2,0.1,0.5,0,0,0,0,0,0](数据我随便写的),然后用Python的argmax去处最大值的索引与one-hot真实值的索引相比,如果相等就是正确的结果。
----本次实验使用的是MSE损失函数
----lr(学习率)设为0.01
----使用的优化器Adam ,其实其他优化器你也可以随便试试。
Adam 算法的主要优点包括:
-
自适应学习率:能够为每个参数自适应地调整学习率。
-
偏差校正:在初始阶段对梯度估计进行校正,加速初期的学习速率。
-
适应性强:在很多不同的模型和数据集上都表现出良好的性能。
-
实现简单,计算高效,对内存需求少。
使用tensorboard进行可视化
五.用模型进行训练预测
需要读取之前训练好的模型,然后用这个模型来实现预测一个自己手写的图片
# 加载整个模型
loaded_model = torch.load('whole_model.pth')
# 保存模型参数
torch.save(loaded_model.state_dict(),'model_params.pth')
代码附上:
import glob
import os.path
import cv2
import torch
import util
class DataAndLabel:
def __init__(self,path='D:\\0MNIST\\raw',is_train=True):
super().__init__()
# 拼接路径
#data里面是path,label
clas='train' if is_train==True else 'test'
path=os.path.join(path,clas)
paths=glob.glob(os.path.join(path,'*','*'))
# print(paths)
# print(path)
self.data=[]
for path in paths:
label=int(path.split('\\')[-2])
self.data.append((path,label))
def __getitem__(self, idx):
#返回一个tensor,one-hot
path,label =self.data[idx]
img=cv2.imread(path)
# cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img_tensor=util.transforms_train(img)
one_hot=torch.zeros(10)
one_hot[label]=1
return img_tensor,one_hot
def __len__(self):
return len(self.data)
# if __name__ == '__main__':
# data=DataAndLabel()
# print(data[0])
# print()
import torch
import torch.nn as nn
from torchkeras import summary
class Net(nn.Module):
def __init__(self):
super().__init__()
self.conv1=nn.Conv2d(3,6,5,1)
self.maxpool1=nn.MaxPool2d(2)
self.conv2=nn.Conv2d(6,16,3,1)
self.maxpool2=nn.MaxPool2d(2)
self.layer1=nn.Linear(16*5*5,10)
self.layer2=nn.Linear(10,10)
self.relu=nn.Softmax()
def forward(self,x):
x=self.conv1(x)
x=self.relu(x)
x=self.maxpool1(x)
x=self.conv2(x)
x=self.relu(x)
x=self.maxpool2(x)
# print(x.shape)
x=torch.flatten(x,1)
# print(x.shape)
x=self.layer1(x)
x=self.layer2(x)
return x
if __name__ == '__main__':
x=torch.randn(2,3,28,28)
net=Net()
out=net(x)
# print(out.shape)
# print(summary(net, x))
train_and_test
import torch
import tqdm
from torch.utils.data import Dataset, DataLoader
from torch.utils.tensorboard import SummaryWriter
from lenet5 import Net
import torch.nn as nn
from dataset import DataAndLabel
class TrainAndTest(Dataset):
def __init__(self):
super().__init__()
# self.writer=SummaryWriter("logs")
net=Net()
self.net=net
self.loss=nn.MSELoss()
self.opt = torch.optim.Adam(net.parameters(), lr=0.1)
self.train_data=DataAndLabel(is_train=True)
self.test_data=DataAndLabel(is_train=False)
self.train_loader=DataLoader(self.train_data,batch_size=100,shuffle=False)
self.test_loader=DataLoader(self.test_data,batch_size=100,shuffle=False)
# 拿到数据,网络
def train(self,epoch):
loss_sum = 0
accurary_sum = 0
for img_tensor, label in tqdm.tqdm(self.train_loader, desc='train...', total=len(self.train_loader)):
out = self.net(img_tensor)
loss = self.loss(out, label)
self.opt.zero_grad()
loss.backward()
self.opt.step()
loss_sum += loss.item()
accurary_sum += torch.mean(
torch.eq(torch.argmax(label, dim=1), torch.argmax(out, dim=1)).to(torch.float32)).item()
loss_avg = loss_sum / len(self.train_loader)
accurary_avg = accurary_sum / len(self.train_loader)
print(f'train---->loss_avg={round(loss_avg, 3)},accurary_avg={round(accurary_avg, 3)}')
# self.writer.add_scalars('loss',{'loss_avg':loss_avg},epoch)
def train1(self):
sum_loss = 0
sum_acc = 0
for img_tensors, targets in tqdm.tqdm(self.train_loader, desc="train...", total=len(self.train_loader)):
out = self.net(img_tensors)
loss = self.loss(out, targets)
self.opt.zero_grad()
loss.backward()
self.opt.step()
sum_loss += loss.item()
pred_cls = torch.argmax(out, dim=1)
target_cls = torch.argmax(targets, dim=1)
accuracy =torch.mean(torch.eq(pred_cls, target_cls).to(torch.float32))
sum_acc += accuracy.item()
avg_loss = sum_loss / len(self.train_loader)
avg_acc = sum_acc / len(self.train_loader)
print(f'train:loss{round(avg_loss, 3)} acc:{round(avg_acc, 3)}')
def run(self):
for epoch in range(10):
self.train1()
# self.test(epoch)
if __name__ == '__main__':
tt=TrainAndTest()
tt.run()
from torchvision import transforms
transforms_train=transforms.Compose([
# transforms.CenterCrop(10),
# transforms.PILToTensor(),
transforms.ToTensor(),#归一化,转tensor
transforms.Resize((28,28)),
transforms.RandomVerticalFlip()
])
transforms_test=transforms.Compose([
transforms.ToTensor(), # 归一化,转tensor
transforms.Resize((28, 28)),
])