今天 reddit 上一篇帖子引起了熱議,博主 jamesonatfritz 稱他將原本具備 1.7M 參數(shù)的風(fēng)格遷移網(wǎng)絡(luò)減少到只有 11,868 個(gè)參數(shù),,該網(wǎng)絡(luò)仍然能夠輸出風(fēng)格化的圖像,。且量化后的最終網(wǎng)絡(luò)體積僅有 17 kB,非常適合移動(dòng) app,。
jamesonatfritz 想解決神經(jīng)網(wǎng)絡(luò)的過參數(shù)化問題,,想要?jiǎng)?chuàng)建體積小但性能優(yōu)的神經(jīng)網(wǎng)絡(luò)。他所試驗(yàn)的第一個(gè)任務(wù)便是藝術(shù)風(fēng)格遷移,。
GitHub 鏈接:https://github.com/fritzlabs/fritz-style-transfer
現(xiàn)在有很多用來訓(xùn)練藝術(shù)風(fēng)格遷移模型的現(xiàn)成工具,,還有上千種開源實(shí)現(xiàn)。其中的多數(shù)工具利用 Johnson 等人在《Perceptual Losses for Real-Time Style Transfer and Super-Resolution》中提出的網(wǎng)絡(luò)架構(gòu)的變體來實(shí)現(xiàn)快速,、前饋的風(fēng)格化,。因此,多數(shù)遷移模型的大小是 7MB,。對(duì)于你的應(yīng)用來說,,這個(gè)負(fù)擔(dān)并非不可承受,但也并非無足輕重,。
研究表明,,神經(jīng)網(wǎng)絡(luò)的體積通常遠(yuǎn)遠(yuǎn)大于所需,數(shù)百萬的權(quán)重中有很多并不重要,。因此作者創(chuàng)造了一個(gè)體積大大縮小的可靠風(fēng)格遷移模型:一個(gè)只有 11686 個(gè)訓(xùn)練權(quán)重的 17KB 神經(jīng)網(wǎng)絡(luò),。
左:原圖;中:來自上述 17KB 模型的風(fēng)格化圖像,;右:來自 7MB 模型的風(fēng)格化圖像,。
快速概覽:
原始模型:
大小:7MB
權(quán)重?cái)?shù):1.7M
在 iPhone X 上的速度:18 FPS
小模型:
大?。?7KB
權(quán)重?cái)?shù):11,868
在 iPhone X 上的速度:29 FPS
如何縮小風(fēng)格遷移模型
作者主要使用了兩種技術(shù),,而且都可以泛化到其他模型:
1. 大刀闊斧地修剪層和權(quán)重;
2. 通過量化將 32 位浮點(diǎn)權(quán)重轉(zhuǎn)換為 8 位整型
機(jī)器之心Synced剪枝小程序
修剪策略
卷積神經(jīng)網(wǎng)絡(luò)通常包含數(shù)百萬甚至上億個(gè)需要在訓(xùn)練階段進(jìn)行調(diào)整的權(quán)重,。通常來講,,權(quán)重越多準(zhǔn)確率越高。但這種增加權(quán)重提高準(zhǔn)確率的做法非常低效,。谷歌 MobileNetV2 的 stock 配置具有 347 萬個(gè)權(quán)重,,內(nèi)存占用達(dá) 16MB。InceptionV3 架構(gòu)大小約為前者的 6 倍,,具備 2400 萬個(gè)權(quán)重,,內(nèi)存占用達(dá) 92MB。盡管多了 2000 多萬個(gè)權(quán)重,,但 InceptionV3 在 ImageNet 上的 top-1 分類準(zhǔn)確率只比 MobileNetV2 高出 7 個(gè)百分點(diǎn)(80% vs 73%),。
因此,我們可以假設(shè)神經(jīng)網(wǎng)絡(luò)中的多數(shù)權(quán)重沒有那么重要并將其移除,。但重點(diǎn)是怎么做呢,?我們可以選擇在三個(gè)層面進(jìn)行修剪:?jiǎn)蝹€(gè)權(quán)重、層,、塊,。
權(quán)重層面:假設(shè)某個(gè)神經(jīng)網(wǎng)絡(luò)上的多數(shù)(>95%)權(quán)重都沒有什么用。如果能找出那些對(duì)準(zhǔn)確率有影響的權(quán)重,,就可以將其留下并將其他移除,。
層層面:每個(gè)層中都包含一些權(quán)重。例如,,2D 卷積層具有一個(gè)權(quán)重張量,,即卷積核,,用戶可以定義其寬度、高度和深度,??s小卷積核可以減小整個(gè)網(wǎng)絡(luò)的大小。
塊層面:多個(gè)層通??梢越Y(jié)合成可重復(fù)利用的子圖,,即塊。以 ResNet 為例,,它的名字來源于重復(fù) 10-50 次的「殘差塊」,。在塊層面進(jìn)行修剪可以移除多個(gè)層,從而一次性移除多個(gè)參數(shù),。
在實(shí)踐中,,稀疏張量運(yùn)算沒有很好的實(shí)現(xiàn),因此權(quán)重層面的修剪沒有多大價(jià)值,。那么就只剩下層和塊層面的修剪了,。
實(shí)踐中的修剪
作者使用的層修剪技術(shù)是引入 width multiplier 作為超參數(shù)。width multiplier 最初由谷歌在其論文《MobileNets: Efficient Convolutional Neural Networks for Mobile Vision》中提出,,非常簡(jiǎn)單,、高效。
width multiplier 利用一個(gè)恒定系數(shù)調(diào)整每個(gè)卷積層中的卷積核數(shù)量,。對(duì)于給定的層及 width multiplier alpha,,卷積核數(shù)量 F 變?yōu)?alpha * F。
有了這個(gè)超參數(shù),,我們就可以生成一系列架構(gòu)相同但權(quán)重?cái)?shù)不同的網(wǎng)絡(luò),。訓(xùn)練每種配置,就可以在模型速度,、大小及準(zhǔn)確率之間做出權(quán)衡,。
下面是作者模仿 Johnson 等人在《The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks》提出的網(wǎng)絡(luò)架構(gòu)構(gòu)建快速風(fēng)格遷移模型的方法,不同之處在于添加了 width multiplier 作為超參數(shù),。
@classmethod
def build(
cls,
image_size,
alpha=1.0,
input_tensor=None,
checkpoint_file=None):
"""Build a Transfer Network Model using keras' functional API.
Args:
image_size - the size of the input and output image (H, W)
alpha - a width parameter to scale the number of channels by
Returns:
model: a keras model object
"""
x = keras.layers.Input(
shape=(image_size[0], image_size[1], 3), tensor=input_tensor)
out = cls._convolution(x, int(alpha * 32), 9, strides=1)
out = cls._convolution(out, int(alpha * 64), 3, strides=2)
out = cls._convolution(out, int(alpha * 128), 3, strides=2)
out = cls._residual_block(out, int(alpha * 128))
out = cls._residual_block(out, int(alpha * 128))
out = cls._residual_block(out, int(alpha * 128))
out = cls._residual_block(out, int(alpha * 128))
out = cls._residual_block(out, int(alpha * 128))
out = cls._upsample(out, int(alpha * 64), 3)
out = cls._upsample(out, int(alpha * 32), 3)
out = cls._convolution(out, 3, 9, relu=False, padding='same')
# Restrict outputs of pixel values to -1 and 1.
out = keras.layers.Activation('tanh')(out)
# Deprocess the image into valid image data. Note we'll need to define
# a custom layer for this in Core ML as well.
out = layers.DeprocessStylizedImage()(out)
model = keras.models.Model(inputs=x, outputs=out)
注意,,模型構(gòu)建器類的其余部分沒有顯示。
當(dāng) alpha=1.0 時(shí),,得到的網(wǎng)絡(luò)包含 170 萬個(gè)權(quán)重,。當(dāng) alpha=0.5 時(shí),得到的網(wǎng)絡(luò)僅有 424,102 個(gè)權(quán)重,。
你可以構(gòu)建一些寬度參數(shù)很小的網(wǎng)絡(luò),,但是也有相當(dāng)多的重復(fù)塊。作者決定修剪掉一些,但實(shí)際操作后卻發(fā)現(xiàn)不能移除太多,。即使參數(shù)量保持不變,,較深的網(wǎng)絡(luò)能夠產(chǎn)生更好的結(jié)果。作者最終刪除了五個(gè)殘差塊中的兩個(gè),,并將每層的默認(rèn)濾波器數(shù)量減少至 32 個(gè),。得到的微型網(wǎng)絡(luò)如下所示:
@classmethod
def build(
cls,
image_size,
alpha=1.0,
input_tensor=None,
checkpoint_file=None):
"""Build a Small Transfer Network Model using keras' functional API.
This architecture removes some blocks of layers and reduces the size
of convolutions to save on computation.
Args:
image_size - the size of the input and output image (H, W)
alpha - a width parameter to scale the number of channels by
Returns:
model: a keras model object
"""
x = keras.layers.Input(
shape=(image_size[0], image_size[1], 3), tensor=input_tensor)
out = cls._convolution(x, int(alpha * 32), 9, strides=1)
out = cls._convolution(out, int(alpha * 32), 3, strides=2)
out = cls._convolution(out, int(alpha * 32), 3, strides=2)
out = cls._residual_block(out, int(alpha * 32))
out = cls._residual_block(out, int(alpha * 32))
out = cls._residual_block(out, int(alpha * 32))
out = cls._upsample(out, int(alpha * 32), 3)
out = cls._upsample(out, int(alpha * 32), 3)
out = cls._convolution(out, 3, 9, relu=False, padding='same')
# Restrict outputs of pixel values to -1 and 1.
out = keras.layers.Activation('tanh')(out)
# Deprocess the image into valid image data. Note we'll need to define
# a custom layer for this in Core ML as well.
out = layers.DeprocessStylizedImage()(out)
model = keras.models.Model(inputs=x, outputs=out)
帶有寬度參數(shù)的較小風(fēng)格遷移網(wǎng)絡(luò)。
通過反復(fù)嘗試,,作者發(fā)現(xiàn)仍然可以用上述架構(gòu)實(shí)現(xiàn)良好的風(fēng)格遷移,一直到寬度參數(shù)為 0.3,,在每一層上留下 9 個(gè)濾波器,。最終結(jié)果是一個(gè)只有 11,868 個(gè)權(quán)重的神經(jīng)網(wǎng)絡(luò)。任何權(quán)重低于 10000 的網(wǎng)絡(luò)都不能持續(xù)訓(xùn)練,,并且會(huì)產(chǎn)生糟糕的風(fēng)格化圖像,。
值得一提的是,剪枝技術(shù)是在網(wǎng)絡(luò)訓(xùn)練之前應(yīng)用的,。在訓(xùn)練期間或訓(xùn)練后反復(fù)修剪,,你可以在很多任務(wù)上實(shí)現(xiàn)更高的性能。
量化
最后一段壓縮是在網(wǎng)絡(luò)訓(xùn)練完成后進(jìn)行的,。神經(jīng)網(wǎng)絡(luò)權(quán)重通常存儲(chǔ)為 64 位或 32 位浮點(diǎn)數(shù),。量化過程將每一個(gè)浮點(diǎn)權(quán)重映射到具有較低位寬的整數(shù)。從 32 位浮點(diǎn)權(quán)重變?yōu)?8 位整型,,使得存儲(chǔ)大小減少了 4 倍,。作者利用 Alexis Creuzot 在博客中提出的方法(https://heartbeat.fritz.ai/reducing-coreml2-model-size-by-4x-with-quantization-in-ios12-b1c854651c4),在不怎么影響風(fēng)格的情況下使浮點(diǎn)數(shù)降低到了 8 位量化,。
現(xiàn)在所有主要的移動(dòng)框架都支持量化,,如 TensorFlow Mobile、TensorFlow Lite,、Core ML 和 Caffe2Go,。
最終結(jié)果
該微型網(wǎng)絡(luò)架構(gòu)有 11,868 個(gè)參數(shù),相比之下,,Johnson 最初的模型具有 170 萬個(gè)參數(shù),,大小為 1.7MB。當(dāng)轉(zhuǎn)化為 Core ML 并量化時(shí),,最終大小僅為 17KB——為原始大小的 1/400,。以下是在梵高的《Starry Night》上的訓(xùn)練結(jié)果。
此微型風(fēng)格遷移結(jié)果的實(shí)時(shí)視頻可在 Heartbeat App 上查看:
http://bit.ly/heartbeat-ios
作者驚訝地發(fā)現(xiàn),,盡管尺寸相差 400 倍,,但在 iPhone X 上,這款微型模型的運(yùn)行速度僅快了 50%。原因可能是計(jì)算與這一通用架構(gòu)相關(guān),,也可能是將圖像遷移到 GPU 進(jìn)行處理時(shí)造成的,。
如果你對(duì)結(jié)果表示懷疑,可以自己下載并運(yùn)行此微型模型,。甚至訓(xùn)練自己的模型,!
下載地址:https://github.com/fritzlabs/fritz-style-transfer/blob/master/example/starry_night_640x480_small_a03_q8.mlmodel
總而言之,作者用兩種簡(jiǎn)單的技術(shù)將風(fēng)格遷移神經(jīng)網(wǎng)絡(luò)的規(guī)模減小了 99.75%,。使用簡(jiǎn)單的 width multiplier 超參數(shù)修剪層,,訓(xùn)練后的權(quán)重從 32 位浮點(diǎn)數(shù)量化為 8 位整數(shù)。未來,,作者期待看到將這些方法泛化到其它神經(jīng)網(wǎng)絡(luò)的效果,。風(fēng)格遷移相對(duì)簡(jiǎn)單,因?yàn)椤笢?zhǔn)確率」肉眼可見,。對(duì)于圖像識(shí)別這樣更加可以量化的任務(wù)而言,,如此極端的修剪可能帶來更明顯的性能下降。
Reddit 討論
這篇帖子下有一些 reddit 網(wǎng)友對(duì)該項(xiàng)目提出了質(zhì)疑:
gwern:
看你的博客,,剪枝部分似乎沒有移除任何層,,只是更改了層的寬度/濾波器,然后對(duì)所有參數(shù)進(jìn)行量化,。如果所有層都在(因?yàn)槟銢]有做任何類似于訓(xùn)練較寬的淺層網(wǎng)絡(luò)的工作來模仿原始深度教師網(wǎng)絡(luò)),,那么它們?nèi)詫暮罄m(xù)計(jì)算的每一層中引入大量延遲,即使每一層都很小,。(由于你可以在手機(jī) GPU 上安裝更多模型,,每個(gè)模型使用較少的 FLOPS,因此整體吞吐量會(huì)變得更好,。但是每個(gè)模型的迭代仍然需要一段時(shí)間,,在特定大小之后,每一層基本上是即時(shí)的,。)
jamesonatfritz 回復(fù):
你說得對(duì),,濾波器剪枝部分確實(shí)沒有移除層,但是我去掉了兩個(gè)殘差塊,,從而消除了一些層,。整體 FLOPs 的降低情況不如全部權(quán)重?cái)?shù)量的減少情況,這一點(diǎn)你說得對(duì),。不幸的是,,Apple 沒法讓你較好地控制模型運(yùn)行的位置。你無法強(qiáng)制該模型使用 GPU,。一些啟發(fā)式方法導(dǎo)致較小的模型僅在 CPU 上運(yùn)行,,這是可能的,。
gwern:
「去掉了兩個(gè)殘差塊,從而消除了一些層,?!?/p>
你借此獲得了一些加速,但是使用更扁平的模型或許會(huì)實(shí)現(xiàn)更多加速,。
jamesonatfritz 回復(fù):
確實(shí)如此,。我試過的最扁平模型只有一個(gè)卷積層、一個(gè)殘差模塊和一個(gè)上采樣模塊,,但我發(fā)現(xiàn)這些變體無法收斂,。
gwern:
這似乎有些過了:只有一個(gè)層有些過于難了。我想的是三四個(gè)層這樣,,在預(yù)訓(xùn)練風(fēng)格遷移模型的確切像素輸出上進(jìn)行訓(xùn)練,。或許值得一試,。
Ikuyas:
這個(gè)方法和直接使用小模型有什么區(qū)別嗎,?我確定使用 11,868 個(gè)參數(shù)進(jìn)行訓(xùn)練結(jié)果會(huì)更好,。另外,,1.7M 參數(shù)太大了,每個(gè)參數(shù)的貢獻(xiàn)估計(jì)會(huì)很小,。但是,,真實(shí)情況是只有幾百個(gè)參數(shù)是真正重要的,其他參數(shù)只是到處吸收一點(diǎn)微小的噪聲,。
從標(biāo)準(zhǔn)回歸的角度來看,,這似乎是完美的預(yù)期結(jié)果。
作者回復(fù):
我應(yīng)該在文章里寫清楚的,,事實(shí)上你所說的正是我所做的,。剪枝發(fā)生在訓(xùn)練之前。反直覺的一件事是,,實(shí)際上使用較少的參數(shù)從頭開始訓(xùn)練模型無法確保能得到一樣的結(jié)果,。盡管一小部分權(quán)重比較重要,但你很難提前知道哪些權(quán)重是重要的,。詳情參見論文:《The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks》,。
Ikuyas:
神經(jīng)網(wǎng)絡(luò)中的參數(shù)缺乏有意義的解釋,這是第一堂機(jī)器學(xué)習(xí)課程中就學(xué)過的,。這并不反直覺,,而是預(yù)料之中。剪枝后的參數(shù)甚至并不被認(rèn)為是吸收噪聲的神經(jīng)元,。對(duì)于標(biāo)準(zhǔn)回歸模型來說,,噪聲有時(shí)似乎像是正態(tài)分布的實(shí)現(xiàn)。而神經(jīng)網(wǎng)絡(luò)擬合并不假設(shè)任何此類事情。因此使用較少的參數(shù),,你可以用完全不同的模型擬合數(shù)據(jù),。剪枝技術(shù)并沒有什么用。