keras使用迁移学习实现二分类问题

问题描述

要解决的是一个医学图像的二分类问题,有AKSK两种病症,根据一定量数据,进行训练,对图像进行预测。

给定图片数据的格式:

解决思路

整体上采用迁移学习来训练神经网络,使用InceptionV3结构,框架采用keras.

具体思路:

  1. 读取图片数据,保存成.npy格式,方便后续加载
  2. 标签采用one-hot形式,由于标签隐藏在文件夹命名中,所以需要自行添加标签,并保存到.npy文件中,方便后续加载
  3. 将数据分为训练集、验证集、测试集
  4. 使用keras建立InceptionV3基本模型,不包括顶层,使用预训练权重,在基本模型的基础上自定义几层神经网络,得到最后的模型,对模型进行训练
  5. 优化模型,调整超参数,提高准确率
  6. 在测试集上对模型进行评估,使用精确率、召回率
  7. 对单张图片进行预测,并输出每种类别的概率

代码结构

具体代码

1. path.py ==> 定义项目根路径

1
2
3
4
5
6
7
8
9
10
import os
import inspect

def mkdir_if_not_exist(dir_list):
for directory in dir_list:
if not os.path.exists(directory):
os.makedirs(directory)

curr_filename = inspect.getfile(inspect.currentframe())
root_dir = os.path.dirname(os.path.abspath(curr_filename))

2. load_datasets.py ==> 读取原始数据,生成.npy文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import os

import matplotlib.pyplot as plt
import numpy as np
from skimage import io
from skimage import transform
from tqdm import tqdm

from paths import root_dir, mkdir_if_not_exist
from sklearn.utils import shuffle

origin_data_dir = os.path.join(root_dir, 'origin_data')
data_dir = os.path.join(root_dir, 'data')
mkdir_if_not_exist(dir_list=[data_dir])


def process_datasets():
images = []
labels = []

for AK_or_SK_dir in tqdm(os.listdir(origin_data_dir)):
# AK ==> [1, 0] SK ==> [0, 1]
if 'AK' in AK_or_SK_dir:
label = [1, 0]
elif 'SK' in AK_or_SK_dir:
label = [0, 1]
else:
print("AK_or_SK_dir error")

for person_dir in tqdm(os.listdir(os.path.join(origin_data_dir, AK_or_SK_dir))):
for fname in os.listdir(os.path.join(origin_data_dir, AK_or_SK_dir, person_dir)):
img_fname = os.path.join(origin_data_dir, AK_or_SK_dir,
person_dir, fname)
image = io.imread(img_fname)
image = transform.resize(image, (224, 224),
order=1, mode='constant',
cval=0, clip=True,
preserve_range=True,
anti_aliasing=True)
image = image.astype(np.uint8)

images.append(image)
labels.append(label)

images = np.stack(images).astype(np.uint8)
labels = np.stack(labels, axis=0)

return images, labels


def load_datasets():
images_npy_filename = os.path.join(data_dir, 'images_data.npy')
labels_npy_filename = os.path.join(data_dir, 'labels.npy')

if os.path.exists(images_npy_filename) and os.path.exists(labels_npy_filename):
images = np.load(images_npy_filename)
labels = np.load(labels_npy_filename)
else:
images, labels = process_datasets()
# 将数据打乱后保存
images, labels = shuffle(images, labels)
np.save(images_npy_filename, images)
np.save(labels_npy_filename, labels)

return images, labels


if __name__ == '__main__':
X, y = load_datasets()
print(X.shape,y.shape)
# plt.imshow(X[5])
# plt.show()
y = np.argmax(y, axis=1)
print(y[:20])
count_SK = np.count_nonzero(y)
print("SK图片数量:", count_SK)

3. load_train_test_data.py ==> 划分训练集、验证集、测试集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import train_test_split
import numpy as np
from load_datesets import load_datasets

X, y = load_datasets()
X_test = X[650:]
y_test = y[650:]
X = X[0:650]
y = y[0:650]


def load_test_data():
return X_test, y_test


def load_train_data(test_split=None, use_cross_validation=None, k_fold=None):
if use_cross_validation:
data = []
sfolder = StratifiedKFold(n_splits=k_fold, random_state=1)
y_label = np.argmax(y, axis=1)
for train_index, valid_index in sfolder.split(X, y_label):
X_train, X_valid, y_train, y_valid = X[train_index], X[valid_index], y[train_index], y[valid_index]
data_tmp = (X_train, X_valid, y_train, y_valid)
data.append(data_tmp)
return data
else:
X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=test_split, random_state=1)

return X_train, X_valid, y_train, y_valid

4. train.py ==> 建立网络模型,进行训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import os

from keras import regularizers
from keras.applications.inception_v3 import InceptionV3
from keras.callbacks import ReduceLROnPlateau, ModelCheckpoint, TensorBoard, EarlyStopping
from keras.layers import Dense
from keras.layers import GlobalAveragePooling2D, Dropout
from keras.losses import categorical_crossentropy
from keras.metrics import categorical_accuracy
from keras.models import Model
from keras.optimizers import Adam
from keras.preprocessing.image import ImageDataGenerator
import tensorflow as tf
from keras.backend.tensorflow_backend import set_session

from load_train_test_data import load_train_data
from paths import root_dir


# 超参数
test_split = 0.2 # 验证机划分比例
num_classes = 2
lr = 1e-4
epochs = 30
dropout_rate = 0.5
kernel_regularizer = regularizers.l1(1e-4) # 正则化
batch_size = 64
use_data_aug = True # 是否使用数据增强
use_cross_validation = False # 是否使用交叉验证
k_fold = 5 # k折交叉验证的k


def build_model():
base_model = InceptionV3(weights='imagenet', include_top=False)
img_input = base_model.output

outputs = GlobalAveragePooling2D(name='avg_pool_my')(img_input)

if dropout_rate > 0.:
outputs = Dropout(rate=dropout_rate)(outputs)

outputs = Dense(256, activation='elu', name='fc1',
kernel_regularizer=kernel_regularizer)(outputs)
outputs = Dropout(rate=dropout_rate)(outputs)
outputs = Dense(128, activation='elu', name='fc2',
kernel_regularizer=kernel_regularizer)(outputs)
outputs = Dropout(rate=dropout_rate)(outputs)
outputs = Dense(num_classes, activation='softmax', name='predictions',
kernel_regularizer=kernel_regularizer)(outputs)

model = Model(inputs=base_model.input, outputs=outputs)

model.summary()

model.compile(optimizer=Adam(lr=lr), loss=categorical_crossentropy,
metrics=[categorical_accuracy, ])

return model


def train_model(model, X_train, y_train, X_valid, y_valid):
# 模型保存路径
model_path = os.path.join(root_dir, 'model_data', 'model_no_cross.h5')

# 定义回调函数
callbacks = [
# 当标准评估停止提升时,降低学习速率
ReduceLROnPlateau(monitor='val_loss',
factor=0.25,
patience=2,
verbose=1,
mode='auto',
min_lr=1e-7),
# 在每个训练期之后保存模型,最后保存的是最佳模型
ModelCheckpoint(model_path,
monitor='val_loss',
save_best_only=True,
verbose=True),
# tensorboard 可视化
TensorBoard(log_dir='./logs',
histogram_freq=0,
write_graph=False,
write_grads=True,
write_images=True,
update_freq='epoch')
]

if use_data_aug:
datagen = ImageDataGenerator(rotation_range=180,
horizontal_flip=True,
vertical_flip=True,
width_shift_range=0.1,
height_shift_range=0.1,
#featurewise_center=True, # 均值为0
#featurewise_std_normalization=True # 标准化
)

model.fit_generator(generator=datagen.flow(X_train, y_train, batch_size=batch_size),
steps_per_epoch=X_train.shape[0] // batch_size * 2,
epochs=epochs,
initial_epoch=0,
verbose=1,
validation_data=(X_valid, y_valid),
callbacks=callbacks)
else:
model.fit(x=X_train,
y=y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(X_valid, y_valid),
callbacks=callbacks)


def set_gpu():
# 指定使用的GPU
os.environ["CUDA_VISIBLE_DEVICES"] = "9"

## keras 默认占满gpu所有内存,所以要手动设定内存使用情况
config = tf.ConfigProto()
'''
# keras 设置gpu内存使用比例
config.gpu_options.per_process_gpu_memory_fraction = 0.5
'''
# keras 设置gpu内存按需分配
config.gpu_options.allow_growth = True
set_session(tf.Session(config=config))


if __name__ == '__main__':
# 指定GPU
set_gpu()

# 构建模型
model = build_model()

if use_cross_validation:
data = load_train_data(use_cross_validation=use_cross_validation, k_fold=k_fold)
for i in range(k_fold):
# 加载数据
X_train, X_valid, y_train, y_valid = data[i]

# 训练模型
train_model(model, X_train, y_train, X_valid, y_valid)
else:
# 加载数据
X_train, X_valid, y_train, y_valid = load_train_data(test_split=test_split)

# 训练模型
train_model(model, X_train, y_train, X_valid, y_valid)

5. eval.py ==> 在测试集上对模型进行评估

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import os
from keras.models import load_model
from paths import root_dir
from sklearn.metrics import accuracy_score,precision_score,recall_score,f1_score
import numpy as np
from load_train_test_data import load_test_data

# 指定使用的GPU
os.environ["CUDA_VISIBLE_DEVICES"] = "9"


if __name__ == '__main__':
# 加载模型
model_path = os.path.join(root_dir, 'model_data', 'model_no_cross.h5')
model = load_model(model_path)

# 评估数据
X_test, y_test = load_test_data()

# y预测
y_pred = model.predict(X_test)
y_pred = np.argmax(y_pred, axis=1)
y_test = np.argmax(y_test, axis=1)
print(y_test)
print(y_pred)

# 准确率,精确率,召回率,F1
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)

print("accuracy_score = %.2f" % accuracy)
print("precision_score = %.2f" % precision)
print("recall_score = %.2f" % recall)
print("f1_score = %.2f" % f1)

6. predict.py ==> 对单张图片进行预测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import os

import cv2 as cv
import numpy as np
from keras.models import load_model
from keras.preprocessing import image

from paths import root_dir

# 指定使用的GPU
os.environ["CUDA_VISIBLE_DEVICES"] = "9"

clsss_name = {0: "AK", 1: "SK"}

if __name__ == '__main__':
# 加载模型
model_path = os.path.join(root_dir, 'model_data', 'model_no_cross.h5')
model = load_model(model_path)

for AK_or_SK_dir in os.listdir(os.path.join(root_dir, "images")):
for fname in os.listdir(os.path.join(root_dir, "images", AK_or_SK_dir)):
# 读取图片
img_path = os.path.join(root_dir, "images", AK_or_SK_dir, fname)
img = image.load_img(img_path, target_size=(224, 224))
img = image.img_to_array(img)

# 扩充维度
img = np.expand_dims(img, axis=0)

# 预测
pred = model.predict(img)

# 打印图片类别
y_pred = np.argmax(pred, axis=1)
img_name = clsss_name[y_pred[0]]
print(fname, "的预测概率是:")
print(pred, " ==> ", img_name)

运行结果

1. 训练结果

2. 评估结果

3. 预测结果

知识点总结

  1. 如何加载实际数据,如何保存成npy文件,如何打乱数据,如何划分数据,如何进行交叉验证
  2. 如何使用keras进行迁移学习
  3. keras中数据增强、回调函数的使用,回调函数涉及:学习速率调整、保存最好模型、tensorboard可视化
  4. 如何使用sklearn计算准确率,精确率,召回率,F1_score
  5. 如何对单张图片进行预测,并打印分类概率
  6. 如何指定特定GPU训练,如何指定使用GPU的内存情况

github地址

github