閱讀375 返回首頁    go 京東網上商城


通過SketchRNN、PCA和t-SNE從Google QuickDraw數據集中顯示矢量圖的潛在空間|附源碼

https://yq.aliyun.com/cloud

79b373d51eb109fd75ac6dd612fe1d96b9107ad9

本文是作者最近發布的Google QuickDraw數據集一係列筆記的第三部分,使用的最近發布的SketchRNN模型。下麵介紹QuickDraw數據集及SketchRNN模型。
        QuickDraw數據集是由世界各地1500多萬人參與的“快速繪畫” AI實驗後收集數百萬幅圖畫建成,要求參與者在20秒內繪製屬於某個類(例如“貓”)的圖像如下圖所示,選擇一個類別,可以看見數據庫中該類所有的形狀。

2fe2089ec0ed5b84a527913712fa475ca9591aea

當然,如果數據集中沒有和你想象一樣的形狀,你也可以幫助向該數據庫中增添一些塗鴉,進行個人繪畫表演。

25e02e433cc92c54480a29cf266faf369ffa9b74 49417fcbdccba38dd7d1d93ee3ca314c04d4d095
        SketchRNN是基於上述數據集訓練的生成模型,被訓練成能夠生成矢量它巧妙地集合了機器學習中最近開發的許多最新的工具和技術,例如Variational AutoencodersHyperLSTMs(一個用於LSTM的HyperNetwork)自回歸模型 ,Layer NormalizationRecurrent DropoutAdam optimizer

        SketchRNN係統是由穀歌探究AI能否創作藝術的新項目的一部分,類似於教AI去繪畫,另外不僅僅是讓人工智能學習如何畫畫,還要能夠“用類似於人類的方式概括歸納抽象的概念”,比如去畫“豬”的廣義概念,而不是畫特定的動物,這顯然不是一件簡單的事情(下圖 SketchRNN係統畫的“豬”)。

70a2d2c1d429e95023c9ba0b0674aa0373548164

e6f82e43b014419c1744cd5f513148c9528ca3762bbd775563fe7e0fa81f490ff7055730bef5f2aa
       本文代碼舍棄了那些旨在解釋或演示代碼塊,隻保留運行實驗所需的代碼。“潛在空間中的主成分分析 ”部分的所有內容直接從以前的博客摘取。隨意跳過該部分,因為接下來是真正有趣的分析。這裏是第一第二博客的鏈接之前所講述的一切都是一些實用功能,以便於現在的可視化分析

        本文是筆記代碼的結合,作者已經做出了風格以及其它一些細微的改變,以確保Python3向前兼容

  • 1. 本文有點令人誤解這是因為本文主要是探索Aaron Koblin羊市場 aaron-sheep數據集,這是一個較小的輕量級數據集,以及一個手冊,演示了在這個數據集已經預先訓練的各種模型。由於數據集模式與QuickDraw數據集相同,因此在此數據集上執行的實驗也不失一般性。
  • 2. Magenta目前隻支持Python 2版本

260a4122bad0cd267dd1ecc681f4616f03269718

接下來都是實驗的所需的python代碼,其中[num]是表示該github工程文件的代碼塊:
在代碼塊[2]中:


%matplotlib inline
%config InlineBackend.figure_format = 'svg'
%load_ext autoreload
%autoreload 2

在代碼塊[3]中


import matplotlib.pyplot as plt
import matplotlib.patches as patches

import numpy as np
import tensorflow as tf

from matplotlib.animation import FuncAnimation
from matplotlib.path import Path
from matplotlib import rc

from sklearn.decomposition import PCA
from sklearn.manifold import TSNE

from itertools import product
from six.moves import map, zip

在代碼塊[5]中:


from magenta.models.sketch_rnn.sketch_rnn_train import \
    (load_env,
     load_checkpoint,
     reset_graph,
     download_pretrained_models,
     PRETRAINED_MODELS_URL)
from magenta.models.sketch_rnn.model import Model, sample
from magenta.models.sketch_rnn.utils import (lerp,
                                             slerp,
                                             get_bounds, 
                                             to_big_strokes,
                                             to_normal_strokes)

在代碼塊[6]中:


# For inine display of animation
# equivalent to rcParams['animation.html'] = 'html5'
rc('animation', html='html5')

在代碼塊[5]中:


# set numpy output to something sensible
np.set_printoptions(precision=8, 
                    edgeitems=6, 
                    linewidth=200, 
                    suppress=True)

在代碼塊[6]中:


tf.logging.info("TensorFlow Version: {}".format(tf.__version__))

INFO:tensorflow:TensorFlow版本:1.1.0

獲得預訓練的模型和數據

在代碼塊[7]中:


DATA_DIR = ('https://github.com/hardmaru/sketch-rnn-datasets/'
            'raw/master/aaron_sheep/')
MODELS_ROOT_DIR = '/tmp/sketch_rnn/models'

在代碼塊[8]中:


DATA_DIR

輸出[8]:

'https://github.com/hardmaru/sketch-rnn-datasets/raw/master/aaron_sheep/'

在代碼塊[9]中:

PRETRAINED_MODELS_URL

輸出[9]:

'https://download.magenta.tensorflow.org/models/sketch_rnn.zip'

在代碼塊[10]中:

download_pretrained_models(
    models_root_dir=MODELS_ROOT_DIR,
    pretrained_models_url=PRETRAINED_MODELS_URL)


接下來讓我們看看aaron_sheep現在數據集訓練的規範化層模型。

在代碼塊[11]中:


MODEL_DIR = MODELS_ROOT_DIR + '/aaron_sheep/layer_norm'

在代碼塊[12]中:


(train_set, 
 valid_set, 
 test_set, 
 hps_model, 
 eval_hps_model, 
 sample_hps_model) = load_env(DATA_DIR, MODEL_DIR)

INFO:tensorflow:Downloading https://github.com/hardmaru/sketch-rnn-datasets/raw/master/aaron_sheep/aaron_sheep.npz
INFO:tensorflow:Loaded 7400/300/300 from aaron_sheep.npz
INFO:tensorflow:Dataset combined: 8000 (7400/300/300), avg len 125
INFO:tensorflow:model_params.max_seq_len 250.
total images <= max_seq_len is 7400
total images <= max_seq_len is 300
total images <= max_seq_len is 300
INFO:tensorflow:normalizing_scale_factor 18.5198.

在代碼塊[13]中:


class SketchPath(Path):
    
    def __init__(self, data, factor=.2, *args, **kwargs):
        
        vertices = np.cumsum(data[::, :-1], axis=0) / factor
        codes = np.roll(self.to_code(data[::,-1].astype(int)), 
                        shift=1)
        codes[0] = Path.MOVETO

        super(SketchPath, self).__init__(vertices, 
                                         codes, 
                                         *args, 
                                         **kwargs)
        
    @staticmethod
    def to_code(cmd):
        # if cmd == 0, the code is LINETO
        # if cmd == 1, the code is MOVETO (which is LINETO - 1)
        return Path.LINETO - cmd

在代碼塊[14]中:


def draw(sketch_data, factor=.2, pad=(10, 10), ax=None):

    if ax is None:
        ax = plt.gca()

    x_pad, y_pad = pad
    
    x_pad //= 2
    y_pad //= 2
        
    x_min, x_max, y_min, y_max = get_bounds(data=sketch_data,
                                            factor=factor)

    ax.set_xlim(x_min-x_pad, x_max+x_pad)
    ax.set_ylim(y_max+y_pad, y_min-y_pad)

    sketch = SketchPath(sketch_data)

    patch = patches.PathPatch(sketch, facecolor='none')
    ax.add_patch(patch)

加載預先訓練好的模型

在代碼塊[15]中:


# construct the sketch-rnn model here:
reset_graph()
model = Model(hps_model)
eval_model = Model(eval_hps_model, reuse=True)
sample_model = Model(sample_hps_model, reuse=True)

INFO:tensorflow:Model using gpu.
INFO:tensorflow:Input dropout mode = False.
INFO:tensorflow:Output dropout mode = False.
INFO:tensorflow:Recurrent dropout mode = True.
INFO:tensorflow:Model using gpu.
INFO:tensorflow:Input dropout mode = False.
INFO:tensorflow:Output dropout mode = False.
INFO:tensorflow:Recurrent dropout mode = False.
INFO:tensorflow:Model using gpu.
INFO:tensorflow:Input dropout mode = False.
INFO:tensorflow:Output dropout mode = False.
INFO:tensorflow:Recurrent dropout mode = False.

在代碼塊[16]中:


sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

在代碼塊[17]中:


# loads the weights from checkpoint into our model
load_checkpoint(sess=sess, checkpoint_path=MODEL_DIR)

INFO:tensorflow:Loading model /tmp/sketch_rnn/models/aaron_sheep/layer_norm/vector.
INFO:tensorflow:Restoring parameters from /tmp/sketch_rnn/models/aaron_sheep/layer_norm/vector

在代碼塊[18]中:


def encode(input_strokes):
    strokes = to_big_strokes(input_strokes).tolist()
    strokes.insert(0, [0, 0, 1, 0, 0])
    seq_len = [len(input_strokes)]
    z = sess.run(eval_model.batch_z,
                 feed_dict={
                    eval_model.input_data: [strokes], 
                    eval_model.sequence_lengths: seq_len})[0]
    return z

在代碼塊[19]中:


def decode(z_input=None, temperature=.1, factor=.2):
    z = None
    if z_input is not None:
        z = [z_input]
    sample_strokes, m = sample(
        sess, 
        sample_model, 
        seq_len=eval_model.hps.max_seq_len, 
        temperature=temperature, z=z)
    return to_normal_strokes(sample_strokes)

用主成分分析探索潛在空間

下麵,我們將測試集中的所有草圖編碼為學習的128維潛在空間中的表示。

在代碼塊[20]中:


Z = np.vstack(map(encode, test_set.strokes))
Z.shape

輸出[20]:

300,128 128

然後,找到潛在空間中編碼數據中代表最大方差方向的兩個主軸。 

在代碼塊[22]中:

pca = PCA(n_components=2)

在代碼塊[23]中:

pca.fit(Z)

輸出[23]:

PCAsvd_solver ='auto' 

這兩個組成部分分別方差的2% 

在代碼塊[24]中:




pca.explained_variance_ratio_

輸出[24]:

[0.02140247 

接下來將數據從128維潛在空間映射為由前兩個主要分量構建的二維空間 

在代碼塊[25]中:



Z_pca = pca.transform(Z)
Z_pca.shape

出[25]:

300,2 2

在代碼塊[26]中:


fig, ax = plt.subplots()

ax.scatter(*Z_pca.T)

ax.set_xlabel('$pc_1$')
ax.set_ylabel('$pc_2$')

plt.show()

10074f8ae3f998247196cc8b710294b002a94514

我們想在上圖中通過圖中的對應點可視化原始草圖,其中每個點對應於草圖降維到2維的隱藏代碼。然而,由於圖像太密集,在沒有重疊的情況下無法適應足夠大的草圖。因此,我們將注意力限製在包含80%數據點的較小區域,藍色陰影矩形突出顯示感興趣的區域。 

在代碼塊[106]中:


((pc1_min, pc2_min), 
 (pc1_max, pc2_max)) = np.percentile(Z_pca, q=[5, 95], axis=0)

在代碼塊[107]中:


roi_rect = patches.Rectangle(xy=(pc1_min, pc2_min),
                             width=pc1_max-pc1_min,
                             height=pc2_max-pc2_min, alpha=.4)

在代碼塊[108]中:


fig, ax = plt.subplots()

ax.scatter(*Z_pca.T)
ax.add_patch(roi_rect)

ax.set_xlabel('$pc_1$')
ax.set_ylabel('$pc_2$')

plt.show()

501b3b92fec57aa1697920cf662a9eedb199614a

在代碼塊[109]中:


fig, ax = plt.subplots(figsize=(10, 10))

ax.set_xlim(pc1_min, pc1_max)
ax.set_ylim(pc2_min, pc2_max)

for i, sketch in enumerate(test_set.strokes):
    sketch_path = SketchPath(sketch, factor=7e+1)
    sketch_path.vertices[::,1] *= -1
    sketch_path.vertices += Z_pca[i]
    patch = patches.PathPatch(sketch_path, facecolor='none')
    ax.add_patch(patch)

ax.set_xlabel('$pc_1$')
ax.set_ylabel('$pc_2$')
    
plt.savefig("../../files/sketchrnn/aaron_sheep_pca.svg", 
            format="svg", dpi=1200)

0bb5ce7f30aadc771cb1f9cb68fcb559424ff2cd

可以在這裏找到SVG圖像。

備注:一個更加聰明的方法涉及使用matplotlib變換Collections API,具體是通過實例化帶有關鍵參數oofets的PathCollection


在先前定義的矩形區域生成100個均勻間隔的網格,下圖網格中的點以橙色表示,覆蓋在測試數據點的頂部。 

在代碼塊[35]中:


pc1 = lerp(pc1_min, pc1_max, np.linspace(0, 1, 10))

在代碼塊[36]中:


pc2 = lerp(pc2_min, pc2_max, np.linspace(0, 1, 10))

在代碼塊[37]中:


pc1_mesh, pc2_mesh = np.meshgrid(pc1, pc2)

在代碼塊[38]中:


fig, ax = plt.subplots()

ax.set_xlim(pc1_min-.5, pc1_max+.5)
ax.set_ylim(pc2_min-.5, pc2_max+.5)

ax.scatter(*Z_pca.T)
ax.scatter(pc1_mesh, pc2_mesh)

ax.set_xlabel('$pc_1$')
ax.set_ylabel('$pc_2$')

plt.show()

ca59cf88c62e628fdbae5b1724e3a83aee2322d8

接下來,通過應用PCA的逆變換來將網格上的100個點投影到原始的128維潛在空間。 

在代碼塊[39]中:


np.dstack((pc1_mesh, pc2_mesh)).shape

輸出[39]:

10102 

在代碼塊[40]中:


z_grid = np.apply_along_axis(pca.inverse_transform,  
                             arr=np.dstack((pc1_mesh, pc2_mesh)),
                             axis=2)
z_grid.shape

輸出[40]:

1010128 

然後,使用這些隱藏代碼及譯碼器重建相應的草圖,並觀察草圖如何在所關注的矩形區域範圍之間轉換特別是草圖從左到右從上到下轉換,因為這些是潛在表示中最大差異的方向。首先,以較低的溫度設置τ=0.1運行譯碼器以最小化樣本的隨機性。 

在代碼塊[94]中:


fig, ax_arr = plt.subplots(nrows=10, 
                           ncols=10, 
                           figsize=(8, 8),
                           subplot_kw=dict(xticks=[],
                                           yticks=[],
                                           frame_on=False))
fig.tight_layout()

for i, ax_row in enumerate(ax_arr):    
    for j, ax in enumerate(ax_row):
        draw(decode(z_grid[-i-1,j], temperature=.1), ax=ax)
        ax.axis('off')
    
plt.show()

fd1df68981c9dbc48f39d82545322415f64ef174

可以從上圖中觀察一些有趣的變換和模式。右下角可以看到一群類似的修剪過或者無修飾的當向左上方移動時,開始看到一些羊毛粗糙的圓形塗鴉的羊;沿著中間部分可以看到最逼真的羊在左上角,可以看到一些抽象的草圖

在代碼塊[95]中:


fig, ax_arr = plt.subplots(nrows=10, 
                           ncols=10, 
                           figsize=(8, 8),
                           subplot_kw=dict(xticks=[],
                                           yticks=[],
                                           frame_on=False))
fig.tight_layout()

for i, ax_row in enumerate(ax_arr):    
    for j, ax in enumerate(ax_row):
        draw(decode(z_grid[-i-1,j], temperature=.6), ax=ax)
        ax.axis('off')
    
plt.show()

3460dfd54dfbfb4243327af44b3460796f9dc624

特征羊分解
本文將草圖學習潛在代表主要組成部分稱為特征羊

使用譯碼器,可以可視化前2個特征羊的草圖表示這些特征羊是將潛在表示轉換為二維子空間的正交權重矢量。通過將它們視為隱藏代碼,並將其重構建草圖,我們能夠提煉出一些有意義的解釋。 

在代碼塊[48]中:


z_pc1, z_pc2 = pca.components_

在代碼塊[49]中:


pca.explained_variance_ratio_

輸出[49]:

[0.02140247 

在代碼塊[50]中:

pca.explained_variance_

輸出[50]:

[2.51394317

我們從τ=.01...1.0增加溫度繪製特征羊的重建草圖每個溫度設置采集5個樣品。 

在代碼塊[58]中:

fig, ax_arr = plt.subplots(nrows=5, 
                           ncols=10, 
                           figsize=(8, 4),
                           subplot_kw=dict(xticks=[],
                                           yticks=[],
                                           frame_on=False))
fig.tight_layout()

for row_num, ax_row in enumerate(ax_arr):    
    for col_num, ax in enumerate(ax_row):
        t = (col_num + 1) / 10.
        draw(decode(z_pc1, temperature=t), ax=ax)

        if row_num + 1 == len(ax_arr):
            ax.set_xlabel(r'$\tau={}$'.format(t))
        
plt.show()

a74dcd0ce663c22315214a172fc0d4ce2fa8a595

在代碼塊[63]中:

fig, ax_arr = plt.subplots(nrows=5, 
                           ncols=10, 
                           figsize=(8, 4),
                           subplot_kw=dict(xticks=[],
                                           yticks=[],
                                           frame_on=False))
fig.tight_layout()

for row_num, ax_row in enumerate(ax_arr):    
    for col_num, ax in enumerate(ax_row):
        t = (col_num + 1) / 10.
        draw(decode(z_pc2, temperature=t), ax=ax)

        if row_num + 1 == len(ax_arr):
            ax.set_xlabel(r'$\tau={}$'.format(t))
        
plt.show()

2da9d11183046ef318eb401fc5507fe5095484f2

對於第一特征羊,在τ=0.1主要看到一個圓形的黑色頭發有兩條長的圓形腿一些尾巴。沿著軸線方向可以看到,羊頭部和腿部結構會發生變化,這占據了草圖的大部分差異。

現在看看第二特征羊,集中τ值較低時生成的樣品,可以看到一些粗略潦草的圓圈代表羊的身體3-4個鬆散連接的腿和一個小圓頭。從上到下的觀察,可以發現,與第一特征不同的是這似乎是沿著羊身體結構變化的方向。


     t分布隨機相鄰嵌入(t-SNE)是一種常用的非線性降維技術,通常用於可視化高維數據。它是幾種嵌入方法之一,其目的是將數據點嵌入到較低維空間中,以保持原始高維空間中的點對點的距離,一般適用於二維空間的可視化

     特別地,t-SNE通常用於深度學習,以檢查和可視化深層神經網絡學到的內容。例如,在圖像分類問題中,可以將卷積神經網絡視為一係列變換,逐漸將圖像轉換為表示,使得類可以更容易地被線性分類器區分。因此,可以分類器前的最後一層輸出作為“代碼”然後使用t-SNE在二維中嵌入和可視化圖像代碼表示。

在這裏,將每個矢量繪圖的128維隱藏代碼嵌入到二維中,並將其在該二維子空間中進行可視化。 

在代碼塊[97]中:

tsne = TSNE(n_components=2, n_iter=5000)

在代碼塊[98]中:

Z_tsne = tsne.fit_transform(Z)

在代碼塊[99]中:

fig, ax = plt.subplots()

ax.scatter(*Z_tsne.T)

ax.set_xlabel('$c_1$')
ax.set_ylabel('$c_2$')

plt.show()

36da666fa84c604b892bab2a677a61dc24e1cb21

在代碼塊[100]中:

tsne.kl_divergence_

輸出[100]:

2.2818214893341064 

像以前一樣,定義一個矩形區域 

在代碼塊[101]中:

((c1_min, c2_min), 
 (c1_max, c2_max)) = np.percentile(Z_tsne, q=[5, 95], axis=0)

在代碼塊[102]中:

roi_rect = patches.Rectangle(xy=(c1_min, c2_min),
                             width=c1_max-c1_min,
                             height=c2_max-c2_min, alpha=.4)

在代碼塊[103]中:

fig, ax = plt.subplots()

ax.scatter(*Z_tsne.T)
ax.add_patch(roi_rect)

ax.set_xlabel('$c_1$')
ax.set_ylabel('$c_2$')

plt.show()

526d1649c235f1118a570d29a9d2143cdca0592a

在代碼塊[104]中:

fig, ax = plt.subplots(figsize=(10, 10))

ax.set_xlim(c1_min, c1_max)
ax.set_ylim(c2_min, c2_max)

for i, sketch in enumerate(test_set.strokes):
    sketch_path = SketchPath(sketch, factor=2.)
    sketch_path.vertices[::,1] *= -1
    sketch_path.vertices += Z_tsne[i]
    patch = patches.PathPatch(sketch_path, facecolor='none')
    ax.add_patch(patch)

ax.axis('off')

plt.savefig("../../files/sketchrnn/aaron_sheep_tsne.svg", 
            format="svg")

f3e240d59e9d21aa95b8ce2ee27d63d13b22c369

完整的SVG圖片可以在此獲得。雖然這產生了很好的結果,但作者認為如果可視化在一個具有多類的更大數據集上會更有說服力。通過這種方式,可以看到t-SNE有效地形成了對不同類的聚類圖,並突出顯示了每個集群中的變化。

88d0d453f9aa1d6741a1f480689abf3b1cf832c3

作者信息

Louis Tiao計算機科學和數學的研究者,研究領域集中於算法設計與分析、計算機科學理論、人工智能和機器學習

Linkedin:https://www.linkedin.com/in/ltiao/?ppe=1

Githubhttps://github.com/scikit-learn/scikit-learn

本文由北郵@愛可可-愛生活老師推薦,阿裏雲雲棲社區組織翻譯。

文章原標題《Visualizing the Latent Space of Vector Drawings from the Google QuickDraw Dataset with SketchRNN, PCA and t-SNE》,作者:Louis Tiao,譯者:海棠,審閱:阿福 

文章為簡譯,更為詳細的內容,請查看

最後更新:2017-06-10 23:32:03

  上一篇:go  萬元大獎邀您參與阿裏雲數加 MaxCompute最佳實踐征文大賽
  下一篇:go  傳統大型企業平滑上雲典型架構實踐