当前位置: 首页 > news >正文

动手学深度学习:CNN和LeNet

前言

该篇文章记述从零如何实现CNN,以及LeNet对于之前数据集分类的提升效果。

从零实现卷积核

import torch
def conv2d(X,k):
    h,w=k.shape
    Y=torch.zeros((X.shape[0]-h+1,X.shape[1]-w+1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i,j]=(X[i:i+h,j:j+w]*k).sum()
    return Y
X=torch.tensor([[0.,1.,2.],[3.,4.,5.],[6.,7.,8.]])
k=torch.tensor([[0.,1.],[2.,3.]])
conv2d(X,k)

在这里插入图片描述

卷积层

from torch import nn
class Conv2D(nn.Module):
    def __init__(self,kernel_size):
        super.__init__()
        self.weight=nn.Parameter(torch.rand(kernel_size))
        self.bias=nn.Parameter(torch.zeros(1))
    def forward(self,x):
        return conv2d(x,self.weight)+self.bias

验证卷积层对于图像的检测作用

x=torch.ones((6,8))
x[:,2:6]=0
x

在这里插入图片描述

k=torch.tensor([[1.0,-1.0]])
y=conv2d(x,k)
y

在这里插入图片描述
很明显这个卷积核提取到了垂直上的特征

conv2d(x.t(),k)

在这里插入图片描述
并没有学习到水平特征

学习卷积核

我们可以让卷积核自己学习里面的参数以达到对不同图像提取的作用

conv2d=nn.Conv2d(1,1,kernel_size=(1,2),bias=False)

x=x.reshape((1,1,x.shape[0],x.shape[1]))
y=y.reshape((1,1,6,7))
lr=3e-2

for i in range(10):
    y_hat=conv2d(x)
    l=(y_hat-y)**2
    conv2d.zero_grad()
    l.sum().backward()
    
    conv2d.weight.data[:]-=lr*conv2d.weight.grad
    print(f"第{i}轮,loss为{l.sum()}")

在这里插入图片描述

conv2d.weight.data

在这里插入图片描述

填充

def comp_conv2d(conv2d,x):
    #(1,1)添加batch大小和通道数
    x=x.reshape((1,1)+x.shape)
    y=conv2d(x)
    return y.reshape(y.shape[2:])
conv2d=nn.Conv2d(1,1,kernel_size=3,padding=1)
x=torch.rand(size=(8,8))
comp_conv2d(conv2d,x).shape

在这里插入图片描述

conv2d=nn.Conv2d(1,1,kernel_size=(5,3),padding=(2,1))
x=torch.rand(size=(8,8))
comp_conv2d(conv2d,x).shape

在这里插入图片描述

步幅

conv2d=nn.Conv2d(1,1,kernel_size=(3,3),padding=1,stride=2)
x=torch.rand(size=(8,8))
comp_conv2d(conv2d,x).shape

在这里插入图片描述

多通道

from d2l import torch as d2l
def corr2d_multi_in(X,K):
    return sum(d2l.corr2d(x,k) for x,k in zip(X,K))
x=torch.randn(size=(4,2,3))
k=torch.randn(size=(4,1,3))
corr2d_multi_in(x,k)

在这里插入图片描述

多输出通道

def corr2d_multi_in_out(X,K):
    return torch.stack([corr2d_multi_in(X,k)for k in K],0)
K=torch.stack((k,k+1,k+2),0)
K.shape

在这里插入图片描述

corr2d_multi_in_out(x,K)

在这里插入图片描述

1x1卷积

def corr2d_multi_in_out_1x1(X,K):
    c_i,h,w=X.shape
    c_o=K.shape[0]
    X=X.reshape((c_i,h*w))
    K=K.reshape((c_o,c_i))
    Y=torch.matmul(K,X)
    return Y.reshape((c_o,h,w))
X=torch.normal(0,1,(3,3,3))
K=torch.normal(0,1,(2,3,1,1))
Y1=corr2d_multi_in_out_1x1(X,K)
Y2=corr2d_multi_in_out(X,K)
Y1==Y2

在这里插入图片描述

汇聚层

def pool2d(x,pool_size,mode='max'):
    p_h,p_w=pool_size
    Y=torch.zeros((X.shape[0]-p_h+1,X.shape[1]-p_w+1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            if mode=='max':
                Y[i,j]=X[i:i+p_h,j:j+p_w].max()
            elif mode=='avg':
                Y[i,j]=X[i:i+p_h,j:j+p_w].mean()
    return Y
X=torch.tensor([[0.0,1.,2.],[3.,4.,5.],[6.,7.,8.]])
pool2d(X,(2,2))

在这里插入图片描述

pool2d(X,(2,2),'avg')

在这里插入图片描述

LeNet

这是最早的神经网络,根据我的测试,这个模型在我的数据集上的效果比MLP要提高了1%以上,在这段时间里面,我页发现了原有数据集在分类上存在问题,所以重新制作了一份,在这份数据集上,随着我数据量的提升以及模型的修改,准确率达到了99.7%,且无过拟合现象。

原始的LeNet

from torch import nn
net=nn.Sequential(
nn.Conv2d(1,6,kernel_size=5,padding=2),nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2,stride=2),
nn.Conv2d(6,16,kernel_size=5),nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2,stride=2),
nn.Flatten(),
nn.Linear(16*3*5,120),nn.Sigmoid(),
nn.Linear(120,84),nn.Sigmoid(),
nn.Linear(84,9))
def init_weight(m):
    if type(m)==nn.Linear or type(m)==nn.Conv2d:
        nn.init.xavier_uniform_(m.weight)
net.apply(init_weight)

在这里插入图片描述

测试结果

我忘记截图了,效果达到了99%以上,同样的数据集在MLP上是98%

改进后的LeNet

我将平均池化层改成了最大池化层

net=nn.Sequential(
nn.Conv2d(1,6,kernel_size=5,padding=2),nn.Sigmoid(),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Conv2d(6,16,kernel_size=5),nn.Sigmoid(),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Flatten(),
nn.Linear(16*3*5,120),nn.Sigmoid(),
nn.Linear(120,84),nn.Sigmoid(),
nn.Linear(84,9))
def init_weight(m):
    if type(m)==nn.Linear or type(m)==nn.Conv2d:
        nn.init.xavier_uniform_(m.weight)
net.apply(init_weight)

在这里插入图片描述

训练修改

我在训练过程中添加了记录test的loss最低时,保存pt和onnx,用于后续推理。

epochs_num=100
train_len=len(train_iter.dataset)
all_acc=[]
all_loss=[]
test_all_acc=[]
shape=None
for epoch in range(epochs_num):
    acc=0
    loss=0
    for x,y in train_iter:
        hat_y=net(x)
        l=loss_fn(hat_y,y)
        loss+=l
        optimer.zero_grad()
        l.backward()
        optimer.step()
        acc+=(hat_y.argmax(1)==y).sum()
    all_acc.append(acc/train_len)
    all_loss.append(loss.detach().numpy())
    test_acc=0
    test_loss=0
    test_len=len(test_iter.dataset)
    with torch.no_grad():
        for x,y in test_iter:
            shape=x.shape
            hat_y=net(x)
            test_loss+=loss_fn(hat_y,y)
            test_acc+=(hat_y.argmax(1)==y).sum()
    test_all_acc.append(test_acc/test_len)
    print(f'{epoch}的test的acc{test_acc/test_len}')
    # 保存测试损失最小的模型
    if test_loss < best_test_loss:
        best_test_loss = test_loss
        torch.save(net, best_model_path)
        dummy_input = torch.randn(shape)  
        torch.onnx.export(net, dummy_input, "./models/LeNet5.onnx", opset_version=11)
        print(f'Saved better model with Test Loss: {best_test_loss:.4f}')

在这里插入图片描述

损失函数可视化

plt.plot(range(1,epochs_num+1),all_loss,'.-',label='train_loss')
plt.text(epochs_num, all_loss[-1], f'{all_loss[-1]:.4f}', fontsize=12, verticalalignment='bottom')

在这里插入图片描述

准确率可视化

plt.plot(range(1,epochs_num+1),all_acc,'-',label='train_acc')
plt.text(epochs_num, all_acc[-1], f'{all_acc[-1]:.4f}', fontsize=12, verticalalignment='bottom')
plt.plot(range(1,epochs_num+1),test_all_acc,'-.',label='test_acc')
plt.legend()

在这里插入图片描述

预测结果

import numpy as np
with torch.no_grad():
    all_num=5
    index=1
    plt.figure(figsize=(12,5))
    for i,label in zip(test_data_path,test_labels):
        if index<=all_num:
            img=cv2.imread(i)
            input_img=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
            img=cv2.cvtColor(input_img,cv2.COLOR_BGR2RGB)
            input_img = np.expand_dims(input_img, axis=2)  # 增加通道维度,形状变为 [1, H, W]
            input_img=transforms.ToTensor()(input_img)
            input_img = input_img.unsqueeze(0)  # 增加批量维度,形状变为 [1, 1, 28, 20]
            print(input_img.shape)
            result=net(input_img).argmax(1)
            plt.subplot(1,all_num,index)
            plt.imshow(img)
            plt.title(f'true{label},predict{result.detach().numpy()}')
            plt.axis("off")
            index+=1

在这里插入图片描述

总结

数据集收集过程中遇到了部分麻烦,数据集还不够完整。

相关文章:

  • ctf-web:php反序列化逃逸 -- GHCTF Escape!
  • Linux和RTOS简析
  • vulnhub靶场之stapler靶机
  • HTML+CSS基础(了解水平)
  • 【数据挖掘】KL散度(Kullback-Leibler Divergence, KLD)
  • 使用WireShark解密https流量
  • 剑指 Offer II 084. 含有重复元素集合的全排列
  • Vue中使用到的padStart方法是什么
  • 2.4 python网络编程
  • java集合总结
  • 问题一:如何理解 sizeof(s = a + 2) (来源:C语言的——操作符详解——(第18篇))
  • 前沿技术趋势:值得关注的创新发展
  • 睡不着营养贴纸
  • Java开发之数据库应用:记一次医疗系统数据库迁移引发的异常:从MySQL到PostgreSQL的“dual“表陷阱与突围之路
  • 高频面试题(含笔试高频算法整理)基本总结回顾43
  • 2024山东大学计算机复试上机真题
  • BUG修复 | 一次钉钉工作台应用远程调试实战(开发者工具)
  • 如何搭配 AI 量化策略选股
  • 优化 Java 数据结构选择与使用,提升程序性能与可维护性
  • ssh通过22端口无法连接服务器问题处理
  • 海南儋州市委副书记任延新已赴市人大常委会履新
  • 普京发表声明感谢协助俄军收复库尔斯克州的朝鲜军人
  • 商超展销延长、专区专柜亮相……上海“外贸拓内销”商品与市民见面
  • 关键词看中国经济“一季报”:稳,开局良好看信心
  • 持续更新丨伊朗内政部长:港口爆炸已致8人死亡750人受伤
  • 手机号旧机主信用卡欠款、新机主被催收骚扰四年,光大银行济南分行回应