只需 130 行代碼!用 GAN 生成二維樣本的小例子
50行GAN代碼的問題
Dev Nag 寫的 50 行代碼的 GAN,大概是網上流傳最廣的,關於GAN最簡單的小例子。這是一份用一維均勻樣本作為特徵空間(latent space)樣本,經過生成網路變換後,生成高斯分布樣本的代碼。結構非常清晰,卻有一個奇怪的問題,就是判別器(Discriminator)的輸入不是2維樣本,而是把整個mini-batch整體作為一個維度是batch size(代碼中batch size等於cardinality)那麼大的樣本。也就是說判別網路要判別的不是一個一維的目標分布,而是batch size那麼大維度的分布:
...
d_input_size = 100 # Minibatch size - cardinality of distributions
...
class Discriminator(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(Discriminator, self).__init__()
self.map1 = nn.Linear(input_size, hidden_size)
self.map2 = nn.Linear(hidden_size, hidden_size)
self.map3 = nn.Linear(hidden_size, output_size)
def forward(self, x):
x = F.elu(self.map1(x))
x = F.elu(self.map2(x))
return F.sigmoid(self.map3(x))
...
D = Discriminator(input_size=d_input_func(d_input_size), hidden_size=d_hidden_size, output_size=d_output_size)
...
for epoch in range(num_epochs):
for d_index in range(d_steps):
# 1. Train D on real+fake
D.zero_grad()
# 1A: Train D on real
d_real_data = Variable(d_sampler(d_input_size))
d_real_decision = D(preprocess(d_real_data))
d_real_error = criterion(d_real_decision, Variable(torch.ones(1))) # ones = true
d_real_error.backward() # compute/store gradients, but don t change params
# 1B: Train D on fake
d_gen_input = Variable(gi_sampler(minibatch_size, g_input_size))
d_fake_data = G(d_gen_input).detach() # detach to avoid training G on these labels
d_fake_decision = D(preprocess(d_fake_data.t()))
d_fake_error = criterion(d_fake_decision, Variable(torch.zeros(1))) # zeros = fake
d_fake_error.backward()
d_optimizer.step() # Only optimizes D s parameters; changes based on stored gradients from backward()
for g_index in range(g_steps):
# 2. Train G on D s response (but DO NOT train D on these labels)
G.zero_grad()
gen_input = Variable(gi_sampler(minibatch_size, g_input_size))
g_fake_data = G(gen_input)
dg_fake_decision = D(preprocess(g_fake_data.t()))
g_error = criterion(dg_fake_decision, Variable(torch.ones(1))) # we want to fool, so pretend it s all genuine
g_error.backward()
g_optimizer.step() # Only optimizes G s parameters
...
不知作者是疏忽了還是有意為之,總之這麼做的結果就是如此簡單的例子收斂都好。可能作者自己也察覺了收斂問題,就想把方差信息也放進來,於是又寫了個預處理函數(decorate_with_diffs)計算出每個樣本距離一批樣本中心的距離平方,作為給判別網路的額外輸入,其實這樣還增加了輸入維度。結果當然是加不加這個方差信息都能勉強收斂,但是都不穩定。甚至作者自己貼出來的生成樣本分布(下圖)都不令人滿意:
如果直接把這份代碼改成二維的,就會發現除了簡單的對稱分布以外,其他分布基本都無法生成。
理論上講神經網路作為一種通用的近似函數,只要capacity夠,學習多少維分布都不成問題,但是這樣寫法顯然極大增加了收斂難度。更自然的做法應該是:判別網路只接受單個二維樣本,通過batch size或是多步迭代學習分布信息。
另:這份代碼其實有130行。
從自定義的二維分布採樣
不管怎樣Dev Nag的代碼還是提供了一個用於理解和試驗GAN的很好的框架,做一些修改就可以得到一份更適合直觀演示,且更容易收斂的代碼,也就是本文的例子。
從可視化的角度二維顯然比一維更直觀,所以我們採用二維樣本。第一步,當然是要設定一個目標分布,作為二維的例子,分布的定義方式應該盡量自由,這個例子中我們的思路是通過灰度圖像定義的概率密度,進而來產生樣本,比如下面這樣:
二維情況下,這種採樣的一個實現方法是:求一個維度上的邊緣(marginal)概率+另一維度上近似的條件概率。比如把圖像中白色像素的值作為概率密度的相對大小,然後沿著x求和,然後在y軸上求出marginal probability density,接著再根據y的位置,近似得到對應x關於y的條件概率。採樣的時候先採y的值,再采x的值就能近似得到符合圖像描述的分布的樣本。具體細節就不展開講解了,看代碼:
from functools import partial
import numpy
from skimage import transform
EPS = 1e-6
RESOLUTION = 0.001
num_grids = int(1/RESOLUTION+0.5)
def generate_lut(img):
"""
linear approximation of CDF & marginal
:param density_img:
:return: lut_y, lut_x
"""
density_img = transform.resize(img, (num_grids, num_grids))
x_accumlation = numpy.sum(density_img, axis=1)
sum_xy = numpy.sum(x_accumlation)
y_cdf_of_accumulated_x = [[0., 0.]]
accumulated = 0
for ir, i in enumerate(range(num_grids-1, -1, -1)):
accumulated += x_accumlation[i]
if accumulated == 0:
y_cdf_of_accumulated_x[0][0] = float(ir+1)/float(num_grids)
elif EPS < accumulated < sum_xy - EPS:
y_cdf_of_accumulated_x.append([float(ir+1)/float(num_grids), accumulated/sum_xy])
else:
break
y_cdf_of_accumulated_x.append([float(ir+1)/float(num_grids), 1.])
y_cdf_of_accumulated_x = numpy.array(y_cdf_of_accumulated_x)
x_cdfs = []
for j in range(num_grids):
x_freq = density_img[num_grids-j-1]
sum_x = numpy.sum(x_freq)
x_cdf = [[0., 0.]]
accumulated = 0
for i in range(num_grids):
accumulated += x_freq[i]
if accumulated == 0:
x_cdf[0][0] = float(i+1) / float(num_grids)
elif EPS < accumulated < sum_xy - EPS:
x_cdf.append([float(i+1)/float(num_grids), accumulated/sum_x])
else:
break
x_cdf.append([float(i+1)/float(num_grids), 1.])
if accumulated > EPS:
x_cdf = numpy.array(x_cdf)
x_cdfs.append(x_cdf)
else:
x_cdfs.append(None)
y_lut = partial(numpy.interp, xp=y_cdf_of_accumulated_x[:, 1], fp=y_cdf_of_accumulated_x[:, 0])
x_luts = [partial(numpy.interp, xp=x_cdfs[i][:, 1], fp=x_cdfs[i][:, 0]) if x_cdfs[i] is not None else None for i in range(num_grids)]
return y_lut, x_luts
def sample_2d(lut, N):
y_lut, x_luts = lut
samples = numpy.zeros(u_rv.shape)
for i, (x, y) in enumerate(u_rv):
ys = y_lut(y)
x_bin = int(ys/RESOLUTION)
xs = x_luts[x_bin](x)
samples[i][0] = xs
samples[i][1] = ys
return samples
if __name__ == __main__ :
from skimage import io
density_img = io.imread( batman.jpg , True)
lut_2d = generate_lut(density_img)
samples = sample_2d(lut_2d, 10000)
from matplotlib import pyplot
fig, (ax0, ax1) = pyplot.subplots(ncols=2, figsize=(9, 4))
ax0.imshow(density_img, cmap= gray )
ax0.xaxis.set_major_locator(pyplot.NullLocator())
ax0.yaxis.set_major_locator(pyplot.NullLocator())
ax1.axis( equal )
ax1.axis([0, 1, 0, 1])
ax1.plot(samples[:, 0], samples[:, 1], k, )
pyplot.show()
二維GAN的小例子
雖然網上到處都有,這裡還是貼一下GAN的公式:
就是一個你追我趕的零和博弈,這在Dev Nag的代碼里體現得很清晰:判別網路訓一撥,然後生成網路訓一撥,不斷往複。按照上節所述,本文例子在Dev Nag代碼的基礎上,把判別網路每次接受一個batch作為輸入的方式變成了:每次接受一個二維樣本,通過每個batch的多個樣本計算loss。GAN部分的訓練代碼如下:
DIMENSION = 2
...
generator = SimpleMLP(input_size=z_dim, hidden_size=args.g_hidden_size, output_size=DIMENSION)
discriminator = SimpleMLP(input_size=DIMENSION, hidden_size=args.d_hidden_size, output_size=1)
...
for train_iter in range(args.iterations):
for d_index in range(args.d_steps):
# 1. Train D on real+fake
discriminator.zero_grad()
# 1A: Train D on real
real_samples = sample_2d(lut_2d, bs)
d_real_data = Variable(torch.Tensor(real_samples))
d_real_decision = discriminator(d_real_data)
labels = Variable(torch.ones(bs))
d_real_loss = criterion(d_real_decision, labels) # ones = true
# 1B: Train D on fake
latent_samples = torch.randn(bs, z_dim)
d_gen_input = Variable(latent_samples)
d_fake_data = generator(d_gen_input).detach() # detach to avoid training G on these labels
d_fake_decision = discriminator(d_fake_data)
labels = Variable(torch.zeros(bs))
d_fake_loss = criterion(d_fake_decision, labels) # zeros = fake
d_loss = d_real_loss + d_fake_loss
d_loss.backward()
d_optimizer.step() # Only optimizes D s parameters; changes based on stored gradients from backward()
for g_index in range(args.g_steps):
# 2. Train G on D s response (but DO NOT train D on these labels)
generator.zero_grad()
latent_samples = torch.randn(bs, z_dim)
g_gen_input = Variable(latent_samples)
g_fake_data = generator(g_gen_input)
g_fake_decision = discriminator(g_fake_data)
labels = Variable(torch.ones(bs))
g_loss = criterion(g_fake_decision, labels) # we want to fool, so pretend it s all genuine
g_loss.backward()
g_optimizer.step() # Only optimizes G s parameters
...
...
和Dev Nag的版本比起來除了上面提到的判別網路,和樣本維度的修改,還加了可視化方便直觀演示和理解,比如用一個二維高斯分布產生一個折線形狀的分布,執行:
python gan_demo.py inputs/zig.jpg
訓練過程的可視化如下:
GIF/497K
更多可視化例子可以參考如下鏈接:
http://t.cn/Ro8aNJz
Conditional GAN
對於一些複雜的分布,原始的GAN就會很吃力,比如用一個二維高斯分布產生兩坨圓形的分布:
GIF/516K
因為latent space的分布就是一坨二維的樣本,所以即使模型有很強的非線性,也難以把這個分布「切開」並變換成兩個很好的圓形分布。因此在上面的動圖裡能看到生成的兩坨樣本中間總是有一些殘存的樣本,像是兩個天體在交換物質。要改進這種情況,比較直接的想法是增加模型複雜度,或是提高latent space維度。也許模型可以學習到用其中部分維度產生一個圓形,用另一部分維度產生另一個圓形。不過我自己試了下,效果都不好。
其實這個例子人眼一看就知道是兩個分布在一個圖裡,假設我們已經知道這個信息,那麼生成依據的就是個條件概率。把這個條件加到GAN里,就是Conditional GAN,公式如下:
示意圖如下:
條件信息變相降低了生成樣本的難度,所以生成的樣本效果好很多。
在網路中加入條件的方式沒有固定的原則,這裡我們採用的是可能最常見的方法:用one-hot方式將條件編碼成一個向量,然後和原始的輸入拼一下。注意對於判別網路和生成網路都要這麼做,所以上面公式和C-GAN原文簡化過度的公式比起來多了兩個y,避免造成迷惑。
C-GAN的代碼實現就是GAN的版本基礎上,利用pytorch的torch.cat()對條件和輸入進行拼接。其中條件的輸入就是多張圖片,每張定義一部分分布的PDF。比如對於上面兩坨分布的例子,就拆成兩張圖像來定義PDF:
具體實現就不貼這裡了,參考本文的Github頁面:
http://t.cn/Ro8Svq4
加入條件信息後,兩坨分布的生成就輕鬆搞定了,執行:
python cgan_demo.py inputs/binary
得到下面的訓練過程可視化:
GIF/298K
對於一些更複雜的分布也不在話下,比如:
這兩個圖案對應的原始GAN和C-GAN的訓練可視化對比可以在這裡看到。
應用樣例
其實現在能見到的基於 GAN 的有意思應用基本都是 Conditional GAN,下篇打算介紹基於 C-GAN 的一個實(dan)用(teng)例子:
本文完整代碼
http://t.cn/Ro8Svq4
研習社特供福利ID:OKweiwu
英偉達現場授課
聽說英偉達開發者票已經快賣光了,今天開始......社長已經沒有優惠碼可送了。
3000+ 人 AI 盛宴現場,CCF-GAIR 還專門開設開發者專場培訓,由 NVIDIA 深度學習學院現場教授深度學習,可遠程接入 NVIDIA 官方伺服器進行項目實操,高級工程師現場指導。培訓結束後,學員可獲得 NVIDIA 認證證書。
※最容易做的圖像分割教程:用英偉達 DIGITS 進行圖像分割,看一遍你也會做!(上)
※AI 助你無碼看片,生成對抗網路大顯身手
※《哈利·波特》出版二十周年,教大家用神經網路寫咒語!
※訓練深度神經網路的必知技巧,你知道哪些?
※聊天機器人還能這麼玩!教你用 Tensorflow 搭建能理解語境的客服小二!
TAG:唯物 |
※比谷歌快46倍GPU助力IBM Snap ML40億樣本訓練模型僅需91.5 秒
※IBM全新機器學習庫SnapML:訓練提速46倍,40億樣本僅需91.5秒
※90後女COO炒幣虧損1.2億?幣圈韭菜哀嚎的樣本
※CVPR 2019提前看:少樣本學習專題
※「合併」 樣本和標籤?IBM 為多標籤小樣本圖像分類帶來新進展!| CVPR 2019
※NASA探測器抵達「貝努」小行星,採集樣本2023年送回地球
※僅用200個樣本就能得到當前最佳結果:手寫字元識別新模型TextCaps
※從 ICLR 2019 一覽小樣本學習最新進展
※僅200個樣本就能得到當前最佳結果:手寫字元識別新模型TextCaps
※失眠到底是什麼造成的?有人為此分析了131萬個DNA樣本
※CVPR 2018 | 零樣本學習新進展:使用鑒別性特徵實現零樣本識別
※CVE-2018-4878 Flash 0day漏洞攻擊樣本解析
※主城區3-5月2000個小區漲幅樣本數據
※視頻購物「1.28造節」樣本:年入66億的東方購物如何醞釀下一個超級IP
※從 CVPR 2019 一覽小樣本學習研究進展
※2090例大樣本分析來襲:確定CTC在早期乳腺癌中的預後價值
※史上最大DNA民間徵集,1.5萬人自願提供樣本……20年前的男童懸案,終於破了!
※歷時十餘年、33種癌症、超11000份樣本繪製泛癌圖譜
※滬深300上證50中證500等指數今年首次調整樣本股
※中科普瑞-限時推出(3500/樣本)850k晶元服務