____tz_zs
前段时间参加了 kaggle 2018 data science bowl ,初生牛犊不怕虎,于是我撸起袖子就开始干了。
尽管,没能得到好的结果,参与过程中的收获和提高,也是很值得高兴的。
这里记录下这次的失败,以便下次吸取教训、更进一步。
同时,也希望能够帮到那些看到我这篇博客的新人朋友。
一个项目的步骤分为:数据预处理、模型构造、模型训练、模型评估
总体思路:训练一个复杂的卷积神经网络需要非常多的标注数据和很长的训练时间。而 kaggle 的比赛项目所提供的数据量比较小,总共只有 670 张不同的原始图片,数据集相对较小。所以,我决定使用迁移学习(迁移学习能将一个问题上训练好的模型通过调整使其适用于一个新的问题,能够在数据量不大及训练时间不足的情况下,训练出令人满意的神经网络模型)。所用模型为 Google 训练好的 Inception-v3 模型。我将本次 kaggle 项目看成是一个二值图像的生成问题(一个二维数组),将卷积神经网络部分作为原始图片特征向量的提取过程。
·此代码仅作为反面例子,这是最初版,问题很大。
# -*- coding: utf-8 -*-
"""
@author: tz_zs
"""
import pathlib
import numpy as np
from skimage import io, data
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu
import matplotlib.pyplot as plt
from scipy import ndimage
import tensorflow as tf
from tensorflow.python.platform import gfile
import os
from PIL import Image
# inception-v3 模型瓶颈层的节点个数
BOTTLENECK_TENSOR_SIZE = 2048
# 下载的谷歌训练好的inception-v3模型文件目录
MODEL_DIR = 'D:/kaggle/inception_dec_2015'
# 下载的谷歌训练好的inception-v3模型文件名
MODEL_FILE = 'tensorflow_inception_graph.pb'
# inception-v3 模型中代表瓶颈层结果的张量名称
BOTTLENECK_TENSOR_NAME = 'pool_3/_reshape:0'
# 图像输入张量所对应的名称
JPEG_DATA_TENSOR_NAME = 'DecodeJpeg/contents:0'
# 学习率
LEARNING_RATE = 0.01
STEPS = 400
BATCH = 1
# 缩放的尺寸
P = 100
# np.set_printoptions(threshold=1e6) # 设置打印数量的阈值
def get_train_data_path():
# 用Path类可以创建path路径对象, 属于比os.path更高抽象级别的对象。
training_paths = pathlib.Path('D:/kaggle/stage1_train').glob('*')
# 每一个文件夹,就是一个样本,获取其路径并保存
train_data_path = {}
for paths in training_paths:
# masks文件夹
masks_list = []
masks_dir = paths.joinpath('masks')
masks_dir_iterdir = masks_dir.iterdir()
for masks_paths in masks_dir_iterdir:
masks_list.append(masks_paths)
# images文件夹
images_dir = paths.joinpath('images')
images_dir_iterdir = images_dir.iterdir()
for images_paths in images_dir_iterdir:
images_id = images_paths.stem
train_data_path[images_id] = [images_paths, masks_list]
return train_data_path
if __name__ == '__main__':
# 数据准备
train_data_path = get_train_data_path() # 得到路径
train_data_masks = []
train_data_im = []
i = 0
for k, v in train_data_path.items():
# 将masks解码并合并
masks = 0
for mask in v[1]:
im_mask = Image.open(mask)
im_mask.thumbnail((P, P))
# im_mask = io.imread(mask)
masks += np.array(im_mask)
print(masks.shape)
train_data_masks.append(masks)
# for mask in v[1]:
# image_data = gfile.FastGFile(mask, 'r').read()
# print(image_data)
# 解码 image,并清理数据
# im = io.imread(v[0])
im = Image.open(mask)
im.thumbnail((P, P))
im = np.array(im)
im_gray = rgb2gray(im) # 使用scikit-image中的rgb2gray,将图像强制转换为灰度格式
# train_data_im.append(im_gray) #加入list
io.imsave("D:/tmp/im_tmp/{}.png".format(k), im_gray)
# 获取图片内容
image_data = gfile.FastGFile("D:/tmp/im_tmp/{}.png".format(k), 'rb').read()
train_data_im.append(image_data)
'''
#清理数据(废弃)
# 去除背景:Otsu方法将图像建模为双峰分布,并找到最优的分离值。(暂时先这样处理)
thresh_val = threshold_otsu(im_gray)
mask = np.where(im_gray > thresh_val, 1, 0)
if np.sum(mask == 0) < np.sum(mask == 1): # 比较0和1的区域的大小,保正是背景占多数
mask = np.where(mask, 0, 1) # 0和1交换
# 使用ndimage.label函数,查找mask中的所有对象,并标记(ndimage.label会将输入中的任何非零值都被视为特性,零值视为背景)
labels, nlabels = ndimage.label(mask)
label_arrays = []
for label_num in range(1, nlabels + 1): # 遍历并用list分门别类的装好
label_mask = np.where(labels == label_num, 1, 0)
label_arrays.append(label_mask)
# 使用ndimage.find_objects函数遍历mask,返回图像中每个标签对象的坐标范围列表,去除那些较小的像素点(噪音),得到新的mask
for label_ind, label_coords in enumerate(ndimage.find_objects(labels)):
cell = im_gray[label_coords]
if np.product(cell.shape) < 10:
mask = np.where(labels == label_ind + 1, 0, mask)
# 重新生成labels
labels, nlabels = ndimage.label(mask)
'''
print("数据准备完成")
# 读取模型
with gfile.FastGFile(os.path.join(MODEL_DIR, MODEL_FILE), 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
# 加载模型,返回对应名称的张量
bottleneck_tensor, image_data_tensor = tf.import_graph_def(graph_def, return_elements=[BOTTLENECK_TENSOR_NAME,
JPEG_DATA_TENSOR_NAME])
# 输入
bottleneck_input = tf.placeholder(tf.float32, [None, BOTTLENECK_TENSOR_SIZE], name='BottleneckInputPlaceholder')
ground_truth_input = tf.placeholder(tf.float32, [None, P * P], name='GroundTruthInput')
# 全连接层
with tf.name_scope('final_training_ops'):
weights = tf.Variable(tf.truncated_normal([BOTTLENECK_TENSOR_SIZE, P * P], stddev=0.001))
biases = tf.Variable(tf.zeros([P * P]))
logits = tf.matmul(bottleneck_input, weights) + biases
final_tensor = tf.nn.softmax(logits)
# 损失
# cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=ground_truth_input)
# cross_entropy_mean = tf.reduce_mean(cross_entropy)
mse = tf.reduce_mean(tf.square(ground_truth_input - final_tensor))
# cross_entropy = -tf.reduce_sum(ground_truth_input * tf.log(final_tensor))
# 优化
train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(mse)
# 正确率
with tf.name_scope('evaluation'):
# correct_prediction = tf.equal(tf.argmax(final_tensor, 1), tf.argmax(ground_truth_input, 1))
# evaluation_step = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
evaluation_step = tf.reduce_mean(tf.square(ground_truth_input - final_tensor))
with tf.Session() as sess:
# 初始化参数
init = tf.global_variables_initializer()
sess.run(init)
for i in range(STEPS):
# 每次获取一个batch的训练数据
# temp = i % BATCH
# print("temp:", temp)
# masks = train_data_masks[temp:temp + BATCH]
# im = train_data_im[temp:temp + BATCH]
masks = train_data_masks[i]
im = train_data_im[i]
# 调整矩阵
reshaped_masks = np.reshape(masks, [1, P * P])
# 迁移
bottleneck_values = sess.run(bottleneck_tensor, feed_dict={image_data_tensor: im}) # (1, 2048)
# 训练
sess.run(train_step, feed_dict={bottleneck_input: bottleneck_values, ground_truth_input: reshaped_masks})
# 检测
validation_accuracy = sess.run(evaluation_step, feed_dict={bottleneck_input: bottleneck_values,
ground_truth_input: reshaped_masks})
print('Step %d ———— %.1f%%' % (i, validation_accuracy * 100))
# RLE方法,用于kaggle提交
def rle_encoding(x):
'''
x: numpy array of shape (height, width), 1 - mask, 0 - background
Returns run length as list
'''
dots = np.where(x.T.flatten() == 1)[0] # .T sets Fortran order down-then-right
run_lengths = []
prev = -2
for b in dots:
if (b > prev + 1): run_lengths.extend((b + 1, 0))
run_lengths[-1] += 1
prev = b
return " ".join([str(i) for i in run_lengths])
·
总结反思:
此次项目总耗时约一个星期,因为思路和方案的缺陷等,失败了╥﹏╥。
后续:
新方案:对于每一个 mask 图像,用一个最小标注框包裹其非零区域,然后在 image 图像上用相同的标注框截取图像。如此,得到一个完整的细胞核图像与其对应的 mask 图像,作为一组数据。所以,在训练集中,如果一张原始图像有 n 个 mask 图像,则可用此方法生成 n 组训练数据。这样,每组数据的图片的尺寸不会很大,然后使用迁移学习。。。
补充本次 kaggle 的数据(2019-11-19):
链接:https://pan.baidu.com/s/1WWb5peDt_2ZStpaHUrzRDg
提取码:35xl
参考:
https://www.kaggle.com/rakhlin/fast-run-length-encoding-python
https://www.kaggle.com/stkbailey/teaching-notebook-for-total-imaging-newbies