├── 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 | ![train acc](results/Figure_acc.png) 43 | 44 | ![loss](results/Figure_loss.png) 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 | --------------------------------------------------------------------------------