丁香五月天婷婷久久婷婷色综合91|国产传媒自偷自拍|久久影院亚洲精品|国产欧美VA天堂国产美女自慰视屏|免费黄色av网站|婷婷丁香五月激情四射|日韩AV一区二区中文字幕在线观看|亚洲欧美日本性爱|日日噜噜噜夜夜噜噜噜|中文Av日韩一区二区

您正在使用IE低版瀏覽器,為了您的雷峰網(wǎng)賬號(hào)安全和更好的產(chǎn)品體驗(yàn),強(qiáng)烈建議使用更快更安全的瀏覽器
此為臨時(shí)鏈接,僅用于文章預(yù)覽,將在時(shí)失效
人工智能開發(fā)者 正文
發(fā)私信給汪思穎
發(fā)送

0

從一次 CycleGAN 實(shí)現(xiàn)聊聊 TF

本文作者: 汪思穎 2017-11-20 19:16
導(dǎo)語:用 TensorFlow 實(shí)現(xiàn) CycleGAN 時(shí)需要注意的小技巧

雷鋒網(wǎng) AI科技評(píng)論按,本文作者Coldwings,該文首發(fā)于知乎專欄為愛寫程序,雷鋒網(wǎng) AI科技評(píng)論獲其授權(quán)轉(zhuǎn)載。以下為原文內(nèi)容,有刪減。

CycleGAN是個(gè)很有趣的想法(Unpaired Image-to-Image Translationusing Cycle-Consistent Adversarial Networks [https://arxiv.org/pdf/1703.10593.pdf]),看完這篇論文之后,隱隱地覺得,這后面有更多的內(nèi)容可以挖,我盡我所能做出了各種嘗試,努力發(fā)掘更多的可能性。

實(shí)現(xiàn)過程中可以說還是略微糾結(jié)的,最初是用Keras快速實(shí)踐了一下,然而其實(shí)并不『快速』,后來反倒是用TensorFlow重寫以及嘗試各種意外想法時(shí)才感覺,當(dāng)需要處理一些比較復(fù)雜的網(wǎng)絡(luò)結(jié)構(gòu)、訓(xùn)練流程甚至op時(shí),TF提供的可以細(xì)化到每個(gè)操作的體驗(yàn)實(shí)際上要比各種上層API都來得更好,而結(jié)合TensorBoard,可視化的訓(xùn)練將取得更好的效果。當(dāng)然,我對(duì)Torch無感,或許用Torch能有更好的體驗(yàn),但我不擅長這個(gè);Chainer(A flexible framework for neural networks)講道理寫出來的代碼會(huì)更好看,但是似乎身邊用的人并不多,姑且放過。

這篇文章倒不是來介紹什么是CycleGAN的,若是不甚了解,我妻子將會(huì)將她的發(fā)表整理一下再發(fā)布出來(CycleGAN(以及DiscoGAN和DualGAN)簡介 - 知乎專欄)。這一陣的嘗試中,我自己也對(duì)GAN,對(duì)Generator中的圖像甚至其它東西的生成,以及單純從寫代碼角度來看,怎么管理TF里的變量,怎么把代碼寫得好看,怎么更好地利用TensorBoard都有了更多地理解,算是不小的提高吧……

所以這里也就大概提一提一些實(shí)現(xiàn)中需要注意的小技巧吧。(雖然我覺得大概大多數(shù)真正拿著TF搞DL研究的人都不需要研究這篇文章)

CycleGAN比較麻煩的地方

其實(shí)CycleGAN麻煩的地方不少,這是一個(gè)挺復(fù)合的模型:兩個(gè)Generator,兩個(gè)Discriminator,這已經(jīng)是四個(gè)比較簡單的網(wǎng)絡(luò)了(是的,考慮到所有可能性,Generator和Discriminator完全可以各自都有兩種不同的結(jié)構(gòu));一組Generator+Discriminator復(fù)合成一個(gè)GAN,又一層復(fù)合模型,并且GAN的訓(xùn)練還得控制,由于G和D的損失相反,訓(xùn)練G時(shí)需要控制D的變量讓其不可訓(xùn)練;我們還要讓Cycle loss作為模型loss的一部分,這個(gè)更高一層的復(fù)合模型由兩個(gè)GAN組成……

良好的代碼結(jié)構(gòu)

TensorFlow的自由度挺高的,類比的話,有那么點(diǎn)DL框架里的C++的意思;Python的語言靈活度也是高得不行,兩個(gè)很靈活的玩意放一起,寫個(gè)簡單模型自然想怎么玩就怎么玩,寫個(gè)復(fù)雜一些的模型,為了保證寫著方便,用著方便,改起來方便,還是需要比較好的代碼結(jié)構(gòu)的。

如果翻翻GitHub上一些比較熱的用TF寫的模型,通常都會(huì)發(fā)現(xiàn)大家比較習(xí)慣于把代碼分成op、module和model三個(gè)部分。

op里是一些通用層或者運(yùn)算的簡化定義,例如寫個(gè)卷積層,總是包含定義變量和定義運(yùn)算。習(xí)慣于Keras這樣不需要自己定義變量的玩意當(dāng)然不會(huì)太糾結(jié),但用TF時(shí),若是寫兩行定義一下變量總是挺讓人傷神的。

如果參照Keras的實(shí)現(xiàn),通過寫個(gè)類來定制op,變量管理看起來方便一點(diǎn),未免太過繁瑣。實(shí)際上TF提供的variable scope已經(jīng)非常方便了,這一部分寫成這樣似乎也不錯(cuò)

def conv2d(input, filter, kernel, strides=1, stddev=0.02, name='conv2d'):
   with tf.variable_scope(name):
       w = tf.get_variable(
           'w',
           (kernel, kernel, input.get_shape()[-1], filter),
           initializer=tf.truncated_normal_initializer(stddev=stddev)
       )
       conv = tf.nn.conv2d(input, w, strides=[1, strides, strides, 1], padding='VALID')
       b = tf.get_variable(
           'b',
           [filter],
           initializer=tf.constant_initializer(0.0)
       )
       conv = tf.reshape(tf.nn.bias_add(conv, b), tf.shape(conv))
       return conv

這樣定義幾個(gè)op之后,寫起代碼來就更有點(diǎn)類似于mxnet那樣的感覺了。

特別的,有些時(shí)候有些簡單結(jié)構(gòu),例如ResNet中的一個(gè)block這樣的玩意,我們也可以用類似的方式,用一個(gè)簡單函數(shù)包裝起來

def res_block(x, dim, name='res_block'):
   with tf.variable_scope(name):
       y = reflect_pad(x, name='rp1')
       y = conv2d(y, dim, 3, name='conv1')
       y = lrelu(y)
       y = reflect_pad(y, name='rp2')
       y = conv2d(y, dim, 3, name='conv2')
       y = lrelu(y)
       return tf.add(x, y)

對(duì)于重復(fù)的模塊,這樣的包裝也方便多次使用。

這些是很常見的做法。同時(shí)我們也發(fā)現(xiàn)了,幾乎每個(gè)這樣的函數(shù)里都少不了一個(gè)variable scope的使用,一方面避免定義變量時(shí)名字的重復(fù)以及訓(xùn)練時(shí)變量的管理,另一方面也方便TensorBoard畫圖的時(shí)候能把有用的東西放到一起。但這樣每個(gè)函數(shù)里帶個(gè)name參數(shù)的做法寫多了也會(huì)煩,加上奇怪的縮進(jìn)……我會(huì)更傾向于用一個(gè)裝飾器來解決這樣的問題,同時(shí)也能減少『忘了用variable scope』的情況。

def scope(default_name):
   def deco(fn):
       def wrapper(*args, **kwargs):
           if 'name' in kwargs:
               name = kwargs['name']
               kwargs.pop('name')
           else:
               name = default_name
           with tf.variable_scope(name):
               return fn(*args, **kwargs)
       return wrapper
   return deco@scope('conv2d')def conv2d(input, filter, kernel, strides=1, stddev=0.02):
   w = tf.get_variable(
       'w',
       (kernel, kernel, input.get_shape()[-1], filter),
       initializer=tf.truncated_normal_initializer(stddev=stddev)
   )
   conv = tf.nn.conv2d(input, w, strides=[1, strides, strides, 1], padding='VALID')
   b = tf.get_variable(
       'b',
       [filter],
       initializer=tf.constant_initializer(0.0)
   )
   conv = tf.reshape(tf.nn.bias_add(conv, b), tf.shape(conv))
   return conv

至于module,也就是一些稍微復(fù)雜的成型結(jié)構(gòu),例如GAN里的Discriminator和Generator,講道理這玩意其實(shí)和op大體上是類似的,就不多說了。

最后是model。通常大家都是用類來做,因?yàn)閙odel中往往還包含了輸入數(shù)據(jù)用的placeholder、訓(xùn)練用的op,甚至一些具體的方法等等內(nèi)容。這一塊的代碼建議,只不過是最好先寫一個(gè)抽象類,把需要的幾個(gè)接口給定義一下,然后讓實(shí)際的model類繼承,代碼會(huì)漂亮很多,也更便于利用諸如PyCharm這樣的IDE來提示你哪些東西該做而沒有做。

關(guān)于config/options

網(wǎng)上常見的代碼里,模型的一些參數(shù)信息大都設(shè)計(jì)成用命令行參數(shù)來傳入,更多是直接使用tf.flags來處理。但無論如何,我仍然覺得定義一個(gè)config類來管理參數(shù)是有一定必要性的,直接使用tf.flags主要是是有大段tf.flags.DEFINE_xxx,不好看,也不方便直觀地反應(yīng)默認(rèn)參數(shù)。相對(duì)的,如果定義一個(gè)參數(shù)類,在__init__里寫下默認(rèn)參數(shù),然后寫個(gè)小方法自動(dòng)地根據(jù)dir來添加這些tf.flags會(huì)漂亮許多。但這個(gè)只是個(gè)人觀點(diǎn),似乎并沒有具體的優(yōu)劣之分。

關(guān)于TensorBoard

不得不說TensorBoard作為TF自帶的配套可視化工具,只要你不是太在意刷新頻率的問題(通常不會(huì)有人在意這個(gè)吧……),用起來實(shí)在太方便。加上能夠自動(dòng)生成運(yùn)算的各個(gè)符號(hào)的結(jié)構(gòu)圖,哪怕不說訓(xùn)練,就是檢查模型結(jié)構(gòu)是否符合自己所想都是個(gè)非常好用的工具。比如封面圖,生成出來用來檢查代碼的模型邏輯,還可以根據(jù)需要點(diǎn)選觀察依賴關(guān)系。

從一次 CycleGAN 實(shí)現(xiàn)聊聊 TF

順帶一提,如果生成的模型圖長得非常奇怪,八成是代碼有問題……

不過要用好TensorBoard,有幾個(gè)小小的要點(diǎn):首先是,至少,你的各個(gè)op和module里,得用上variable scope或者name scope。對(duì)于一個(gè)scope,在TensorBoard的Graph里會(huì)將其聚集成一個(gè)小塊,內(nèi)部結(jié)構(gòu)可以展開觀察,而如果不用scope,你會(huì)看到滿眼都是一堆一堆的基本op,當(dāng)模型復(fù)雜時(shí),圖基本沒法看……

此外,對(duì)于圖片處理,用好TensorBoard的ImageSummary當(dāng)然是很不錯(cuò)的選擇。但是記得一定要為添加圖片的summary op定義一個(gè)喂數(shù)據(jù)的placeholder。

self.p_img = tf.placeholder(tf.float32, shape=[1, 256 * 6, 256 * 4, 3])

self.img_op = tf.summary.image('sample', self.p_img)

……

img = np.array([img])

s_img = self.sess.run(self.img_op, feed_dict={self.p_img: img})

self.writer.add_summary(s_img, count)

這樣才是正確的。網(wǎng)上有些材料里告訴你可以直接用tf.summary.image('tag', data)來生成圖片summary,這樣其實(shí)每次都會(huì)構(gòu)造一個(gè)新的summary,不便于圖片歸類,但更大的問題是,這樣做會(huì)使得每次都申請(qǐng)一個(gè)新的變量(用來裝你的圖片數(shù)據(jù)),倘若你有定周期存儲(chǔ)訓(xùn)練權(quán)重的習(xí)慣,會(huì)發(fā)現(xiàn)沒幾個(gè)小時(shí)就會(huì)因?yàn)闄?quán)重變量總量超過2GB而使得程序跑崩……想想看晚上跑著訓(xùn)練的代碼想著可以回家休息了,結(jié)果前腳剛進(jìn)家門,程序就罷工了,大好的訓(xùn)練時(shí)間就給直接浪費(fèi)了。

另外,這里的圖片可以是重新歸為0~255的整形的數(shù)據(jù),也可以直接給浮點(diǎn)數(shù)據(jù)[-1, 1]。更不錯(cuò)的想法是,先使用matplotlib/pil/numpy來合成、拼湊甚至生成圖像,然后再來添加,會(huì)讓效果更令人滿意,比如這樣:

從一次 CycleGAN 實(shí)現(xiàn)聊聊 TF

最后補(bǔ)充一句……雙顯示器確實(shí)有利于提高寫代碼、改代碼以及碼字的效率……

雷峰網(wǎng)版權(quán)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見轉(zhuǎn)載須知。

從一次 CycleGAN 實(shí)現(xiàn)聊聊 TF

分享:
相關(guān)文章

編輯

關(guān)注AI學(xué)術(shù),例如論文
當(dāng)月熱門文章
最新文章
請(qǐng)?zhí)顚懮暾?qǐng)人資料
姓名
電話
郵箱
微信號(hào)
作品鏈接
個(gè)人簡介
為了您的賬戶安全,請(qǐng)驗(yàn)證郵箱
您的郵箱還未驗(yàn)證,完成可獲20積分喲!
請(qǐng)驗(yàn)證您的郵箱
立即驗(yàn)證
完善賬號(hào)信息
您的賬號(hào)已經(jīng)綁定,現(xiàn)在您可以設(shè)置密碼以方便用郵箱登錄
立即設(shè)置 以后再說