深度学习实战基础案例——卷积神经网络(CNN)基于SqueezeNet的眼疾识别|第1例

news2024/11/22 14:58:51

文章目录

  • 前言
  • 一、数据准备
      • 1.1 数据集介绍
      • 1.2 数据集文件结构
  • 二、项目实战
      • 2.1 数据标签划分
      • 2.2 数据预处理
      • 2.3 构建模型
      • 2.4 开始训练
      • 2.5 结果可视化
  • 三、数据集个体预测

前言

SqueezeNet是一种轻量且高效的CNN模型,它参数比AlexNet少50倍,但模型性能(accuracy)与AlexNet接近。顾名思义,Squeeze的中文意思是压缩和挤压的意思,所以我们通过算法的名字就可以猜想到,该算法一定是通过压缩模型来降低模型参数量的。当然任何算法的改进都是在原先的基础上提升精度或者降低模型参数,因此该算法的主要目的就是在于降低模型参数量的同时保持模型精度。


我的环境:

  • 基础环境:python3.7
  • 编译器:pycharm
  • 深度学习框架:pytorch
  • 数据集代码获取:链接(提取码:2357 )

一、数据准备

本案例使用的数据集是眼疾识别数据集iChallenge-PM。

1.1 数据集介绍

iChallenge-PM是百度大脑和中山大学中山眼科中心联合举办的iChallenge比赛中,提供的关于病理性近视(Pathologic Myopia,PM)的医疗类数据集,包含1200个受试者的眼底视网膜图片,训练、验证和测试数据集各400张。

  • training.zip:包含训练中的图片和标签
  • validation.zip:包含验证集的图片
  • valid_gt.zip:包含验证集的标签

该数据集是从AI Studio平台中下载的,具体信息如下:
在这里插入图片描述

1.2 数据集文件结构

数据集中共有三个压缩文件,分别是:

  • training.zip
├── PALM-Training400
│   ├── PALM-Training400.zip
│   │   ├── H0002.jpg
│   │   └── ...
│   ├── PALM-Training400-Annotation-D&F.zip
│   │   └── ...
│   └── PALM-Training400-Annotation-Lession.zip
        └── ...
  • valid_gt.zip:标记的位置 里面的PM_Lable_and_Fovea_Location.xlsx就是标记文件
├── PALM-Validation-GT
│   ├── Lession_Masks
│   │   └── ...
│   ├── Disc_Masks
│   │   └── ...
│   └── PM_Lable_and_Fovea_Location.xlsx

  • validation.zip:测试数据集
├── PALM-Validation
│   ├── V0001.jpg
│   ├── V0002.jpg
│   └── ...

二、项目实战

项目结构如下:
在这里插入图片描述

2.1 数据标签划分

该眼疾数据集格式有点复杂,这里我对数据集进行了自己的处理,将训练集和验证集写入txt文本里面,分别对应它的图片路径和标签。

import os
import pandas as pd
# 将训练集划分标签
train_dataset = r"F:\SqueezeNet\data\PALM-Training400\PALM-Training400"
train_list = []
label_list = []


train_filenames = os.listdir(train_dataset)

for name in train_filenames:
    filepath = os.path.join(train_dataset, name)
    train_list.append(filepath)
    if name[0] == 'N' or name[0] == 'H':
        label = 0
        label_list.append(label)
    elif name[0] == 'P':
        label = 1
        label_list.append(label)
    else:
        raise('Error dataset!')


with open('F:/SqueezeNet/train.txt', 'w', encoding='UTF-8') as f:
    i = 0
    for train_img in train_list:
        f.write(str(train_img) + ' ' +str(label_list[i]))
        i += 1
        f.write('\n')
# 将验证集划分标签
valid_dataset = r"F:\SqueezeNet\data\PALM-Validation400"
valid_filenames = os.listdir(valid_dataset)
valid_label = r"F:\SqueezeNet\data\PALM-Validation-GT\PM_Label_and_Fovea_Location.xlsx"
data = pd.read_excel(valid_label)
valid_data = data[['imgName', 'Label']].values.tolist()

with open('F:/SqueezeNet/valid.txt', 'w', encoding='UTF-8') as f:
    for valid_img in valid_data:
        f.write(str(valid_dataset) + '/' + valid_img[0] + ' ' + str(valid_img[1]))
        f.write('\n')

2.2 数据预处理

这里采用到的数据预处理,主要有调整图像大小、随机翻转、归一化等。

import os.path
from PIL import Image
from torch.utils.data import DataLoader, Dataset
from torchvision.transforms import transforms

transform_BZ = transforms.Normalize(
    mean=[0.5, 0.5, 0.5],
    std=[0.5, 0.5, 0.5]
)


class LoadData(Dataset):
    def __init__(self, txt_path, train_flag=True):
        self.imgs_info = self.get_images(txt_path)
        self.train_flag = train_flag

        self.train_tf = transforms.Compose([
            transforms.Resize(224),  # 调整图像大小为224x224
            transforms.RandomHorizontalFlip(),  # 随机左右翻转图像
            transforms.RandomVerticalFlip(),  # 随机上下翻转图像
            transforms.ToTensor(),  # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
            transform_BZ  # 执行某些复杂变换操作
        ])
        self.val_tf = transforms.Compose([
            transforms.Resize(224),  # 调整图像大小为224x224
            transforms.ToTensor(),  # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
            transform_BZ  # 执行某些复杂变换操作
        ])

    def get_images(self, txt_path):
        with open(txt_path, 'r', encoding='utf-8') as f:
            imgs_info = f.readlines()
            imgs_info = list(map(lambda x: x.strip().split(' '), imgs_info))
        return imgs_info

    def padding_black(self, img):
        w, h = img.size
        scale = 224. / max(w, h)
        img_fg = img.resize([int(x) for x in [w * scale, h * scale]])
        size_fg = img_fg.size
        size_bg = 224
        img_bg = Image.new("RGB", (size_bg, size_bg))
        img_bg.paste(img_fg, ((size_bg - size_fg[0]) // 2,
                              (size_bg - size_fg[1]) // 2))

        img = img_bg
        return img

    def __getitem__(self, index):
        img_path, label = self.imgs_info[index]

        img_path = os.path.join('', img_path)
        img = Image.open(img_path)
        img = img.convert("RGB")
        img = self.padding_black(img)
        if self.train_flag:
            img = self.train_tf(img)
        else:
            img = self.val_tf(img)
        label = int(label)
        return img, label

    def __len__(self):
        return len(self.imgs_info)

2.3 构建模型

import torch
import torch.nn as nn
import torch.nn.init as init


class Fire(nn.Module):

    def __init__(self, inplanes, squeeze_planes,
                 expand1x1_planes, expand3x3_planes):
        super(Fire, self).__init__()
        self.inplanes = inplanes
        self.squeeze = nn.Conv2d(inplanes, squeeze_planes, kernel_size=1)
        self.squeeze_activation = nn.ReLU(inplace=True)
        self.expand1x1 = nn.Conv2d(squeeze_planes, expand1x1_planes,
                                   kernel_size=1)
        self.expand1x1_activation = nn.ReLU(inplace=True)
        self.expand3x3 = nn.Conv2d(squeeze_planes, expand3x3_planes,
                                   kernel_size=3, padding=1)
        self.expand3x3_activation = nn.ReLU(inplace=True)

    def forward(self, x):
        x = self.squeeze_activation(self.squeeze(x))
        return torch.cat([
            self.expand1x1_activation(self.expand1x1(x)),
            self.expand3x3_activation(self.expand3x3(x))
        ], 1)


class SqueezeNet(nn.Module):

    def __init__(self, version='1_0', num_classes=1000):
        super(SqueezeNet, self).__init__()
        self.num_classes = num_classes
        if version == '1_0':
            self.features = nn.Sequential(
                nn.Conv2d(3, 96, kernel_size=7, stride=2),
                nn.ReLU(inplace=True),
                nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                Fire(96, 16, 64, 64),
                Fire(128, 16, 64, 64),
                Fire(128, 32, 128, 128),
                nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                Fire(256, 32, 128, 128),
                Fire(256, 48, 192, 192),
                Fire(384, 48, 192, 192),
                Fire(384, 64, 256, 256),
                nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                Fire(512, 64, 256, 256),
            )
        elif version == '1_1':
            self.features = nn.Sequential(
                nn.Conv2d(3, 64, kernel_size=3, stride=2),
                nn.ReLU(inplace=True),
                nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                Fire(64, 16, 64, 64),
                Fire(128, 16, 64, 64),
                nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                Fire(128, 32, 128, 128),
                Fire(256, 32, 128, 128),
                nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                Fire(256, 48, 192, 192),
                Fire(384, 48, 192, 192),
                Fire(384, 64, 256, 256),
                Fire(512, 64, 256, 256),
            )
        else:
            # FIXME: Is this needed? SqueezeNet should only be called from the
            # FIXME: squeezenet1_x() functions
            # FIXME: This checking is not done for the other models
            raise ValueError("Unsupported SqueezeNet version {version}:"
                             "1_0 or 1_1 expected".format(version=version))

        # Final convolution is initialized differently from the rest
        final_conv = nn.Conv2d(512, self.num_classes, kernel_size=1)
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),
            final_conv,
            nn.ReLU(inplace=True),
            nn.AdaptiveAvgPool2d((1, 1))
        )

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                if m is final_conv:
                    init.normal_(m.weight, mean=0.0, std=0.01)
                else:
                    init.kaiming_uniform_(m.weight)
                if m.bias is not None:
                    init.constant_(m.bias, 0)

    def forward(self, x):
        x = self.features(x)
        x = self.classifier(x)
        return torch.flatten(x, 1)

2.4 开始训练

import torch
import torch.nn as nn
from torch.utils.data import DataLoader, Dataset
from model import SqueezeNet
import torchsummary
from dataloader import LoadData
import copy

device = "cuda:0" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))

model = SqueezeNet(num_classes=2).to(device)
# print(model)
#print(torchsummary.summary(model, (3, 224, 224), 1))


# 加载训练集和验证集
train_data = LoadData(r"F:\SqueezeNet\train.txt", True)
train_dl = torch.utils.data.DataLoader(train_data, batch_size=16, pin_memory=True,
                                           shuffle=True, num_workers=0)
test_data = LoadData(r"F:\SqueezeNet\valid.txt", True)
test_dl = torch.utils.data.DataLoader(test_data, batch_size=16, pin_memory=True,
                                           shuffle=True, num_workers=0)


# 编写训练函数
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)  # 训练集的大小
    num_batches = len(dataloader)  # 批次数目, (size/batch_size,向上取整)
    print('num_batches:', num_batches)
    train_loss, train_acc = 0, 0  # 初始化训练损失和正确率

    for X, y in dataloader:  # 获取图片及其标签
        X, y = X.to(device), y.to(device)
        # 计算预测误差
        pred = model(X)  # 网络输出
        loss = loss_fn(pred, y)  # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失

        # 反向传播
        optimizer.zero_grad()  # grad属性归零
        loss.backward()  # 反向传播
        optimizer.step()  # 每一步自动更新

        # 记录acc与loss
        train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
        train_loss += loss.item()

    train_acc /= size
    train_loss /= num_batches

    return train_acc, train_loss

# 编写验证函数
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)  # 测试集的大小
    num_batches = len(dataloader)  # 批次数目, (size/batch_size,向上取整)
    test_loss, test_acc = 0, 0

    # 当不进行训练时,停止梯度更新,节省计算内存消耗
    with torch.no_grad():
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)

            # 计算loss
            target_pred = model(imgs)
            loss = loss_fn(target_pred, target)

            test_loss += loss.item()
            test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()

    test_acc /= size
    test_loss /= num_batches

    return test_acc, test_loss




# 开始训练

epochs = 20

train_loss = []
train_acc = []
test_loss = []
test_acc = []

best_acc = 0  # 设置一个最佳准确率,作为最佳模型的判别指标


loss_function = nn.CrossEntropyLoss()  # 定义损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 定义Adam优化器

for epoch in range(epochs):

    model.train()
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_function, optimizer)

    model.eval()
    epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_function)

    # 保存最佳模型到 best_model
    if epoch_test_acc > best_acc:
        best_acc = epoch_test_acc
        best_model = copy.deepcopy(model)

    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)

    # 获取当前的学习率
    lr = optimizer.state_dict()['param_groups'][0]['lr']

    template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')
    print(template.format(epoch + 1, epoch_train_acc * 100, epoch_train_loss,
                          epoch_test_acc * 100, epoch_test_loss, lr))

# 保存最佳模型到文件中
PATH = './best_model.pth'  # 保存的参数文件名
torch.save(best_model.state_dict(), PATH)

print('Done')

在这里插入图片描述

2.5 结果可视化

import matplotlib.pyplot as plt
#隐藏警告
import warnings
warnings.filterwarnings("ignore")               #忽略警告信息
plt.rcParams['font.sans-serif']    = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号
plt.rcParams['figure.dpi']         = 100        #分辨率

epochs_range = range(epochs)

plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)

plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Test Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Test Loss')
plt.show()

可视化结果如下:
在这里插入图片描述
可以自行调整学习率以及batch_size,这里我的超参数并没有调整。

三、数据集个体预测

import matplotlib.pyplot as plt
from PIL import Image
from torchvision.transforms import transforms
from model import SqueezeNet
import torch

data_transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Resize((224, 224)),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

img = Image.open("F:\SqueezeNet\data\PALM-Validation400\V0008.jpg")
plt.imshow(img)
img = data_transform(img)
img = torch.unsqueeze(img, dim=0)
name = ['非病理性近视', '病理性近视']
model_weight_path = r"F:\SqueezeNet\best_model.pth"
model = SqueezeNet(num_classes=2)
model.load_state_dict(torch.load(model_weight_path))
model.eval()
with torch.no_grad():
    output = torch.squeeze(model(img))

    predict = torch.softmax(output, dim=0)
    # 获得最大可能性索引
    predict_cla = torch.argmax(predict).numpy()
    print('索引为', predict_cla)
print('预测结果为:{},置信度为: {}'.format(name[predict_cla], predict[predict_cla].item()))
plt.show()
索引为 1
预测结果为:病理性近视,置信度为: 0.9768268465995789

在这里插入图片描述

更详细的请看paddle版本的实现:深度学习实战基础案例——卷积神经网络(CNN)基于SqueezeNet的眼疾识别

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/871757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4.深入对象

4.1创建对象三种方式 1.利用对象字面量创建对象 const obj{ name : 佩奇 }2.利用new 0bject创建对象 const obj new Object({ name: 佩奇 }) console.log(obj) // {name: 佩奇}3.利用构造函数创建对象 4.2构造函数 构造函数:是一种特殊的函数,主要用来初始化…

hive 中最常用日期处理函数

hive 常用日期处理函数 在工作中,日期函数是提取数据计算数据必须要用到的环节。哪怕是提取某个时间段下的明细数据也得用到日期函数。今天和大家分享一下常用的日期函数。为什么说常用呢?其实这些函数在数据运营同学手上是几乎每天都在使用的。 技术交…

Metasploit教程 - 基本命令

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、metasploit版本二、命令1.help2.msfupdate 命令3.search命令 三.Armitage GUI四,扫描总结 我想我得对我的博客做一些美化,而不是只有…

【Power BI】使用 Power BI 处理结构化复杂表单数据 | 文末送书

文章目录 前言使用 Power BI 处理结构化复杂表单数据案例一、处理标题与内容同行的数据表案例二、处理标题与内容同单元格的数据表 文末总结Power BI 新书推荐 前言 数据处理是数据分析的奠基石,只有使用处理干净的数据,分析才会产生价值。简单而言&…

Nacos AP架构集群搭建(Windows)

手写SpringCloud项目地址,求个star github:https://github.com/huangjianguo2000/spring-cloud-lightweight gitee:https://gitee.com/huangjianguo2000/spring-cloud-lightweigh 目录: 一:初始化MySQL 二:复制粘贴三份Nacos文…

【设计模式】拦截过滤器模式

拦截过滤器模式(Intercepting Filter Pattern)用于对应用程序的请求或响应做一些预处理/后处理。定义过滤器,并在把请求传给实际目标应用程序之前应用在请求上。过滤器可以做认证/授权/记录日志,或者跟踪请求,然后把请…

Docker启动相关的命令

1.Docker服务相关命令 操作daemon CentOS7版本启动docker systemctl start docker systemctl status docker停掉docker服务: systemctl stop docker重启命令 systemctl restart docker开机自启docker systemctl enable docker2.小结

8.13黄金是否进入下行通道?下周开盘如何布局

近期有哪些消息面影响黄金走势?黄金多空该如何研判? ​黄金消息面解析:周五(8月11日)现货黄金小幅收低,受累于美元走强和美国国债收益率上升,本周录得6月底以来最差单周表现。投资者在评估最新一批通胀报告和消费者信…

欧拉公式之证明

首先,我们考虑复数函数的泰勒级数展开式。对于任意一个复数函数f(z),我们可以将其在za处进行泰勒级数展开: f(z) f(a) f(a)(z-a) f(a)(z-a)^2/2! f(a)(z-a)^3/3! ... 其中f(a)表示f(z)在za处的导数,f(a)表示f(z)在…

Docker查看、创建、进入容器相关的命令

1.查看、创建、进入容器的指令 用-it指令创建出来的容器,创建完成之后会立马进入容器。退出之后立马关闭容器。 docker run -it --namec1 centos:7 /bin/bash退出容器: exit查看现在正在运行的容器命令: docker ps查看历史容器&#xff0…

【前端】求职必备知识点4-CSS:flex、隐藏元素(7种方法)、单位

文章目录 flex隐藏元素(7种方法)不占位置占位置 单位思维导图 flex 【前端】CSS3弹性布局(flex)、媒体查询实现响应式布局和自适应布局_css媒体查询 自适应_karshey的博客-CSDN博客 flex缩写: flex-grow 和 flex-shr…

聚类与回归

聚类 聚类属于非监督式学习(无监督学习),往往不知道因变量。 通过观察学习,将数据分割成多个簇。 回归 回归属于监督式学习(有监督学习),知道因变量。 通过有标签样本的学习分类器 聚类和…

vs使用def导出文件简介

vs使用def导出文件简介 1.首先需要创建一个dll项目,否则没地方配置使用def文件的指向 2.定义一系列函数并创建一个def文件 3.配置使用def文件的指向编译即可 配置到导出格式时候可以通过NONANE选项使到处函数的符号名字为空,X为导出的序号&#xff0c…

SQL-每日一题【1378. 使用唯一标识码替换员工ID】

题目 Employees 表: EmployeeUNI 表: 展示每位用户的 唯一标识码(unique ID );如果某位员工没有唯一标识码,使用 null 填充即可。 你可以以 任意 顺序返回结果表。 返回结果的格式如下例所示。 示例 1&a…

04 - 分离头指针情况、理解HEAD和branch

查看所有文章链接:(更新中)GIT常用场景- 目录 文章目录 1. 分离头指针2. HEAD和branch2.1 branch的一些操作2.2 HEAD 1. 分离头指针 分离头指针detached HEAD是一种HEAD指针指向了某一个具体的 commit id,而不是分支的情况。 切换…

D. Andrey and Escape from Capygrad Round 892 (Div. 2) 1859D

Problem - D - Codeforces 题目大意:在一个从0到1e9的数轴上,有n个传送门,每个传送门有4个参数,l,r,a,b,可以从[l,r]之间的任意内进入传送门,并传送到[a,b]之间的任意位置,[l,r]一定包含[a,b]&…

使用Python解析通达信本地lday数据结构

通达信软件中的vipdoc是一个存储股票行情数据的文件夹。在通达信软件的安装目录下,可以找到一个名为vipdoc的文件夹,里面存放着各个股票的分时、日线、周线、月线等行情数据文件。这些数据文件可以用于自定义分析和回测股票的走势和交易策略,…

第 358 场LeetCode周赛题解

A 数组中的最大数对和 数据范围小&#xff0c;直接暴力枚举数对 class Solution { public:int mx(int x) {//返回10进制表示的数的最大数字int res 0;for (; x; x / 10)res max(res, x % 10);return res;}int maxSum(vector<int> &nums) {int n nums.size();int r…

LVS简介及LVS-DR搭建

目录 一. LVS简介&#xff1a; 1.简介 2. LVS工作模式&#xff1a; 3. LVS调度算法&#xff1a; 4. LVS-DR集群介绍&#xff1a; 二.LVS-DR搭建 1.RS配置 1&#xff09;两台RS&#xff0c;需要下载好httpd软件并准备好配置文件 2&#xff09;添加虚拟IP&#xff08;vip&…

使用GUI Guider工具开发嵌入式GUI应用 (2) - 在MCU上部署源码

使用GUI Guider工具开发嵌入式GUI应用 (2) - 在MCU上部署源码 文章目录 使用GUI Guider工具开发嵌入式GUI应用 (2) - 在MCU上部署源码引言创建LVGL基本MCU工程获取移植LVGL的源码工程通过bootloader使用外扩qspiflash存储大尺寸固件程序 创建LVGL应用源码在GUI Guider中创建新项…