【跨年博客/轻松向】Pytorch卷积神经网络图像识别

【跨年博客/轻松向】Pytorch卷积神经网络图像识别

写于2021-12-31 23:04,2021年最后一篇博客了,也是2022年第一篇博客。


今年的格言:有一分热,发一分光,就令萤火一般,也可以在黑暗里发一点光,不必等候炬火,此后如竟没有炬火,我便是唯一的光。


导语

前几天不是发了一个MNIST图像识别的文章吗,但那篇还是有一些东西没讲的很好。MNIST图像是灰度图像,很多小伙伴想看看RGB的写法。这次拿RGB图做一个细胞分类吧~
看右下角时间~

注意

此篇博客是对上篇博客的修正、补充以及拓展。
请先参阅上期博客:【Pytorch】MNIST 图像分类代码 - 超详细解读_CSDN_千鱼干的博客
有地方看不懂没事,看完后看这篇的补充内容。

程序

需要导入的库

import torch
import torch.nn as nn
from torch.nn import Sequential
from matplotlib import pyplot as plt
import torchvision.datasets as datasets
from torch.utils.data import DataLoader
import torchvision.transforms as transforms

torch库就不解释了。
torch.nn库是一个包含了神经网络的Modules和用来继承的包以及一些函数方法,比如nn.functional。因为我们之后定义网络时是从nn.Module继承的,还用了nn里面的卷积、激活函数等等,所以这个库必不可少。
Sequential(中文:序列)是torch.nn里面“整合”层用的。就相当于饼干盒,把饼干“像序列一样”装进去。
matplotlib在这里是一个显示图像的库。我们引入了pyplot,用来显示图像。
torchvision包含一些数据集、模型、图像处理方法。这里用datasets来处理数据集(我们自己的图片)。
torch.utils.data里面的DataLoader是用来将数据集装载用的,以便训练。
torchvision.transforms在这里用于定义数据集处理形式。往下看就知道。

设置数据集

这里假设我们数据集的路径是当前你的python文件所在文件夹下data/CELLS/。这个文件夹下有两个子文件夹,一个叫“linba”,另一个叫“xianxingli”

插播一句,现在是2022-1-1 0:00,这篇博客写了一年(笑)

也就是说,我们的数据集分为淋巴细胞和线性粒细胞两种细胞的图片。
所以我们要设置两个类别:classes = [“linba”, “zhongxingli”]
我们这里设置一下批次大小位64,迭代250次,学习率0.001。
学习率选一个小小的数,这样有助于梯度下降。具体原因见上一篇博客。
这里还要写一个get_variable()函数以获取cuda加速后的自动求导结果。


这里 再次 详细解释一下epochs和batch_szie:
->batch_size表示每轮迭代训练时每次训练的数据量;
->epochs表示训练几轮。

每一次迭代(Iteration)都是一次权重更新,每一次权重更新需要batch_size个数据进行正向传递(Forward)运算得到损失函数,再通过反向传导(Backward)更新参数(注意,在这个过程中需要把梯度(Grad)设置为0,这个后面再讲)。1个迭代等于使用batch_size个样本训练一次。比如有256个样本数据,完整训练完这些样本数据需要:
->batch_size=64;
->迭代4次;
->epochs=1。


而通常会将epochs设为不仅1次,这就跟磨面一样,磨完一轮不够,磨多轮才能得到更加精细的面粉。

这时候因为我们处理的是图片,而我们处理的应该是张量(Pytorch处理的是张量,类似向量,矩阵)。我们怎么让数据集图片变成数据集矩阵呢?
这时就要设置transform(翻译:转换)了。
同时,我们的图片不能太大,否则会让训练很慢。
所以我们首先将图片转化为张量,然后将裁剪为 w ∗ h = 128 ∗ 128 w*h=128*128 wh=128128(w是width,宽;h是height,高;c是channel,通道,就是颜色通道,RGB是红绿蓝三通道)大小的张量。同时我们还要让每个像素数值服从标准为0.5的正态分布。
因为我们需要训练集和测试集两部分,所以将数据集分成两个处理内容,最后将训练集和测试集装载,数据集处理完毕。

最后,我们展示一下某张数据集图片转换为向量并处理之后的样子(可选)。

全部代码如下:

path = "./data/CELLS/"
classes = ["linba", "zhongxingli"]

def get_variable(x):
    x = torch.autograd.Variable(x)
    return x.cuda() if torch.cuda.is_available() else x

batch_size = 64
epochs = 250
lr = 0.001

test_path = "./data/CELLS/"

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Resize((128, 128)),
    transforms.CenterCrop(128),
    transforms.Normalize(
        mean=[0.5, 0.5, 0.5],
        std=[0.5, 0.5, 0.5]
    )
])

data_train = datasets.ImageFolder(root=path, transform=transform)
train_loader = DataLoader(data_train, batch_size=batch_size, shuffle=True)

data_test = datasets.ImageFolder(root=test_path, transform=transform)
test_loader = DataLoader(data_test, batch_size=batch_size, shuffle=True)

# -----------------------展示数据集---------------------------
images, labels = next(iter(train_loader))
img = images[0].numpy().transpose(1, 2, 0)
plt.imshow(img)
plt.title(labels[0])
plt.show()


# -----------------------展示数据集---------------------------

定义网络

网络结构如下图:
结构
注意,输入出的“?x128x128x3”的意思是“?”张图片,“128x128x3”是 w ∗ h ∗ c = 128 ∗ 128 ∗ 3 w*h*c=128*128*3 whc=1281283

怎样定义网络呢?首先我们要从nn.Module继承,然后“装填”入我们的架构,最后在前向计算(forward)函数中进行前向计算。
这里注意一下:forward函数不需要显式调用,因为nn.Module类中有一个函数会自动调用forward。


上一篇博客没写明这里每层的参数是怎么计算的。这一部分相当重要,因为最后传入全连接层时必须要求输入和输出匹配,但这里我上篇没细讲。
这里贴出nn.Conv2d()在Pytorch官方文档里的图:

–>猛戳我 - 原文链接<–

计算的方式
注意这里的符号, H o u t H_{out} Hout是进行的向下取整
定义类的方式和上一篇相似。
代码:

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()

        self.conv1 = Sequential(
            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )

        self.conv2 = Sequential(
            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
        self.conv3 = Sequential(
            nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )

        self.conv4 = Sequential(
            nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )

        self.dense = Sequential(
            nn.Linear(8 * 8 * 256, 256),
            nn.ReLU(),
            nn.Dropout(p=0.5),
            nn.Linear(256, 128),
            nn.ReLU(),
            nn.Dropout(p=0.5),
            nn.Linear(128, 2)
        )

    def forward(self, x):
        x1 = self.conv1(x)
        x2 = self.conv2(x1)
        x3 = self.conv3(x2)
        x4 = self.conv4(x3)
        x5 = x4.view(-1, 8 * 8 * 256)
        out = self.dense(x5)
        return out

训练

这一部分我上一篇讲的很详细了。我只贴出代码:

cnn = CNN()
if torch.cuda.is_available():
    cnn = cnn.cuda()

lossF = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(cnn.parameters(), lr=lr)

cnn.train()

loss_pth = 999999999.99
i_pth = 0

for epoch in range(epochs):
    running_loss = 0.0
    running_correct = 0.0
    print("Epochs [{}/{}]".format(epoch, epochs))

    for data in train_loader:
        X_train, y_train = data
        X_train, y_train = get_variable(X_train), get_variable(y_train)
        outputs = cnn(X_train)
        _, predict = torch.max(outputs.data, 1)
        # ----------------------------------
        optimizer.zero_grad()
        loss = lossF(outputs, y_train)
        loss.backward()
        optimizer.step()
        # ----------------------------------
        running_loss += loss.item()
        running_correct += torch.sum(predict == y_train.data)

    testing_correct = 0.0

    for data in test_loader:
        X_test, y_test = data
        X_test, y_test = get_variable(X_test), get_variable(y_test)
        outputs = cnn(X_test)
        _, predict = torch.max(outputs.data, 1)
        testing_correct += torch.sum(predict == y_test.data)

    print("Loss: {}    Training Accuracy: {}%    Testing Accuracy:{}%".format(
        running_loss,
        100 * running_correct / len(data_train),
        100 * testing_correct / len(data_test)
    ))

    if running_loss < loss_pth:
        loss_pth = running_loss
        torch.save(cnn, "./models/cell_classify_%d.pth" % i_pth)
        i_pth = i_pth + 1

torch.save(cnn, "cell_classify.pth")
print("训练完成!最小损失为:%f" % loss_pth)

预测

这一部分我在上一篇博客也是讲得比较详细。我只贴出代码。
这里设pics文件夹下还有一个文件夹,里面有待预测图片:

import torch
import torch.nn as nn
from torch.nn import Sequential
from matplotlib import pyplot as plt
import torchvision.datasets as datasets
from torch.utils.data import DataLoader
import torchvision.transforms as transforms

model_path = "./models/cell_classify.pth"
test_path = "./pics/"

classes = ["linbaxibao", "zhongxinglixibao"]

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Resize((128, 128)),
    transforms.CenterCrop(128),
    transforms.Normalize(
        mean=[0.5, 0.5, 0.5],
        std=[0.5, 0.5, 0.5]
    )
])

data_test = datasets.ImageFolder(root=test_path, transform=transform)
test_loader = DataLoader(data_test, batch_size=64, shuffle=True)


class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()

        self.conv1 = Sequential(
            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )

        self.conv2 = Sequential(
            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
        self.conv3 = Sequential(
            nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )

        self.conv4 = Sequential(
            nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )

        self.dense = Sequential(
            nn.Linear(8 * 8 * 256, 256),
            nn.ReLU(),
            nn.Dropout(p=0.5),
            nn.Linear(256, 128),
            nn.ReLU(),
            nn.Dropout(p=0.5),
            nn.Linear(128, 2)
        )

    def forward(self, x):
        x1 = self.conv1(x)
        x2 = self.conv2(x1)
        x3 = self.conv3(x2)
        x4 = self.conv4(x3)
        x5 = x4.view(-1, 8 * 8 * 256)
        out = self.dense(x5)
        return out


net = torch.load(model_path)
net.eval()


def get_variable(x):
    x = torch.autograd.Variable(x)
    return x.cuda() if torch.cuda.is_available() else x


def inference_model(test_img):
    for data in test_loader:
        test, _ = data
        img, _ = data
        test = get_variable(test)
        outputs = net(test)
        rate, predict = torch.max(outputs.data, 1)
        for i in range(len(data_test)):
            print("It may be %s." % classes[predict[i]])
            img0 = img[i]
            img0 = img0.numpy().transpose(1, 2, 0)
            plt.imshow(img0)
            plt.title("It may be %s." % classes[predict[i]])
            # plt.title("It may be %s, probability is %f." % (classes[predict[i]], rate[i]))
            plt.show()


inference_model(test_path)

结果(预测结果在图片上方和console里):预测结果在图片上方
预测结果在图片上方
可以看到预测还是蛮准的嘛~

一些闲谈

现在已经是2022年1月1日1:24了。我又长大了一岁(唉,我又老了
回首2021年。我2021年4月19日突发感想 脑瓜一热 记下了这么一句话:
哈哈
可惜的是,这个愿望只实现了,但没完全实现(谁说人工智能就一定只是会聊天的机器人啊(~o ̄3 ̄)~),我的初衷是做一个和小爱同学一样的人工智能…
目前在做超分辨率重构。
2021年初,我报名了CSDN上的一个深度学习课,当时只是打算听着玩,谁知道听的感兴趣了,就自己自学。没人指导,走了不少弯路,但还是走下来了。
笔者小时候最讨厌电脑了(小学6年级之前)。可是六年级一次电脑课上我接触了画画,我就觉得很好玩,就去研究怎么下载这样的软件,结果接触了Photoshop。谁知道这东西收费,就自己去学怎么破解。初中一年级我想为什么我自己不能编一个这样的程序呢?我就这样接触了编程。(后来还因为进了某网站后台差点惹出事,幸亏自己悬崖勒马)。
笔者从前并不喜欢数学,从前数学一直是我最讨厌的科目之一,因为我觉得我学的数学知识没有什么用到的地方,很枯燥,不理解为什么有人那么喜欢数学(我从前单纯觉得这些人是骗人的)(尽管为了高考要自我欺骗我很爱数学)。大学在某普通本科读计算机科学与技术。大一学了半年ACM,但是并不能听懂(呜呜呜)。大一上学期的寒假接触的机器学习。有趣的是,从接触机器学习开始,我发现数学原来这么实用。和编程结合后,似乎孕育出了某种神奇的力量。
啊。已经1:47了啊。以后再聊吧,我睡了()。

最后祝大家:

2022 新年快乐!

贴一张喜欢的图(侵删):
长草颜团子

  • 13
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值