@vivounicorn
2018-10-06T07:10:48.000000Z
字数 12595
阅读 3081
机器学习 语义分割

FCN在《Fully Convolutional Networks for Semantic Segmentation》中第一次被提出,个人认为是实现图像end to end语义分割的开山之作,第一次做到了低成本的像素级分类预测(end-to-end, pixels-to-pixels),另外这个方法用在目标检测、识别上效果好于传统新方法(如:Faster R-CNN)。
所谓语义分割简单说就是不但要知道你属于哪一类,还要知道你在哪儿:
CNN网络无疑是特征提取的利器,尤其在图像领域,回顾我们的做法:CNN做特征提取+全连接层做特征组合+分类/回归,为了能提高模型预测能力,需要通过多个全连接层(做笛卡尔积)做特征组合,这里是参数数量最多的地方,成为模型训练,尤其是inference时的最大瓶颈(所以模型压缩和剪枝算法会把第一把刀放在全连接层),而由于全连接层的存在,导致整个网络的输入必须是固定大小的:由于卷积和采样操作更本不关心输入大小如何,试想如果输入大小不一,不同图片到了全连接层时其输入节点数是不一样的,而网络的定义必须事先定义好,所以没法儿玩儿了,于是有了前面的SPP及RoI pooling来解决这个问题,FCN则是解决这个问题的另一个思路。
总结该算法要解决的问题如下:
1、取消网络对输入数据大小必须固定的限制;
2、提高模型效果且加快其训练和inference速度。
相比于传统CNN,FCN把全连接层全部替换成卷积层,并在feature map(可以是其中任何一个)上做上采样,使其恢复到原始图片大小,这样不但保留了每个像素的空间信息,而且每个像素都会有一个分类预测。比如下图中pixelwise prediction那一层,小猫、小狗、电视、背景都会在像素级别做分类预测:
前面我们在介绍各种经典识别网络中介绍了1×1卷积核,回顾下它的作用,尤其对多通道而言:
1、每个1×1卷积核会有一个参数,利用它们可以做跨通道特征融合,即对多个通道的feature map做线性组合;
2、具有降维或升维作用,如:在GoogleNet中它可以跟在pooling层后面做降维,也可以直接通过减少通道数做降维,大大减少了参数量;
3、可以在不损失feature map信息的前提下利用后面的激活函数增加模型非线性表征能力,可以低成本的把网络变深。
使用传统CNN做像素级分类的问题:
1、为了考虑上下文信息,需要一个滑动窗口,利用滑动窗口内的feature map对每个像素做分类,分类效果及存储空间随滑动窗口的大小上升;
2、为了考虑上下文信息,导致相邻两个窗口之间有大量的像素重复,意味着大量计算重复;
3、原图的空间信息没有被很好的利用;
4、原图需要固定大小,图像的resize(本质就是图像的下采样)导致信息损失。
FCN则很好的解决了上面几个问题。
上图是传统CNN工作流程,下图是FCN工作流程,它最终可以得到关于目标的热图,这种变换除了在语义分割、检测、识别上用到,也会在feature map可视化上用来帮助分析特征。
一张图说明:
理解FCN最关键的一步是理解上采样(upsampling)。
关于采样,这个话题可大可小,从定义上说,采样是这么一个过程:在尽可能减少信息损失的情况下,将信号从一种采样率下的形态转换为另外一种,对于图片,这个过程叫做图像缩放。详细定义参见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示例:
import skimage.transformfrom numpy import ogrid, repeat, newaxisfrom skimage import iodef upsample_with_skimage(img, factor):# order=1表示bilinear resampling,参见:http://scikit-image.org/docs/dev/api/skimage.transform.html。# order的含义:# 0: Nearest-neighbor# 1: Bi-linear (default)# 2: Bi-quadratic# 3: Bi-cubic# 4: Bi-quartic# 5: Bi-quinticreturn skimage.transform.rescale(img,factor,mode='constant',cval=0,order=1)if __name__ == '__main__':target = upsample_with_skimage(img=io.imread("feature_map.jpg"), factor=5)io.imsave("upsampling.png", target, interpolation='none')
很多人把这个过程叫做“反卷积(deconvolution)”,但我认为这么叫是错误的,它的过程并不是对卷积的逆运算,它除了用在FCN中还会用在卷积可视化、对抗神经网络中。
原理如下:
假设,输入为4×4、输出为2×2、卷积核为3×3,则把输出、输入和卷积核按照从左到右、从上到下展开为向量,前向传播的卷积过程相当于输入与以下稀疏矩阵的乘积:
误差反向传播(如果记不清了可以回看5.1节):
那么反过来,我们希望从4维向量映射回16维向量怎么做呢:把上面过程逆反一下(当然该做padding还得做):
前向传播:
整个过程平滑柔顺,多种情况下的详细解释可以看:《Convolution arithmetic tutorial》
keras下做转置卷积,输入feature map及最终效果与8.7.4。
# -*- coding: utf-8 -*-from __future__ import divisionimport numpy as npimport tensorflow as tffrom skimage import ioimport skimageimport ioimport osimport keras.backend as Kdef get_kernel_size(factor):"""给定上采样因子,返回核大小,上采样因子大小等于转置卷积步长。"""return 2 * factor - factor % 2def upsample_filt(size):"""返回上采样bilinear kernel矩阵。"""factor = (size + 1) // 2if size % 2 == 1:center = factor - 1else:center = factor - 0.5og = np.ogrid[:size, :size]return (1 - abs(og[0] - center) / factor) * \(1 - abs(og[1] - center) / factor)def bilinear_upsample_weights(factor, channel):"""使用bilinear filter初始化转置卷积权重矩阵。"""filter_size = get_kernel_size(factor)weights = np.zeros((filter_size,filter_size,channel,channel), dtype=np.float32)upsample_kernel = upsample_filt(filter_size)for i in xrange(channel):weights[:, :, i, i] = upsample_kernelreturn weightsdef upsample_keras(factor, input_img):SCALE = 256channel = input_img.shape[2]scale_height = input_img.shape[0] * factorscale_width = input_img.shape[1] * factorexpanded_img = np.expand_dims(input_img, axis=0)with tf.device("/gpu:1"):gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=1, allow_growth=True)os.environ["CUDA_VISIBLE_DEVICES"] = "1"sess = tf.Session(config=K.tf.ConfigProto(allow_soft_placement=True,log_device_placement=True,gpu_options=gpu_options))input_value = tf.placeholder(tf.float32)trans_filter = tf.placeholder(tf.float32)upsample_filter_np = bilinear_upsample_weights(factor, channel)res = K.conv2d_transpose(input_value, trans_filter,output_shape=[1, scale_height, scale_width, channel],padding='same',strides=(factor, factor))final_result = sess.run(res,feed_dict={trans_filter: upsample_filter_np,input_value: expanded_img})if channel != 1:return final_result.squeeze() / SCALEreturn final_result.squeeze()upsampled_img_keras = upsample_keras(factor=5, input_img=skimage.io.imread("feature_map.jpg"))skimage.io.imsave("bilinear_feature_map.jpg",upsampled_img_keras, interpolation='none')
开源代码可参见:Keras-FCN,虽然缺点是训练有点慢,模型有点大,但对于理解如何实现很有帮助。
里面实现了五种模型,两种基于vgg-16,两种基于resnet-50,一种基于densenet。
上采样操作做为一个新的网络层意味着它需要能够前向传播、反向传播、更新权重,其实现在代码中为BilinearUpSampling.py。
inference.py的代码需要稍微变下:
import numpy as npimport matplotlib.pyplot as pltfrom pylab import *import osimport sysimport cv2from PIL import Imagefrom keras.preprocessing.image import *from keras.models import load_modelimport keras.backend as Kfrom keras.applications.imagenet_utils import preprocess_inputfrom models import *def inference(model_name, weight_file, image_size, image_list, data_dir, label_dir, return_results=True, save_dir=None,label_suffix='.png',data_suffix='.jpg'):current_dir = os.path.dirname(os.path.realpath(__file__))# mean_value = np.array([104.00699, 116.66877, 122.67892])batch_shape = (1, ) + image_size + (3, )save_path = os.path.join(current_dir, 'Models/'+model_name)model_path = os.path.join(save_path, "model.json")checkpoint_path = os.path.join(save_path, weight_file)# model_path = os.path.join(current_dir, 'model_weights/fcn_atrous/model_change.hdf5')# model = FCN_Resnet50_32s((480,480,3))#config = tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True))#session = tf.Session(config=config)#K.set_session(session)model = globals()[model_name](batch_shape=batch_shape, input_shape=(512, 512, 3))model.load_weights(checkpoint_path, by_name=True)model.summary()results = []total = 0for img_num in image_list:img_num = img_num.strip('\n')total += 1print('#%d: %s' % (total,img_num))image = Image.open('%s/%s%s' % (data_dir, img_num, data_suffix))image = img_to_array(image) # , data_format='default')label = Image.open('%s/%s%s' % (label_dir, img_num, label_suffix))label_size = label.sizeimg_h, img_w = image.shape[0:2]# long_side = max(img_h, img_w, image_size[0], image_size[1])pad_w = max(image_size[1] - img_w, 0)pad_h = max(image_size[0] - img_h, 0)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.)# image -= mean_value'''img = array_to_img(image, 'channels_last', scale=False)img.show()exit()'''# image = cv2.resize(image, image_size)image = np.expand_dims(image, axis=0)image = preprocess_input(image)result = model.predict(image, batch_size=1)result = np.argmax(np.squeeze(result), axis=-1).astype(np.uint8)result_img = Image.fromarray(result, mode='P')result_img.palette = label.palette# result_img = result_img.resize(label_size, resample=Image.BILINEAR)result_img = result_img.crop((pad_w/2, pad_h/2, pad_w/2+img_w, pad_h/2+img_h))# result_img.show(title='result')if return_results:results.append(result_img)if save_dir:result_img.save(os.path.join(save_dir, img_num + '.png'))return resultsif __name__ == '__main__':with tf.device('/gpu:1'):gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=1, allow_growth=True)os.environ["CUDA_VISIBLE_DEVICES"] = "1"tf.Session(config=K.tf.ConfigProto(allow_soft_placement=True,log_device_placement=True,gpu_options=gpu_options))model_name = 'AtrousFCN_Resnet50_16s'weight_file = 'checkpoint_weights.hdf5'image_size = (512, 512)data_dir = os.path.expanduser('~/.keras/datasets/VOC2012/VOCdevkit/VOC2012/JPEGImages')label_dir = os.path.expanduser('~/.keras/datasets/VOC2012/VOCdevkit/VOC2012/SegmentationClass')image_list = sys.argv[1:]#'2007_000491'results = inference(model_name, weight_file, image_size, image_list, data_dir, label_dir, save_dir="result")for result in results:result.show(title='result', command=None)
如有遗漏请提醒我补充:
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/