ResNet101是一种深度残差网络,它是ResNet系列中的一种,下面详解ResNet101网络结构。
ResNet101网络结构中有101层,其中第一层是7×7的卷积层,然后是4个阶段(Stage),每个阶段包含若干残差块(Residual Block)。之后是全局平均池化(Global Average Pooling)层以及全连接层(Fully Connected Layer)。全连接层的作用是将全局平均池化层的输出展开成一个向量,并通过一个全连接层将其映射到类别数量的维度上。
ResNet101的每个残差块由两个3×3的卷积层组成,每个卷积层后面都跟有批量归一化(Batch Normalization)和ReLU激活函数。在残差块之间也有批量归一化和ReLU激活函数,但没有卷积层。每个阶段的第一个残差块使用1×1的卷积层将输入的通道数转换为输出的通道数,以便与后续的残差块进行加和操作。
ResNet101模型的主要贡献是引入了残差块的概念,使得网络可以更深,更容易训练。它在ImageNet数据集上的表现非常出色,达到了当时的最优水平。
本项目使用的数据集是一个猴痘病毒分类的数据集,包含猴痘和其他病毒两类样本。数据集划分为训练集和验证集,猴痘类别的图片位于本书配套源码包的monkeypox目录下,其他病毒类别图片位于Others目录下。
本项目借鉴了迁移学习的思想,使用了在 ImageNet 上训练的 ResNet101 网络模型,ImageNet是供计算机视觉识别研究的大型可视化图像数据集,其中包含超过140万手动标注的图像数据,并包含1 000个图像类别,经过预训练的ResNet101网络的全连接层输出1 000个节点,在本实验中为了适应猴痘病数据集类别数,将节点数量由1 000改为2。但是注意需要冻结其他层的参数,防止训练过程中将其进行改动,然后训练微调最后一层即可。该方法既能提高模型的泛化能力和鲁棒性,也能够减少训练的时间,节约算力的开销。
我们知道损失函数是将随机事件或其有关随机变量的取值映射为非负实数,表示该随机事件的风险或损失的函数,在实际任务中则通过最小化损失函数求解和评估模型。本项目使用交叉熵损失表达预测值和真实值的不一致程度,交叉熵损失常用于在图像识别任务中作为损失函数,能够有效地衡量同一个随机变量中的两个不同概率分布的差异程度。
深度学习是以最小化损失函数为目标,其本质上是一种优化问题,目前应用于深度学习的优化算法均是由梯度下降算法发展而来的,其主要思想为利用链式求导法则计算损失函数值相对于神经网络中的每一个权重参数的梯度,通过更新权重参数达到降低损失函数值的效果。本项目使用的优化器Adam算法是一种基于梯度下降的优化算法。Adam算法的优点是收敛速度快,不需要手动调整学习率,兼顾了稳定性和速度。
我们使用PyTorch来搭建猴痘病毒识别模型,完整代码如下:
###############monkeypox.py####### import torchvision from torch import nn import os import pickle import torch from torchvision import transforms, datasets from tqdm import tqdm from PIL import Image import matplotlib.pyplot as plt epochs = 10 lr = 0.03 batch_size = 32 image_path = './monkeypoxdata' model_path = './chk/resnet101-cd907fc2.pth' save_path = './chk/monkeypox_model.pkl' device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') # 1.数据转换 data_transform = { # 训练中的数据增强和归一化 'train': transforms.Compose([ transforms.RandomResizedCrop(224), # 随机裁剪 transforms.RandomHorizontalFlip(), # 左右翻转 transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 均值方差归一化 ]), # 验证集不增强,仅进行归一化 'val': transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), } # 2.形成训练集 train_dataset = datasets.ImageFolder(root=os.path.join(image_path, 'train'), transform=data_transform['train']) # 3.形成迭代器 train_loader = torch.utils.data.DataLoader(train_dataset, batch_size, True) print('using {} images for training.'.format(len(train_dataset))) # 4.建立分类标签与索引的关系 cloth_list = train_dataset.class_to_idx class_dict = {} for key, val in cloth_list.items(): class_dict[val] = key with open('class_dict.pk', 'wb') as f: pickle.dump(class_dict, f) # 5.加载ResNet101模型 model = torchvision.models.resnet101( weights=torchvision.models.ResNet101_Weights.DEFAULT) # 加载预训练好的ResNet模型 model.load_state_dict(torch.load(model_path, 'cpu')) # 冻结模型参数 for param in model.parameters(): param.requires_grad = False # 修改最后一层的全连接层 model.fc = nn.Linear(model.fc.in_features, 2) # 将模型加载到cpu中 model = model.to(device) criterion = nn.CrossEntropyLoss() # 损失函数 optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # 优化器 # 6.模型训练 best_acc = 0 # 最优精确率 best_model = None # 最优模型参数 for epoch in range(epochs): model.train() running_loss = 0 # 损失 epoch_acc = 0 # 每个epoch的准确率 epoch_acc_count = 0 # 每个epoch训练的样本数 train_count = 0 # 用于计算总的样本数,方便求准确率 train_bar = tqdm(train_loader) for data in train_bar: images, labels = data optimizer.zero_grad() output = model(images.to(device)) loss = criterion(output, labels.to(device)) loss.backward() optimizer.step() running_loss += loss.item() train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1, epochs, loss) # 计算每个epoch正确的个数 epoch_acc_count += (output.argmax(axis=1) == labels.view(-1)).sum() train_count += len(images) # 每个epoch对应的准确率 epoch_acc = epoch_acc_count / train_count # 打印信息 print("【EPOCH: 】%s" % str(epoch + 1)) print("训练损失为%s" % str(running_loss)) print("训练精度为%s" % (str(epoch_acc.item() * 100)[:5]) + '%') if epoch_acc > best_acc: best_acc = epoch_acc best_model = model.state_dict() # 在训练结束保存最优的模型参数 if epoch == epochs - 1: # 保存模型 torch.save(best_model, save_path) print('Finished Training') # 加载索引与标签映射字典 with open('class_dict.pk', 'rb') as f: class_dict = pickle.load(f) # 数据变换 data_transform = transforms.Compose( [transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]) # 图片路径 img_path = r'./monkeypoxdata/test/test_01.jpg' # 打开图像 img = Image.open(img_path) # 对图像进行变换 img = data_transform(img) plt.imshow(img.permute(1, 2, 0)) plt.show() # 将图像升维,增加batch_size维度 img = torch.unsqueeze(img, dim=0) # 获取预测结果 pred = class_dict[model(img).argmax(axis=1).item()] print('【预测结果分类】:%s' % pred)
运行结果如下:
using 2142 images for training. train epoch[1/10] loss:0.882: 100%|██████████| 67/67 [05:46<00:00, 5.17s/it] 【EPOCH: 】1 训练损失为38.31112961471081 训练精度为73.90% train epoch[2/10] loss:0.460: 100%|██████████| 67/67 [06:33<00:00, 5.88s/it] 【EPOCH: 】2 训练损失为37.73484416306019 训练精度为77.40% train epoch[3/10] loss:0.225: 100%|██████████| 67/67 [06:00<00:00, 5.38s/it] 0%| | 0/67 [00:00, ?it/s]【EPOCH: 】3 训练损失为31.319448485970497 训练精度为80.06% train epoch[4/10] loss:0.490: 100%|██████████| 67/67 [06:41<00:00, 6.00s/it] 0%| | 0/67 [00:00, ?it/s]【EPOCH: 】4 训练损失为36.781765565276146 训练精度为78.94% train epoch[5/10] loss:0.440: 100%|██████████| 67/67 [06:16<00:00, 5.62s/it] 0%| | 0/67 [00:00, ?it/s]【EPOCH: 】5 训练损失为29.949161008000374 训练精度为81.93% train epoch[6/10] loss:0.253: 100%|██████████| 67/67 [06:17<00:00, 5.63s/it] 【EPOCH: 】6 训练损失为27.939718201756477 训练精度为82.63% train epoch[7/10] loss:0.341: 100%|██████████| 67/67 [06:25<00:00, 5.75s/it] 【EPOCH: 】7 训练损失为29.68729281425476 训练精度为82.77% train epoch[8/10] loss:0.337: 100%|██████████| 67/67 [06:57<00:00, 6.22s/it] 【EPOCH: 】8 训练损失为28.97513736784458 训练精度为82.77% train epoch[9/10] loss:0.089: 100%|██████████| 67/67 [06:17<00:00, 5.63s/it] 0%| | 0/67 [00:00, ?it/s]【EPOCH: 】9 训练损失为26.791129417717457 训练精度为83.05% train epoch[10/10] loss:0.625: 100%|██████████| 67/67 [06:06<00:00, 5.46s/it] 【EPOCH: 】10 训练损失为33.004408583045006 训练精度为80.85% Finished Training Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
如图16-3所示,预测结果分类为Monkeypox。
图16-3
这个项目能够使有相关症状的感染者有效地识别出是否为猴痘病,提出了一种改进的基于迁移学习残差网络的图像自动识别方法。该方法使用了ResNet101网络并使用该网络预训练权重进行迁移学习,在猴痘病数据集上进行了网络的训练,可以增加训练轮次,最终达到了比较高的识别准确率。
《PyTorch深度学习与企业级项目实战(人工智能技术丛书)》(宋立桓,宋立林)【摘要 书评 试读】- 京东图书 (jd.com)