基于ResNet的乳腺超声图像分类

简单介绍

残差网络ResNet(Residual Network)是一种基于深度卷积神经网络(CNN)架构,由微软研究院的何凯明等人于2015年提出。ResNet的关键是引入了残差学习的概念,解决了深度神经网络在训练过程中的退化问题。
图像分类算法是计算机视觉中的一个重要任务,它的目标是将输入的图像分配到一个或多个预定义的类别中,图像分类算法通常包括以下几个步骤,数据准备、特征提取和分类器,分类器是在CNN的顶层使用全连接层或其他分类器将提取的特征映射到类别标签上。
乳腺超声图像数据集拥有三个类别,分别是良性肿瘤、恶性肿瘤和正常。通过使用预训练的ResNet权重可以准确的识别测试数据中的肿瘤类型。这对于诊断乳腺癌等疾病具有重要意义。同时,也很容易的将该分类算法迁移到同类型的分类任务中。
下面是算法运行的文件介绍

1、模型文件model.py

import torch.nn as nn
import torch

class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, in_channel, out_channel, stride=1, downsample=None, **kwargs):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,
                               kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channel)
        self.relu = nn.ReLU()
        self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,
                               kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channel)
        self.downsample = downsample

    def forward(self, x):
        identity = x
        if self.downsample is not None:
            identity = self.downsample(x)

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        out += identity
        out = self.relu(out)

        return out
        
class Bottleneck(nn.Module):

    expansion = 4

    def __init__(self, in_channel, out_channel, stride=1, downsample=None,
                 groups=1, width_per_group=64):
        super(Bottleneck, self).__init__()

        width = int(out_channel * (width_per_group / 64.)) * groups

        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=width,
                               kernel_size=1, stride=1, bias=False)  # squeeze channels
        self.bn1 = nn.BatchNorm2d(width)
        # -----------------------------------------
        self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, groups=groups,
                               kernel_size=3, stride=stride, bias=False, padding=1)
        self.bn2 = nn.BatchNorm2d(width)
        # -----------------------------------------
        self.conv3 = nn.Conv2d(in_channels=width, out_channels=out_channel*self.expansion,
                               kernel_size=1, stride=1, bias=False)  # unsqueeze channels
        self.bn3 = nn.BatchNorm2d(out_channel*self.expansion)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample

    def forward(self, x):
        identity = x
        if self.downsample is not None:
            identity = self.downsample(x)

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        out += identity
        out = self.relu(out)

        return out
class ResNet(nn.Module):

    def __init__(self,
                 block,
                 blocks_num,    # 残差块的个数
                 num_classes=1000,
                 include_top=True,
                 groups=1,
                 width_per_group=64):
        super(ResNet, self).__init__()
        self.include_top = include_top
        self.in_channel = 64

        self.groups = groups
        self.width_per_group = width_per_group

        self.conv1 = nn.Conv2d(3, self.in_channel, kernel_size=7, stride=2,
                               padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(self.in_channel)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self._make_layer(block, 64, blocks_num[0])
        self.layer2 = self._make_layer(block, 128, blocks_num[1], stride=2)
        self.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)
        self.layer4 = self._make_layer(block, 512, blocks_num[3], stride=2)
        if self.include_top:
            self.avgpool = nn.AdaptiveAvgPool2d((1, 1))  # output size = (1, 1)
            self.fc = nn.Linear(512 * block.expansion, num_classes)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')

    def _make_layer(self, block, channel, block_num, stride=1):
        downsample = None
        if stride != 1 or self.in_channel != channel * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(channel * block.expansion))

        layers = []
        layers.append(block(self.in_channel,
                            channel,
                            downsample=downsample,
                            stride=stride,
                            groups=self.groups,
                            width_per_group=self.width_per_group))
        self.in_channel = channel * block.expansion

        for _ in range(1, block_num):
            layers.append(block(self.in_channel,
                                channel,
                                groups=self.groups,
                                width_per_group=self.width_per_group))

        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        if self.include_top:
            x = self.avgpool(x)
            x = torch.flatten(x, 1)
            x = self.fc(x)

        return x


def resnet34(num_classes=1000, include_top=True):
    # https://download.pytorch.org/models/resnet34-333f7ec4.pth
    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)


def resnet50(num_classes=1000, include_top=True):
    # https://download.pytorch.org/models/resnet50-19c8e357.pth
    return ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)


def resnet101(num_classes=1000, include_top=True):
    # https://download.pytorch.org/models/resnet101-5d3b4d8f.pth
    return ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes, include_top=include_top)


def resnext50_32x4d(num_classes=1000, include_top=True):
    # https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth
    groups = 32
    width_per_group = 4
    return ResNet(Bottleneck, [3, 4, 6, 3],
                  num_classes=num_classes,
                  include_top=include_top,
                  groups=groups,
                  width_per_group=width_per_group)


def resnext101_32x8d(num_classes=1000, include_top=True):
    # https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth
    groups = 32
    width_per_group = 8
    return ResNet(Bottleneck, [3, 4, 23, 3],
                  num_classes=num_classes,
                  include_top=include_top,
                  groups=groups,
                  width_per_group=width_per_group)

代码文件解释:
上面的代码是名为model.py的文件代码。里面构建了resnet34、resnet50、resnet101等模型,因为resne是在ImageNet数据集上进行的实验,该数据集有1000个类别。在官方的代码中类别数因此设置的是1000。同时,代码中也提供了相应模型在ImageNet数据集上的训练权重文件。可以通过网址进行下载。

2、训练文件train.py

import os
import sys
import json

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import transforms, datasets
from tqdm import tqdm

from model import resnet34

def main():
	device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
	print("using {} device.".format(device))
	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])])}
	
	data_root = "./cf_project/resnet_classification"
    image_path = os.path.join(data_root, "dataset", "breast_data")  # flower data set path
    assert os.path.exists(image_path), "{} path does not exist.".format(image_path)
    train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"),
                                         transform=data_transform["train"])
    train_num = len(train_dataset)
    breast_list = train_dataset.class_to_idx
    cla_dict = dict((val, key) for key, val in breast_list.items())
    json_str = json.dumps(cla_dict, indent=4)
    with open('class_indices.json', 'w') as json_file:
    	json_file.write(json_str)

	batch_size = 16
    nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])  # number of workers
    print('Using {} dataloader workers every process'.format(nw))
   	train_loader = torch.utils.data.DataLoader(train_dataset,
                                               batch_size=batch_size, shuffle=True,
                                               num_workers=nw)

    validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"),
                                            transform=data_transform["val"])
    val_num = len(validate_dataset)
    validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                                  batch_size=batch_size, shuffle=False,
                                                  num_workers=nw)
    print("using {} images for training, {} images for validation.".format(train_num, val_num))

	net = resnet34()  # 使用resnet34模型
	# net = resnet50()
	# net = resnet101()
	model_weight_path = "./resnet34-333f7ec4.pth"
	assert os.path.exists(model_weight_path), "file {} does not exist.".format(model_weight_path)
    net.load_state_dict(torch.load(model_weight_path, map_location='cpu'))

	in_channel = net.fc.in_features   # 获取网络中全连接层(‘fc’)的输入特征数量
    net.fc = nn.Linear(in_channel, 3)   # 将resnet34网络中的全连接层分类器替换为nn.Linear(),输出维度为3
    net.to(device)
	loss_function = nn.CrossEntropyLoss()

	params = [p for p in net.parameters() if p.requires_grad]
    optimizer = optim.Adam(params, lr=0.0001)

    epochs = 10
    best_acc = 0.0
    save_path = './cf_project/resnet_classification/resnet34.pth'
    train_steps = len(train_loader)
       loss_function = nn.CrossEntropyLoss()

    # construct an optimizer
    params = [p for p in net.parameters() if p.requires_grad]
    optimizer = optim.Adam(params, lr=0.0001)

    epochs = 10
    best_acc = 0.0
    save_path = 'C:/Users/sxxyc/Desktop/cf_project/resnet_classification/resnet34.pth'
    train_steps = len(train_loader)
    for epoch in range(epochs):
        # train
        net.train()
        running_loss = 0.0
        train_bar = tqdm(train_loader, file=sys.stdout)
        for step, data in enumerate(train_bar):
            images, labels = data
            optimizer.zero_grad()
            logits = net(images.to(device))
            loss = loss_function(logits, labels.to(device))
            loss.backward()
            optimizer.step()
            running_loss += loss.item()
            train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1, epochs, loss)
        # validate
        net.eval()
        acc = 0.0  # accumulate accurate number / epoch
        with torch.no_grad():
            val_bar = tqdm(validate_loader, file=sys.stdout)
            for val_data in val_bar:
                val_images, val_labels = val_data
                outputs = net(val_images.to(device))
                # loss = loss_function(outputs, test_labels)
                predict_y = torch.max(outputs, dim=1)[1]
                acc += torch.eq(predict_y, val_labels.to(device)).sum().item()
                val_bar.desc = "valid epoch[{}/{}]".format(epoch + 1, epochs)
        val_accurate = acc / val_num
        print('[epoch %d] train_loss: %.3f  val_accuracy: %.3f' %
              (epoch + 1, running_loss / train_steps, val_accurate))

        if val_accurate > best_acc:
            best_acc = val_accurate
            torch.save(net.state_dict(), save_path)
    print('Finished Training')

if __name__ == '__main__':
    main()                                                                 

训练代码文件补充:上面的训练文件我们使用了在ImageNet上预训练的权重文件”resnet34-333f7ec4.pth“,这里用到了迁移学习的知识,在迁移学习中,当加载预训练的权重文件时,模型的参数(如卷积核的权重)会被这些预训练的权重初始化,随后,在我们自己的数据集上进行训练,这个训练过程会在预训练权重的基础上进一步更新模型的参数。通过迁移学习,我们能够借用在大规模数据集上预训练得到的特征提取能力,从而在较小的数据集上实现更好的性能核更快的收敛速度。预训练的权重相当于一个良好的初始化,后续的训练会在这个基础上进行参数更新。
如果想要从头开始训练resnet模型可以将上述的部分代码进行替换:替换方法如下:

net = resnet34()  # 使用resnet34模型
# net = resnet50()
# net = resnet101()
model_weight_path = "./resnet34-333f7ec4.pth"
assert os.path.exists(model_weight_path), "file {} does not exist.".format(model_weight_path)
net.load_state_dict(torch.load(model_weight_path, map_location='cpu'))
net = resnet34(pretrained=False)

3、测试文件test.py:

import os
import json
import torch
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt
from model import resnet34

def main():
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    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])])
    # load image
    img_path = "./data/image/Case-24-U-35-2.jpg"
    assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path)
    img = Image.open(img_path)
    plt.imshow(img)
    # [N, C, H, W]
    img = data_transform(img)
    # expand batch dimension
    img = torch.unsqueeze(img, dim=0)
    # read class_indict
    json_path = './class_indices.json'
    assert os.path.exists(json_path), "file: '{}' dose not exist.".format(json_path)
    with open(json_path, "r") as f:
        class_indict = json.load(f)
    # create model
    model = resnet34(num_classes=3).to(device)
    # load model weights
    weights_path = "./resnet34.pth"
    assert os.path.exists(weights_path), "file: '{}' dose not exist.".format(weights_path)
    model.load_state_dict(torch.load(weights_path, map_location=device))
    # prediction
    model.eval()
    with torch.no_grad():
        # predict class
        output = torch.squeeze(model(img.to(device))).cpu()
        predict = torch.softmax(output, dim=0)
        predict_cla = torch.argmax(predict).numpy()
    print_res = "class: {}   prob: {:.3}".format(class_indict[str(predict_cla)], predict[predict_cla].numpy())
    plt.title(print_res)
    for i in range(len(predict)):
        print("class: {:10}   prob: {:.3}".format(class_indict[str(i)], predict[i].numpy()))
    plt.show()

if __name__ == '__main__':
    main()

4、类别索引文件

最后需要新建一个class_indices.json文件,将我们的类别名称及编号写入其中。数据集放置时,相同类别的数据放置在同一文件夹下,文件名称冠以类别名。参考如下:

{
    "0": "benign",
    "1": "malignant",
    "2": "normal"
}

在这里插入图片描述

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: ResNet是一种基于残差网络(Residual Network)的神经网络架构。它被广泛应用于图像识别领域,包括遥感图像场景分类。 在基于ResNet的遥感图像场景分类中,网络通过学习图像中的特征来识别不同的场景类别。首先,遥感图像被输入到网络中,然后经过一系列卷积和池化层的处理,提取图像的高层特征。最后,这些特征被输入到全连接层中,以预测图像的场景类别。 ResNet的优点在于它能够解决深层网络中的梯度消失问题,从而使得网络可以训练更深的模型。这对于遥感图像场景分类来说非常重要,因为遥感图像中的特征通常具有多层次和复杂的结构。 总的来说,基于ResNet的遥感图像场景分类是一种有效且高效的方法,可以在遥感图像识别领域取得良好的结果。 ### 回答2: 基于resnet的遥感图像场景分类是指利用深度学习模型resnet对遥感图像进行场景分类的方法。遥感图像场景分类广泛应用于城市规划、农业监测、环境保护等领域,通过对遥感图像进行自动分类,可以快速获取丰富的场景信息和空间分布,为相关研究和决策提供支持。 ResNet是一种非常有效的深度卷积神经网络模型,其具有多个残差块,可以有效解决深度网络的梯度消失和模型退化问题。基于resnet的遥感图像场景分类主要包括以下步骤: 1. 数据预处理:对遥感图像进行预处理,包括图像增强、降噪、尺度归一化等处理,以提高图像质量和样本的可分性。 2. 模型构建:使用resnet作为基础模型,根据任务需求对其进行适当的调整和扩展,如增加全连接层、改变输出维度等,以适应遥感图像的场景分类任务。 3. 特征提取:利用已搭建好的resnet模型对预处理后的遥感图像进行特征提取,将图像转换为高维特征向量。 4. 分类训练:利用提取到的特征向量作为输入,使用监督学习方法对模型进行训练,通过大量的遥感图像样本进行模型的参数学习和调整,以实现图像场景分类的自动化。 5. 模型评估:使用独立的遥感图像数据集对训练好的模型进行评估,比较预测结果与真实标签的一致性,评估模型的分类准确率和性能。 基于resnet的遥感图像场景分类方法具有较高的分类准确率和鲁棒性,能够有效地识别出遥感图像中的不同场景,为相关应用提供重要的数据支持。同时,该方法还可以通过迁移学习等手段进行模型优化和改进,以应用于更广泛的场景分类任务中。 ### 回答3: 基于ResNet的遥感图像场景分类是指利用深度学习中的ResNet模型来对遥感图像中的场景进行分类和识别的任务。遥感图像场景分类是遥感技术在地理信息系统和环境监测中的一项重要应用。 ResNet是一种深度卷积神经网络模型,在图像识别任务中取得了良好的效果。通过使用ResNet模型,我们可以有效地提取遥感图像中的特征和信息,从而实现准确的场景分类。 基于ResNet的遥感图像场景分类的步骤如下: 1. 数据预处理:收集遥感图像数据,并对数据进行预处理,包括数据增强、数据划分等。数据增强可以通过随机翻转、旋转、缩放等技术来扩充数据集,提升模型的鲁棒性。 2. 构建ResNet模型:根据场景分类的需求,在ResNet的基础上进行调整和修改,增加适合遥感图像场景分类的输出层。ResNet模型的深度和宽度可根据具体问题进行调整。 3. 模型训练:使用已标注的遥感图像数据集对构建的ResNet模型进行训练。在训练过程中,使用合适的损失函数(如交叉熵损失函数)和优化算法(如随机梯度下降算法)来优化模型的参数。同时,可以使用学习率衰减和正则化等技术来提升模型的泛化能力。 4. 模型评估和调优:使用独立的测试数据集对训练好的模型进行评估。可以通过计算分类准确率、召回率、精确度等指标来评估模型的性能,并对模型进行调优,如调整超参数、增加训练数据量等。 5. 场景分类应用:通过对新的遥感图像数据使用训练好的模型进行预测,实现对遥感图像场景的自动分类和识别。可以应用于土地利用规划、环境监测、城市规划等领域。 基于ResNet的遥感图像场景分类通过深度学习的方法,可以有效地提取遥感图像中的特征,并实现遥感图像场景的自动分类和识别,为遥感技术的应用提供了一种新的解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CF996a

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

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

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

打赏作者

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

抵扣说明:

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

余额充值