FCN训练日记(Win10+tensorflow+CPU)新人报道

FCN训练笔记与问题总结

本人学生小白一枚 ~ 这段时间搞比赛,入坑深度学习,经过一个月的奋斗,总算是完成了作品,撒花 ~ 前期准备时参考了众多大神们的精品博客,手痒了,第一次尝试写博客,不足之处恳请指教。

前言

FCN的介绍我就不细说了,全卷积嘛,理论上接受任意大小尺寸的图片输入(但是分辨率越大的图片越吃电脑性能,仅仅是测试一张图,4248*6016大小的图直接报错溢出)我的MX150小显存根本跑不动大的图片,只好放弃GPU,用CPU跑程序。

配置环境

啊,这个很重要,我最终是在Win10+tensorflow1.6(CPU)+python3.6 中实现的。如果是使用GPU的朋友可以参考如下配置:Win10+python3.6+tensorflow-gpu 1.6+CUDA9.1+CUDNN 7。当然啦,CUDA版本得看NVIDIA驱动的版本,总之CUDA9.x的tensorflow-gpu版本别下载太高的,会出问题的。另外FCN对电脑配置要求挺高的,我的是2个G显存,跑YOLO调低batchsize还能跑通,可是FCN一开始训练直接OOM(用了释放显存,调batchsize为1,切小输入图片等无数种办法,最后果断放弃了 。。捂脸)。我估计4个G(甚至还要更高)的显存才能跑得动吧。

运行速度
那既然选择了CPU,你就别指望它的训练速度能有多快咯,我大概测试了下,batchsize=32时,迭代10次需要15分钟;batchsize=16的时候,迭代10次大约六分半,我最终是选择16大小的batchsize进行训练,刚好内存够,不至于运行得太卡。

OK,不多废话,开始FCN的训练之旅。

下载所需的文件

1.FCN源码下载
2.网络权重文件下载(提取码:4i8j)

创建数据集格式

  1. 其实这一步也不是必须这么设置,只是源码中写好了数据集呀,权重呀存放的位置的路径,如果你自定义文件夹名就会找不到。如果想自定义文件夹名字就去修改FCN.py中的路径。如果懒得改源码就按照作者的数据集格式来创建文件夹。具体如下:在FCN-TensorFlow-master文件夹中创建Model_zoo和Data_zoo两个文件夹,其中Model_zoo中存放网络权重文件(mat文件),然后在Data_zoo文件夹中创建MIT_SceneParsing文件夹和 test文件夹(这个是后面测试会用到的),再继续,MIT_SceneParsing文件夹中创建ADEChallengeData2016文件夹,多提一句,后面开始训练之后,这个目录下会生成一个MITSceneParsing.pickle文件,这个文件是程序读取你自己的数据集时的记录文件,也就是说,如果你要中途换数据集了,请记得把这个pickle文件删除哦。

  2. 好的,我们继续。在这个ADEChallengeData2016文件夹中,我们需要分别创建annotations和images文件夹,annotations中放的是你的数据集标签(掩模图),images中放的是你的数据集原图。还没结束,在annotations文件夹中创建training和validation两个文件夹,根据英文不难猜出,分别是放训练集和验证集的标签;images文件夹中也一样创建training和validation两个文件夹,里面放原图。标签与原图要一 一对应,这就不需要我唠叨了吧。

  3. OK,至此,你的整个框架都弄好啦,下面就需要将数据集放入训练就可以了。

数据集标签制作

  • 这个我不细说,哈哈哈哈,为啥呢,因为比赛主办方直接提供了标准掩模图呀,不用我自己去标注。而且有很多很好的博文详细介绍了怎么去标注,如果是自己制作数据集标签的朋友可以搜搜看,一般是用labelme吧,生成json然后写个脚本转成png格式*
  • 当然,标签的格式需要有几点注意:
  1. 转成8位位深的png图片,其实就是三通道转单通道
  2. 灰度值的数目要与类别数一致,实质上是归一化操作。我们知道,对于二值图像,0代表黑,255代表白,但是在FCN中,它要求标签图像的灰度值满足分类数分布,打个比方,二分类(包括背景),那么灰度值就应该是0和1;三分类,灰度值就应该是0,1,2。之前我尝试了一下0和255行不行,报错说我的类别数(二分类)与256不一致,大概意思就是程序默认每个灰度值代表一类,虽然标签里只有0和255两种灰度值,但它却认为我有256个分割类别。
  3. 我用的转换脚本(只适用于二分类哦,多分类需要修改部分代码)贴出来分享,如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
----------------------------
#实现批量转8位深,灰度值归一化
----------------------------


import glob
import cv2
import os

path='C:/Users/AS/Desktop/1/'   #标签路径(二值图像哦)

def picture_shape(picture):
 img=cv2.imread(picture,2)
 cv2.cvtcolor
 ret2,th2=cv2.threshold(img,0,1,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
 cv2.imwrite(path + '%03d.png'%i,th2)  #图片保存格式,可自定义
 #print(th2.item(100,100))

def calculation(path):
    global i
    i=0
    for picture in glob.glob(path + '*.png'):
        i+=1
        print('%03d'%i)
        picture_shape(picture)

calculation(path)

4.将处理好的标签和数据集分为训练集,验证集和测试集,训练集的标签和原图分别放入annotations和images文件夹中的training中,再次提醒,检查原图与标签一 一对应(格式不要求一样哦,比如原图jpg,标签png),验证集则分别放入annotations和images文件夹中的validation中。

5.OK,数据集制作完成,当然咯,想训练出一个比较好的模型,你可以进行打乱数据集,数据平衡与清洗,数据扩充等操作。电脑性能不好的童鞋建议将输入图片切小再输入,一可以缓解内存或显卡的压力,二可以到达扩充数据集的目的。虽然FCN是可以接受任意尺寸的图片输入,但我也在刚开始的时候说了这只是理论上,事实上,我们穷苦人家的电脑配置大都支撑不了大分辨率的图片输入。当然咯,测试的时候我们直接输入原图尺寸就好了,测试图也会以原尺寸输出,FCN的优势就展现出来啦!

调整参数

  • 参数的设置主要在主函数FCN.py中进行,粘贴部分如下:
#参数设置
FLAGS = tf.flags.FLAGS
# batchsize设置 
tf.flags.DEFINE_integer("batch_size", "16", "batch size for training")
# log路径,一般存放权重文件,预测结果图等  
tf.flags.DEFINE_string("logs_dir", "logs/", "path to logs directory")
# data路径,就是咱们之前创建的那么多的文件夹,如果想自定义的话把这里改成自己的文件名就可以了
tf.flags.DEFINE_string("data_dir", "Data_zoo/MIT_SceneParsing/", "path to dataset")
# 学习率,很重要的一个参数,前期不宜调太大
tf.flags.DEFINE_float("learning_rate", "1e-6", "Learning rate for Adam Optimizer")
# model路径,放之前下载的网络权重mat文件
tf.flags.DEFINE_string("model_dir", "Model_zoo/", "Path to vgg model mat")
# debuge这个可以不用管他
tf.flags.DEFINE_bool('debug', "False", "Debug mode: True/ False")
# 模式切换,训练时就默认的train,后面的验证及单张图检测,就需要用visualize和test
tf.flags.DEFINE_string('mode', "train", "Mode train/ test/ visualize")
# 源码中提供了mat文件在线下载,但个人建议先下载好
MODEL_URL = 'http://www.vlfeat.org/matconvnet/models/beta16/imagenet-vgg-verydeep-19.mat'
#最大迭代次数
MAX_ITERATION = 20001
# 类别数(分割目标+背景),我做的是二分类     
NUM_OF_CLASSESS = 2  
# 这是个困扰我挺久的地方,FCN将输入图像都统一resize了,resize之后的尺寸就是由这个参数控制的。默认就好。     
IMAGE_SIZE = 224
# 改成True,比如说你中断后想继续训练,它可以帮助你restore模型     
fine_tuning = True

训练提醒

  • 调好参数之后,直接运行FCN.py就开始训练了。训练过程就没啥好说的了,如果刚开始运行有报错,一般是缺少包吧,pip安装一下就好了,显存不够OOM报错的问题我是没办法解决了,2G很难跑起来。可以和我一样换CPU去跑。如果开始正常训练了,那就基本代码没问题了。

  • 奥对了,源码中设置的是每10次迭代打印一次train_loss,每500次验证一次,打印validation_loss,每500次生成一次权重,我数据集不大,就把它们都改小了一些,修改的地方大概在250行左右吧,贴出我修改后的样子:

if itr % 1 == 0:
   # 迭代每次打印显示	
    train_loss, summary_str = sess.run([loss, summary_op], feed_dict=feed_dict)
    print("Step: %d, Train_loss:%g" % (itr, train_loss))
    summary_writer.add_summary(summary_str, itr)
if itr % 10 == 0:
   # 迭代10 次验证
    valid_images, valid_annotations = validation_dataset_reader.next_batch(FLAGS.batch_size)
    valid_loss = sess.run(loss, feed_dict={image: valid_images, annotation: valid_annotations,
                                                       keep_probability: 1.0})
    print("%s ---> Validation_loss: %g" % (datetime.datetime.now(), valid_loss))
   # 每迭代250次保存模型
if itr % 250 == 0:
   saver.save(sess, FLAGS.logs_dir + "model.ckpt", itr)
  • 权重文件生成频率不建议设置的太高,一般来说迭代100次前后的权重差别很细微,而且一个权重很大,1.56G左右,如果迭代个1000次就生成那么多,磁盘空间早就满了。建议设置为250或者500次生成一次。

单张图测试

源代码中虽然有test模式,但是却没有写test的代码段,只有train和visualize两种模式的代码。visualize模式是从验证集中随机抽取一个batchsize 的图片进行预测。那么如果我们想要测试特定的单张图像该怎么办呢?我查了很多资料,都没有讲这一块的,唯一的方法就是在FCN.py源码中补充,我补充的如下(大概是在280行左右):


'''visualize模式'''
    elif FLAGS.mode == "visualize":
        # get_random_batch()随机抽取
        valid_images, valid_annotations = validation_dataset_reader.get_random_batch(FLAGS.batch_size)
        # pred_annotation预测结果图
        pred = sess.run(pred_annotation, feed_dict={image: valid_images, annotation: valid_annotations,
                                                    keep_probability: 1.0})
        valid_annotations = np.squeeze(valid_annotations, axis=3)
        pred = np.squeeze(pred, axis=3)
        for itr in range(FLAGS.batch_size):
        #输出inp_原图
            utils.save_image(valid_images[itr].astype(np.uint8), FLAGS.logs_dir, name="inp_" + str(5+itr))
        #输出gt_标签
            utils.save_image(valid_annotations[itr].astype(np.uint8), FLAGS.logs_dir, name="gt_" + str(5+itr))
        #输出pred_验证结果
            utils.save_image(pred[itr].astype(np.uint8), FLAGS.logs_dir, name="pred_" + str(5+itr))
            print("Saved image: %d" % itr)
            
'''重点来了!!!需要自己补充的test模式'''

    else:
    #还记得Data_zoo下我们还创建的一个test文件夹嘛,在这里就用到了,当然咯,你可以自定义路径。
        test_image = scipy.misc.imread('./Data_zoo/test/038.jpg')
        #resize_image = scipy.misc.imresize(test_image, [224, 224], interp='nearest')
        a = np.expand_dims(test_image, axis=0)#resize_image
        a = np.array(a)
        pred = sess.run(pred_annotation, feed_dict={image: a, keep_probability: 1.0})
        pred = np.squeeze(pred, axis=3)
        #输出的预测图在log文件夹中,图名可以自己修改
        utils.save_image(pred[0].astype(np.uint8), FLAGS.logs_dir, name="pred_" + str(5))
        print("Saved image: succeed")            

到这里还没结束!!!请接着看下面

测试的问题总结

  • 我们兴冲冲的开始测试了,结果测出来一看,嗯???图像怎么是全黑的?为什么输出图像变成了224*224的尺寸?我们期望测试的时候任意尺寸输入,原尺寸输出该怎么做?你还有几个需要修改的地方:
    这边再提醒一下,这只是测试的时候用,训练时开着resize,对模型训练的流畅度和缓解内存压力都有不小的作用

  • 我们需要修改三处,大约是第24行,第188行,第224行,贴代码如下:

#mode模式改成test
tf.flags.DEFINE_string('mode', "test", "Mode train/ test/ visualize")
#将原本的注释掉,多加一行,其实就是把IMAGE_SIZE改成None
#image = tf.placeholder(tf.float32, shape=[None, IMAGE_SIZE, IMAGE_SIZE, 3], name="input_image")
    image = tf.placeholder(tf.float32, shape=[None, None,None, 3], name="input_image")
#将resize属性改为False,如果不关掉,当你重启模型时,还是将你的测试图先resize成224*224了
image_options = {'resize':False,'resize_size': IMAGE_SIZE}
  • OK啦,现在再测试,输出的就是原图像尺寸啦,测试一张图时间蛮久的,我测试一张就需要5分钟,可能是原图太大的缘故吧。

  • 那测试出来是全黑的咋办?你别忘了,之前我们将灰度值归一化了,0,1,2,3…这些灰度值都是近似于黑色的,肉眼分辨不出来,所以我们需要把灰度值再还原回来,二分类就用简单的阈值就可以看到结果啦,其实就是将上面提到的归一化代码中稍微改改就行了,贴出代码如下:

import cv2
import numpy

path='C:/Users/AS/Desktop/FCN-TensorFlow-master/logs/'
#换成自己的图名
img=cv2.imread(path+'pred1_35.png',2)
 #简单阈值分割
ret2,th2 = cv2.threshold(img,0.5,255,cv2.THRESH_BINARY) 

cv2.namedWindow('',cv2.WINDOW_NORMAL)
cv2.imshow('',th2)
cv2.waitKey(0)
#保存的图名、路径可以自定义
cv2.imwrite(path+'p35(1).png',th2)
  • 三分类及以上应该需要先遍历像素值吧,再把每个像素还原放大,我就没有去写了。应该也是很容易实现的。

  • 还有一点,我在log文件夹中生成了好几个权重,我想指定某个权重测试咋办?这个好办,我们先去log目录下,找到一个叫做checkpoint的文件,这里面存放的就是你生成的所有权重路径啦,一般默认的是最新的权重,如果想要调用之前的,只需要将第一行改成那个权重的名字就好啦!

测试结果评价

图像分割一般用的是Dice吧,医学领域会用到灵敏度和特异度。感觉Dice用的最为广泛,贴出计算Dice的代码:

import cv2
from matplotlib import pyplot as plt

#解决matplotlib不能正常显示中文
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False

path_1 =r'C:\Users\AS\Desktop\035.png'   #标准掩模图路径
path_2 = r'C:\Users\AS\Desktop\FCN-TensorFlow-master\logs\processed35(1).png'  #预测图路径

# 计算DICE系数,即DSI
def calDSI(img_GT,img_R):
    row, col = img_GT.shape  # 矩阵的行与列
    DSI_s,DSI_t = 0,0,0,0
    for i in range(row):
        for j in range(col):
            if img_GT[i][j] == 255 and img_R[i][j] == 255:
                DSI_s += 1
            if img_GT[i][j] == 255:
                DSI_t += 1
            if img_R[i][j]  == 255:
                DSI_t += 1
              
    DSI = 2*DSI_s/DSI_t
  
    print(' DICE计算结果 : {0:.4} '.format(DSI))  # 保留四位有效数字

if __name__ == '__main__':
    # step 1:读入图像,并灰度化
    img_GT = cv2.imread(path_1,2)
    img_R  = cv2.imread(path_2,2)

#二值化
    ret3,img_GT = cv2.threshold(img_GT,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
    ret3,img_R = cv2.threshold(img_R,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

    calDSI(img_GT,img_R)

总结

  • 唔,基本流程大家都走完了,如果一切顺利的话,祝贺大家!撒花~
  • 训练过程中出现loss不下降啊等问题,先调低学习率,一般来说1e-6是比较合理的,如果来回震荡,就调大batchsize平滑数据间的梯度,如果上述修改都没用,那就需要去检查数据集了,从以下几点去检查:
  1. 标签和原图是否一 一对应
  2. 数据集是否打乱了,这一点也很重要
  3. 数据集中各分类比例平衡嘛?如果是二分类,训练集中大比例的全是背景,那训练效果肯定不会太好啦
  • 基本都是数据预处理的问题,如果这些都处理好了,loss应该会正常下降的(当然啦,有时候也会是代码的问题,比如权重没初始化呀,优化器出问题呀等等,但我用的这个代码貌似没啥问题)

补充一点,有童鞋问我要ADEChallengeData2016数据集,直接把网盘链接贴在这里了(提取码:rkbb ):link

哎,第一次写博客,权当是这次比赛自己的一点收获和小结吧。有不懂的童鞋可以在评论区戳我,我只要看到了都会认真回复的。有写错的也请大家多多包涵哈!也期望有大神能指点指点,一起交流~

你可能感兴趣的:(深度学习,计算机视觉,tensorflow,opencv,python)