Aerosolve Demo 之 Image Impressionism

這個(gè) demo 展示了如何將圖片轉(zhuǎn)換成印象派風(fēng)格, 非常有趣, 本文將從數(shù)據(jù)流的角度分析作者是如何做的.

Demo 的具體步驟和效果可見 GitHub.

從圖片生成訓(xùn)練數(shù)據(jù)

讀取每個(gè)像素 RGB 的值, 并歸一化.

下圖是由(0,0)坐標(biāo)像素生成的 Pixel 對象, RGB 的值都是由原值除以 255 得到的.

Pixel -> Example

接著通過 pixelToExample 方法, 每個(gè)像素會被轉(zhuǎn)換為一個(gè) Example 對象.

RGB 分別對應(yīng)3個(gè) FeatureVector, 其中 C 是 channel 的縮寫, 存儲了對應(yīng)的顏色, $target 的值會作為輸出參與訓(xùn)練.

$target 這個(gè)鍵名是在 image_impressionism.conf 中配置的, 該文件還記錄了數(shù)據(jù)路徑/模型參數(shù)/特征轉(zhuǎn)換方法和參數(shù)等其他配置.

下圖是一個(gè) Example 實(shí)例.

這是完全展開后轉(zhuǎn)成 json 格式的示例.

{
    "example": [
        {
            "stringFeatures": {
                "C": [
                    "Red"
                ]
            },
            "floatFeatures": {
                "$target": {
                    "": 0.5529411764705883
                }
            }
        },
        {
            "stringFeatures": {
                "C": [
                    "Green"
                ]
            },
            "floatFeatures": {
                "$target": {
                    "": 0.5725490196078431
                }
            }
        },
        {
            "stringFeatures": {
                "C": [
                    "Blue"
                ]
            },
            "floatFeatures": {
                "$target": {
                    "": 0.5568627450980392
                }
            }
        }
    ],
    "context": {
        "floatFeatures": {
            "LOC": {
                "X": 0,
                "Y": 0
            }
        }
    }
}

存儲

最后用 thrift 將特征對象序列化后壓縮存儲.

sc.parallelize(pixels)
  .map(x => pixelToExample(x, true))
  .map(Util.encode)
  .saveAsTextFile(output, classOf[GzipCodec])

訓(xùn)練模型

特征轉(zhuǎn)換

所有特征會按順序依次應(yīng)用3類 transform: context_transform, item_transform, combined_transform.

每類 transform 的名稱及相關(guān)參數(shù)都需要在 image_impressionism.conf 中配置.

Context Transform

Context transform 會轉(zhuǎn)換 Example 中的 context 屬性, 并將新特征存入 context.stringFeatures.

quantize_pixel_location {
  transform: multiscale_grid_quantize
  # Grid up the pixels into squares of the following sizes.
  # Use relatively prime grids to create jitter.
  buckets : [ 3.0, 7.0, 17.0, 31.0, 47.0, 67.0, 79.0, 89.0, 97.0 ]
  field1: "LOC"
  value1: "Y"
  value2: "X"
  output: "QLOC"
}

此處用的是 multiscale_grid_quantize 方法, 實(shí)現(xiàn)該方法的類為 MultiscaleGridQuantizeTransform.

該方法將二維平面劃分成不同大小的正方形格子, 然后將每個(gè)格子里的點(diǎn)都映射到該格子.

即用更大的粒度來描述平面, 借此消除局部差異, 提取共同特征.

格子的 ID 由其邊長和左上角點(diǎn)的坐標(biāo)拼接生成.
例如 [3.0]=(0.0,0.0) 包含了 (0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2) 9個(gè)點(diǎn).

buckets 配置了會用哪些邊長的格子, field1/value1/value2 可結(jié)合之前的特征示例體會含義, output 是新特征的名字.

下圖是坐標(biāo)(0,0)轉(zhuǎn)換后得到的9個(gè)新特征.

Item Transform

Item transform 將轉(zhuǎn)換 Example.example 中每個(gè) FeatureVector, 轉(zhuǎn)換后的新特征會存入 stringFeatures.

identity_transform {
  transform: list
  transforms: []
}

list 表示將逐個(gè)應(yīng)用 transforms 列表中的變換, 空列表意味著不做任何轉(zhuǎn)換.

Combined Transform

Combined transform 將會把 contextexample 中每個(gè) FeatureVector 結(jié)合起來, 并存入后者的 stringFeatures 中.

代碼實(shí)現(xiàn)分為兩步:

  1. 拷貝 context.stringFeaturesFeatureVector.stringFeatures.
  2. example 中每個(gè) FeatureVectorstringFeatures 應(yīng)用配置中指定的 transform.
C_X_QLOC {
  transform: cross
  field1: "C" // Color channel
  field2: "QLOC" // Quantized location
  output: "C_x_QLOC"
}

combined_transform {
  transform: list
  transforms: [
    C_X_QLOC
  ]
}

cross 對應(yīng)的類為 CrossTransform, 它會把 field1/field2 的值拼接起來作為 output 的值.

需要注意的是:

  • 轉(zhuǎn)換前每個(gè) Example 對象中 example 屬性有3個(gè) FeatureVector.
  • 轉(zhuǎn)換后3個(gè) FeatureVector 將分別轉(zhuǎn)換為1個(gè) Example 對象, 每個(gè) Example 對象的 example 屬性只有1個(gè) FeatureVector.

下圖為轉(zhuǎn)換后的一個(gè) Example 對象:

訓(xùn)練

Aerosolve 的訓(xùn)練算法都是基于 Spark 實(shí)現(xiàn)的, 所以和訓(xùn)練有關(guān)的代碼都放在一個(gè)獨(dú)立的子項(xiàng)目 training 中.

這個(gè) demo 用的是線性回歸模型, 訓(xùn)練方法為 SGD (Stochastic Gradient Descent), 代碼實(shí)現(xiàn)在 LinearRankerTrainer 中.

每次迭代計(jì)算權(quán)重前, 會從 FeatureVector 取出相關(guān)的目標(biāo)值和特征, 如下圖:

權(quán)重訓(xùn)練完后的形式為 ((feature family, feature), weigth), 如下圖:

保存模型

模型由兩部分組成: 一個(gè) ModelHeader 和 若干個(gè) ModelRecord
(ModelHeader 實(shí)際上會保存為成一個(gè)特殊的 ModelRecord).

線性模型的 ModelHeader 只用到了兩個(gè)屬性: modelType 的值會設(shè)置為 linear, numRecords 會設(shè)為 weights.size.

模型也是用 thrift 序列化后存儲.

應(yīng)用模型輸出印象派圖片

宏觀視角

前面從微觀角度觀察了整個(gè)數(shù)據(jù)流, 接著我們從宏觀的角度看看它和線性模型是怎么對接的.

線性模型實(shí)質(zhì)是一個(gè)方程組, 訓(xùn)練權(quán)重的過程即求解自變量系數(shù)的過程.

該 demo 的方程組共有 num_pixels * 3 個(gè)方程, 每個(gè)像素會對應(yīng)3個(gè)方程,
這是因?yàn)槊總€(gè)像素有3個(gè) color channel (red/green/blue).

方程的因變量 y 即 r/g/b 歸一化后的值.

自變量的個(gè)數(shù) = 不同特征的總個(gè)數(shù), 自變量只有0和1兩種取值, 0表示該方程中不含此特征, 1表示包含.

特征有3類:

  1. Red, Green, Blue
  2. 離散化后的坐標(biāo), 例如: [3.0]=(0.0,0.0)
  3. 前兩類的交叉組合, 例如: Red^[3.0]=(0.0,0.0)

第2類特征總數(shù) num_loc 和像素個(gè)數(shù)有關(guān).

num_loc = sum([(int(image_width / b) + 1) * (int(image_height / b) + 1) for b in buckets])

總的特征個(gè)數(shù) = 3 + num_loc + num_loc * 3 = num_loc * 4 + 3

每個(gè)方程只有少數(shù)特征對應(yīng)的自變量取值為1, 所以不同類特征的影響范圍是不同的.

  • 第1類會影響 1/3 的方程.
  • 第2, 3類特征只會影響和格子中像素有關(guān)的方程.
  • 第2類會影響 邊長^2 * 3 個(gè), 第3類為 邊長^2 個(gè).

單張圖片是怎么生成的

輸入數(shù)據(jù)是每個(gè)點(diǎn)的坐標(biāo), 帶入模型后按 color channel 輸出預(yù)測值, 合并后就得到了該點(diǎn)的 RGB 值.

其過程就像在大方格上摞小方格, 最終的高度即預(yù)測值.

這樣的預(yù)測結(jié)果肯定好于只做單一劃分的方法, 即包含了整體信息, 也包含了局部差異.
我覺得 airbnb 預(yù)測房價(jià)也應(yīng)該是類似的想法.

動圖是怎么生成的

生成每一幀的方法和單張圖片一樣, 只是每幀用到的權(quán)重個(gè)數(shù)不一樣.

假設(shè)總共有 N 個(gè)權(quán)重, 第 i 幀只會用前 i/(N-1) 個(gè)來繪制圖像, i∈{0, 1, ..., N-1}.
這樣圖片就會漸漸的由模糊變清晰.

其他

  • Readme 中對最紅, 最藍(lán)的解釋不太恰當(dāng), 詳見 Google Group.
  • 項(xiàng)目還在發(fā)展階段, 從新舊代碼的質(zhì)量就能看出來. 例如會看到一些復(fù)制粘貼的實(shí)現(xiàn).
  • 代碼中有一些重復(fù)計(jì)算的問題. 例如理論上 context 只會計(jì)算一次, 但實(shí)際會計(jì)算多次, 不過我覺得影響不大.
    • 還未成為性能瓶頸. 我測試了修改后的速度, 并未提升多少. 該demo的時(shí)間多花在文件讀取上, 每輪迭代約5分鐘, 約一半時(shí)間是在讀文件.
    • 新代碼中重復(fù)計(jì)算的情況有所改善, 說明作者知道這個(gè)事情.
    • 項(xiàng)目還在發(fā)展期, 不需要過早優(yōu)化.

Debug 的小技巧

  • 換一個(gè)像素較少的圖片, 這樣會大大節(jié)省每個(gè)步驟的時(shí)間.
  • 換圖后需修改 image_impressionism.confmake_impression 的寬高, 讓生成的圖片大小更合適.
  • build.gradle 中 spark 的依賴從 provided 改為 compile.
  • JobRunner.scala 作為 debug 的入口, 記得加上 .setMaster("local").
  • com.airbnb.aerosolve:training 的版本有點(diǎn)低, 可更換為最新版.
  • Ubuntu 用戶如不想編譯安裝 thrift, 可用 docker thrift. Mac 用戶可用 brew 安裝.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阁苞,一起剝皮案震驚了整個(gè)濱河市秉氧,隨后出現(xiàn)的幾起案子淳附,更是在濱河造成了極大的恐慌,老刑警劉巖彰居,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匀谣,死亡現(xiàn)場離奇詭異含末,居然都是意外死亡服协,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門唯绍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狂秦,“玉大人,你說我怎么就攤上這事推捐×盐剩” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵牛柒,是天一觀的道長堪簿。 經(jīng)常有香客問我,道長皮壁,這世上最難降的妖魔是什么椭更? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蛾魄,結(jié)果婚禮上虑瀑,老公的妹妹穿的比我還像新娘。我一直安慰自己滴须,他們只是感情好舌狗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扔水,像睡著了一般痛侍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上魔市,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天主届,我揣著相機(jī)與錄音,去河邊找鬼待德。 笑死君丁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的将宪。 我是一名探鬼主播绘闷,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼橡庞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了簸喂?” 一聲冷哼從身側(cè)響起毙死,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤燎潮,失蹤者是張志新(化名)和其女友劉穎喻鳄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體确封,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡除呵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爪喘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颜曾。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖秉剑,靈堂內(nèi)的尸體忽然破棺而出泛豪,到底是詐尸還是另有隱情,我是刑警寧澤侦鹏,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布诡曙,位于F島的核電站,受9級特大地震影響略水,放射性物質(zhì)發(fā)生泄漏价卤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一渊涝、第九天 我趴在偏房一處隱蔽的房頂上張望慎璧。 院中可真熱鬧,春花似錦跨释、人聲如沸胸私。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盖文。三九已至,卻和暖如春蚯姆,著一層夾襖步出監(jiān)牢的瞬間五续,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工龄恋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疙驾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓郭毕,卻偏偏與公主長得像它碎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容