2017年10月21日 星期六

AlphaGo Zero 雜感




本文是 Facebook 上本人的貼文
==========
AlphaGo Zero 自學已成的強度帶給你多少的感想?
現今棋王柯潔 感嘆:"一个纯净、纯粹自我学习的alphago是最强的……对于alphago的自我进步来讲……人类太多余了。"
有人半開玩笑的說天網將至, 也有人說這是對人類知識的當頭棒喝
身為工程師的角色, 亦獻醜地統整一下最近對 AlphaGo Zero 的看法:
在 DeepMind 一路自以往的 AI 建立 AlphaGo 的研究, 而從歷史性的 AlphaGo 與李世乭對弈與勝出, 到 Master 勝過當今棋王柯潔, 最後經由 AlphaGo/Master 推進到即便棋王亦望之興嘆的 AlphaGo Zero, 這幾年來一步步的演進, 實則展示了建立任務型 NN 的開發該有的演進過程, 以知識的角度來看, 與其說人類多餘, 或是對人類知識與經驗的打臉或是暮鼓晨鐘, 這更是一個可以套用有階段方法性的推進過程
1. 既有資料的收集, 既有知識的分析, 許多機械學習科班出身的人都知道, prior 並不是個好方法, 但對於較為龐大的問題 prior 是個短時間快速有效的切入點
談人們下圍棋所圍繞的定石, 子效, 跳, 飛, 星, 目, 邊, 場, 局, 角, 形 等等判斷與對應的工具, 其實都是為了應付棋局變化繁複而嘗試以簡馭繁的工具, 真的細較會發現這些工具與規則在棋局中間衝突與矛盾亦多, 長遠的利益與損失難以計較, 人的思考能力是只能提綱挈領而無法盡數.
2. 建立正確的"網路模型", "評估優劣方式"與"可自我改善的機制"
有了資料並不是就解決所有問題了
AlphaGo 的論文早已公開, 照理說之後各家推出的 Go Machine 都能有 AlphaGo 強度, 應當殺得職業棋士大喊不如歸去, 但是這一年看來很明顯並非如此. 在這領域打滾的研究者都清楚, 有了資料後該如何建立有效的網路模型也是相當的難題, 除了學習速率外還得兼顧有效性, 此外還必須在未知中找到可能的改進方式. 台上的一步 30 秒棋以及與高段棋手的勝局, 這階段上代表的是對於圍棋問題在本質上更正確的認識(數學, 分析與工程上, 與職業棋手不同面向)
3. 透過 2. 建立起純資料訓練的 NN
在大規模且複雜的問題上, 沒有 1, 2. 的努力無法走到這步, , 透過 1. 2. 所建立起對於圍棋本身的了解與分析的方法, 回過頭去作做法上的優化, 這些包含的是 1. 的 prior data 亦或是 2. 的妥協設計與初期有效但不理解的架構, 甚至是根本架構與有效性的提升, 在 AlphaGo/Master 已是領先世界的情況下, DeepMind 依然有著知識與眼界上的高度, 再優化既有的 AlphaGo/Master 來達到更效的且令人為之一亮的 AlphaGo Zero.
這其實更是人類知識上的累積, 對問題理解層次的演進, 理論模型細膩度的提升

2017年9月15日 星期五

整合 MT-32 摹擬音源的 dosbox-patched

dosbox 對於許多老玩家並不陌生
提供了簡單的方式讓使用者能執行 DOS, Win 3.1/9x 的程式

相信多數用途是用來懷舊喜愛的遊戲
而音樂在遊戲中扮演相當重要的角色
在沒有強力計算與空間存放來支援 MP3/AAC 等 HQ 音樂
除了 sample rate 不高的 PCM 播放外
另外而靠的就是 MIDI (General MIDI 或是著名的音源器)
能透過少少的資料得到很棒的音樂輸出
(小時候不能理解同個遊戲在店家聽到的音樂跟我在家玩時怎麼差這麼多)

dosbox 本身並不俱備 MIDI 裝置的摹擬 (qemu, vbox 亦是)
加上現有的音效裝置多半移除了 MIDI 的硬體支援
(回到 20 年前, 內建高容量 WaveTable 的 SB AWE 可是一大訴求)
所以 dosbox 0.74 在 Linux 上想要聽美妙的 MIDI 音樂是很困難的
( Windows 上雖然輸出不佳, 但是 XP 以後至少內建了軟體 MIDI 音源)
通常 Linux 上的方案是設定 FluidSynth, Timidity 或是 Munt
千辛萬苦設定完之後, 會發現音樂播放中的切換可能造成沒有結尾的尾音
結果只能望遊戲而興嘆 (eg: 波斯王子1/2, A列車3/4, 三國志, Raptor, SimCity 2000 ...)

最近發現非官方版的 dosbox-patched ubuntu ppa
除了 code base 使用 SVN 上功能增加許多的新版外 (0.74 已經發佈多年)
最令人雀躍不已的就是直接整合了 MT32 的摹擬
(需要 Roland MT-32 ROM file, 不難找到)
維護者也很有心提供了相當多 ubuntu 版本的套件
著實方便了眾多老遊戲玩家

2017年9月4日 星期一

Kirin 970 NPU - 看圖說故事

Image Sensors World 看到了華為旗艦手機晶片 Kirin 970 中 NPU 的投影片
其中有趣的是第五張圖


"25X performance, 50X efficiency"
相較於 CPU 有著 25倍性能 與 50倍的效率
由於 Performance 有相當多比較的面向, 因此不清楚是用絕對的指標(total GFLOPs, or per core) 或是相對指標 (OPs/cycle/core), 甚至是實際應用的時間來做比對(eg. FPS)
但是若是 Efficiency 談的比較可能的是 Performance Per Watt (另外還有 area efficiency, 甚至一起綜合看)
Efficiency 比較上為了有較好的數字, 比較可能的是與大核等級的 CPU 相比, 因此這裡以 Kirin 970 中的 Cortex-A73 為對比
以 NEON 的 GFLOPS 來看2.4Ghz Cortex-A73 單核的 GFLOPs 為:
2.4Ghz * 4 lane * 2 mad * 2 unit = 38.4 GFLOPS 
(寫到這就知道 Performance 不是以絕對 GFLOPS 計算)
而若 TSMC 10nm 製程能將 2.4Ghz Cortex-A7x 最大功耗(Peak Power)壓低至 1 Watt/Core(這是個人經驗粗略推估數字), 那麼 50X 效率可以推得:
38.4 x 50 = 1920 GFLOPs/Watt =  1.92 TFlops/Watt
(寫到這覺得蠻剛好的)
又可以從投影片上得知華為對外宣稱其 Kirin 970 內的 NPU 有著高達 1.92 TFLOPS/s (FP16) computation throughput




那麼可以由上面的比較, 對於 NPU 的最大功耗能推估為 1 Watt
以此看推估觀察運作的功耗圖介於 0.3~0.7 Watt 看來也是合理的(畢竟應該有著 Thermal/Power 管理面向的 DVFS)


2017年5月12日 星期五

Android 軟體架構轉變的進行式

今日在 Google 的 Android Developer Blog 上貼出了篇名為的 "Here comes Treble: A modular base for Android" 貼文, 這是一件對於 Android 生態系統的大事, 也是 Google 方面嘗試來解決 Android 的碎片化與安全性上的問題.

這件事必須先從 Android 裝置的軟體是經過什麼樣的流程抵達每個使用者手上, 在這篇文章中即是一開始的五步驟圖:
  1. Android 團隊向世界發佈最新版本的原始程式碼
  2. 晶片製造公司為了讓他們各自的晶片在 Android 裝置上俱備更多優勢, 針對他們特定的硬體, 修改了來自 1. 的原始碼與增添驅動程式.
  3. 這些晶片製造商接著將這些修改提供給他們的客戶 - 也就是那些設計與生產 Android 裝置的品牌商. 這時裝置生產商再次針對他們的產品對 1. 的軟體做了修改.
  4. 裝置生產商與電信網路商測試與驗證新的版本.
  5. 裝置生產商與電信網路商提供新的版本給他們的使用者

 在以往 Android 裝置間是透過相容性文件 Compatibility Definition Document (CDD) 所規範的介面以及現在已經涵蓋上百萬對應測項的相容性測試套件 Compatibility Test Suite (CTS) 來避免軟體相容性上的問題. 然而面對來自上述 2. ~ 5. 項目的修改與限制, 造成了 Android 軟體層上碎片化不容易解決, 1. ~ 3. 表示裝置生產商會 follow 的不是 1. 的官方實作而是 2. 的 chip vendor 的 Android Framework 的修改, 此外 4. 與 5. 反映了 Android 裝置的安全性修正完全取決於裝置生產商自己的更新意願(特別是裝置生產商不願意透入成本在維護已發售許久的裝置).

為此 Google 在 Android O 開始要著手解決這樣的問題, 提出了 Project Treble, 其目的在於強化對於 Android 系統軟體這塊的控制與掌握, 這必須談到在以往即已經存在, 但是界線並沒有明確規範的 Vendor Implementation (廠商實作層), 現在 Google 將明確的對 Android Framework 與 Vendor Implementation 切出這一條線:
因此先前所採用的 CDD + CTS 方式, 將轉為 Vendor Interface + Vendor Test Suite(VTS), 這件事最大的意義在於 Chip Vendor(晶片商) (或是很有可能以後包含 Device Vendor(裝置生產商) )將不再能夠直接修改上述五步驟中 1. 裡面的 Android Framework 的原始碼, 而優化與性能提升的方式也被限縮在圖中灰黑色(這顏色是不是特別選過阿...) 的 Vendor Implementation 這一層, 介面也是所定義規範出的 Vendor Interface.
藉由明確規範 Vendor Interface 的好處如上圖, 也就是 Android OS framework 的更新將獨立於 Vendor Implementation, 而在該篇文章是這麼說的 "device makers can choose to deliver a new Android release to consumers by just updating the Android OS framework without any additional work required from the silicon manufacturers", 翻成中文就是裝置生產商能夠無需藉助於晶片商額外的處理下, 就能夠自行更新 Android OS framework 的部分(這或多或少都是裝置商對於更新推托的理由與藉口). 是的, 目前 Project Treble 並沒有強勢到一次將 Android Framework 客製的權力全部收回, 這或多或少是出於市場考量, 但是至少讓 Google 頭疼的源頭 - Chip Vendor 的客製已然受限, 當然這樣作法背後還是在於 Google 對於 Chip Vendor 的客製只能道德勸說並無其他的約束能力, 而 Chip Vendor 還是會嘗試對客戶提供他們自己客製的 Android Framework, 一方面這樣介面的建立會讓裝置系統商意識到不正確與不當的客製修改, 另一方面來說對於裝置生產商來說來自 Google 相關的軟體生態系(Google Play)的授權是相當重要的, 因此 Google 還是有一定其他要求與控制能力. 一旦架構建立了 Treble Architecture, Google 即更能順利的要求裝置廠商去提供裝置的軟體更新(而以往的理由會凸顯背後的問題, 讓 Google 更能第一時間掌握有問題的平台).

最後 Google 還在文末說道目前 Pixel 上運作的 Developer Preview of O 已經採用了這樣的架構方式.

2017年5月11日 星期四

有趣的 cache line 效應

在忙著處理一些個人事務的時候, Jim Huang 請我幫忙回覆學弟妹碰到的 一些問題
主要是 Jim Huang 修改了之前 Matrix Transpose 的例子作為範例給學弟妹們練功使用
而學弟妹碰到了一個問題, 就是對於 Matrix Transpose 的寬做調整時
執行時間與長寬度的調整的分佈圖如下:
如上圖所示, 隨著對寬作 4096 + (i*64)的調整時經分佈呈現鋸齒狀的變化!
依照文中敘述的實驗平台為俱備 6MB cache 的 Core-i5 6300HQ
以 Intel Skylake 架構而言, 為 64 bytes cache line, 8-way associative cache
基本上俱備 6MB/64/8 = 12288 sets (entries)
以這個 int matrix 而言, 原始的寬度為 4096x4=16384 bytes
16384/64 = 256
因此每增加一行記憶體基本上會跳躍 256 cache line index

接著再觀察 naive transpose 的實作:
https://github.com/yangyang95/prefetcher/blob/master/impl/naive_transpose.c
若 buffer 對應的 cache line index offset 為 X

首先考慮最簡單的 i == 0 的情況
對於第 n 行的 cache line index 計算為: (X + 256*n) % 12288
1. 12288 / 256 = 48 (自 cache line 0 ~ 12288, 每次跳 256 可以消耗 48 行)
2. 12288 % 256 = 0 (每次的 cache line index shift)
所以填完 output 第一行來說, 這也代表著當資料(二維座標以(x,y)表示)自 (0, 0), (0, 1) .... (0, 4095) 要讀 (1, 0) 時
相關的 cache line 早已被反覆地填入了 4096/48 = 85.33次 > 8
當 CPU 需要讀入 (1, 0) 時, 因為讀入 (0, 0) 所讀入的 cache line 有相當高的機會早已被 replace

接著當每次寬度增加 64 時(也就是 256 byte, 4 cache lines)
這裡我們先考慮 i == 1 的情況
對於第 n 行的 cache line index 計算為: (X + 260*n) % 12288
1. 12288 / 260 = 47.26 ...
2. 12288 % 260 = 68
260 與 68 兩者最小公倍數為 4420
4420/68 = 65  (表示反覆繞 cache line index 0 ~ 12288 到第 65 次才開始 cache line index 有所重疊)
而 4160/47.25 約為 88.02, 88.02 / 65 = 1.35 < 8
這表示在讀取 (1, 0) 時, 有很大的機會 (0, 0) 所填入的 cache line 還在

再考慮 i == 8 的情況
對於第 n 行的 cache line index 計算為: (X + 288*n) % 12288
1. 12288 / 288 = 42.6666....
2. 12288 % 288 = 192
而 288 與 192 兩者最小公倍數為 576
576/192 = 3 (表示反覆繞填 cache line index 0 ~ 12288 到第 3 次 cache line index 就有所重疊)
而 4608/42.666 約為 108, 108 / 3 = 36 > 8
這表示在讀取 (1, 0) 時, (0, 0) 所填入的 cache line 有相當高的機會早已被 replace

由類似的計算可以得知, 造成這樣的時間分佈的原因, 而 SSE 版本的成因也是基於相同道理
而這樣的效應更顯示程式的記憶體使用方式, data layout 與 cache 影響程式的效能甚巨

2017年5月5日 星期五

Blocks 初探與 multithreading 應用

近日由於個人一項工作的緣故, 為了能在短時間內能夠加速複雜程式的運作
因此現學現賣地採用了 OpenMP 做快速的優化實作, 最後得到 3倍左右的加速

儘管 OpenMP 表現不俗, 然而在 Android 上的實作必須仰賴已經被 deprecated 的 GCC
讓現今預設使用 clang 的環境必須特別撰寫 makefile
而儘管亦能夠使用先前撰文介紹的 Grand Central Dispatch 作為方案
然而一方面 libdispatch 的 Android port 已經過舊
而官方版必須限定 Android API-level 21 外 (另外現在編譯也有問題)
另一個問題是 GCD 的實作規模不可以說小

由於 Blocks 的使用其簡潔與動態的特性對於個人而言是充滿魅力的
基於如此的動機便開始構思結合 thread pool 與 Blocks 的方式
以此來簡化 multi-threading 程式的撰寫, 並且得到能夠有效修改的實作
為此目的必須先去了解 Blocks 是如何運作的
儘管 clang 提供了 "Language Specification for Blocks" 的頁面
然而個人認為 Apple 的 Blocks Programming - Introduction 撰寫的比較淺顯易懂

基本上 Blocks 提供了將 C/C++ 的大括號內的 code 區塊轉化作為類似下列型別作為"變數"的能力
void (*func)(void);
而 Blocks 中最有趣以及最為實用的功能是對於使用 global/local variable 上數值的"擷取"
以 local variable 為例, 一般的 serial code 毫無疑問會在 stack memory 中
若使用了 Blocks 並於 Blocks 中使用了該 Block 區間外的 global/local variable
這時的流程會產生了類似 process fork 的分歧, 理解上應為未明確寫出的 call by value
Blocks 中對於外部的 global/local variable 本身的修改是不具有寫回的效果
除此之外型別為 Blocks 的變數在使用上的概念其實與一般變數無異
程式撰寫的過程中同樣地必須考量與處理 variable lifetime 的問題
這時就必須藉由使用 Block_copy/Block_release 來手動複製與釋放 Blocks 所含的內容
對於程式中 local/global variable 處理概念上的不同是 OpenMP 與 Blocks 最大差異
而兩者所使用的方式, 在應用上來說真的是各有優缺

透過閱讀上述的參考資料建立概念後
接著就動手來做類似於 GCD 的 Blocks dispatching 的功能

首先是建立等同於 GCD 中使用來作為 task dispatch 的 dispatch_block_t 的型別
接著就是增加 thread pool 的 dispatch function
基本上是將原本的介面的參數自 function pointer 與型別為 (void *) 的 argument
改為直接使用 Block 型別
可不用再撰寫型別為 void* func(void*) 的 pthread glue code 的好處不用再多說了

在建立概念之後, 動手實作上就簡單多了
為了快速而採用了現成的 thread pool 實作 - C-Thread-Pool
而 Blocks 的操作是基於 BlocksRuntime (提供了 Block_copy / Block_release)
而初步的成果我暫且名為 gunshot
請 git clone 後記得 git submodule init/update

修改的 example.c 中, 嘗試比較填入一個 buffer 數值
    for(int pidx = 0; pidx < TEST_DEPTH; pidx++){
        int *plane = buf0 + pidx*TEST_W*TEST_H;
        for(int yidx = 0; yidx < TEST_H; yidx++){
            for(int xidx = 0; xidx < TEST_W; xidx++){
                plane[yidx*TEST_W + xidx] = pidx*4096 + (yidx + xidx);
            }
        }
    }
而 thread pool + Blocks 的版本可以寫為
    for(int pidx = 0; pidx < TEST_DEPTH; pidx++){
        int *plane = buf1 + pidx*TEST_W*TEST_H;
        thpool_add_block(thpool, ^{
            for(int yidx = 0; yidx < TEST_H; yidx++){
                for(int xidx = 0; xidx < TEST_W; xidx++){
                    plane[yidx*TEST_W + xidx] = pidx*4096 + (yidx + xidx);
                }
            }
        });
    }

在個人使用的 Quad-Core A8-5545M 平台得到了以下結果
single thread - 34832 us
4 threads - 13129 us

得到了 2.65 倍的加速

2017年4月19日 星期三

浮點數的美麗與哀愁

這幾年個人在影像處理程式優化的領域打滾, 如果問到感到棘手的工作, floating point 的處理應該可以排上很前面的名次

在許多演算來說由於同時對於 precision 與 dynamic range 的需求, 因此在計算過程中對於浮點數的使用是非常常見的 (若要避免使用會有很高的專業與困難度), floating 主要優點在於可以表示極大與極小值, 相較整數能大幅避免 overflow 與 underflow, 缺點是有效位數的減少, 而且現今多數的計算單元都俱備 floating 的支援, 已經讓一些人疏於了使用浮點的問題, (包含486與之前的時代 FPU是高檔貨, ARM 也自 ARMv7 才列標配)
然而若橫跨了 PC 與 手機, CPU 與 GPU, CPU 與 DSP, 甚至於三者 ( PC, 手機, GPU), floating point 就變成非常難以考量與處理的負擔, 而為了區分是程式錯誤或是誤差就必須耗費相當的心力

為了簡化問題, 因此文中談到 floating point 若無指名, 一律是指 32bit single precision, 但相同的問題 64bit double precision 中一樣存在

IEEE 754

首先必須要談的是問題的核心 - IEEE 754
對於計算機而言, 浮點數是以上圖的格式存放
fraction 一共 23 bits 存放一個介於 1~2 之間的數目, 這 b22 ~ b0 存放的是二進位小數以下的部分, 也就是說 fraction 所表示的數值為:
1 + b22*(2^-1) + b21*(2^-2) + ... + b0*(2^-23)
而 exponent 代表著指數, 一樣以 2 的只數次方表示, 具有 8bit, 因此值域為 0~255, 但是預設會減去 127, 所以即為 -127 ~ 128, 因此對於 exponent 本身所表示的數值為:
2^(exponent - 127)
而 sign 就不用多談了, 這是用以表示正負

然而 IEEE 754 定義的不僅僅只是 format 而已, 還有著 rounding mode, required operations 以及 exception handling, 符合了 IEEE 754 的規範下, 才可能有相同的輸出結果 (這當然只是一個最低門檻)

Format 本身的問題

扣除浮點數因格式問題不可能表示全部的數目外
格式本身最大的問題是因為 dynamic range 的移動, 像是 (A+B)+C A+(B+C) 單以代數考量這無疑是相等的, 但是若以 floating 格式去思考, 你就會意會到輸出結果很有可能會不同, 而原始演算實作所採用的累加或相乘的順序, 必須在優化實作上努力維持才能產生一致的輸出結果, 這樣的問題對於程式優化影響最大的部分是平行計算, 無論 TLP or ILP, 因為平行度優化考量, 都會有分割與不同面向個別累計的需求, 如此勢必都會產生一定的誤差

CPU 間的問題

或許有些人會認為只使用 CPU 那麼就不會碰到浮點數問題了, 這樣說只算對了一半, 而且你還是必須要只使用一種平台以及指令集, 對於多數演算法設計工程師而言, 他們很慣於使用 PC 平台, 甚至會使用 MATLAB, x86 PC 上的程式預設會使用 x87 浮點數協同單元 指令集, 而這是許多問題的開始 - x87 內部使用 80bit 浮點數表示
通常 x86 CPU 是在 x87 FPU 的內部以 80b 計算得到結果後, 再 truncate 為 IEEE-754 的 float(32b) or double(64b), 這就表示這與 IEEE 754 FPU 的輸出結果會有微小的差異, 目前常見的手機的 ARM 架構, 即為 IEEE 754 compatible FPU, 所以光是 ARM 與 x86 PC 相同的程式碼其輸出結果基本上就會有所不同, 而對於 ARM 與 x86 的部分, 就必須仰賴以 IEEE 754 設計的 SSE2 指令集, 若是 gcc 與 clang 很早就可透過 -mfpmath=sse2 的編譯參數來達到, 但是 Visual Studio 必須是 2013 版後才有正確的 code generation 實作, (也就是說 Windows 的使用者要安裝 VS 2012以後的版本 才有可能透過 /arch:sse2 有一致的輸出)

對於 ARM 與 x86 平台的一致性方案反而又揭露了另一個層面問題:
就算使用了單一CPU架構, 在 ISA 指令集間的支援還是會有所不一致!
類似於 x86 平台上 x87 指令與 SSE2 指令有著不同輸出, 同樣地 ARM VFP 指令(IEEE 754 相容)與 NEON 指令(非完全 IEEE 754 相容) 也可能會有輸出結果不同, 而這樣的問題還會再帶到 libmath 的實作方式, 讓要處理一致性的問題再度的變得更嚴重

GPU

GPU 本身有著龐大的浮點數計算能力, 但是通常為了能達到更高的吞吐量以及加速上的考量, 在計算結果與 IEEE-754 可能存在差異, 不同代的 GPU 或是不同架構都有可能有所不同. 像是 CUDA 是在 compute compatibilty v2.0 之後才完備了 IEEE 754 的支援, 除此之外許多硬體加速的數學函數的輸出上也不保證與 CPU 一致, 這點 Nvidia 在 2011 GTC 中給的 Floating Point and IEEE-754 Compliance for Nvidia GPUs 簡報中有很詳盡的說明. 對於其他GPU 以及各CPU/GPU 平台上的 OpenCL 中的 built-in functions 的實作與支援也有著相同的道理.

DSP

對於 DSP 而言這樣的痛苦並不在於 IEEE 754 本身, 而是多數的多媒體面向的 DSP 為了考量計算能力與面積, 結果多半是直接不俱備 floating 能力的, 像是 Qualcomm S82x 中的 Hexagon 680 HVX 就不俱備 floating 運算的 SIMD 指令, 而通常的處理作法是採用 fixed-point (quantization) 的浮點模擬, 然而若採用靜態位數的方式容易失真, 而動態的方式有著實作上的複雜度以及多餘計算的負擔. 而數學函數上的實作若難以避免則通常必須透過相當紆迴的方式.

Lookup-Table or Frame-based Parameters

對於跨裝置的正確性驗證, 由單一裝置輸出的 Lookup Table(單一的 math function 像是 sin, cos, log, exp 等等) 或是一整張預先透過單一裝置計算的 Frame-based Parameters(複雜的並結合多個 math function 的運算) , 是常用來確認誤差單純是由 floating 計算造成的技巧. 以此來確保實作上的流程與邏輯無誤.

延伸閱讀: The pitfalls of verifying floating-point computations

AlphaGo Zero 雜感

本文是 Facebook 上本人的 貼文 ========== AlphaGo Zero 自學已成的強度帶給你多少的感想? 現今棋王柯潔 感嘆:"一个纯净、纯粹自我学习的alphago是最强的……对于alphago的自我进步来讲……人类太多余了。" ...