[关闭]
@vivounicorn 2018-10-06T15:10:48.000000Z 字数 12595 阅读 2655

机器学习与人工智能技术分享-第九章 语义分割

机器学习 语义分割

回到目录


9. 语义分割

9.1 FCN

FCN在《Fully Convolutional Networks for Semantic Segmentation》中第一次被提出,个人认为是实现图像end to end语义分割的开山之作,第一次做到了低成本的像素级分类预测(end-to-end, pixels-to-pixels),另外这个方法用在目标检测、识别上效果好于传统新方法(如:Faster R-CNN)。
所谓语义分割简单说就是不但要知道你属于哪一类,还要知道你在哪儿:



9.1.1 算法概述

CNN网络无疑是特征提取的利器,尤其在图像领域,回顾我们的做法:CNN做特征提取+全连接层做特征组合+分类/回归,为了能提高模型预测能力,需要通过多个全连接层(做笛卡尔积)做特征组合,这里是参数数量最多的地方,成为模型训练,尤其是inference时的最大瓶颈(所以模型压缩和剪枝算法会把第一把刀放在全连接层),而由于全连接层的存在,导致整个网络的输入必须是固定大小的:由于卷积和采样操作更本不关心输入大小如何,试想如果输入大小不一,不同图片到了全连接层时其输入节点数是不一样的,而网络的定义必须事先定义好,所以没法儿玩儿了,于是有了前面的SPP及RoI pooling来解决这个问题,FCN则是解决这个问题的另一个思路。
总结该算法要解决的问题如下:
1、取消网络对输入数据大小必须固定的限制;
2、提高模型效果且加快其训练和inference速度。
相比于传统CNN,FCN把全连接层全部替换成卷积层,并在feature map(可以是其中任何一个)上做上采样,使其恢复到原始图片大小,这样不但保留了每个像素的空间信息,而且每个像素都会有一个分类预测。比如下图中pixelwise prediction那一层,小猫、小狗、电视、背景都会在像素级别做分类预测:


9.1.2 1×1卷积回顾

前面我们在介绍各种经典识别网络中介绍了1×1卷积核,回顾下它的作用,尤其对多通道而言:
1、每个1×1卷积核会有一个参数,利用它们可以做跨通道特征融合,即对多个通道的feature map做线性组合;
2、具有降维或升维作用,如:在GoogleNet中它可以跟在pooling层后面做降维,也可以直接通过减少通道数做降维,大大减少了参数量;
3、可以在不损失feature map信息的前提下利用后面的激活函数增加模型非线性表征能力,可以低成本的把网络变深。

9.1.3 全卷积网络

使用传统CNN做像素级分类的问题:
1、为了考虑上下文信息,需要一个滑动窗口,利用滑动窗口内的feature map对每个像素做分类,分类效果及存储空间随滑动窗口的大小上升;
2、为了考虑上下文信息,导致相邻两个窗口之间有大量的像素重复,意味着大量计算重复;
3、原图的空间信息没有被很好的利用;
4、原图需要固定大小,图像的resize(本质就是图像的下采样)导致信息损失。
FCN则很好的解决了上面几个问题。


上图是传统CNN工作流程,下图是FCN工作流程,它最终可以得到关于目标的热图,这种变换除了在语义分割、检测、识别上用到,也会在feature map可视化上用来帮助分析特征。
一张图说明:


理解FCN最关键的一步是理解上采样(upsampling)。

9.1.4 Nyquist–Shannon采样定理

关于采样,这个话题可大可小,从定义上说,采样是这么一个过程:在尽可能减少信息损失的情况下,将信号从一种采样率下的形态转换为另外一种,对于图片,这个过程叫做图像缩放。详细定义参见Resampling
对计算机而言无法处理连续信号(读者想想为什么?),必须通过采样做信号离散化,那就必须回答一个问题:理想情况下,以什么样的频率采样能完美重构连续信号的信息。
Nyquist–Shannon采样定理回答了上面的问题:当对信号均匀间隔离散采样且信号的带宽小于采样率的一半时,原始连续信号可以被其得到的采样样本完全重构,不满足该条件则会出现混叠(Aliasing)现象。
理论上连续信号可以通过以下公式重构(信息重构器):



其中采样率为:1/T,s(n*T)是s(x)的采样样本,sinc(x)是采样核(resampling kernel)。
一般来说 信息重构器有以下性质:
1、确实是信号的样本;
2、;
3、resampling kernel:
4、resampling kernel:是对称的,
5、resampling kernel:是处处可微的。

当然还有其他形式的resampling kernel,比如bilinear resampling kernel,满足上述性质2、3、4:


这个函数在FCN里广泛用到。
我利用scikit-image library给个简单的bilinear resampling示例:

  1. import skimage.transform
  2. from numpy import ogrid, repeat, newaxis
  3. from skimage import io
  4. def upsample_with_skimage(img, factor):
  5. # order=1表示bilinear resampling,参见:http://scikit-image.org/docs/dev/api/skimage.transform.html。
  6. # order的含义:
  7. # 0: Nearest-neighbor
  8. # 1: Bi-linear (default)
  9. # 2: Bi-quadratic
  10. # 3: Bi-cubic
  11. # 4: Bi-quartic
  12. # 5: Bi-quintic
  13. return skimage.transform.rescale(img,
  14. factor,
  15. mode='constant',
  16. cval=0,
  17. order=1)
  18. if __name__ == '__main__':
  19. target = upsample_with_skimage(img=io.imread("feature_map.jpg"), factor=5)
  20. io.imsave("upsampling.png", target, interpolation='none')


9.1.5 转置卷积(Transposed Convolution)

很多人把这个过程叫做“反卷积(deconvolution)”,但我认为这么叫是错误的,它的过程并不是对卷积的逆运算,它除了用在FCN中还会用在卷积可视化、对抗神经网络中。
原理如下:


假设,输入为4×4、输出为2×2、卷积核为3×3,则把输出、输入和卷积核按照从左到右、从上到下展开为向量,前向传播的卷积过程相当于输入与以下稀疏矩阵的乘积:


前向传播过程就表述为:

误差反向传播(如果记不清了可以回看5.1节):

那么反过来,我们希望从4维向量映射回16维向量怎么做呢:把上面过程逆反一下(当然该做padding还得做):
前向传播:


反向传播:

整个过程平滑柔顺,多种情况下的详细解释可以看:《Convolution arithmetic tutorial》

keras下做转置卷积,输入feature map及最终效果与8.7.4。

  1. # -*- coding: utf-8 -*-
  2. from __future__ import division
  3. import numpy as np
  4. import tensorflow as tf
  5. from skimage import io
  6. import skimage
  7. import io
  8. import os
  9. import keras.backend as K
  10. def get_kernel_size(factor):
  11. """
  12. 给定上采样因子,返回核大小,上采样因子大小等于转置卷积步长。
  13. """
  14. return 2 * factor - factor % 2
  15. def upsample_filt(size):
  16. """
  17. 返回上采样bilinear kernel矩阵。
  18. """
  19. factor = (size + 1) // 2
  20. if size % 2 == 1:
  21. center = factor - 1
  22. else:
  23. center = factor - 0.5
  24. og = np.ogrid[:size, :size]
  25. return (1 - abs(og[0] - center) / factor) * \
  26. (1 - abs(og[1] - center) / factor)
  27. def bilinear_upsample_weights(factor, channel):
  28. """
  29. 使用bilinear filter初始化转置卷积权重矩阵。
  30. """
  31. filter_size = get_kernel_size(factor)
  32. weights = np.zeros((filter_size,
  33. filter_size,
  34. channel,
  35. channel), dtype=np.float32)
  36. upsample_kernel = upsample_filt(filter_size)
  37. for i in xrange(channel):
  38. weights[:, :, i, i] = upsample_kernel
  39. return weights
  40. def upsample_keras(factor, input_img):
  41. SCALE = 256
  42. channel = input_img.shape[2]
  43. scale_height = input_img.shape[0] * factor
  44. scale_width = input_img.shape[1] * factor
  45. expanded_img = np.expand_dims(input_img, axis=0)
  46. with tf.device("/gpu:1"):
  47. gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=1, allow_growth=True)
  48. os.environ["CUDA_VISIBLE_DEVICES"] = "1"
  49. sess = tf.Session(config=K.tf.ConfigProto(allow_soft_placement=True,
  50. log_device_placement=True,
  51. gpu_options=gpu_options))
  52. input_value = tf.placeholder(tf.float32)
  53. trans_filter = tf.placeholder(tf.float32)
  54. upsample_filter_np = bilinear_upsample_weights(factor, channel)
  55. res = K.conv2d_transpose(input_value, trans_filter,
  56. output_shape=[1, scale_height, scale_width, channel],
  57. padding='same',
  58. strides=(factor, factor))
  59. final_result = sess.run(res,
  60. feed_dict={trans_filter: upsample_filter_np,
  61. input_value: expanded_img})
  62. if channel != 1:
  63. return final_result.squeeze() / SCALE
  64. return final_result.squeeze()
  65. upsampled_img_keras = upsample_keras(factor=5, input_img=skimage.io.imread("feature_map.jpg"))
  66. skimage.io.imsave("bilinear_feature_map.jpg",upsampled_img_keras, interpolation='none')

9.1.6 代码实践

开源代码可参见:Keras-FCN,虽然缺点是训练有点慢,模型有点大,但对于理解如何实现很有帮助。
里面实现了五种模型,两种基于vgg-16,两种基于resnet-50,一种基于densenet。
上采样操作做为一个新的网络层意味着它需要能够前向传播、反向传播、更新权重,其实现在代码中为BilinearUpSampling.py。
inference.py的代码需要稍微变下:

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. from pylab import *
  4. import os
  5. import sys
  6. import cv2
  7. from PIL import Image
  8. from keras.preprocessing.image import *
  9. from keras.models import load_model
  10. import keras.backend as K
  11. from keras.applications.imagenet_utils import preprocess_input
  12. from models import *
  13. def inference(model_name, weight_file, image_size, image_list, data_dir, label_dir, return_results=True, save_dir=None,
  14. label_suffix='.png',
  15. data_suffix='.jpg'):
  16. current_dir = os.path.dirname(os.path.realpath(__file__))
  17. # mean_value = np.array([104.00699, 116.66877, 122.67892])
  18. batch_shape = (1, ) + image_size + (3, )
  19. save_path = os.path.join(current_dir, 'Models/'+model_name)
  20. model_path = os.path.join(save_path, "model.json")
  21. checkpoint_path = os.path.join(save_path, weight_file)
  22. # model_path = os.path.join(current_dir, 'model_weights/fcn_atrous/model_change.hdf5')
  23. # model = FCN_Resnet50_32s((480,480,3))
  24. #config = tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True))
  25. #session = tf.Session(config=config)
  26. #K.set_session(session)
  27. model = globals()[model_name](batch_shape=batch_shape, input_shape=(512, 512, 3))
  28. model.load_weights(checkpoint_path, by_name=True)
  29. model.summary()
  30. results = []
  31. total = 0
  32. for img_num in image_list:
  33. img_num = img_num.strip('\n')
  34. total += 1
  35. print('#%d: %s' % (total,img_num))
  36. image = Image.open('%s/%s%s' % (data_dir, img_num, data_suffix))
  37. image = img_to_array(image) # , data_format='default')
  38. label = Image.open('%s/%s%s' % (label_dir, img_num, label_suffix))
  39. label_size = label.size
  40. img_h, img_w = image.shape[0:2]
  41. # long_side = max(img_h, img_w, image_size[0], image_size[1])
  42. pad_w = max(image_size[1] - img_w, 0)
  43. pad_h = max(image_size[0] - img_h, 0)
  44. image = np.lib.pad(image, ((pad_h/2, pad_h - pad_h/2), (pad_w/2, pad_w - pad_w/2), (0, 0)), 'constant', constant_values=0.)
  45. # image -= mean_value
  46. '''img = array_to_img(image, 'channels_last', scale=False)
  47. img.show()
  48. exit()'''
  49. # image = cv2.resize(image, image_size)
  50. image = np.expand_dims(image, axis=0)
  51. image = preprocess_input(image)
  52. result = model.predict(image, batch_size=1)
  53. result = np.argmax(np.squeeze(result), axis=-1).astype(np.uint8)
  54. result_img = Image.fromarray(result, mode='P')
  55. result_img.palette = label.palette
  56. # result_img = result_img.resize(label_size, resample=Image.BILINEAR)
  57. result_img = result_img.crop((pad_w/2, pad_h/2, pad_w/2+img_w, pad_h/2+img_h))
  58. # result_img.show(title='result')
  59. if return_results:
  60. results.append(result_img)
  61. if save_dir:
  62. result_img.save(os.path.join(save_dir, img_num + '.png'))
  63. return results
  64. if __name__ == '__main__':
  65. with tf.device('/gpu:1'):
  66. gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=1, allow_growth=True)
  67. os.environ["CUDA_VISIBLE_DEVICES"] = "1"
  68. tf.Session(config=K.tf.ConfigProto(allow_soft_placement=True,
  69. log_device_placement=True,
  70. gpu_options=gpu_options))
  71. model_name = 'AtrousFCN_Resnet50_16s'
  72. weight_file = 'checkpoint_weights.hdf5'
  73. image_size = (512, 512)
  74. data_dir = os.path.expanduser('~/.keras/datasets/VOC2012/VOCdevkit/VOC2012/JPEGImages')
  75. label_dir = os.path.expanduser('~/.keras/datasets/VOC2012/VOCdevkit/VOC2012/SegmentationClass')
  76. image_list = sys.argv[1:]#'2007_000491'
  77. results = inference(model_name, weight_file, image_size, image_list, data_dir, label_dir, save_dir="result")
  78. for result in results:
  79. result.show(title='result', command=None)

9.2 FCN-CRF

9.3 SegNet

9.4 UberNet

References

如有遗漏请提醒我补充:
1、《Understanding the Bias-Variance Tradeoff》
http://scott.fortmann-roe.com/docs/BiasVariance.html
2、《Boosting Algorithms as Gradient Descent in Function Space》
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.51.6893&rep=rep1&type=pdf
3、《Optimal Action Extraction for Random Forests and
Boosted Trees》
http://www.cse.wustl.edu/~ychen/public/OAE.pdf
4、《Applying Neural Network Ensemble Concepts for Modelling Project Success》
http://www.iaarc.org/publications/fulltext/Applying_Neural_Network_Ensemble_Concepts_for_Modelling_Project_Success.pdf
5、《Introduction to Boosted Trees》
https://homes.cs.washington.edu/~tqchen/data/pdf/BoostedTree.pdf
6、《Machine Learning:Perceptrons》
http://ml.informatik.uni-freiburg.de/_media/documents/teaching/ss09/ml/perceptrons.pdf
7、《An overview of gradient descent optimization algorithms》
http://sebastianruder.com/optimizing-gradient-descent/
8、《Ad Click Prediction: a View from the Trenches》
https://www.eecs.tufts.edu/~dsculley/papers/ad-click-prediction.pdf
9、《ADADELTA: AN ADAPTIVE LEARNING RATE METHOD》
http://www.matthewzeiler.com/pubs/googleTR2012/googleTR2012.pdf
9、《Improving the Convergence of Back-Propagation Learning with Second Order Methods》
http://yann.lecun.com/exdb/publis/pdf/becker-lecun-89.pdf
10、《ADAM: A METHOD FOR STOCHASTIC OPTIMIZATION》
https://arxiv.org/pdf/1412.6980v8.pdf
11、《Adaptive Subgradient Methods for Online Learning and Stochastic Optimization》
http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf
11、《Sparse Allreduce: Efficient Scalable Communication for Power-Law Data》
https://arxiv.org/pdf/1312.3020.pdf
12、《Asynchronous Parallel Stochastic Gradient Descent》
https://arxiv.org/pdf/1505.04956v5.pdf
13、《Large Scale Distributed Deep Networks》
https://papers.nips.cc/paper/4687-large-scale-distributed-deep-networks.pdf
14、《Introduction to Optimization —— Second Order Optimization Methods》
https://ipvs.informatik.uni-stuttgart.de/mlr/marc/teaching/13-Optimization/04-secondOrderOpt.pdf
15、《On the complexity of steepest descent, Newton’s and regularized Newton’s methods for nonconvex unconstrained optimization》
http://www.maths.ed.ac.uk/ERGO/pubs/ERGO-09-013.pdf
16、《On Discriminative vs. Generative classifiers: A comparison of logistic regression and naive Bayes 》
http://papers.nips.cc/paper/2020-on-discriminative-vs-generative-classifiers-a-comparison-of-logistic-regression-and-naive-bayes.pdf
17、《Parametric vs Nonparametric Models》
http://mlss.tuebingen.mpg.de/2015/slides/ghahramani/gp-neural-nets15.pdf
18、《XGBoost: A Scalable Tree Boosting System》
https://arxiv.org/abs/1603.02754
19、一个可视化CNN的网站
http://shixialiu.com/publications/cnnvis/demo/
20、《Computer vision: LeNet-5, AlexNet, VGG-19, GoogLeNet》
http://euler.stat.yale.edu/~tba3/stat665/lectures/lec18/notebook18.html
21、François Chollet在Quora上的专题问答:
https://www.quora.com/session/Fran%C3%A7ois-Chollet/1
22、《将Keras作为tensorflow的精简接口》
https://keras-cn.readthedocs.io/en/latest/blog/keras_and_tensorflow/
23、《Upsampling and Image Segmentation with Tensorflow and TF-Slim》
https://warmspringwinds.github.io/tensorflow/tf-slim/2016/11/22/upsampling-and-image-segmentation-with-tensorflow-and-tf-slim/

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注