├── README.md
├── results
├── Figure_acc.png
└── Figure_loss.png
├── train.py
└── validation.py
/README.md:
--------------------------------------------------------------------------------
1 | # fundus_binary_classification
2 | ## 眼底图二分类——inception v3 迁移学习
3 | 参考[https://blog.csdn.net/White_Idiot/article/details/78816850](https://blog.csdn.net/White_Idiot/article/details/78816850)
4 | tensorflow 实现谷歌[INCEPTION-V3](https://storage.googleapis.com/download.tensorflow.org/models/inception_dec_2015.zip)迁移学习
5 |
6 | LEARNING_RATE = 0.01
7 | STEPS = 10000
8 | BATCH = 100
9 | CHECKPOINT_EVERY = 100
10 | NUM_CHECKPOINTS = 5
11 | VALIDATION_PERCENTAGE = 10
12 | TEST_PERCENTAGE = 10
13 |
14 | ## 图像对比
15 | * 正样本
16 |
17 | * 负样本
18 |
19 |
20 |
21 | ## Dependencies
22 | * tensorflow
23 |
24 |
25 | 其中,训练集正负样本各约3600
26 |
27 |
28 | 训练数据集: data/
29 |
30 |
31 | 训练:
32 |
33 | python train.py
34 |
35 | 验证:
36 | python validation.py
37 |
38 |
39 | ## result
40 | Validation accuracy on random sampled 100 examples = 83.2%
41 |
42 | 
43 |
44 | 
45 |
46 |
--------------------------------------------------------------------------------
/results/Figure_acc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jiangyiqiao/fundus_transfer_v3_classification/2d7378e1a5b5a1cfbc1864150e70249132c098e7/results/Figure_acc.png
--------------------------------------------------------------------------------
/results/Figure_loss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jiangyiqiao/fundus_transfer_v3_classification/2d7378e1a5b5a1cfbc1864150e70249132c098e7/results/Figure_loss.png
--------------------------------------------------------------------------------
/train.py:
--------------------------------------------------------------------------------
1 | import glob
2 | import os.path
3 | import random
4 | import numpy as np
5 | import tensorflow as tf
6 | from tensorflow.python.platform import gfile
7 |
8 |
9 | MODEL_DIR = 'model/'
10 | MODEL_FILE = 'tensorflow_inception_graph.pb'
11 | CACHE_DIR = 'data/tmp/bottleneck'
12 | INPUT_DATA = 'data/fundus_photos'
13 | VALIDATION_PERCENTAGE = 10
14 | TEST_PERCENTAGE = 10
15 |
16 |
17 | BOTTLENECK_TENSOR_SIZE = 2048 # inception-v3模型瓶颈层的节点个数
18 | BOTTLENECK_TENSOR_NAME = 'pool_3/_reshape:0' # inception-v3模型中代表瓶颈层结果的张量名称
19 | JPEG_DATA_TENSOR_NAME = 'DecodeJpeg/contents:0' # 图像输入张量对应的名称
20 |
21 | # 神经网络的训练参数
22 | LEARNING_RATE = 0.01
23 | STEPS = 10000
24 | BATCH = 100
25 | CHECKPOINT_EVERY = 100
26 | NUM_CHECKPOINTS = 5
27 |
28 |
29 | # 从数据文件夹中读取所有的图片列表并按训练、验证、测试分开
30 | def create_image_lists(validation_percentage, test_percentage):
31 | result = {} # 保存所有图像。key为类别名称。value也是字典,存储了所有的图片名称
32 | sub_dirs = [x[0] for x in os.walk(INPUT_DATA)] # 获取所有子目录
33 | is_root_dir = True # 第一个目录为当前目录,需要忽略
34 |
35 | # 分别对每个子目录进行操作
36 | for sub_dir in sub_dirs:
37 | if is_root_dir:
38 | is_root_dir = False
39 | continue
40 |
41 | # 获取当前目录下的所有有效图片
42 | extensions = {'jpg', 'jpeg', 'JPG', 'JPEG'}
43 | file_list = [] # 存储所有图像
44 | dir_name = os.path.basename(sub_dir) # 获取路径的最后一个目录名字
45 | for extension in extensions:
46 | file_glob = os.path.join(INPUT_DATA, dir_name, '*.' + extension)
47 | file_list.extend(glob.glob(file_glob))
48 | if not file_list:
49 | continue
50 |
51 | # 将当前类别的图片随机分为训练数据集、测试数据集、验证数据集
52 | label_name = dir_name.lower() # 通过目录名获取类别的名称
53 | training_images = []
54 | testing_images = []
55 | validation_images = []
56 | for file_name in file_list:
57 | base_name = os.path.basename(file_name) # 获取该图片的名称
58 | chance = np.random.randint(100) # 随机产生100个数代表百分比
59 | if chance < validation_percentage:
60 | validation_images.append(base_name)
61 | elif chance < (validation_percentage + test_percentage):
62 | testing_images.append(base_name)
63 | else:
64 | training_images.append(base_name)
65 |
66 | # 将当前类别的数据集放入结果字典
67 | result[label_name] = {
68 | 'dir': dir_name,
69 | 'training': training_images,
70 | 'testing': testing_images,
71 | 'validation': validation_images
72 | }
73 |
74 | # 返回整理好的所有数据
75 | return result
76 |
77 |
78 | # 通过类别名称、所属数据集、图片编号获取一张图片的地址
79 | def get_image_path(image_lists, image_dir, label_name, index, category):
80 | label_lists = image_lists[label_name] # 获取给定类别中的所有图片
81 | category_list = label_lists[category] # 根据所属数据集的名称获取该集合中的全部图片
82 | mod_index = index % len(category_list) # 规范图片的索引
83 | base_name = category_list[mod_index] # 获取图片的文件名
84 | sub_dir = label_lists['dir'] # 获取当前类别的目录名
85 | full_path = os.path.join(image_dir, sub_dir, base_name) # 图片的绝对路径
86 | return full_path
87 |
88 |
89 | # 通过类别名称、所属数据集、图片编号获取特征向量值的地址
90 | def get_bottleneck_path(image_lists, label_name, index, category):
91 | return get_image_path(image_lists, CACHE_DIR, label_name, index,
92 | category) + '.txt'
93 | # 使用inception-v3处理图片获取特征向量
94 | def run_bottleneck_on_image(sess, image_data, image_data_tensor,
95 | bottleneck_tensor):
96 | bottleneck_values = sess.run(bottleneck_tensor,
97 | {image_data_tensor: image_data})
98 | bottleneck_values = np.squeeze(bottleneck_values) # 将四维数组压缩成一维数组
99 | return bottleneck_values
100 |
101 |
102 | # 获取一张图片经过inception-v3模型处理后的特征向量
103 | def get_or_create_bottleneck(sess, image_lists, label_name, index, category,
104 | jpeg_data_tensor, bottleneck_tensor):
105 | # 获取一张图片对应的特征向量文件的路径
106 | label_lists = image_lists[label_name]
107 | sub_dir = label_lists['dir']
108 | sub_dir_path = os.path.join(CACHE_DIR, sub_dir)
109 | if not os.path.exists(sub_dir_path):
110 | os.makedirs(sub_dir_path)
111 | bottleneck_path = get_bottleneck_path(image_lists, label_name, index,
112 | category)
113 |
114 | # 如果该特征向量文件不存在,则通过inception-v3模型计算并保存
115 | if not os.path.exists(bottleneck_path):
116 | image_path = get_image_path(image_lists, INPUT_DATA, label_name, index,
117 | category) # 获取图片原始路径
118 | image_data = gfile.FastGFile(image_path, 'rb').read() # 获取图片内容
119 | bottleneck_values = run_bottleneck_on_image(
120 | sess, image_data, jpeg_data_tensor,
121 | bottleneck_tensor) # 通过inception-v3计算特征向量
122 |
123 | # 将特征向量存入文件
124 | bottleneck_string = ','.join(str(x) for x in bottleneck_values)
125 | with open(bottleneck_path, 'w') as bottleneck_file:
126 | bottleneck_file.write(bottleneck_string)
127 | else:
128 | # 否则直接从文件中获取图片的特征向量
129 | with open(bottleneck_path, 'r') as bottleneck_file:
130 | bottleneck_string = bottleneck_file.read()
131 | bottleneck_values = [float(x) for x in bottleneck_string.split(',')]
132 |
133 | # 返回得到的特征向量
134 | return bottleneck_values
135 |
136 |
137 | # 随机获取一个batch图片作为训练数据
138 | def get_random_cached_bottlenecks(sess, n_classes, image_lists, how_many,
139 | category, jpeg_data_tensor,
140 | bottleneck_tensor):
141 | bottlenecks = []
142 | ground_truths = []
143 | for _ in range(how_many):
144 | # 随机一个类别和图片编号加入当前的训练数据
145 | label_index = random.randrange(n_classes)
146 | label_name = list(image_lists.keys())[label_index]
147 | image_index = random.randrange(65535)
148 | bottleneck = get_or_create_bottleneck(
149 | sess, image_lists, label_name, image_index, category,
150 | jpeg_data_tensor, bottleneck_tensor)
151 | ground_truth = np.zeros(n_classes, dtype=np.float32)
152 | ground_truth[label_index] = 1.0
153 | bottlenecks.append(bottleneck)
154 | ground_truths.append(ground_truth)
155 | return bottlenecks, ground_truths
156 |
157 | # 获取全部的测试数据
158 | def get_test_bottlenecks(sess, image_lists, n_classes, jpeg_data_tensor,
159 | bottleneck_tensor):
160 | bottlenecks = []
161 | ground_truths = []
162 | label_name_list = list(image_lists.keys())
163 | # 枚举所有的类别和每个类别中的测试图片
164 | for label_index, label_name in enumerate(label_name_list):
165 | category = 'testing'
166 | for index, unused_base_name in enumerate(
167 | image_lists[label_name][category]):
168 | bottleneck = get_or_create_bottleneck(
169 | sess, image_lists, label_name, index, category,
170 | jpeg_data_tensor, bottleneck_tensor)
171 | ground_truth = np.zeros(n_classes, dtype=np.float32)
172 | ground_truth[label_index] = 1.0
173 | bottlenecks.append(bottleneck)
174 | ground_truths.append(ground_truth)
175 | return bottlenecks, ground_truths
176 |
177 | def main(_):
178 | # 读取所有的图片
179 | image_lists = create_image_lists(VALIDATION_PERCENTAGE, TEST_PERCENTAGE)
180 | n_classes = len(image_lists.keys())
181 |
182 | with tf.Graph().as_default() as graph:
183 | # 读取训练好的inception-v3模型
184 | with gfile.FastGFile(os.path.join(MODEL_DIR, MODEL_FILE), 'rb') as f:
185 | graph_def = tf.GraphDef()
186 | graph_def.ParseFromString(f.read())
187 | # 加载inception-v3模型,并返回数据输入张量和瓶颈层输出张量
188 | bottleneck_tensor, jpeg_data_tensor = tf.import_graph_def(
189 | graph_def,
190 | return_elements=[
191 | BOTTLENECK_TENSOR_NAME, JPEG_DATA_TENSOR_NAME
192 | ])
193 |
194 | # 定义新的神经网络输入
195 | bottleneck_input = tf.placeholder(
196 | tf.float32, [None, BOTTLENECK_TENSOR_SIZE],
197 | name='BottleneckInputPlaceholder')
198 |
199 | # 定义新的标准答案输入
200 | ground_truth_input = tf.placeholder(
201 | tf.float32, [None, n_classes], name='GroundTruthInput')
202 |
203 | # 定义一层全连接层解决新的图片分类问题
204 | with tf.name_scope('final_training_ops'):
205 | weights = tf.Variable(
206 | tf.truncated_normal(
207 | [BOTTLENECK_TENSOR_SIZE, n_classes], stddev=0.1))
208 | biases = tf.Variable(tf.zeros([n_classes]))
209 | logits = tf.matmul(bottleneck_input, weights) + biases
210 | final_tensor = tf.nn.softmax(logits)
211 |
212 | # 定义交叉熵损失函数
213 | cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
214 | logits=logits, labels=ground_truth_input)
215 | cross_entropy_mean = tf.reduce_mean(cross_entropy)
216 | train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(
217 | cross_entropy_mean)
218 |
219 | # 计算正确率
220 | with tf.name_scope('evaluation'):
221 | correct_prediction = tf.equal(
222 | tf.argmax(final_tensor, 1), tf.argmax(ground_truth_input, 1))
223 | evaluation_step = tf.reduce_mean(
224 | tf.cast(correct_prediction, tf.float32))
225 |
226 | # 训练过程
227 | with tf.Session(graph=graph) as sess:
228 | init = tf.global_variables_initializer().run()
229 |
230 | # 模型和摘要的保存目录
231 | import time
232 | timestamp = str(int(time.time()))
233 | out_dir = os.path.abspath(
234 | os.path.join(os.path.curdir, 'runs', timestamp))
235 | print('\nWriting to {}\n'.format(out_dir))
236 | # 损失值和正确率的摘要
237 | loss_summary = tf.summary.scalar('loss', cross_entropy_mean)
238 | acc_summary = tf.summary.scalar('accuracy', evaluation_step)
239 | # 训练摘要
240 | train_summary_op = tf.summary.merge([loss_summary, acc_summary])
241 | train_summary_dir = os.path.join(out_dir, 'summaries', 'train')
242 | train_summary_writer = tf.summary.FileWriter(train_summary_dir,
243 | sess.graph)
244 | # 开发摘要
245 | dev_summary_op = tf.summary.merge([loss_summary, acc_summary])
246 | dev_summary_dir = os.path.join(out_dir, 'summaries', 'dev')
247 | dev_summary_writer = tf.summary.FileWriter(dev_summary_dir, sess.graph)
248 | # 保存检查点
249 | checkpoint_dir = os.path.abspath(os.path.join(out_dir, 'checkpoints'))
250 | checkpoint_prefix = os.path.join(checkpoint_dir, 'model')
251 | if not os.path.exists(checkpoint_dir):
252 | os.makedirs(checkpoint_dir)
253 | saver = tf.train.Saver(
254 | tf.global_variables(), max_to_keep=NUM_CHECKPOINTS)
255 |
256 | for i in range(STEPS):
257 | # 每次获取一个batch的训练数据
258 | train_bottlenecks, train_ground_truth = get_random_cached_bottlenecks(
259 | sess, n_classes, image_lists, BATCH, 'training',
260 | jpeg_data_tensor, bottleneck_tensor)
261 | _, train_summaries = sess.run(
262 | [train_step, train_summary_op],
263 | feed_dict={
264 | bottleneck_input: train_bottlenecks,
265 | ground_truth_input: train_ground_truth
266 | })
267 |
268 | # 保存每步的摘要
269 | train_summary_writer.add_summary(train_summaries, i)
270 |
271 | # 在验证集上测试正确率
272 | if i % 100 == 0 or i + 1 == STEPS:
273 | validation_bottlenecks, validation_ground_truth = get_random_cached_bottlenecks(
274 | sess, n_classes, image_lists, BATCH, 'validation',
275 | jpeg_data_tensor, bottleneck_tensor)
276 | validation_accuracy, dev_summaries = sess.run(
277 | [evaluation_step, dev_summary_op],
278 | feed_dict={
279 | bottleneck_input: validation_bottlenecks,
280 | ground_truth_input: validation_ground_truth
281 | })
282 | print(
283 | 'Step %d : Validation accuracy on random sampled %d examples = %.1f%%'
284 | % (i, BATCH, validation_accuracy * 100))
285 |
286 | # 每隔checkpoint_every保存一次模型和测试摘要
287 | if i % CHECKPOINT_EVERY == 0:
288 | dev_summary_writer.add_summary(dev_summaries, i)
289 | path = saver.save(sess, checkpoint_prefix, global_step=i)
290 | print('Saved model checkpoint to {}\n'.format(path))
291 |
292 | # 最后在测试集上测试正确率
293 | test_bottlenecks, test_ground_truth = get_test_bottlenecks(
294 | sess, image_lists, n_classes, jpeg_data_tensor, bottleneck_tensor)
295 | test_accuracy = sess.run(
296 | evaluation_step,
297 | feed_dict={
298 | bottleneck_input: test_bottlenecks,
299 | ground_truth_input: test_ground_truth
300 | })
301 | print('Final test accuracy = %.1f%%' % (test_accuracy * 100))
302 |
303 | # 保存标签
304 | with tf.gfile.FastGFile(output_labels, 'w') as f:
305 | keys = list(image_lists.keys())
306 | for i in range(len(keys)):
307 | keys[i] = '%2d -> %s' % (i, keys[i])
308 | f.write('\n'.join(keys) + '\n')
309 |
310 |
311 | if __name__ == '__main__':
312 | tf.app.run()
313 |
--------------------------------------------------------------------------------
/validation.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | import numpy as np
3 |
4 | # 模型目录
5 | CHECKPOINT_DIR = 'data/fundus_photos/0/34680_left_0.jpeg'
6 | '
7 | INCEPTION_MODEL_FILE = 'models/tensorflow_inception_graph.pb'
8 |
9 | # inception-v3模型参数
10 | BOTTLENECK_TENSOR_NAME = 'pool_3/_reshape:0' # inception-v3模型中代表瓶颈层结果的张量名称
11 | JPEG_DATA_TENSOR_NAME = 'DecodeJpeg/contents:0' # 图像输入张量对应的名称
12 |
13 | # 测试数据
14 | file_path = 'data/fundus_photos/0/34680_left_0.jpeg'
15 | y_test = [4]
16 |
17 | # 读取数据
18 | image_data = tf.gfile.FastGFile(file_path, 'rb').read()
19 |
20 | # 评估
21 | checkpoint_file = tf.train.latest_checkpoint(CHECKPOINT_DIR)
22 | with tf.Graph().as_default() as graph:
23 | with tf.Session().as_default() as sess:
24 | # 读取训练好的inception-v3模型
25 | with tf.gfile.FastGFile(INCEPTION_MODEL_FILE, 'rb') as f:
26 | graph_def = tf.GraphDef()
27 | graph_def.ParseFromString(f.read())
28 | # 加载inception-v3模型,并返回数据输入张量和瓶颈层输出张量
29 | bottleneck_tensor, jpeg_data_tensor = tf.import_graph_def(
30 | graph_def,
31 | return_elements=[BOTTLENECK_TENSOR_NAME, JPEG_DATA_TENSOR_NAME])
32 |
33 | # 使用inception-v3处理图片获取特征向量
34 | bottleneck_values = sess.run(bottleneck_tensor,
35 | {jpeg_data_tensor: image_data})
36 | # 将四维数组压缩成一维数组,由于全连接层输入时有batch的维度,所以用列表作为输入
37 | bottleneck_values = [np.squeeze(bottleneck_values)]
38 |
39 | # 加载元图和变量
40 | saver = tf.train.import_meta_graph('{}.meta'.format(checkpoint_file))
41 | saver.restore(sess, checkpoint_file)
42 |
43 | # 通过名字从图中获取输入占位符
44 | input_x = graph.get_operation_by_name(
45 | 'BottleneckInputPlaceholder').outputs[0]
46 |
47 | # 我们想要评估的tensors
48 | predictions = graph.get_operation_by_name('evaluation/ArgMax').outputs[
49 | 0]
50 |
51 | # 收集预测值
52 | all_predictions = []
53 | all_predictions = sess.run(predictions, {input_x: bottleneck_values})
54 | # 如果提供了标签则打印正确率
55 | if y_test is not None:
56 | correct_predictions = float(sum(all_predictions == y_test))
57 | print('\nTotal number of test examples: {}'.format(len(y_test)))
58 | print('Accuracy: {:g}'.format(correct_predictions / float(len(y_test))))
59 |
60 |
--------------------------------------------------------------------------------