Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update-eye-disease-recognition-ResNet #749

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,879 changes: 1,879 additions & 0 deletions examples/Eye-disease-recognition-ResNet/README.md

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions examples/Eye-disease-recognition-ResNet/data_utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
### 数据集介绍

如今近视已经成为困扰人们健康的一项全球性负担,在近视人群中,有超过35%的人患有重度近视。近视会拉长眼睛的光轴,也可能引起视网膜或者络网膜的病变。随着近视度数的不断加深,高度近视有可能引发病理性病变,这将会导致以下几种症状:视网膜或者络网膜发生退化、视盘区域萎缩、漆裂样纹损害、Fuchs斑等。因此,及早发现近视患者眼睛的病变并采取治疗,显得非常重要。

`iChallenge-PM`是百度大脑和中山大学中山眼科中心联合举办的`iChallenge`比赛中,提供的关于病理性近视(Pathologic Myopia,PM)的医疗类数据集,包含1200个受试者的眼底视网膜图片,训练、验证和测试数据集各400张。
其中训练集名称第一个字符表示类别,如下图9 所示。
![图9 train data](https://ai-studio-static-online.cdn.bcebos.com/e6c61f9425d14269a9e24525aba5d32a363d16ed74834d11bf58f4be681814f2)
图9 train data

H:高度近视HighMyopia
N:正常视力Normal
P:病理性近视Pathologic Myopia

**P是病理性近似,正样本,类别为1;H和N不是病理性近似,负样本,类别为0。**

验证集的类别信息储存在PALM-Validation-GT的PM_Label_and_Fovea_Location.xlsx文件中,如下图9 所示。
![图10 validation](https://ai-studio-static-online.cdn.bcebos.com/53a6f31c7d5a4de0a7927bc66901a4d23b1b69bcd39543e99bf42ca11a2203bc)
图10 validation

其中`imgName`列表示图片的名称,`Label`列表示图片对应的标签。

### 数据解压

源数据链接:https://aistudio.baidu.com/aistudio/datasetdetail/19469

在`aistudio`平台通过以下代码进行数据集的解压。

```
if not os.path.isdir("train_data"):
os.mkdir("train_data")
else:
print('Train_data exist')
if not os.path.isdir('PALM-Training400'):
!unzip -oq /home/aistudio/data/data19469/training.zip
!unzip -oq /home/aistudio/data/data19469/validation.zip
!unzip -oq /home/aistudio/data/data19469/valid_gt.zip
!unzip -oq /home/aistudio/PALM-Training400/PALM-Training400.zip -d /home/aistudio/train_data/
else:
print('The data has been decompressed')
```

解压后的文件目录级别为:

1./home/aistudio/trian_data

2./home/aistudio/PALM-Training400

3./home/aistudio/PALM-Validation400

4./home/aistudio/PALM-Validation-GT

训练数据位置:/home/aistudio/train_data/PALM-Training400/

验证集位置:/home/aistudio/PALM-Validation400

验证集标签位置:/home/aistudio/PALM-Validation-GT/PM_Label_and_Fovea_Location.xlsx

可以通过以下代码查看训练集情况:

```
# 查看训练集
! dir /home/aistudio/train_data/PALM-Training400/
```

38 changes: 38 additions & 0 deletions examples/Eye-disease-recognition-ResNet/data_utils/data_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import os
import numpy as np
import cv2

from .transform_img import transform_img

def data_loader(datadir,batch_size=10,mode='train'):
filenames=os.listdir(datadir)
def reader():
if mode =='train':
np.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':
label=0
elif name[0]=='P':
label=1
elif name[0]=='V':
continue
else:
raise('Not excepted file name')
batch_imgs.append(img)
batch_labels.append(label)
if len(batch_imgs)==batch_size:
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:
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import cv2
import numpy as np

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.astype('float32')
img=img/255.0
img=img*2.0-1.0
return img
32 changes: 32 additions & 0 deletions examples/Eye-disease-recognition-ResNet/data_utils/valid_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import cv2
import numpy as np
import openpyxl

from .transform_img import transform_img

def valid_data_loader(datadir, annotiondir):
labeldir = annotiondir

def reader(batch_size=50):
images = []
labels = []
workbook = openpyxl.load_workbook(labeldir, data_only=True)
worksheet = workbook.active
for row in worksheet.iter_rows(min_row=2, max_row=worksheet.max_row):
image = cv2.imread(datadir + '/' + row[1].value)
image = transform_img(image)
images.append(image)
label = float(row[2].value)
labels.append(label)
if len(images) == batch_size:
images_array = np.array(images).astype('float32')
labels_array = np.array(labels).astype('float32').reshape(-1, 1)
yield images_array, labels_array
images = []
labels = []
if len(images) > 0:
images_array = np.array(images).astype('float32')
labels_array = np.array(labels).astype('float32').reshape(-1, 1)
yield images_array, labels_array

return reader
54 changes: 54 additions & 0 deletions examples/Eye-disease-recognition-ResNet/model/BottleneckBlock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import paddle.nn as nn
import paddle

from .ConvBNLayer import ConvBNLayer
# 定义残差块
# 每个残差块会对输入图片做三次卷积,然后跟输入图片进行短接
# 如果残差块中第三次卷积输出特征图的形状和输入不一致,则对输入图片做1x1卷积,将其输出形状调整为一致
class BottleneckBlock(nn.Layer):
def __init__(self,
num_channels,
num_filters,
stride=1,
shortcut=True,
version='O'
):
super(BottleneckBlock,self).__init__()
pathA_dict={}
pathB_dict={}
# default版本
pathA_default=nn.Sequential(
ConvBNLayer(num_channels=num_channels,num_filters=num_filters,filter_size=1,stride=stride,),
ConvBNLayer(num_channels=num_filters,num_filters=num_filters,filter_size=3,),
ConvBNLayer(num_channels=num_filters,num_filters=num_filters*4,filter_size=1,act='None'),
)
pathB_default=nn.Sequential(
ConvBNLayer(num_channels=num_channels,num_filters=num_filters*4,filter_size=1,stride=stride,act='None'),
)
# B版本修改
pathA_tweak=nn.Sequential(
ConvBNLayer(num_channels=num_channels,num_filters=num_filters,filter_size=1,),
ConvBNLayer(num_channels=num_filters,num_filters=num_filters,filter_size=3,stride=stride,),
ConvBNLayer(num_channels=num_filters,num_filters=num_filters*4,filter_size=1,),
)
pathA_dict['B']=pathA_tweak
# D 版本修改
pathB_tweak=nn.Sequential(
nn.AvgPool2D(kernel_size=stride,stride=stride),
ConvBNLayer(num_channels=num_channels,num_filters=num_filters*4,filter_size=1),
)
pathB_dict['D']=pathB_tweak
pathA_dict['D']=pathA_tweak
self.shortcut=shortcut
self.pathA=pathA_dict.get(version,pathA_default)
self.pathB=pathB_dict.get(version,pathB_default)
self._num_channels_out=num_filters*4
def forward(self,inputs):
pathA=self.pathA(inputs)
if self.shortcut:
pathB=inputs
else:
pathB=self.pathB(inputs)
output=paddle.add(x=pathA,y=pathB)
output=nn.functional.relu(output)
return output
31 changes: 31 additions & 0 deletions examples/Eye-disease-recognition-ResNet/model/ConvBNLayer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import paddle.nn as nn

# 定义卷积BN块
class ConvBNLayer(nn.Layer):
def __init__(self,
num_channels,
num_filters,
filter_size,
stride=1,
groups=1,
act='relu'):
super(ConvBNLayer,self).__init__()
self._conv=nn.Conv2D(
in_channels=num_channels,
out_channels=num_filters,
kernel_size=filter_size,
stride=stride,
padding=(filter_size-1)//2,# 确保下采样和尺寸不变
groups=groups,
bias_attr=False,
)
self._batch_norm=nn.BatchNorm2D(num_filters)
self.act=act
def forward(self,inputs):
x=self._conv(inputs)
x=self._batch_norm(x)
if self.act=='leaky':
x=nn.functional.leaky_relu(x=x,negative_slope=0.1)
elif self.act=='relu':
x=nn.functional.relu(x=x)
return x
89 changes: 89 additions & 0 deletions examples/Eye-disease-recognition-ResNet/model/ResNet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import paddle.nn as nn
import paddle

from .ConvBNLayer import ConvBNLayer
from .BottleneckBlock import BottleneckBlock


# 定义ResNet模型
class ResNet(nn.Layer):
def __init__(self, layers=50, class_dim=10, version='O'):
"""
layers,网络层数,可以可选项:50,101,152
class_dim,分类标签的类别数
"""
super(ResNet, self).__init__()
self.version = version
self.layers = layers
self.max_accuracy = 0.0

supported_layers = [50, 101, 152]
assert layers in supported_layers, \
"supported layers are {} but input layer is {}".format(supported_layers, layers)
# ResNet50包含的stage1-4模块分别包括3,4,6,3个残差块
if layers == 50:
depth = [3, 4, 6, 3]
# ResNet101包含的stage1-4模块分别包括3,4,23,3个残差块
if layers == 101:
depth = [3, 4, 23, 3]
# ResNet152包含的stage1-4分别包括3,8,36,3个残差块
if layers == 152:
depth = [3, 8, 36, 3]
# stage1-4所使用残差块的输出通道数
num_filters = [64, 128, 256, 512]

# input stem模块,default版本:64个7x7的卷积加上一个3x3最大化池化层,步长均为2
input_stem_dict = {}
input_stem_default = nn.Sequential(
ConvBNLayer(num_channels=3, num_filters=64, filter_size=7, stride=2, ),
nn.MaxPool2D(kernel_size=3, stride=2, padding=1, ),
)
# C版本修改
input_stem_tweak = nn.Sequential(
ConvBNLayer(num_channels=3, num_filters=64, filter_size=3, stride=2, ),
ConvBNLayer(num_channels=64, num_filters=64, filter_size=3, ),
ConvBNLayer(num_channels=64, num_filters=64, filter_size=3, ),
nn.MaxPool2D(kernel_size=3, stride=2, padding=1, ),
)
input_stem_dict['C'] = input_stem_tweak

self.input_stem = input_stem_dict.get(version, input_stem_default)

# stage1-4模块,使用各个残差块进行卷积操作
self.bottleneck_block_list = []
num_channels = 64
for block in range(len(depth)):
shortcut = False
for i in range(depth[block]):
bottleneck_block = self.add_sublayer(
'bb_%d_%d' % (block, i),
BottleneckBlock(
num_channels=num_channels,
num_filters=num_filters[block],
stride=2 if i == 0 and block != 0 else 1,
shortcut=shortcut,
version=version))
num_channels = bottleneck_block._num_channels_out
self.bottleneck_block_list.append(bottleneck_block)
shortcut = True

# 在stage4的输出特征图上使用全局池化
self.pool2d_avg = nn.AdaptiveAvgPool2D(output_size=1)

# stdv用来作为全连接层随机初始化参数的方差
import math
stdv = 1.0 / math.sqrt(2048 * 1.0)
# 创建全连接层,输出大小为类别数目,经过残差网络的卷积核全局池化后,
# 卷积特征的维度是[B,2048,1,1],故最后一层全连接层的输入维度是2048
self.out = nn.Linear(in_features=2048, out_features=class_dim,
weight_attr=paddle.ParamAttr(
initializer=paddle.nn.initializer.Uniform(-stdv, stdv)))

def forward(self, inputs):
x = self.input_stem(inputs)
for bottleneck_block in self.bottleneck_block_list:
x = bottleneck_block(x)
x = self.pool2d_avg(x)
x = paddle.reshape(x, [x.shape[0], -1])
x = self.out(x)
return x
9 changes: 9 additions & 0 deletions examples/Eye-disease-recognition-ResNet/model/save.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import paddle

# 构建模型保存函数
def save(accuracy,model):
print('model save success !')
if model==None:
return
model.max_accuracy=accuracy # 覆盖当前的最大正确率
paddle.save(model.state_dict(),f'./model/resnet{model.layers}_v{model.version}_PALM.pdparams') # 保存模型
Loading