吳恩達Coursera深度學習課程 deeplearning.ai (4-4) 人臉識別和神經風格轉換--編程作業

Part 1:Happy House 的人臉識別

本週的第一個作業我們將完成一個人臉識別系統。

人臉識別問題可以分爲兩類:

  • 人臉驗證: 輸入圖片,驗證是不是A
    • 1:1 識別
    • 舉例:人臉解鎖手機,人臉刷卡
  • 人臉識別: 有一個庫,輸入圖片,驗證是不是庫裏的一員
    • 1:K 識別
    • 舉例:員工門禁

FaceNet 通過神經網絡學習將圖片編碼爲128維數字向量。通過比較兩個128維向量的相似度來確定兩張圖片是否是同一個人。

在這個作業中,你需要:

  • 實現三元組損失函數
  • 使用一個預訓練模型將圖片轉換爲128維數字向量
  • 運用這些128維的數字向量來執行人臉驗證和人臉識別

在這個練習中,你將使用一個預訓練模型,該模型表示圖片時使用”通道在前”的維度表示,(m,nC,nH,nW) 而不是之前的 (m,nH,nW,nC)。

開源社區中兩種表示方法都很常見,沒有統一的標準。

導包
from keras.models import Sequential
from keras.layers import Conv2D, ZeroPadding2D, Activation, Input, concatenate
from keras.models import Model
from keras.layers.normalization import BatchNormalization
from keras.layers.pooling import MaxPooling2D, AveragePooling2D
from keras.layers.merge import Concatenate
from keras.layers.core import Lambda, Flatten, Dense
from keras.initializers import glorot_uniform
from keras.engine.topology import Layer
from keras import backend as K
K.set_image_data_format('channels_first')
import cv2
import os
import numpy as np
from numpy import genfromtxt
import pandas as pd
import tensorflow as tf
from fr_utils import *
from inception_blocks_v2 import *

%matplotlib inline
%load_ext autoreload
%autoreload 2

np.set_printoptions(threshold=np.nan)

0 樸素人臉驗證

在人臉驗證中,給你兩張圖片,你需要給出這兩張圖片是否是同一個人。最簡單的方式是一一比較兩張圖片的像素,如果兩張圖片的像素距離小於一個閾值,就認爲是同一個人。

image

當然,這個算法的表現着實很差,因爲圖片的像素會隨着燈光,角度,頭部位置等元素而劇烈變化。

你會發現,不通過原始圖片進行比較,而是通過機器學習將圖片進行編碼爲相同維度的向量,然後計算向量的距離來識別是否是同一個人,擁有更高的準確率。

1 將圖片編碼爲128維的向量

1.1 使用ConvNet編碼圖片

FaceNet模型需要大量的數據和時間去訓練,這裏我們使用別人已經訓練好的模型,你也可以從inception_blocks.py看到模型是怎麼實現的。

你需要知道:

  • 輸入:96x96 RGB:(m,nC,nH,nW) = (m,3,96,96)
  • 輸出: (m,128)
FRmodel = faceRecoModel(input_shape=(3, 96, 96))
print("Total Params:", FRmodel.count_params())
# Total Params: 3743280

在模型的最後一層使用128個神經元的全連接層,模型確保輸出維度爲128,然後就可以利用編碼後的向量比較兩個圖片了。

image

如果符合以下條件,編碼方式就是好的:

  • 同一個人的兩張照片的編碼非常相似
  • 不同人的兩張照片編碼差別很大

三元組損失函數(triplet loss function) 很好的實現了上述要求,將同一個人的兩張照片 (anchor 和 Positive) 的編碼 推得很近,將不同人的兩張照片 (anchor 和 Negative) 的編碼拉的很遠。

image

1.2 三元組損失

對於一張圖片 x,另其編碼爲f(x), 其中f是從神經網絡計算出來的

image

三元組圖片:(A, P, N)

  • A:Anchor – 一個人的圖片
  • P:Positive – 與Ancher同一個人的圖片
  • N:Negative – 與Ancher不同人的圖片

我們訓練集中已經有這些三元組數據,用(A(i), P(i), N(i)) 來表示訓練集第i個樣本。

你需要確保 A(i) 到 P(i) 的距離至少比到 N(i) 的距離遠一個alpha。

f(A(i))f(P(i))22+α<∣∣f(A(i))f(N(i))22

我們將最小化如下的三元組損失 J:

z=i=1N[f(A(i))f(P(i))22(1)f(A(i))f(N(i))22(2)+α]

J=max(z,0)

注意:
  • 公式中(1)部分是”A”到”P”的平方距離,我們想要縮小
  • 公式中(2)部分是”A”到”N”的平方距離,我們想要擴大,所以這裏用了減法,而減完之後是個距離的負值
  • alpha 是一個距離閾值的超參數,需要手動設定,這裏使用0.2

很多實現都是先將圖片編碼進行歸一化再比較的,這裏我們不用考慮。

練習:實現三元組損失函數
  1. 計算”A”到”P”的距離
    f(A(i))f(P(i))22
  2. 計算”A”到”N”的距離
    f(A(i))f(N(i))22
  3. 計算 z
    z=∣∣f(A(i))f(P(i))∣∣f(A(i))f(N(i))22+α
  4. 計算 z 與 0 的最大值
    J=max(z,0)

有用的函數:tf.reduce_sum(), tf.square(), tf.subtract(), tf.add(), tf.maximum()

# GRADED FUNCTION: triplet_loss

def triplet_loss(y_true, y_pred, alpha = 0.2):
    """
    Implementation of the triplet loss as defined by formula (3)

    Arguments:
    y_true -- true labels, required when you define a loss in Keras, you don't need it in this function.
    y_pred -- python list containing three objects:
            anchor -- the encodings for the anchor images, of shape (None, 128)
            positive -- the encodings for the positive images, of shape (None, 128)
            negative -- the encodings for the negative images, of shape (None, 128)

    Returns:
    loss -- real number, value of the loss
    """

    anchor, positive, negative = y_pred[0], y_pred[1], y_pred[2]

    ### START CODE HERE ### (≈ 4 lines)
    # Step 1: Compute the (encoding) distance between the anchor and the positive, you will need to sum over axis=-1
    pos_dist = tf.reduce_sum(tf.square(tf.subtract(anchor, positive)))
    # Step 2: Compute the (encoding) distance between the anchor and the negative, you will need to sum over axis=-1
    neg_dist = tf.reduce_sum(tf.square(tf.subtract(anchor, negative)))
    # Step 3: subtract the two previous distances and add alpha.
    basic_loss = tf.add(tf.subtract(pos_dist,neg_dist), alpha)
    # Step 4: Take the maximum of basic_loss and 0.0. Sum over the training examples.
    loss = tf.reduce_sum(tf.maximum(basic_loss, 0.))
    ### END CODE HERE ###

    return loss

#########################################################

with tf.Session() as test:
    tf.set_random_seed(1)
    y_true = (None, None, None)
    y_pred = (tf.random_normal([3, 128], mean=6, stddev=0.1, seed = 1),
              tf.random_normal([3, 128], mean=1, stddev=1, seed = 1),
              tf.random_normal([3, 128], mean=3, stddev=4, seed = 1))
    loss = triplet_loss(y_true, y_pred)

    print("loss = " + str(loss.eval()))

# loss = 350.026

2 導入預訓練模型

FaceNet已經使用最小化三元組損失訓練過了,但是訓練需要大量的數據和時間,這裏我們使用訓練好的模型。

FRmodel.compile(optimizer = 'adam', loss = triplet_loss, metrics = ['accuracy'])
load_weights_from_FaceNet(FRmodel)

三組個人圖片的編碼和距離示例

image

現在我們用這個模型來進行人臉驗證和人臉識別。

3 應用模型

回到Happy House! 因爲你在之前的作業中設計了快樂識別模型,成員們幸福快樂的住在Happy House裏。

然而,還是出現了一些問題:由於Happy House 特別Happy,很多鄰居都來串門,使房間變得很擁擠,對大家產生了負面的影響。這些隨機進來的快樂的人還吃光了你們所有的食物。

所以,你決定改變門禁策略,不再讓快樂的人隨便進來,而是採用一個人臉驗證系統來保證只有名單上的人可以進來,給每個乘客發一張ID卡,每個進來的人都必須刷卡,人臉驗證系統來驗證刷卡的人是不是本人。

3.1 人臉驗證

讓我們建立一個訪客照片的編碼向量數據集, 使用 img_to_encoding(image_path, model) 方法將圖片轉換爲128維的向量。

database = {}
database["danielle"] = img_to_encoding("images/danielle.png", FRmodel)
database["younes"] = img_to_encoding("images/younes.jpg", FRmodel)
database["tian"] = img_to_encoding("images/tian.jpg", FRmodel)
database["andrew"] = img_to_encoding("images/andrew.jpg", FRmodel)
database["kian"] = img_to_encoding("images/kian.jpg", FRmodel)
database["dan"] = img_to_encoding("images/dan.jpg", FRmodel)
database["sebastiano"] = img_to_encoding("images/sebastiano.jpg", FRmodel)
database["bertrand"] = img_to_encoding("images/bertrand.jpg", FRmodel)
database["kevin"] = img_to_encoding("images/kevin.jpg", FRmodel)
database["felix"] = img_to_encoding("images/felix.jpg", FRmodel)
database["benoit"] = img_to_encoding("images/benoit.jpg", FRmodel)
database["arnaud"] = img_to_encoding("images/arnaud.jpg", FRmodel)

現在你就可以驗證刷卡的是不是本人了。

練習: 實現 verify() 方法
  1. 計算門前人照片的編碼向量
  2. 計算ID所有者編碼向量與門前照片向量的距離
  3. 如果距離小於0.7 就開門,否則不開門

這裏我們是有L2距離:np.linalg.norm,而不是L2距離的平方
門檻閾值爲 0.7

# GRADED FUNCTION: verify

def verify(image_path, identity, database, model):
    """
    Function that verifies if the person on the "image_path" image is "identity".

    Arguments:
    image_path -- path to an image
    identity -- string, name of the person you'd like to verify the identity. Has to be a resident of the Happy house.
    database -- python dictionary mapping names of allowed people's names (strings) to their encodings (vectors).
    model -- your Inception model instance in Keras

    Returns:
    dist -- distance between the image_path and the image of "identity" in the database.
    door_open -- True, if the door should open. False otherwise.
    """

    ### START CODE HERE ###

    # Step 1: Compute the encoding for the image. Use img_to_encoding() see example above. (≈ 1 line)
    encoding = img_to_encoding(image_path, model)

    # Step 2: Compute distance with identity's image (≈ 1 line)
    dist = np.linalg.norm(encoding - database[identity])

    # Step 3: Open the door if dist < 0.7, else don't open (≈ 3 lines)
    if dist < 0.7:
        print("It's " + str(identity) + ", welcome home!")
        door_open = True
    else:
        print("It's not " + str(identity) + ", please go away")
        door_open = False

    ### END CODE HERE ###

    return dist, door_open

Younes 想進來,然後門上攝像頭拍了一張圖片(“images/camera_0.jpg”), 然我們驗證一下吧。

image

verify("images/camera_0.jpg", "younes", database, FRmodel)

# It's younes, welcome home!
# 
# (0.65939283, True)

Benoit 上週打破了魚缸,已經被趕出去了,數據庫也除名了,他拿了 Kian 的ID卡想進來試圖說自己就是Kian, 門上攝像頭採集看他一張照片(“images/camera_2.jpg”), 讓我們驗證一下吧。

image

verify("images/camera_2.jpg", "kian", database, FRmodel)

#It's not kian, please go away
#
#(0.86224014, False)

3.2 人臉識別

你的人臉驗證系統通常工作的很好,但是自從Kian的ID卡被偷了,晚上他回來後都進不來了。

爲了減少這種烏龍的發生,你想將你的人臉驗證系統改造成人臉識別系統。這樣大家就不用帶卡了,但有權限的人走到門前,門將會自動爲他解鎖。

爲了實現人臉識別系統,你需要輸入一張圖片,然後系統識別這個人在不在有權限人員集合中。和之前的人臉驗證系統不一樣,這次你不再需要輸入某人的姓名或ID了。

練習:實現 who_is_it()
  1. 計算門前人照片的編碼向量
  2. 從數據庫找出與門前人編碼向量距離最小的編碼向量。
    • 初始化最小距離(min_dist)爲一個足夠大的值(100)。用來跟蹤距離最小值。
    • 循環數據庫中的人的編碼向量:for (name, db_enc) in database.items()
    • 計算門前向量和當前循環向量的L2距離
    • 如果當前距離小於最小距離(min_dist),更新最小距離,記錄當前循環變量的人的名字
# GRADED FUNCTION: who_is_it

def who_is_it(image_path, database, model):
    """
    Implements face recognition for the happy house by finding who is the person on the image_path image.

    Arguments:
    image_path -- path to an image
    database -- database containing image encodings along with the name of the person on the image
    model -- your Inception model instance in Keras

    Returns:
    min_dist -- the minimum distance between image_path encoding and the encodings from the database
    identity -- string, the name prediction for the person on image_path
    """

    ### START CODE HERE ### 

    ## Step 1: Compute the target "encoding" for the image. Use img_to_encoding() see example above. ## (≈ 1 line)
    encoding = img_to_encoding(image_path, model)

    ## Step 2: Find the closest encoding ##

    # Initialize "min_dist" to a large value, say 100 (≈1 line)
    min_dist = 100

    # Loop over the database dictionary's names and encodings.
    for (name, db_enc) in database.items():

        # Compute L2 distance between the target "encoding" and the current "emb" from the database. (≈ 1 line)
        dist = np.linalg.norm(encoding - db_enc)

        # If this distance is less than the min_dist, then set min_dist to dist, and identity to name. (≈ 3 lines)
        if dist < min_dist:
            min_dist = dist
            identity = name

    ### END CODE HERE ###

    if min_dist > 0.7:
        print("Not in the database.")
    else:
        print ("it's " + str(identity) + ", the distance is " + str(min_dist))

    return min_dist, identity

Younes 走到門前,攝像頭採集了他的一張照片(”images/camera_0.jpg”),讓我們使用 who_it_is() 來試試吧。

who_is_it("images/camera_0.jpg", database, FRmodel)

# it's younes, the distance is 0.659393
# 
# (0.65939283, 'younes')

你可以將“camera_0.jpg” (younes) 換成 “camera_1.jpg” (bertrand) 然後看下結果的變化。

你的 Happy House 系統工作的很好,只允許有權限的人進入,大家也不用再帶 ID 卡了。

現在你知道高效的人臉識別系統是怎麼工作的了。

有些改進我們算法的方式 (這裏沒有給出實現):

  • 每個人輸入更多的圖片(不同燈光的,不同背景的,不同角度的,不同日期拍攝的等等),人臉識別時與這些圖片進行比較。可以提高識別的準確率。
  • 剪裁圖片只保留人臉部分,減少人臉之外的背景干擾區,相當於減少了一些不相關的像素。可以使算法更具有魯棒性。

謹記

  • 人臉驗證解決的是1:1匹配問題,人臉識別解決的是1:K 匹配問題
  • 三元組損失是一個有效的編碼圖片爲向量的神經網絡方法
  • 同一個編碼向量可以用於人臉驗證,也可以用於人臉識別。計算兩個圖片的編碼向量距離可以判斷兩張圖片是否是同一個人

恭喜你完成了這個作業!

相關引用

Part 2:深度學習和藝術-神經風格遷移

歡迎來到本週的第二個作業,我們將學習神經風格遷移。這個算法是2015年Gatys et al 創建的(https://arxiv.org/abs/1508.06576)

在這個作業中,你將會:

  • 實現神經風格遷移算法
  • 使用你的算法產生新奇的藝術圖片

之前學習的算法絕大部分都是優化代價函數來得到參數的值。在神經風格遷移中,是優化代價函數來獲得像素的值。

導包
import os
import sys
import scipy.io
import scipy.misc
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
from PIL import Image
from nst_utils import *
import numpy as np
import tensorflow as tf

%matplotlib inline

1 問題描述

神經風格遷移(Neural Style Transfer: NST)是深度學習中最有趣的技術之一。如下圖所示,它將兩張圖片,一張 content(C)和一張 style (S)合併起來產生了圖片 geenerated (G)。G 圖片擁有 C 的內容和 S 的風格。

例子中,我們將巴黎盧浮宮的圖片(C)合併到了印象派畫家莫奈的畫作中(S)。

image

讓我們看看怎麼實現它。

2 遷移學習

神經風格遷移(NST) 是建立在預訓練神經網絡之上的。在一個任務上訓練一個神經網絡然後運用在一個新任務上的方法叫做遷移學習。

根據 NST 論文(https://arxiv.org/abs/1508.06576)我們使用一個VGG-19的神經網絡。這個模型已經在大量圖片數據集上訓練好了,已經學習到可以在前些層識別大量的簡單特徵,在深些層識別高級特徵。

導入模型

model = load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")
print(model)

# {'input': <tf.Variable 'Variable:0' shape=(1, 300, 400, 3) dtype=float32_ref>, 'conv1_1': <tf.Tensor 'Relu:0' shape=(1, 300, 400, 64) dtype=float32>, 'conv1_2': <tf.Tensor 'Relu_1:0' shape=(1, 300, 400, 64) dtype=float32>, 'avgpool1': <tf.Tensor 'AvgPool:0' shape=(1, 150, 200, 64) dtype=float32>, 'conv2_1': <tf.Tensor 'Relu_2:0' shape=(1, 150, 200, 128) dtype=float32>, 'conv2_2': <tf.Tensor 'Relu_3:0' shape=(1, 150, 200, 128) dtype=float32>, 'avgpool2': <tf.Tensor 'AvgPool_1:0' shape=(1, 75, 100, 128) dtype=float32>, 'conv3_1': <tf.Tensor 'Relu_4:0' shape=(1, 75, 100, 256) dtype=float32>, 'conv3_2': <tf.Tensor 'Relu_5:0' shape=(1, 75, 100, 256) dtype=float32>, 'conv3_3': <tf.Tensor 'Relu_6:0' shape=(1, 75, 100, 256) dtype=float32>, 'conv3_4': <tf.Tensor 'Relu_7:0' shape=(1, 75, 100, 256) dtype=float32>, 'avgpool3': <tf.Tensor 'AvgPool_2:0' shape=(1, 38, 50, 256) dtype=float32>, 'conv4_1': <tf.Tensor 'Relu_8:0' shape=(1, 38, 50, 512) dtype=float32>, 'conv4_2': <tf.Tensor 'Relu_9:0' shape=(1, 38, 50, 512) dtype=float32>, 'conv4_3': <tf.Tensor 'Relu_10:0' shape=(1, 38, 50, 512) dtype=float32>, 'conv4_4': <tf.Tensor 'Relu_11:0' shape=(1, 38, 50, 512) dtype=float32>, 'avgpool4': <tf.Tensor 'AvgPool_3:0' shape=(1, 19, 25, 512) dtype=float32>, 'conv5_1': <tf.Tensor 'Relu_12:0' shape=(1, 19, 25, 512) dtype=float32>, 'conv5_2': <tf.Tensor 'Relu_13:0' shape=(1, 19, 25, 512) dtype=float32>, 'conv5_3': <tf.Tensor 'Relu_14:0' shape=(1, 19, 25, 512) dtype=float32>, 'conv5_4': <tf.Tensor 'Relu_15:0' shape=(1, 19, 25, 512) dtype=float32>, 'avgpool5': <tf.Tensor 'AvgPool_4:0' shape=(1, 10, 13, 512) dtype=float32>}

模型存儲在一個Python字典中,key是變量名,value是變量的值。我們可以將圖片傳給模型,在TensorFlow中:

model["input"].assign(image)

如果你想使用某個層次的激活函數(例如),可以運行以下命令:

sess.run(model["conv4_2"])

3 神經風格遷移

NST算法分爲3步:

  • 建立內容損失函數J_content(C, G)
  • 建立風格損失函數J_style(C, G)
  • 放到一起
    J(G)=αJcontent(C,G)+βJstyle(C,G)

3.1 計算內容損失

例子中,內容圖片(C)是一張巴黎盧浮宮的圖片,然我們來看一下。

content_image = scipy.misc.imread("images/louvre.jpg")
imshow(content_image)

image

圖中顯示了巴黎盧浮宮以及周圍的一些建築,晴朗的天空中飄着幾朵白雲。

3.1.1 怎樣確保新產生的圖片G能夠匹配C的內容?

我們討論過,ConvNet的較淺層次一般識別圖片的低級特徵,比如邊緣,紋理等;而較深層次則是別圖像的高級特徵,比如圖像的複雜結構。

我們想要新生成的圖片G的內容和內容圖片的C的內容相似。假設你選取了某些層的激活函數來表示圖片的內容。在實踐中,你選擇中間的層次會比較淺和較深的層次表現更好。(你可以嘗試使用不同的層次,看看效果)

假定你已經選好了一個特定的隱藏層。將圖片C輸入到預訓練的 VGG 網絡中,然後執行前向傳播。令a(C)表示我們選取的隱藏層的激活函數(nH × nW × nC), 在圖片G 上運用同樣的操作得到a(G),我們定義內容損失函數:

Jcontent(C,G)=14×nH×nW×nCall entries(a(C)a(G))2

代價函數標準項中的 nH, nW 和 nC 分別表示我們選取的隱藏層的高,寬和通道數。清晰起見,我們可以將a(C)和a(G)在每個通道上展開。

image

練習:用 TensorFlow 計算內容損失

提示:三步走
1. 得到a(G)的維度:X.get_shape().as_list()
2. 沿着通道將a(C)和a(G)展開到二維
3. 計算內容損失

# GRADED FUNCTION: compute_content_cost

def compute_content_cost(a_C, a_G):
    """
    Computes the content cost

    Arguments:
    a_C -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing content of the image C 
    a_G -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing content of the image G

    Returns: 
    J_content -- scalar that you compute using equation 1 above.
    """

    ### START CODE HERE ###
    # Retrieve dimensions from a_G (≈1 line)
    m, n_H, n_W, n_C = a_G.get_shape().as_list()

    # Reshape a_C and a_G (≈2 lines)
    a_C_unrolled = tf.reshape(a_C, [n_H*n_W, n_C])
    a_G_unrolled = tf.reshape(a_G, [n_H*n_W, n_C])

    # compute the cost with tensorflow (≈1 line)
    J_content = 1./(4 * n_H * n_W * n_C)*tf.reduce_sum(tf.square(tf.subtract(a_C_unrolled, a_G_unrolled)))
    ### END CODE HERE ###

    return J_content

############################################################

tf.reset_default_graph()

with tf.Session() as test:
    tf.set_random_seed(1)
    a_C = tf.random_normal([1, 4, 4, 3], mean=1, stddev=4)
    a_G = tf.random_normal([1, 4, 4, 3], mean=1, stddev=4)
    J_content = compute_content_cost(a_C, a_G)
    print("J_content = " + str(J_content.eval()))

# J_content = 6.76559

謹記

  • 內容損失採用了神經網絡的某一個隱藏層激活函數,然後計算衡量a(C)和a(G)的差距
  • 稍後我們將損失函數最小化,幫助我們保證G和C擁有相似的內容

3.2 計算風格損失

例子中,我們使用莫奈的畫作:

style_image = scipy.misc.imread("images/monet_800600.jpg")
imshow(style_image)

image

讓我們看看如何定義風格函數。

3.2.1 風格矩陣

風格矩陣又叫格拉姆矩陣(Gram matrix)。在線性代數中格拉姆矩陣G由一組向量(v1,…,vn)相互點乘(對應元素相乘)得到

Gij=viTvj=np.dot(vi,vj)

換言之,Gij 比較的是向量vivj 的相似度,相似度越高,則乘積越大,Gij 就越大。

注意: 這裏G表示的是Gram matrix的G,表示格拉姆矩陣,而不是新產生圖片的G。

在神經風格轉移(NST)中,我們可以利用展開矩陣和其轉置矩陣相乘來計算格拉姆矩陣。

image

計算結果的格拉姆矩陣維度(nC, nC), 其中 nC 表示通道數(卷積核(filter)個數)。Gij的值表徵的是filter i 的激活函數和filter j的激活函數的相似度。

格拉姆矩陣一個重要的部分是斜對角線上的元素Gii也表徵了filter i 的活躍度。舉個例子,假設 filter i 負責檢測垂直紋理,則Gii表示了圖片中垂直紋理的活躍度:Gii越大沒標明圖片中有越多的垂直紋理。

G中Gii表徵了各個filter自己的普遍情況,Gij表徵了不同filter共同作用的情況,這樣G可以衡量一張圖片的風格。

練習:實現矩陣的A的格拉姆矩陣(G = A*A^T)
# GRADED FUNCTION: gram_matrix

def gram_matrix(A):
    """
    Argument:
    A -- matrix of shape (n_C, n_H*n_W)

    Returns:
    GA -- Gram matrix of A, of shape (n_C, n_C)
    """

    ### START CODE HERE ### (≈1 line)
    GA = tf.matmul(A, tf.transpose(A))
    ### END CODE HERE ###

    return GA

##################################################

tf.reset_default_graph()

with tf.Session() as test:
    tf.set_random_seed(1)
    A = tf.random_normal([3, 2*1], mean=1, stddev=4)
    GA = gram_matrix(A)

    print("GA = " + str(GA.eval()))

# GA = [[  6.42230511  -4.42912197  -2.09668207]
#  [ -4.42912197  19.46583748  19.56387138]
#  [ -2.09668207  19.56387138  20.6864624 ]]

3.2.2 風格損失

到此,我們得到了一層的風格,如果我們結合幾個層次的風格損失將會獲得更好的結果。在完成這個練習後,你可以回過頭來試試給各層不同的權重,這裏平均分配了權重。

STYLE_LAYERS = [
    ('conv1_1', 0.2),
    ('conv2_1', 0.2),
    ('conv3_1', 0.2),
    ('conv4_1', 0.2),
    ('conv5_1', 0.2)]

像下面這樣合併不同層次的風格損失,其中 lambda_l 是 STYLE_LAYERS 中的各層權重

Jstyle(S,G)=lλ[l]Jstyle[l](S,G)
def compute_style_cost(model, STYLE_LAYERS):
    """
    Computes the overall style cost from several chosen layers

    Arguments:
    model -- our tensorflow model
    STYLE_LAYERS -- A python list containing:
                        - the names of the layers we would like to extract style from
                        - a coefficient for each of them

    Returns: 
    J_style -- tensor representing a scalar value, style cost defined above by equation (2)
    """

    # initialize the overall style cost
    J_style = 0

    for layer_name, coeff in STYLE_LAYERS:

        # Select the output tensor of the currently selected layer
        out = model[layer_name]

        # Set a_S to be the hidden layer activation from the layer we have selected, by running the session on out
        a_S = sess.run(out)

        # Set a_G to be the hidden layer activation from same layer. Here, a_G references model[layer_name] 
        # and isn't evaluated yet. Later in the code, we'll assign the image G as the model input, so that
        # when we run the session, this will be the activations drawn from the appropriate layer, with G as input.
        a_G = out

        # Compute style_cost for the current layer
        J_style_layer = compute_layer_style_cost(a_S, a_G)

        # Add coeff * J_style_layer of this layer to overall style cost
        J_style += coeff * J_style_layer

    return J_style

for 循環中,a_G 尚未計算,我們將會在下面的 model_nn 中對其進行循環計算和更新。

謹記

  • 可以使用一個隱藏層的激活函數的格拉姆矩陣表示圖片的風格。可以採用多個不同層次合併(權重分佈)來獲得更好的結果。這一點與內容損失不同,內容損失通常只使用一個隱藏層就足夠了。
  • 最小化風格損失可以使圖片G的風格相似於圖片S的風格。

3.3 定義優化函數的總體損失

讓我們定義一個方法來同時最小化風格和內容的損失

J(G)=αJcontent(C,G)+βJstyle(S,G)
# GRADED FUNCTION: total_cost

def total_cost(J_content, J_style, alpha = 10, beta = 40):
    """
    Computes the total cost function

    Arguments:
    J_content -- content cost coded above
    J_style -- style cost coded above
    alpha -- hyperparameter weighting the importance of the content cost
    beta -- hyperparameter weighting the importance of the style cost

    Returns:
    J -- total cost as defined by the formula above.
    """

    ### START CODE HERE ### (≈1 line)
    J = alpha * J_content + beta * J_style
    ### END CODE HERE ###

    return J

#########################################################

tf.reset_default_graph()

with tf.Session() as test:
    np.random.seed(3)
    J_content = np.random.randn()    
    J_style = np.random.randn()
    J = total_cost(J_content, J_style)
    print("J = " + str(J))

# J = 35.34667875478276

謹記

  • 總體損失是內容損失和風格損失的線性組合
  • alpha 和 beta 用來控制內容損失和風格損失的相對權重

4 解決最優化的問題

最後,我們將各部分組合到一起實現神經風格遷移。

程序需要完成如下事情:

  1. 創建一個交互式session
  2. 載入內容圖片
  3. 載入風格圖片
  4. 隨機初始化將要產生的圖片
  5. 載入VGG16模型
  6. 構建TensorFlow 結構圖:
    • 利用VGG16模型計算內容圖片的內容損失
    • 利用VGG16模型計算風格圖片的風格損失
    • 計算總體損失
    • 定義優化器和學習率
  7. 初始化TensorFolw 結構圖,執行多倫迭代,逐步更新新產生的圖片

下面我們給出每一步的細節

前文我們已經實現了總體損失函數J(G), 現在我們啓動TensorFlow來優化這個G。爲此,你需要重置這個結構圖並使用交互式Session,和普通Session不同,交互式Session將自己作爲一個默認的session來安裝並構建結構圖。這可以讓你在不需要因爲session對象的情況下允許變量,使編碼更加簡潔。

讓我們啓動交互式Session

# Reset the graph
tf.reset_default_graph()

# Start interactive session
sess = tf.InteractiveSession()

讓我們載入、轉換、和歸一化我們的”內容”圖片 (盧浮宮圖片)

content_image = scipy.misc.imread("images/louvre_small.jpg")
content_image = reshape_and_normalize_image(content_image)

讓我們載入、轉換、和歸一化我們的”風格”圖片 (莫奈畫作圖片)

style_image = scipy.misc.imread("images/monet.jpg")
style_image = reshape_and_normalize_image(style_image)

現在,我們按照內容圖片初始化一張隨機噪點的圖片。初始化這麼一個隨機噪點但是輕微偏向內容圖片一些的圖片,可以使訓練時更快的匹配內容圖片。

generated_image = generate_noise_image(content_image)
imshow(generated_image[0])

接下來,導入 VGG16模型

model = load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")

爲了計算內容損失,我們用 a_C,a_G 表示內容圖片和產生圖片的激活函數。我們將使用 conv4_2 隱藏側來計算內容損失。

  1. 建內容圖片作爲輸入給 VGG 模型
  2. 使用 a_C 表示隱藏層 conv4_2 的隱藏層激活函數
  3. 使用 a_G 作爲與內容圖片相同隱藏層的激活函數
  4. 使用 a_C 和 a_G 計算損失函數
# Assign the content image to be the input of the VGG model.  
sess.run(model['input'].assign(content_image))

# Select the output tensor of layer conv4_2
out = model['conv4_2']

# Set a_C to be the hidden layer activation from the layer we have selected
a_C = sess.run(out)

# Set a_G to be the hidden layer activation from same layer. Here, a_G references model['conv4_2'] 
# and isn't evaluated yet. Later in the code, we'll assign the image G as the model input, so that
# when we run the session, this will be the activations drawn from the appropriate layer, with G as input.
a_G = out

# Compute the content cost
J_content = compute_content_cost(a_C, a_G)

注意:這裏 a_G 尚未計算,將在後面 model_nn 時迭代計算和更新

# Assign the input of the model to be the "style" image 
sess.run(model['input'].assign(style_image))

# Compute the style cost
J_style = compute_style_cost(model, STYLE_LAYERS)

練習:現在我們得到了 J_content 和 J_style, 接下來計算總體損失 total_cost() ,這裏 alpha = 10, beta = 40

### START CODE HERE ### (1 line)
J = total_cost(J_content, J_style, 10, 40)
### END CODE HERE ###

練習:利用 TensorFlow 中的初始化參數實現 model_nn 模型,將新產生的隨機像素圖片作爲 VGG16 模型的輸入然後跑很多次訓練步驟。

def model_nn(sess, input_image, num_iterations = 200):

    # Initialize global variables (you need to run the session on the initializer)
    ### START CODE HERE ### (1 line)
    sess.run(tf.global_variables_initializer())
    ### END CODE HERE ###

    # Run the noisy input image (initial generated image) through the model. Use assign().
    ### START CODE HERE ### (1 line)
    sess.run(model["input"].assign(input_image))
    ### END CODE HERE ###

    for i in range(num_iterations):

        # Run the session on the train_step to minimize the total cost
        ### START CODE HERE ### (1 line)
        sess.run(train_step)
        ### END CODE HERE ###

        # Compute the generated image by running the session on the current model['input']
        ### START CODE HERE ### (1 line)
        generated_image =sess.run(model["input"])
        ### END CODE HERE ###

        # Print every 20 iteration.
        if i%20 == 0:
            Jt, Jc, Js = sess.run([J, J_content, J_style])
            print("Iteration " + str(i) + " :")
            print("total cost = " + str(Jt))
            print("content cost = " + str(Jc))
            print("style cost = " + str(Js))

            # save current generated image in the "/output" directory
            save_image("output/" + str(i) + ".png", generated_image)

    # save last generated image
    save_image('output/generated_image.jpg', generated_image)

    return generated_image

執行下列命令產生藝術圖片。

model_nn(sess, generated_image)

# Iteration 0 :
# total cost = 5.05035e+09
# content cost = 7877.67
# style cost = 1.26257e+08
# Iteration 20 :
# total cost = 9.43272e+08
# content cost = 15187.1
# style cost = 2.3578e+07
# Iteration 40 :
# total cost = 4.84905e+08
# content cost = 16785.1
# style cost = 1.21184e+07

完成!跑完上線的程序,你將會在輸出文件夾看到如下圖的例子

image

我們不想讓你等太長時間纔看到結果,所以設置好了相應的參數。算法跑的時間越長將得到越好的效果。在提交這次作業後,可以回頭去調試模型,看看能不能產生更好的結果。

下面是一些例子:

image

image

image

5 測試你自己的圖片(可選)

最後,你可以在你自己的圖片上執行這個模型!

上傳你的圖片(width=300, height=225), 將代碼從

content_image = scipy.misc.imread("images/louvre.jpg")
style_image = scipy.misc.imread("images/claude-monet.jpg")

改成

content_image = scipy.misc.imread("images/my_content.jpg")
style_image = scipy.misc.imread("images/my_style.jpg")

你也可以調試你自己的超參數

  • STYLE_LAYERS 表示多個隱藏層風格的權重
  • num_iterations 表示算法迭代的次數
  • alpha/beta 表示內容/風格的權重

6 總結

恭喜你完成了這個作業。現在你能夠使用神經風格遷移算法來生成藝術圖片了。這也是你第一次利用優化算法更新像素值而不是神經網絡的參數來創建模型。深度學習有許多模型類型,這只是其中的一個!

謹記

  • 神經風格遷移是一個由內容圖片 C 和風格圖片 S 生成藝術圖片的算法
  • 使用了預訓練的卷積神經網絡模型的隱藏層激活函數的表現
  • 內容損失使用一層隱藏層的激活函數來計算
  • 一層的風格損失函數的計算使用該層激活函數的格拉姆矩陣來計算,總體風格損失函數使用幾個隱藏層的激活函數共同獲得
  • 優化總體損失函數可以生成新的圖片

恭喜你完成卷積神經網絡部分所有的作業,希望在接下來第五課的循環神經網絡模型中還能見到你。

引用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章