Udacity机器人软件工程师课程笔记(二十九) - 全卷积网络(FCN)
全卷積網絡(FCN)
1.全卷積神經網絡介紹
FCN對圖像進行像素級的分類,從而解決了語義級別的圖像分割(semantic segmentation)問題。與經典的CNN在卷積層之后使用全連接層得到固定長度的特征向量進行分類(全聯接層+softmax輸出)不同,FCN可以接受任意尺寸的輸入圖像,采用反卷積層對最后一個卷積層的feature map進行上采樣, 使它恢復到輸入圖像相同的尺寸,從而可以對每個像素都產生了一個預測, 同時保留了原始輸入圖像中的空間信息, 最后在上采樣的特征圖上進行逐像素分類。
全卷積網絡利用三種特殊技術
- 以一層層卷積層替代全連接層(1x1卷積)
- 利用轉置卷積層進行上采樣
- 跳躍連接
全卷積網絡在結構上通常有編碼器(Encoder)和解碼器(Decoder)組成。編碼器是一系列卷積層,如VGG和ResNet。編碼器的目標是從圖像中提取特征,解碼器放大編碼器的輸出,使它和原來的圖像大小相同。
2.1x1卷積
卷積運算的輸出是通過用滑動窗口來掃描輸入的卷積核,以及執行元素相乘和求和來實現的。
一個1x1的卷積本質上是與一組維數的濾波器進行卷積:
- 1x1xfilter_size (HxWxD),
- stride = 1,
- padding為全零填充(‘SAME‘)。
1x1卷積有助于降低該層的維數。相同大小的全連接層會產生相同數量的特征。然而,用卷積層替換全連接層有一個額外的好處,在(測試模型期間,可以將任何大小的圖像輸入到訓練好的網絡中。
import numpy as np
import tensorflow as tf# 自定義初始化,默認設置種子為0
def custom_init(shape, dtype=tf.float32, partition_info=None, seed=0):return tf.random_normal(shape, dtype=dtype, seed=seed)# TODO:使用“tf.layers。conv2d '來重現' tf.layers. density '的結果。
# 設置 `kernel_size` 和 `stride`.
def conv_1x1(x, num_outputs):kernel_size = 1stride = 1return tf.layers.conv2d(x, num_outputs, kernel_size, stride, weights_initializer=custom_init)num_outputs = 2
x = tf.constant(np.random.randn(1, 2, 2, 1), dtype=tf.float32)
# 如果>的秩是2,則輸入張量被‘tf. layer’壓扁,然后再把它重塑回原來的秩作為輸出
dense_out = tf.layers.dense(x, num_outputs, weights_initializer=custom_init)
conv_out = conv_1x1(x, num_outputs)with tf.Session() as sess:sess.run(tf.global_variables_initializer())a = sess.run(dense_out)b = sess.run(conv_out)print("Dense Output =", a)print("Conv 1x1 Output =", b)print("Same output? =", np.allclose(a, b, atol=1.e-5))
但是我發現并不能運行,程序報如下錯誤:
TypeError: conv2d() got an unexpected keyword argument 'weights_initializer'
經查詢之后,tf.layers.dense和tf.layers.conv2d中并沒有相關參數,于是將weights_initializer=custom_init更改為kernel_initializer=custom_init,程序正確輸出如下:
Dense Output = [[[[-0.07399321 0.3901071 ][-0.21965009 1.1580395 ]][[ 0.29445606 -1.5524316 ][-0.7433553 3.9191186 ]]]]
Conv 1x1 Output = [[[[-0.07399321 0.3901071 ][-0.21965009 1.1580395 ]][[ 0.29445606 -1.5524316 ][-0.7433553 3.9191186 ]]]]
Same output? = True
1x1卷積函數注釋:
tf.layers.conv2d(x, num_outputs, 1, 1, kernel_initializer=custom_init)。
- num_outputs 定義輸出通道或內核的數量
- 第三個參數是內核大小,即
1。 - 第四個參數是步幅,我們將其設置為
1。 kernel_initializer=custom_init:我們使用自定義初始化程序,因此dense層和卷積層中的權重相同。
這就是保留空間信息的矩陣乘法運算。
3.轉置卷積
我們可以使用轉置卷積來創建全卷積網絡的解碼器。轉置卷積本質上是一個反向卷積 ,其中前向和反向傳播被調換。因此,我們稱它為轉置卷積。
有些人可能稱它為反卷積,因為它撤銷了前一個卷積,由于我們所做的只是調換前向傳播和反向傳播的順序,這里的數學計算實際上和之前做的完全一樣。因此,其可微性質保留了下來。而訓練與之前的神經網絡完全相同。
轉置卷積有助于將上一層上采樣到所需的分辨率或尺寸。假設有一個3x3的輸入,并且希望將其上采樣到所需的6x6尺寸。該過程涉及將輸入的每個像素與內核或過濾器相乘。如果此過濾器的大小為5x5,則此操作的輸出將為大小為5x5的加權內核。然后,該加權內核定義您的輸出層。
但是,該過程的上采樣部分由步幅和填充定義。在TensorFlow中,使用tf.layers.conv2d_transpose,跨度為2和“ SAME”填充將導致輸出尺寸為6x6。
如果我們有2x2輸入和3x3內核;使用“ SAME”填充,并且跨度為2,我們可以預期輸出尺寸為4x4。下圖給出了該過程的想法。3x3加權內核(輸入像素與3x3內核的乘積)由紅色和藍色正方形表示,它們之間的步幅為2。虛線正方形表示輸出周圍的填充。隨著加權核的移動,步幅決定了輸出的最終尺寸。這些參數的不同值將導致上采樣輸出的尺寸不同。
在TensorFlow中,API tf.layers.conv2d_transpose用于創建轉置的卷積層,程序如下:
import tensorflow as tf
import numpy as npdef upsample(x):"""對x應用兩倍upsample并返回結果"""output = tf.layers.conv2d_transpose(x, 3, [2, 2], (2, 2), padding='SAME')return outputx = tf.constant(np.random.randn(1, 4, 4, 3), dtype=tf.float32)
conv = upsample(x)with tf.Session() as sess:sess.run(tf.global_variables_initializer())result = sess.run(conv)print('Input Shape: {}'.format(x.get_shape()))print('Output Shape: {}'.format(result.shape))
輸出如下:
Input Shape: (1, 4, 4, 3)
Output Shape: (1, 8, 8, 3)
程序注釋
使用tf.layers.conv2d_transpose(x, 3, (2, 2), (2, 2))上采樣。
- 第二個參數3是內核/輸出通道的數量。
- 第三個參數是內核大小(2,2)。注意,內核大小也可以是(1,1),輸出形狀也可以是相同的。但是,如果將其更改為(3,3),請注意形狀將為(9,9),至少使用“VALID”填充。
- 第四個參數,步幅數,是我們如何從高到寬從(4, 4)到(8, 8)。如果這是一個常規的卷積,則輸出的高度和寬度將為(2, 2)。
4.跳躍鏈接
全卷積網絡使用的第三種特殊技術,是跳躍連接。一般來說 卷積或編碼的一個缺點是,當你仔細觀察某張圖片,會導致范圍縮小,從而失去了更大的圖片。因此 即使我們要將編碼器的輸出解碼回原始圖像尺寸,一些信息卻已經丟失。
跳躍連接是輕松保留信息的一種方式。跳躍連接工作的方式是使一層的輸出與一個非相鄰層連接。這里使用按元素添加操作 ,將來自編碼器的池化層的輸出與當前層的輸出相結合,最終的結果連接到下一層。這些跳躍連接使網絡可以使用來自多分辨率的信息,因此網絡能夠做出更精確的分割決策。
總結
以上是生活随笔為你收集整理的Udacity机器人软件工程师课程笔记(二十九) - 全卷积网络(FCN)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Udacity机器人软件工程师课程笔记(
- 下一篇: Udacity机器人软件工程师课程笔记(