想像你正在解決一個拼圖遊戲。你已經完成了大部分。假設您需要在一幅幾乎完成的圖片中間修復一塊。你需要從盒子裡選擇一塊,它既適合空間,又能完成整個畫面。
我相信你很快就能做到。但是你的大腦是怎麼做到的呢?
首先,它會分析空槽周圍的圖片(在這裡你需要固定拼圖的一塊)。如果圖片中有一棵樹,你會尋找綠色的部分(這是顯而易見的!)所以,簡而言之,我們的大腦能夠通過知道圖像周圍的環境來預測圖像(它將適合放入槽中)。
在本教程中,我們的模型將執行類似的任務。它將學習圖像的上下文,然後利用學習到的上下文預測圖像的一部分(缺失的部分)。
在這篇文章之前,我們先看一下代碼實現
我建議您在另一個選項卡中打開這個筆記本(TF實現),這樣您就可以直觀地了解發生了什麼。
colab/drive/1zFe9TmMCK2ldUOsVXenvpbNY2FLrLh5k#scrollTo=UXjElGKzyiey&forceEdit=true&sandboxMode=true
問題
我們希望我們的模型能預測圖像的一部分。給定一個有部份缺失圖像(只有0的圖像陣列的一部分),我們的模型將預測原始圖像是完整的。
因此,我們的模型將利用它在訓練中學習到的上下文重建圖像中缺失的部分。
數據
我們將為任務選擇一個域。我們選擇了一些山地圖像,它們是Puneet Bansal在Kaggle上的 Intel Image Classification數據集的一部分。
為什麼只有山脈的圖像?
在這裡,我們選擇屬於某個特定域的圖像。如果我們選擇的數據集中有更廣泛圖像,我們的模型將不能很好地執行。因此,我們將其限制在一個域內。
使用wget下載我在GitHub上託管的數據
!wget github/shubham0204/Dataset_Archives/blob/master/mountain_images.zip?raw=true -O images.zip !unzip images.zip
為了生成訓練數據,我們將遍歷數據集中的每個圖像,並對其執行以下任務,
首先,我們將使用PIL.Image.open()讀取圖像文件。使用np.asarray()將這個圖像對象轉換為一個NumPy數組。
確定窗口大小。這是正方形的邊長這是從原始圖像中得到的。
在[ 0 , image_dim — window_size ]範圍內生成2個隨機數。image_dim是我們的方形輸入圖像的大小。
這兩個數字(稱為px和py)是從原始圖像剪裁的位置。選擇圖像數組的一部分,並將其替換為零數組。
代碼如下
x = [] y = [] input_size = ( 228 , 228 , 3 ) # Take out a square region of side 50 px. window_size = 50 # Store the original images as target images. for name in os.listdir( 'mountain_images/' ): image = Image.open( 'mountain_images/{}'.format( name ) ).resize( input_size[0:2] ) image = np.asarray( image ).astype( np.uint8 ) y.append( image ) for name in os.listdir( 'mountain_images/' ): image = Image.open( 'mountain_images/{}'.format( name ) ).resize( input_size[0:2] ) image = np.asarray( image ).astype( np.uint8 ) # Generate random X and Y coordinates within the image bounds. px , py = random.randint( 0 , input_size[0] - window_size ) , random.randint( 0 , input_size[0] - window_size ) # Take that part of the image and replace it with a zero array. This makes the "missing" part of the image. image[ px : px + window_size , py : py + window_size , 0:3 ] = np.zeros( ( window_size , window_size , 3 ) ) # Append it to an array x.append( image ) # Normalize the images x = np.array( x ) / 255 y = np.array( y ) / 255 # Train test split x_train, x_test, y_train, y_test = train_test_split( x , y , test_size=0.2 )
自動編碼器模型與跳連接
我們添加跳轉連接到我們的自動編碼器模型。這些跳過連接提供了更好的上採樣。通過使用最大池層,許多空間信息會在編碼過程中丟失。為了從它的潛在表示(由編碼器產生)重建圖像,我們添加了跳過連接,它將信息從編碼器帶到解碼器。
alpha = 0.2 inputs = Input( shape=input_size ) conv1 = Conv2D( 32 , kernel_size=( 3 , 3 ) , strides=1 )( inputs ) relu1 = LeakyReLU( alpha )( conv1 ) conv2 = Conv2D( 32 , kernel_size=( 3 , 3 ) , strides=1 )( relu1 ) relu2 = LeakyReLU( alpha )( conv2 ) maxpool1 = MaxPooling2D()( relu2 ) conv3 = Conv2D( 64 , kernel_size=( 3 , 3 ) , strides=1 )( maxpool1 ) relu3 = LeakyReLU( alpha )( conv3 ) conv4 = Conv2D( 64 , kernel_size=( 3 , 3 ) , strides=1 )( relu3 ) relu4 = LeakyReLU( alpha )( conv4 ) maxpool2 = MaxPooling2D()( relu4 ) conv5 = Conv2D( 128 , kernel_size=( 3 , 3 ) , strides=1 )( maxpool2 ) relu5 = LeakyReLU( alpha )( conv5 ) conv6 = Conv2D( 128 , kernel_size=( 3 , 3 ) , strides=1 )( relu5 ) relu6 = LeakyReLU( alpha )( conv6 ) maxpool3 = MaxPooling2D()( relu6 ) conv7 = Conv2D( 256 , kernel_size=( 1 , 1 ) , strides=1 )( maxpool3 ) relu7 = LeakyReLU( alpha )( conv7 ) conv8 = Conv2D( 256 , kernel_size=( 1 , 1 ) , strides=1 )( relu7 ) relu8 = LeakyReLU( alpha )( conv8 ) upsample1 = UpSampling2D()( relu8 ) concat1 = Concatenate()([ upsample1 , conv6 ]) convtranspose1 = Conv2DTranspose( 128 , kernel_size=( 3 , 3 ) , strides=1)( concat1 ) relu9 = LeakyReLU( alpha )( convtranspose1 ) convtranspose2 = Conv2DTranspose( 128 , kernel_size=( 3 , 3 ) , strides=1 )( relu9 ) relu10 = LeakyReLU( alpha )( convtranspose2 ) upsample2 = UpSampling2D()( relu10 ) concat2 = Concatenate()([ upsample2 , conv4 ]) convtranspose3 = Conv2DTranspose( 64 , kernel_size=( 3 , 3 ) , strides=1)( concat2 ) relu11 = LeakyReLU( alpha )( convtranspose3 ) convtranspose4 = Conv2DTranspose( 64 , kernel_size=( 3 , 3 ) , strides=1 )( relu11 ) relu12 = LeakyReLU( alpha )( convtranspose4 ) upsample3 = UpSampling2D()( relu12 ) concat3 = Concatenate()([ upsample3 , conv2 ]) convtranspose5 = Conv2DTranspose( 32 , kernel_size=( 3 , 3 ) , strides=1)( concat3 ) relu13 = LeakyReLU( alpha )( convtranspose5 ) convtranspose6 = Conv2DTranspose( 3 , kernel_size=( 3 , 3 ) , strides=1 , activation='relu' )( relu13 ) model = tf.keras.models.Model( inputs , convtranspose6 ) model.compile( loss='mse' , optimizer='adam' , metrics=[ 'mse' ] )
最後,訓練我們的自動編碼器模型,
model.fit( x_train , y_train , epochs=150 , batch_size=25 , validation_data=( x_test , y_test ) )
結論
以上結果是在少數測試圖像上得到的。我們觀察到模型幾乎已經學會了如何填充黑盒!但我們仍然可以分辨出盒子在原始圖像中的位置。這樣,我們就可以建立一個模型來預測圖像缺失的部分。
這裡我們只是用了一個簡單的模型來作為樣例,如果我們要推廣到現實生活中,就需要使用更大的數據集和更深的網絡,例如可以使用現有的sota模型,加上imagenet的圖片進行訓練。
作者 Shubham Panchal
deephub 翻譯組