《专业创新实践Ⅱ》大作业
项目名称 LeNet在眼疾识别数据集iChallenge-PM上的应用
学 院 信息与通信工程学院
年级专业 20级智能科学与技术
姓 名 孙成
学 号 20203101694
完成时间 2021-7-7
指导教师 邵春艳老师
开 课 时 间 2020 至 2021 学年第 二 学期
一、引言
LeNet在眼疾识别数据集iChallenge-PM上的应用
iChallenge-PM是百度大脑和中山大学中山眼科中心联合举办的iChallenge比赛中,提供的关于病理性近视(Pathologic Myopia,PM)的医疗类数据集,包含1200个受试者的眼底视网膜图片,训练、验证和测试数据集各400张。
说明:
如今近视已经成为困扰人们健康的一项全球性负担,在近视人群中,有超过35%的人患有重度近视。近视会拉长眼睛的光轴,也可能引起视网膜或者络网膜的病变。随着近视度数的不断加深,高度近视有可能引发病理性病变,这将会导致以下几种症状:视网膜或者络网膜发生退化、视盘区域萎缩、漆裂样纹损害、Fuchs斑等。因此,及早发现近视患者眼睛的病变并采取治疗,显得非常重要。
二、方法
(1)数学模型
LeNet是最早的卷积神经网络之一[1]。1998年,Yann LeCun第一次将LeNet卷积神经网络应用到图像分类上,在手写数字识别任务中取得了巨大成功。LeNet通过连续使用卷积和池化层的组合提取图像特征,其架构如 图1 所示,这里展示的是用于MNIST手写体数字识别任务中的LeNet-5模型:
第一模块:包含5×5的6通道卷积和2×2的池化。卷积提取图像中包含的特征模式(激活函数使用Sigmoid),图像尺寸从28减小到24。经过池化层可以降低输出特征图对空间位置的敏感性,图像尺寸减到12。
第二模块:和第一模块尺寸相同,通道数由6增加为16。卷积操作使图像尺寸减小到8,经过池化后变成4。
第三模块:包含4×4的120通道卷积。卷积之后的图像尺寸减小到1,但是通道数增加为120。将经过第3次卷积提取到的特征图输入到全连接层。第一个全连接层的输出神经元的个数是64,第二个全连接层的输出神经元个数是分类标签的类别数,对于手写数字识别的类别数是10。然后使用Softmax激活函数即可计算出每个类别的预测概率。
(2)方法工具
1、运行环境:
百度人工智能平台Paddle
2、数据集:
眼疾识别数据集iChallenge-PM中既有病理性近视患者的眼底图片,也有非病理性近视患者的图片,命名规则如下:
病理性近视(PM):文件名以P开头
非病理性近视(non-PM):
高度近视(high myopia):文件名以H开头
正常眼睛(normal):文件名以N开头
我们将病理性患者的图片作为正样本,标签为1; 非病理性患者的图片作为负样本,标签为0。从数据集中选取两张图片,通过LeNet提取特征,构建分类器,对正负样本进行分类,并将图片显示出来。
实验过程
数据集准备
training.zip:包含训练中的图片和标签
validation.zip:包含验证集的图片
valid_gt.zip:包含验证集的标签
其中需要注意,需要将“/Users/suncheng/PycharmProjects/测试/MNIST数据集/眼疾识别/PALM-Validation-GT”目录下“PM_Label_and_Fovea_Location.xlsx”文件转存成.csv格式,本节代码示例中已经提前转成文件labels.csv。
(2)查看数据集图片
iChallenge-PM中既有病理性近视患者的眼底图片,也有非病理性近视患者的图片,命名规则如下:
病理性近视(PM):文件名以P开头
非病理性近视(non-PM):
高度近视(high myopia):文件名以H开头
正常眼睛(normal):文件名以N开头
我们将病理性患者的图片作为正样本,标签为1; 非病理性患者的图片作为负样本,标签为0。从数据集中选取两张图片,通过LeNet提取特征,构建分类器,对正负样本进行分类,并将图片显示出来。
代码如下:
- import os
- import numpy as np
- import matplotlib.pyplot as plt
- %matplotlib inline
- from PIL import Image
- DATADIR = '/home/aistudio/work/palm/PALM-Training400/PALM-Training400'
- # 文件名以N开头的是正常眼底图片,以P开头的是病变眼底图片
- file1 = 'N0012.jpg'
- file2 = 'P0095.jpg'
- # 读取图片
- img1 = Image.open(os.path.join(DATADIR, file1))
- img1 = np.array(img1)
- img2 = Image.open(os.path.join(DATADIR, file2))
- img2 = np.array(img2)
- # 画出读取的图片
- plt.figure(figsize=(16, 8))
- f = plt.subplot(121)
- f.set_title('Normal', fontsize=20)
- plt.imshow(img1)
- f = plt.subplot(122)
- f.set_title('PM', fontsize=20)
- plt.imshow(img2)
- plt.show()
- # 查看图片形状
- img1.shape, img2.shape
- ((2056, 2124, 3), (2056, 2124, 3))
- 定义数据读取器
- 使用OpenCV从磁盘读入图片,将每张图缩放到224×224224×224大小,并且将像素值调整到[−1,1][−1,1]之间,代码如下所示:
- import cv2
- import random
- import numpy as np
- import os
- # 对读入的图像数据进行预处理
- def transform_img(img):
- # 将图片尺寸缩放道 224x224
- img = cv2.resize(img, (224, 224))
- # 读入的图像数据格式是[H, W, C]
- # 使用转置操作将其变成[C, H, W]
- img = np.transpose(img, (2,0,1))
- img = img.astype('float32')
- # 将数据范围调整到[-1.0, 1.0]之间
- img = img / 255.
- img = img * 2.0 - 1.0
- return img
- # 定义训练集数据读取器
- def data_loader(datadir, batch_size=10, mode = 'train'):
- # 将datadir目录下的文件列出来,每条文件都要读入
- filenames = os.listdir(datadir)
- def reader():
- if mode == 'train':
- # 训练时随机打乱数据顺序
- random.shuffle(filenames)
- batch_imgs = []
- batch_labels = []
- for name in filenames:
- filepath = os.path.join(datadir, name)
- img = cv2.imread(filepath)
- img = transform_img(img)
- if name[0] == 'H' or name[0] == 'N':
- # H开头的文件名表示高度近似,N开头的文件名表示正常视力
- # 高度近视和正常视力的样本,都不是病理性的,属于负样本,标签为0
- label = 0
- elif name[0] == 'P':
- # P开头的是病理性近视,属于正样本,标签为1
- label = 1
- else:
- raise('Not excepted file name')
- # 每读取一个样本的数据,就将其放入数据列表中
- batch_imgs.append(img)
- batch_labels.append(label)
- if len(batch_imgs) == batch_size:
- # 当数据列表的长度等于batch_size的时候,
- # 把这些数据当作一个mini-batch,并作为数据生成器的一个输出
- imgs_array = np.array(batch_imgs).astype('float32')
- labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)
- yield imgs_array, labels_array
- batch_imgs = []
- batch_labels = []
- if len(batch_imgs) > 0:
- # 剩余样本数目不足一个batch_size的数据,一起打包成一个mini-batch
- imgs_array = np.array(batch_imgs).astype('float32')
- labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)
- yield imgs_array, labels_array
- return reader
- # 定义验证集数据读取器
- def valid_data_loader(datadir, csvfile, batch_size=10, mode='valid'):
- # 训练集读取时通过文件名来确定样本标签,验证集则通过csvfile来读取每个图片对应的标签
- # 请查看解压后的验证集标签数据,观察csvfile文件里面所包含的内容
- # csvfile文件所包含的内容格式如下,每一行代表一个样本,
- # 其中第一列是图片id,第二列是文件名,第三列是图片标签,
- # 第四列和第五列是Fovea的坐标,与分类任务无关
- # ID,imgName,Label,Fovea_X,Fovea_Y
- # 1,V0001.jpg,0,1157.74,1019.87
- # 2,V0002.jpg,1,1285.82,1080.47
- # 打开包含验证集标签的csvfile,并读入其中的内容
- filelists = open(csvfile).readlines()
- def reader():
- batch_imgs = []
- batch_labels = []
- for line in filelists[1:]:
- line = line.strip().split(',')
- name = line[1]
- label = int(line[2])
- # 根据图片文件名加载图片,并对图像数据作预处理
- filepath = os.path.join(datadir, name)
- img = cv2.imread(filepath)
- img = transform_img(img)
- # 每读取一个样本的数据,就将其放入数据列表中
- batch_imgs.append(img)
- batch_labels.append(label)
- if len(batch_imgs) == batch_size:
- # 当数据列表的长度等于batch_size的时候,
- # 把这些数据当作一个mini-batch,并作为数据生成器的一个输出
- imgs_array = np.array(batch_imgs).astype('float32')
- labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)
- yield imgs_array, labels_array
- batch_imgs = []
- batch_labels = []
- if len(batch_imgs) > 0:
- # 剩余样本数目不足一个batch_size的数据,一起打包成一个mini-batch
- imgs_array = np.array(batch_imgs).astype('float32')
- labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)
- yield imgs_array, labels_array
- return reader
- # 查看数据形状
- DATADIR = '/home/aistudio/work/palm/PALM-Training400/PALM-Training400'
- train_loader = data_loader(DATADIR,
- batch_size=10, mode='train')
- data_reader = train_loader()
- data = next(data_reader)
- data[0].shape, data[1].shape
- eval_loader = data_loader(DATADIR,
- batch_size=10, mode='eval')
- data_reader = eval_loader()
- data = next(data_reader)
- data[0].shape, data[1].shape