项目实战:Torch实现简单分类网络

目录

简介

图像分类

深度学习分类原理

卷积层

全连接层

Softmax 函数

Loss计算

Torch代码实战

数据集

数据加载

模型搭建

损失函数

优化器

训练流程

模型预测

简介

这是一个简单的resnet实现猫狗分类例子,项目代码可在github获取,希望你能够学习如下内容:

1)神经网络实现分类的原理

2)了解模型优化流程:数据处理、损失计算、反向传播等

3)熟悉如何使用torch加载图片数据、搭建模型、训练模型。

图像分类

定义:给定一张图像,判断图像中目标类别

深度学习分类原理

在深度学习中,通过卷积神经网络对图像进行特征提取,得到一个代表图像特征的向量,然后通过全连接层将特征向量映射到类别输出,最后通过softmax层得到类别概率输出,整体流程如下。通过训练,不断的更新优化模型的参数,让模型具备预测新数据的能力。

卷积层

作用:通过滑动窗口(‌卷积核)‌在特征图上进行滑动并进行计算,‌实现图像的特征提取。‌卷积的特性包括局部感知机制、‌权值共享等,‌这些特性使得卷积神经网络能够有效地处理图像数据,‌同时减少模型的参数数量,‌防止过拟合。‌

图像在计算机中就是一个多维向量,比如(3x224x224),经过卷积网络后假设得到:(7x7x512)大小的特征图(32倍下采样)。

全连接层

简单来说,全连接层就是一个线性分类层,图像经过卷积网络得到的特征图经过池化或者flatten后,变成一维向量,比如长度512的向量,然后经过全连接层进行线性映射,得到类别输出。比如二分类,得到长度为2的向量。举一个简单的例子:

 

Softmax 函数

作用就是将一组数值转换为对应的概率,且概率和为 1。公式:

比如,经过全连接层得到每个类别输出值为[-0.3241, -0.8814],经过 Softmax 之后,得到类别概率[0.6358, 0.3642],原始的输出转换成一组概率,并且概率的和为 1 。原始输出中值最大的类别具有最大的概率。

Loss计算

深度学习模型训练流程:前向传播计算模型输出、模型输出和标签根据损失函数计算损失、通过损失计算模型参数的梯度、优化器根据参数梯度更新模型参数。损失函数在模型优化中比较重要,它影响模型参数的梯度计算。Softmax对应使用交叉熵损失函数,其计算公式如下:

上式中, a是softmax的计算结果,y是训练样本的标签(one-hot编码),表示该样本正确的分类类型,如果以向量表示的话,其中只有一个元素为1,其余元素都为0:

基于这个特性,所以损失大小只与网络判断正确分类的概率有关。所以损失的公式可以简化为(t是正确的那个类别):

Torch代码实战

数据集

使用Kaggle的猫狗数据集进行分类,该数据集总共25000图片,猫和狗数量各占一半,图片大小不固定。随机挑选1000张猫和1000张狗的图片作为验证集。训练集真理成如下目录结构:

部分数据集示例:

数据集下载连接:https://www.microsoft.com/en-us/download/details.aspx?id=54765

数据加载

Torch中数据加载涉及两个类torch.utils.data中的Dataset和DataLoader。

Dataset:数据集类。需要实现__getitem__方法来获取数据集中的单个样本,实现__len__方法来获取数据集的大小,数据预处理也在该类实现。

DataLoader:数据加载类,设置适当的参数来加载数据,比如一次加载的批次大小,加载数据线程数等。它是一个可迭代的对象,每次迭代会返回一个批次的图片数据

使用示例:

train_dataset = build_data_set(224, data)  # 调用dataset.py中的build_data_set()方法
train_loader = DataLoader(train_dataset, 10, shuffle=True, num_workers=1)

for i, (images, target) in enumerate(train_loader):
    print(images.shape, target)
    break

输出:

torch.Size([10, 3, 224, 224]) tensor([0, 1, 1, 1, 1, 1, 1, 0, 1, 1])

在这个案例中,我们使用datasets.ImageFolder来代替Dataset对象,ImageFolder类会根据给定文件夹中的子文件读取图片并根据子文件顺序,自动生成类别索引。比如上述训练集文件,Cat、Dog对应类别分别为0、1。

ImageFolder类同时接收一个transform对象,用来定义图片的预处理方式,通常图片数据在进入模型之前需要进行如下处理:1)图片缩放固定大小;2)转为tensor,并归一化到[0,1];3)标准化到正态分布。

# 构建dataset
transform = transforms.Compose([
        transforms.Resize((img_size, img_size)),  # 图片缩放到(224,224)
        transforms.ToTensor(),  # 将图像数据格式转换为Tensor格式。所有数除以255,将数据归一化到[0,1]
        transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])  # 标准正态分布变换,三通道归一化到[-1,1]。(input-mean)/std
    ])

标准正态分布参考:https://www.cnblogs.com/oliyoung/p/transforms_Normalize.html

模型搭建

模型使用resnet18,这里没有自己搭建,而是使用了官方的预选连模型,然后把全连接层输出设置为自己的类别数。有时间的话可以自己搭建一个,熟悉网络结构。注意模型fc输出结果是没有经过softmax的。

# 构建一个分类模型
model = torchvision.models.resnet18(pretrained=True)
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, num_class)  # 将全连接层输出设置为自己的类别数
return model
损失函数

交叉熵损失

# 损失函数选择
criterion = nn.CrossEntropyLoss()
优化器

优化器决定了模型参数的更新方式,这个案例使用SGD(随机梯度下降),学习率设置为0.001,动量设置为0.9,权重衰减为0.0001。

# 优化器
optimizer = torch.optim.SGD(model.parameters(), args.lr, momentum=args.momentum, weight_decay=args.weight_decay)

优化器详细参考:

一个框架看懂优化算法之异同 SGD/AdaGrad/Adam:https://zhuanlan.zhihu.com/p/32230623

训练流程

训练的基本流程:前向传播、计算损失、参数梯度归零、损失反向传播计算梯度、优化器更具梯度更新参数。训练过程,每训练一完一个epcoh,验证模型一次,保存一次模型权重。优于训练集样本较多,且分类任务比较简单,模型训练2-3轮后,就能在测试集上达到一个不错的效果。

    # 正式训练
    for epoch in range(args.epochs):  # args.epochs,epochs=10
        print(f'Epoch {epoch}/{args.epochs}')
        # switch to train mode
        model.train()
        for i, (images, target) in enumerate(tqdm(train_loader)):
            images, target = images.to(device), target.to(device)
            # 计算输出
            output = model(images)
            # 计算loss
            loss = criterion(output, target)
            # 梯度清零
            optimizer.zero_grad()
            # 反向传播
            loss.backward()
            # 梯度优化
            optimizer.step()

        # 一轮验证一次模型
        val(model, device, test_loader, criterion)

        # 模型保存,一轮保存一次
        if not os.path.exists(args.checkpoint_dir):
            os.mkdir(args.checkpoint_dir)
        torch.save(model.state_dict(), os.path.join(args.checkpoint_dir, 'checkpoint_epoch_{}.pth'.format(epoch)))
        print("model saved success")

训练输出:

模型预测

模型训练好后,保存的权重文件在./ckpts/下,可以加载模型权重,对单张图片进行预测,判断类别。

    # 读取图像
    img = Image.open(img_path)
    img1 = img.copy()
    # 图片预处理,保证和训练是一样
    transform = transforms.Compose([
        transforms.Resize((224, 224)),  # 图片缩放到(224,224)
        transforms.ToTensor(),  # 将图像数据格式转换为Tensor格式。所有数除以255,将数据归一化到[0,1]
        transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])  # 标准正态分布变换,三通道归一化到[-1,1]。(input-mean)/std
    ])
    image = transform(img)  # (3, 224, 224)
    image = image.unsqueeze(0)  # 增加batch维度  (1, 3, 224, 224)
    # print(image.shape)

    # 模型加载
    model = buid_model(2)
    model.load_state_dict(torch.load(model_path), strict=True)
    model.eval()  # 必须是eval模式,否则模型预测不准

    # 预测
    out = model(image)  # out: (1, 2)
    pro = F.softmax(out, 1)  # 结果进行softmax,得到每个类别的预测概率 pro: (1, 2)
    _, pred = torch.max(out.data, 1)  # 获取模型输出结果最大值索引,得到预测类别
    cls_index = int(pred)  # 类别索引
    cls_pro = round(float(pro[0][cls_index]), 2)  # 类别概率,保留两位小数
    print(cls_index, cls_pro)

    # 可视化模型分类结果
    # 创建一个可以在给定图片上绘图的对象
    cl_dic = {0: "Cat", 1: "Dog"}
    draw = ImageDraw.Draw(img1)
    # 定义字体和大小
    font = ImageFont.truetype("arial.ttf", 10)
    # 写字
    draw.text((10, 10), "class:" + cl_dic[cls_index]+"   pro:"+str(cls_pro), font=font, fill=(0, 255, 255))
    # 保存新的图片
    img1.show()

预测结果:

项目地址:https://github.com/wzl639/classification-torch

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值