UNet 眼底血管分割实战教程


Blog’s 主页: 白乐天_ξ( ✿>◡❛)
🌈 个人Motto:他强任他强,清风拂山冈!
💫 欢迎来到我的学习笔记!

在医学影像分析领域,准确地分割眼底血管对于眼科疾病的诊断和治疗至关重要。本教程将详细介绍如何利用 UNet 进行眼底血管分割,包括云实例配置、数据集处理以及模型训练和测试。

一、云实例配置与启动

  1. 登录注册
    打开丹摩平台,进入登录界面进行注册并登录账号,为后续操作奠定基础。
  2. 配置 SSH 密钥对
    SSH 密钥对的配置能让远程登录服务器更加便捷。首先在本地.ssh 目录下输入ssh-keygen -o命令创建本地公钥,可自行设置文件名。生成的两个文件中,id_dsa.pub为所需的公钥文件。接着进入密钥对配置,将公钥文件内容复制到此处完成配置。
  3. 创建实例
    进入GPU云实例页面,根据需求选择合适的 GPU 型号和镜像。在配置过程中,务必记得选择之前创建的密钥对。确认所有选项无误后,点击立即创建,等待实例创建完成。
  4. 登录云实例
    实例创建完成后,复制访问链接。然后在任意一个 SSH 连接终端,如 VSCode 中进行云实例登录。登录成功后,可通过 nvidia-smitorch.cuda.is_available () 等命令简单验证功能是否正常。

二、云存储与数据集处理

  1. 文件存储的优势
    1. 可在不同实例间共享。
    2. 支持多点读写。
    3. 不受实例释放影响。
    4. 存储后端有多冗余副本,数据可靠性高。
  2. 文件存储的不足
    IO 性能一般。
  3. 使用建议
    1. 将重要数据或代码存放于文件存储中,以便所有实例共享,保障数据可靠性。
    2. 在训练时,对于需要高 IO 性能的数据(如训练数据),先将其拷贝到实例本地数据盘,从本地盘读取数据以获得更好的 IO 性能。
  4. 上传训练数据的方法
    使用 scp 工具,命令如scp -rP 35740./DRIVE-SEG-DATA root@cn-north-b.ssh.damodel.com:/root/workspace,其中35740为端口号,cn-north-b.ssh.damodel.com为远程地址,./DRIVE-SEG-DATA是本地数据集路径,/root/workspace是远程实例数据集路径,需根据实际情况替换这些参数。数据下载的命令与之类似。

三、云开发之眼底血管分割案例

3.1 案例背景

  1. 眼底结构:包含黄斑、视网膜和视网膜中央动静脉等。
  2. 眼底图像作用:在眼科医生诊断中重要。
  3. 深度学习对医学影像分割的影响:
    • 卷积神经网络能学习高级特征表示,实现更精确分割。
    • 训练后的模型泛化能力好,对未见过数据预测准确。
    • 支持端到端学习,简化开发流程,提高效率和准确性。
    • 能处理多模态数据,融合信息,提高分割准确性和全面性。

3.2 UNet 在眼底血管分割中的优势

  1. 编码器 - 解码器结构和跳跃连接:有效捕获不同尺度特征信息,效果好。
  2. 推理阶段全卷积网络结构:快速分割新眼底图像,提供实时性支持。

3.3 网络搭建

  1. 结合信息提高准确性
    U-Net 通过编解码器架构,有效结合局部和全局信息,提高分割准确性。
  2. 保留细节边缘信息
    跳跃连接结构有助于保留和恢复图像细节及边缘信息。
  3. 小样本表现好
    在小样本情况下表现优异,能充分利用有限数据有效训练。
  4. 广泛用于医学分割
    广泛应用于医学图像分割任务。
  5. 网络架构如下:
class UNet(nn.Module):
    def __init__(self, n_channels, n_classes, bilinear=True):
        super(UNet, self).__init__()
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.bilinear = bilinear

        self.inc = DoubleConv(n_channels, 64)
        self.down1 = Down(64, 128)
        self.down2 = Down(128, 256)
        self.down3 = Down(256, 512)
        self.down4 = Down(512, 512)
        self.up1 = Up(1024, 256, bilinear)
        self.up2 = Up(512, 128, bilinear)
        self.up3 = Up(256, 64, bilinear)
        self.up4 = Up(128, 64, bilinear)
        self.outc = OutConv(64, n_classes)

    def forward(self, x):
        x1 = self.inc(x)
        x2 = self.down1(x1)
        x3 = self.down2(x2)
        x4 = self.down3(x3)
        x5 = self.down4(x4)
        x = self.up1(x5, x4)
        x = self.up2(x, x3)
        x = self.up3(x, x2)
        x = self.up4(x, x1)
        logits = self.outc(x)
        return logits

3.4 网络训练

基于 PyTorch 的神经网络训练流程可以分为以下步骤(不考虑前期数据准备和模型结构):

  1. 定义损失函数 根据任务类型选择合适的损失函数(loss function),如分类任务常用的交叉熵损失(Cross-Entropy Loss)或回归任务中的均方误差(Mean Square Error)。
  2. 选择优化器 选择合适的优化器(optimizer),如随机梯度下降(SGD)、Adam 或 RMSprop,并设置初始学习率及其它优化参数。
  3. 训练模型 在训练过程中,通过迭代训练数据集来调整模型参数。每个迭代周期称为一个epoch。对于每个 epoch,数据会被分成多个 batch,每个 batch 被输入到模型中进行前向传播、计算损失、反向传播更新梯度,并最终优化模型参数。
  4. 保存模型 当满足需求时,可以将训练好的模型保存下来,以便后续部署和使用。
def train_net(net, device, data_path, epochs=40, batch_size=1, lr=0.00001):
    dataset = Dateset_Loader(data_path)
    per_epoch_num = len(dataset) / batch_size
    train_loader = torch.utils.data.DataLoader(dataset=dataset,
                                               batch_size=batch_size,
                                               shuffle=True)
    optimizer = optim.Adam(net.parameters(),lr=lr,betas=(0.9, 0.999),eps=1e-08, weight_decay=1e-08,amsgrad=False)
    criterion = nn.BCEWithLogitsLoss()
    best_loss = float('inf')
    loss_record = []
    with tqdm(total=epochs*per_epoch_num) as pbar:
        for epoch in range(epochs):
            net.train()
            for image, label in train_loader:
                optimizer.zero_grad()
                image = image.to(device=device, dtype=torch.float32)
                label = label.to(device=device, dtype=torch.float32)
                pred = net(image)
                loss = criterion(pred, label)
                pbar.set_description("Processing Epoch: {} Loss: {}".format(epoch+1, loss))
                if loss < best_loss:
                    best_loss = loss
                    torch.save(net.state_dict(), 'best_model.pth')
                loss.backward()
                optimizer.step()
                pbar.update(1)
            loss_record.append(loss.item())

    plt.figure()
    plt.plot([i+1 for i in range(0, len(loss_record))], loss_record)
    plt.title('Training Loss')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.savefig('/root/shared-storage/results/training_loss.png')

按照这个脚本进行运行,可以看见进度:

训练损失函数如下,可以看出来已经收敛了:

3.5 模型测试

测试逻辑如下所示,主要是计算 IoU 指标

def cal_miou(test_dir="/root/workspace/DRIVE-SEG-DATA/Test_Images",
             pred_dir="/root/workspace/DRIVE-SEG-DATA/results", gt_dir="/root/workspace/DRIVE-SEG-DATA/Test_Labels",
             model_path='best_model_drive.pth'):
    name_classes = ["background", "vein"]
    num_classes = len(name_classes)
    if not os.path.exists(pred_dir):
        os.makedirs(pred_dir)
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    net = UNet(n_channels=1, n_classes=1)
    net.to(device=device)
    net.load_state_dict(torch.load(model_path, map_location=device))
    net.eval()

    img_names = os.listdir(test_dir)
    image_ids = [image_name.split(".")[0] for image_name in img_names]

    time.sleep(1)
    for image_id in tqdm(image_ids):
        image_path = os.path.join(test_dir, image_id + ".png")
        img = cv2.imread(image_path)
        origin_shape = img.shape
        img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
        img = cv2.resize(img, (512, 512))
        img = img.reshape(1, 1, img.shape[0], img.shape[1])
        img_tensor = torch.from_numpy(img)
        img_tensor = img_tensor.to(device=device, dtype=torch.float32)
        pred = net(img_tensor)
        pred = np.array(pred.data.cpu()[0])[0]
        pred[pred >= 0.5] = 255
        pred[pred < 0.5] = 0
        pred = cv2.resize(pred, (origin_shape[1], origin_shape[0]), interpolation=cv2.INTER_NEAREST)
        cv2.imwrite(os.path.join(pred_dir, image_id + ".png"), pred)

    hist, IoUs, PA_Recall, Precision = compute_mIoU_gray(gt_dir, pred_dir, image_ids, num_classes, name_classes)
    miou_out_path = "/root/shared-storage/results/"
    show_results(miou_out_path, hist, IoUs, PA_Recall, Precision, name_classes)

模型保存的时候保存到共享存储路径/root/shared-storage,其他实例可以直接从共享存储中获取训练后的模型:

### 血管图像分割常用的损失函数 对于血管图像分割任务,选择合适的损失函数至关重要。由于医学图像特有的模糊性和不均匀性[^1]以及视网膜血管系统的复杂结构[^2],传统的交叉熵损失可能无法充分捕捉到目标区域的细节特征。 #### 二元交叉熵 (Binary Cross Entropy, BCE) BCE 是最常用的一种损失函数之一,在像素级分类问题上表现良好。该方法适用于标签为0或1的情况: ```python import torch.nn as nn criterion = nn.BCELoss() ``` 但是当正负样本比例失衡时,简单的BCE可能导致模型偏向多数类预测。 #### 加权二元交叉熵 (Weighted Binary Cross Entropy, WBCE) 针对类别不平衡的问题,可以通过调整不同类别的权重来改善性能: \[ L_{WBCE}=-\sum w_i[y_ilog(p_i)+(1-y_i)log(1-p_i)] \] 其中 \(w_i\) 可以为前景赋予更大的权重从而平衡数据分布的影响。 #### Dice Loss Dice系数衡量两个集合之间的相似程度,其对应的损失定义如下: \[ DSC=\frac{2|X∩Y|}{|X|+|Y|},L_{dice}=1-DSC \] 它特别适合于处理边界不清的目标检测场景,并能有效缓解类别不平衡带来的影响。 #### Focal Loss Focal loss是在标准交叉熵基础上引入调制因子以聚焦困难样例的学习过程: \[ FL(y)= -(1-\hat y)^γ log(\hat y), γ≥0 \] 此机制有助于增强网络对稀疏但重要的解剖结构的关注度。 #### 结合策略 实际应用中往往采用组合方式比如将Dice与BCE联合起来作为最终优化目标\(L=α*L_{bce}+(1-α)*L_{dice}\),这样既能保证整体轮廓准确又能突出局部精细结构。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值