自定义LeNet5,开启你的炼丹之路

        LeNet5是卷积神经网络的开山之作,在手写数字识别上达到了出色的效果。LeNet5具有5层神经网络,其中2层卷积神经网络,3层全连接。两层卷积神经网络之后分别接了一个激活层和一个平均池化层。这里通过对lenet进行解析和修改,实现对自定义图像数据的分类。

         首先来解析lenet5手写数字识别模型:

         第一个卷积层:输入图像为32*32大小的灰度图,图像通道为1,宽高为32*32,卷积层包含6个卷积核,每个卷积核大小为5*5,经过第一个卷积层后,生成通道为6的宽高为28*28的特征图,之后通过sigmoid激活函数和池化层,池化层为均值池化,采用2*2卷积核,步长为2进行池化卷积,对特征图进行下采样,经过池化以后,特征图变为6*14*14;

         第二个卷积层:这一层有16个卷积核,卷积核大小5*5,经过该层卷积后特征图大小变为16*10*10,池化层同样采用2*2卷积核,步长为2,经过池化后,特征图变为16*5*5;

       在全连接层之前需要对特征图进行打平,16*5*5=400,通过第一个全连接层,降维到120,第二个全连接层后降维到84,第三个全连接层降维到10,也就是手写数字识别的分类数。

lenet5的pytorch实现:

net = torch.nn.Sequential(
    nn.Conv2d(1,6,kernel_size=5),    #[1,1,28,28]*[6,5*5]-->[1,6,28,28],图像与6个5*5的卷积核计算,得到6个28*28特征图
    nn.Sigmoid(),                              #激活函数
    nn.AvgPool2d(2,stride=2),                  #[1,6,28,28]-->[1,6,14,14],平均池化,卷积核2*2,步长2(每四个像素合并为一个)
    nn.Conv2d(6,16,kernel_size=5),             #[1,6,14,14]*[16,5*5]-->[1,16,10,10],图像与16个5*5卷积核计算,得到16个10*10特征图
    nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),      #[1,16,10,10]-->[1,16,5,5]
    nn.Flatten(),                              #[1,16,5,5]-->[1,400] 打平
    nn.Linear(400,120),                      #[1,400]-->[1,120]
    nn.Sigmoid(),
    nn.Linear(120,84),                       #[1,120]-->[1,84]
    nn.Sigmoid(),
    nn.Linear(84,10)                         #[1,84]-->[1,10]
)

        使用lenet5训练自定义图像分类模型,根据数据和分类对模型进行修改。

        数据集路径类似如下结构:

                                dataset/dogs/train/(husky,labrado)

                                dataset/dogs/val/(husky,labrado)

         由于这里使用的彩色图像,所以将第一个卷积层输入通道改为3,使用的数据集只包含两个分类,那么需要最后一个全连接层输出改为分类数,也就是2。

构建网络:

net = torch.nn.Sequential(
    nn.Conv2d(3,6,kernel_size=5),    
    nn.Sigmoid(),                             
    nn.AvgPool2d(2,stride=2),                 
    nn.Conv2d(6,16,kernel_size=5),             
    nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),      
    nn.Flatten(),                              
    nn.Linear(400,120),                      
    nn.Sigmoid(),
    nn.Linear(120,84),                      
    nn.Sigmoid(),
    nn.Linear(84,2)                         
)

        定义训练函数,由于是图像分类,将不同类别图像放到不同文件夹下,使用ImageFolder加数据集并自动分配标签。

def train():
    # 如有GPU,默认使用第一块GPU
    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
    print('using device {}'.format(device))
    #数据预处理
    data_transform = {
        "train": transforms.Compose([
            transforms.RandomResizedCrop(32),   #随机缩放裁剪
            transforms.RandomHorizontalFlip(),   #随机水平翻转
            transforms.ToTensor(),               #转换为Tensor
            transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))   #归一化
        ]),
        "val": transforms.Compose([
            transforms.RandomResizedCrop(32),
            transforms.ToTensor(),
            transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
        ])
    }
    #加载数据集
    batch_size = 32
    data_path = 'dataset/dogs'
    assert os.path.exists(data_path), "{} does not exist".format(data_path)
    train_dataset = datasets.ImageFolder(root=os.path.join(data_path, 'train'), transform=data_transform['train'])
    val_dataset = datasets.ImageFolder(root=os.path.join(data_path, 'val'), transform=data_transform['val'])
    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=8)
    val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=5, shuffle=False, num_workers=8)
    net.load_state_dict(torch.load('lenet.pt'))
    net.to(device)
    loss_function = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(net.parameters(), lr=0.01)
    epochs = 10
    save_path = 'lenet.pt'
    best_acc = 0.0
    steps = len(train_loader)
    for epoch in range(epochs):
        net.train()
        running_loss = 0.0
        for step, data in enumerate(train_loader):
            images, labels = data
            optimizer.zero_grad()
            outputs = net(images.to(device))
            loss = loss_function(outputs, labels.to(device))
            loss.backward()
            optimizer.step()
            running_loss += loss.item()
            print('step:{}/{},loss:{}'.format(step + 1, steps, loss))
        net.eval()
        acc = 0.0
        with torch.no_grad():
            for val_data in val_loader:
                images, labels = val_data
                outputs = net(images.to(device))
                predict = torch.max(outputs, dim=1)[1]
                acc += torch.eq(predict, labels.to(device)).sum().item()
        val_acc = acc / len(val_dataset)
        print('epoch:{}, acc:{}'.format(epoch + 1, val_acc))
        if val_acc > best_acc:
            best_acc = val_acc
            torch.save(net.state_dict(), save_path)
    print("finish train")

        开始训练,经过10轮训练后,分类精度达到82%,最高精度85%。

         这里只是简单修改了lenet模型的输入输出,如果对模型其他地方进行修改,将激活函数替换为Relu又会如何呢?

         修改以后,经过第一轮训练后,模型精度就达到了71%,训练过程最高精度达到85%,看来更换激活函数还是有一定效果。可以操作的地方还有很多,比如将平均池化改为最大池化,增加卷积层数,修改卷积核数量,调整卷积核大小,使用padding自定义卷积层输出大小,或者修改全连接层等,直到调出一个性能不错的模型,炼丹不就成了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TheMatrixs

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值