375
京東網上商城
通過SketchRNN、PCA和t-SNE從Google QuickDraw數據集中顯示矢量圖的潛在空間|附源碼
本文是作者最近發布的Google QuickDraw數據集一係列筆記中的第三部分,使用的最近發布的SketchRNN模型。下麵介紹QuickDraw數據集及SketchRNN模型。
QuickDraw數據集是由世界各地1500多萬人參與的“快速繪畫” AI實驗後收集數百萬幅圖畫建成,要求參與者在20秒內繪製出屬於某個類(例如“貓”)的圖像。如下圖所示,選擇一個類別,可以看見數據庫中該類所有的形狀。
當然,如果數據集中沒有和你想象一樣的形狀,你也可以幫助向該數據庫中增添一些塗鴉,進行個人繪畫表演。


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


本文是筆記與代碼的結合,作者已經做出了風格以及其它一些細微的改變,以確保Python3能向前兼容。
- 1. 本文有點令人誤解,這是因為本文主要是探索Aaron Koblin羊市場 (aaron-sheep)數據集,這是一個較小的輕量級數據集,以及一個手冊,演示了在這個數據集上已經預先訓練好的各種模型。由於該數據集模式與QuickDraw數據集相同,因此在此數據集上執行的實驗也不失一般性。
- 2. Magenta目前隻支持Python 2版本。
接下來都是實驗的所需的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
'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()
我們想在上圖中通過圖中的對應點可視化出原始草圖,其中每個點對應於草圖降維到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()
在代碼塊[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)
可以在這裏找到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()
接下來,通過應用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()
可以從上圖中觀察到一些有趣的變換和模式。在右下角,可以看到一群類似的修剪過或者無修飾的羊;當向左上方移動時,會開始看到一些羊毛、粗糙的圓形塗鴉的羊;沿著中間部分,可以看到最逼真的羊;在左上角,可以看到一些抽象的草圖。
在代碼塊[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()
特征羊分解
本文將草圖學習的潛在代表主要組成部分稱為特征羊。
使用譯碼器,可以可視化前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()
在代碼塊[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()
對於第一特征羊,在τ=0.1,主要看到一個圓形的黑色頭發、有兩條長的圓形腿和一些尾巴。沿著軸線方向可以看到,羊的頭部和腿部結構會發生變化,這占據了草圖的大部分差異。
現在看看第二特征羊,集中τ值較低時生成的樣品,可以看到一些粗略潦草的圓圈代表羊的身體、3-4個鬆散連接的腿和一個小圓頭。從上到下的觀察,可以發現,與第一特征不同的是這似乎是沿著羊身體結構變化的方向。
t分布隨機相鄰嵌入(t-SNE)是一種常用的非線性降維技術,通常用於可視化高維數據。它是幾種嵌入方法之一,其目的是將數據點嵌入到較低維空間中,以保持原始高維空間中的點對點的距離,一般適用於二維空間的可視化。
特別地,t-SNE通常用於深度學習,以檢查和可視化深層神經網絡學到的內容。例如,在圖像分類問題中,可以將卷積神經網絡視為一係列變換,逐漸將圖像轉換為表示,使得類可以更容易地被線性分類器區分。因此,可以將分類器前的最後一層輸出作為“代碼”,然後使用t-SNE在二維中嵌入和可視化圖像的代碼表示。
- 1.關於PCA與t-SNE的詳細比較可以查看筆者先前的博客。
- 2.Wattenberg, et al.“How to Use t-SNE Effectively”, Distill, 2016.
- 3.斯坦福大學CS231n:卷積神經網絡視覺識別課程中關於網絡的可視化課程筆記。
在這裏,將每個矢量繪圖的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()
在代碼塊[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()
在代碼塊[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")
完整的SVG圖片可以在此獲得。雖然這產生了很好的結果,但作者認為如果可視化在一個具有多類的更大數據集上會更有說服力。通過這種方式,可以看到t-SNE有效地形成了對不同類的聚類圖,並突出顯示了每個集群中的變化。
- 1. Visualizing MNIST
- 2. Visualizing Representations
- 3. Latent space visualization
- 4. Analyzing 50k fonts using deep neural networks
- 5. Organizing the World of Fonts with AI
作者信息
Louis Tiao:計算機科學和數學的研究者,研究領域集中於算法設計與分析、計算機科學理論、人工智能和機器學習。
Linkedin:https://www.linkedin.com/in/ltiao/?ppe=1
Github:https://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