研习社2020肺炎X光病灶数据集和识别

摘要:

合集:AI案例-CV-医疗
赛题:AI研习社肺炎X光病灶识别大赛
主办方:AI研习社
主页:https://god.yanxishe.com/18
数据集:研习社2020肺炎X光病灶数据集
AI问题:图像分类
数据集价值:肺炎检测
解决方案:PyTorch框架、ResNet18模型

一、赛题描述

肺炎是由多种病源菌引起的肺充血,水肿,炎性细胞浸润和渗出性病变,是生活中较为常见的一种疾病。早期感冒咳嗽如果护理不当,既有可能转化为肺炎。临床常见且可发生于任何人群。临床表现主要有发热,咳嗽,咳痰,呼吸困难等。

X线检查是肺部疾病诊断的基本方法。一般在X光片上显示为双肺纹理增多增粗紊乱。并见斑片状密度增高影,边缘模糊。如何用胸部X光检测是否发生肺炎?我们搜集来了专业医生的解答:早期肺纹理增多、变粗是各类肺炎的特点。在支气管肺炎发病不久可以出现斑点状的小片阴影,以两肺下部和纵隔边缘多为常见,逐渐融合成较大的斑片状阴影,常伴发肺不张或者是局部的肺气肿。大叶性肺炎的病变涉及一叶或者是阶段式发生实变阴影,出现肺部并发症时出现相应的病变,比如,肺脓肿可以看见气胸压缩所在侧的肺叶,脓胸则可以看见胸腔积液,肋隔角变钝,积液多的时候多呈现一片浓密的阴影。

任务

训练模型正确识别肺炎X光病灶数量。

结果文件上传

提交CSV文件。

第一个字段位:测试集图片ID(注意ID即文件名是从0开始的)

第二个字段:病灶数量(0、1、2、3、4)

建议使用UTF-8编码,共计6671个结果,数量不足可能导致无法评分。

二、数据集内容

基本信息

数据集:训练集2,0013张,测试集 6,671张。

数据结构

工作路径:xray_dataset

训练图片:./xray_dataset/train/{0}.jpeg

测试图片:./xray_dataset/test/{0}.jpeg

train_bboxes.csv

训练图片文件中病灶区域。若一个图片中包括多个病灶区域,则该图片对应多条记录。

数据样例:

filenamexywidthheight
6287673188220
8651476250344
18220414142120
24327138108157
27273550139146
28761512146153
29326287191292
30645296208545
30286197159447

train.csv

训练图片文件中病灶区域个数。

数据样例:

00
10
20
30
40
50
61
70
81
90
100
110
120
130
140
150
160
170
181
190
200
210
220
230
241
250
260
271
281
291
302

图片样例:

数据集使用许可协议

GPL

三、解决方案样例

解决方案

利用PyTorch框架,使用ResNet18进行TTA(测试时增强)来进行计算机视觉分类。ResNet18 简介:

  • 提出时间:2015年(何恺明团队在CVPR最佳论文中提出)
  • 核心创新:残差学习框架(Residual Learning)
  • 网络深度:18层(包含卷积层、全连接层和跳跃连接)
  • 定位:轻量级ResNet变体,适合计算资源有限的场景

在深度学习中,残差(Residual) 是指“预测目标与当前模型输出之间的差异”。这一概念在残差学习框架(如ResNet)中至关重要,其核心思想是让网络直接学习残差,而非直接学习目标函数。

残差学习的直观理解:想象你在教一个学生调整预测结果:

  • 传统网络:直接要求学生给出最终答案(例如“预测值=100”),学习难度大。
  • 残差网络:让学生基于当前答案(例如“当前值=90”)只学习需要调整的部分(残差=10),最终输出为 90+10=100。这种方式更易优化。

导入开发包

import os, sys, glob, argparse
import pandas as pd
import numpy as np
from tqdm import tqdm

import time, datetime
import pdb, traceback

import cv2
from PIL import Image

from sklearn.model_selection import train_test_split, StratifiedKFold, KFold

from efficientnet_pytorch import EfficientNet
import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True

import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data.dataset import Dataset

处理流程

1、读取标注数据

   # 读取标注数据(正样本)
  train_bboxes = pd.read_csv('./xray_dataset/train_bboxes.csv')

  # 获取所有有标注的图像文件名(正样本)
  annotated_images = set(train_bboxes['filename'].unique())

  # 获取全部图像文件列表
  all_images = {Path(x).stem for x in glob.glob('./xray_dataset/train/*.jpg')} # 更安全的文件名提取

  # 计算负样本:总图像 - 有标注的图像
  negative_images = list(all_images - annotated_images)

  # 创建正样本DataFrame(保留原始标注信息)并添加标签列
  train_pos = train_bboxes.copy()
  train_pos['label'] = 1 # 根据实际情况调整标签值???

  # 创建负样本DataFrame(补充标记为0)
  train_neg = pd.DataFrame({
      'filename': negative_images,
      'x': [0]*len(negative_images),     # 补充占位值
      'y': [0]*len(negative_images),
      'width': [0]*len(negative_images),
      'height': [0]*len(negative_images),
      'label': [0]*len(negative_images) # 新增标签字段(0表示无病灶)
  }, dtype=int) # 强制指定为整数

  # 合并正负样本并打乱顺序
  train_label = pd.concat([train_pos, train_neg], axis=0).sample(frac=1).reset_index(drop=True)
  train_label['label'].astype(int) # 二次确保类型
  assert train_label['label'].between(0, 4).all(), "标签值超出0-4范围"

  # 生成完整文件路径
  train_label['filepath'] = './xray_dataset/train/' + train_label['filename'].astype(str) + '.jpg'
  print(train_label.head())

输出:train_label

   filename      x      y  width  height  label                        filepath
0     18552 280.0 379.0 270.0   369.0     1 ./xray_dataset/train/18552.jpg
1     13632   0.0   0.0   0.0     0.0     0 ./xray_dataset/train/13632.jpg
2     14321   0.0   0.0   0.0     0.0     0 ./xray_dataset/train/14321.jpg
3     9988   0.0   0.0   0.0     0.0     0   ./xray_dataset/train/9988.jpg
4     17331   0.0   0.0   0.0     0.0     0 ./xray_dataset/train/17331.jpg

2、训练函数

def train(train_loader, model, criterion, optimizer, epoch):
  batch_time = AverageMeter('Time', ':6.3f')
  # data_time = AverageMeter('Data', ':6.3f')
  losses = AverageMeter('Loss', ':.4e')
  top1 = AverageMeter('Acc@1', ':6.2f')
  # top5 = AverageMeter('Acc@5', ':6.2f')
  progress = ProgressMeter(len(train_loader), batch_time, losses, top1)

  # switch to train mode
  model.train()

  end = time.time()
  for i, (input, target) in enumerate(train_loader):
      input = input.cuda(non_blocking=True)
      target = target.cuda(non_blocking=True)

      # compute output
      output = model(input)
      loss = criterion(output, target)

      # measure accuracy and record loss
      acc1, acc5 = accuracy(output, target, topk=(1, 2))
      losses.update(loss.item(), input.size(0))
      top1.update(acc1[0], input.size(0))
      # top5.update(acc5[0], input.size(0))

      # compute gradient and do SGD step
      optimizer.zero_grad()
      loss.backward()
      optimizer.step()

      # measure elapsed time
      batch_time.update(time.time() - end)
      end = time.time()

      if i % 100 == 0:
          progress.pr2int(i)

3、执行训练

skf = KFold(n_splits=10, random_state=233, shuffle=True)
for flod_idx, (train_idx, val_idx) in enumerate(skf.split(train_label['filename'].values, train_label['filename'].values)):
  # print(flod_idx, train_idx, val_idx)
  if flod_idx == 0:
      continue
   
  train_loader = torch.utils.data.DataLoader(
      QRDataset(train_label.iloc[train_idx],
              transforms.Compose([
                          # transforms.RandomGrayscale(),
                          transforms.Resize((512, 512)),
                          # transforms.RandomAffine(5),
                          # transforms.ColorJitter(hue=.05, saturation=.05),
                          # transforms.RandomCrop((88, 88)),
                          transforms.RandomHorizontalFlip(),
                          transforms.RandomVerticalFlip(),
                          transforms.ToTensor(),
                          transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
          ])
      ), batch_size=10, shuffle=True, num_workers=20, pin_memory=True
  )
   
  val_loader = torch.utils.data.DataLoader(
      QRDataset(train_label.iloc[val_idx],
              transforms.Compose([
                          transforms.Resize((512, 512)),
                          # transforms.Resize((124, 124)),
                          # transforms.RandomCrop((88, 88)),
                          transforms.ToTensor(),
                          transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
          ])
      ), batch_size=10, shuffle=False, num_workers=10, pin_memory=True
  )
       
   
  model = VisitNet().cuda()
  # model = nn.DataParallel(model).cuda()
  criterion = nn.CrossEntropyLoss().cuda()
  optimizer = torch.optim.SGD(model.parameters(), 0.01)
  scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=4, gamma=0.85)
  best_acc = 0.0
  for epoch in range(10):
      scheduler.step()
      print('Epoch: ', epoch)

      train(train_loader, model, criterion, optimizer, epoch)
      val_acc = validate(val_loader, model, criterion)
       
      if val_acc.avg.item() > best_acc:
          best_acc = val_acc.avg.item()
          torch.save(model.state_dict(), './resnet18_fold{0}.pt'.format(flod_idx))

执行结果

python3 1_train.py
python3 2_predict.py

训练过程:

Epoch:  0
[   0/2447]     Time 149.766 (149.766) Loss 2.1923e+00 (2.1923e+00)   Acc@1   0.00 ( 0.00)
[ 100/2447]     Time 1.029 ( 2.339)   Loss 4.9953e-01 (6.6956e-01)   Acc@1 70.00 ( 69.90)
[ 200/2447]     Time 1.148 ( 1.711)   Loss 4.7603e-01 (6.2537e-01)   Acc@1 80.00 ( 70.30)
[ 300/2447]     Time 1.474 ( 1.598)   Loss 5.1365e-01 (6.2905e-01)   Acc@1 80.00 ( 70.20)
[ 400/2447]     Time 1.892 ( 1.615)   Loss 2.7140e-01 (6.0420e-01)   Acc@1 90.00 ( 71.12)
[ 500/2447]     Time 2.084 ( 1.664)   Loss 5.1338e-01 (5.9641e-01)   Acc@1 80.00 ( 71.20)
[ 600/2447]     Time 2.156 ( 1.759)   Loss 5.7790e-01 (5.9011e-01)   Acc@1 70.00 ( 71.35)
[ 700/2447]     Time 2.632 ( 1.962)   Loss 6.2282e-01 (5.7978e-01)   Acc@1 70.00 ( 71.81)
[ 800/2447]     Time 2.633 ( 2.061)   Loss 7.2958e-01 (5.7660e-01)   Acc@1 80.00 ( 71.86)
[ 900/2447]     Time 2.427 ( 2.121)   Loss 4.9049e-01 (5.7273e-01)   Acc@1 60.00 ( 71.93)
[1000/2447]     Time 2.263 ( 2.143)   Loss 1.1263e+00 (5.6758e-01)   Acc@1 40.00 ( 72.03)
[1100/2447]     Time 2.107 ( 2.160)   Loss 2.4103e-01 (5.6858e-01)   Acc@1 100.00 ( 71.95)
[1200/2447]     Time 2.216 ( 2.172)   Loss 2.7349e-01 (5.6507e-01)   Acc@1 100.00 ( 72.03)
。。。

源码开源协议

GPL-3.0 license

四、获取案例套装

文件包大小:1.6 GB

获取:医疗行业视觉案例套装

发表评论