近日,由CSDN出品的2017中國雲端計算技術大會(簡稱CCTC,Cloud Computing Technology Conference)在北京盛大召開,第四正規化機器學習演算法研發工程師塗威威出席人工智慧專場並作主題演講。
作為第四正規化•先知平臺核心機器學習框架GDBT的設計者,塗威威在大規模分散式機器學習系統架構、機器學習演算法設計和應用等方面有深厚積累。演講中,塗威威表示,現在有越來越多的企業開始利用機器學習技術,把資料轉換成智慧決策引擎。企業機器學習應用系統中的核心模型訓練系統有著什麼樣的設計和優化的考慮?與教科書中的機器學習應用相比,企業實際的機器學習應用中有哪些容易被人忽略的陷阱?塗威威對此作了經驗分享,同時給出了一些可供參考的解決方案。
工業界大規模分散式機器學習計算框架的設計經驗
機器學習的經典定義,是利用經驗(資料)來改善系統效能。在應用過程中,首先要明確機器學習目標的定義,也就是用機器學習來做什麼事情。以谷歌提升搜尋廣告業務收入為例,谷歌首先對提升收入的目標進行拆解,廣告收入=平均單次點選價格點選率廣告展現量,其中“廣告展現量”被硬性控制(考慮到政策法規和使用者體驗),“單次點選價格”受廣告主主動出價影響,與上面兩者不同,“點選率”的目標明確,搜尋引擎記錄了大量的展現點選日誌,而廣告候選集很大,不同廣告的點選率差別很大,谷歌廣告平臺有控制廣告展現的自主權,因此對於谷歌提升搜尋廣告收入的問題而言,機器學習最適合用來優化“廣告點選率”。在確定了機器學習具體的優化目標是廣告點選率之後,谷歌機器學習系統會迴圈執行四個系統:資料收集→資料預處理→模型訓練→模型服務(模型服務產生的資料會被下一個迴圈的資料收集系統收集)。在這四個系統中,與機器學習演算法最相關的就是模型訓練系統。
在塗威威看來,計算框架設計上,沒有普適的最好框架,只有最適合實際計算問題的框架。
在工業應用中,有效資料、特徵維度正在迅速攀升。在資料量方面,以往一個機器學習任務僅有幾萬個資料,如今一個業務的資料量已很容易達到千億級別。在特徵維度方面,傳統的機器學習採用“抓大放小”的方式—只使用高頻巨集觀特徵,忽略包含大量資訊的低頻微觀特徵—進行訓練,但隨著演算法、計算能力、資料收集能力的不斷增強,更多的低頻微觀特徵被加入到機器學習訓練中,使模型的效果更加出色。
特徵頻率分佈
機器學習技術也在工業應用中不斷髮展,最早期的機器學習工業應用只利用巨集觀特徵、簡單模型,到後來發展為兩個不同的流派:以微軟、雅虎為代表的只利用巨集觀特徵但使用複雜模型流派,以谷歌為代表的使用簡單模型但利用微觀特徵流派,到現在,利用更多微觀特徵以及複雜模型去更精細地刻畫複雜關係已是大勢所趨。這便對模型訓練提出了更高的要求。
其一,訓練系統需要分散式並行。由於功率牆(Power Wall,晶片密度不能無限增長)和延遲牆(Latency Wall,光速限制,晶片規模和時脈頻率不能無限增長)的限制,摩爾定律正在慢慢失效,目前,提升計算能力的方式主要是依靠平行計算,從早期的以降低執行延遲為主到現在的以提升吞吐量為主。在模型訓練的高效能運算要求下,單機在IO、儲存、計算方面的能力力不從心,機器學習模型訓練系統需要分散式並行化。當然我們也需要牢記Amdahl定律。
Power Wall,功耗隨著積體電路密度指數提升
其二,訓練框架需要高開發效率。機器學習領域裡,一個著名的定理叫No Free Lunch[Wolpert and Macready 1997],是指任意演算法(包括隨機演算法)在所有問題上的期望效能一樣,不存在通用的演算法,因此需要針對不同的實際問題,研發出不同的機器學習演算法。這就需要機器學習計算框架的開發效率非常高。
典型的機器學習建模過程
其三,訓練系統需要高執行效率。在面對實際問題時,需要對資料、特徵表達、模型、模型引數等進行多種嘗試,且每一次嘗試,都需要單獨做模型訓練。所以,模型訓練是整個機器學習建模過程中被重複執行最多的模組,執行效率也就成為了重中之重。
機器學習核心系統對計算資源的需求對比
其四,底層框架的No Free Lunch。對於不同的計算問題,計算的模式和對各種計算資源的需求都是不一樣的,因此沒有在所有問題上最好的架構,只有最適合實際問題的架構。針對機器學習任務的特性進行框架設計才能更有效地解決大規模機器學習模型訓練的計算問題。
在提高開發效率上,這裡分享計算和程式設計模式的選擇、程式語言的選擇兩個方面。
平行計算正規化分為兩種,一種是基於共享記憶體的平行計算正規化,不同的計算節點共享同一塊記憶體,這裡底層需要處理訪存衝突等問題,這種模式一般被用在小規模處理器的情況,比如單機多處理器;另外一種是基於訊息傳遞的平行計算正規化,每個計算節點使用自己的記憶體,計算節點之間通過訊息傳遞的模式進行平行計算。在實際的分散式並行系統中,多機器之間一般基於訊息傳遞,單機內部一般基於共享記憶體(也有一些系統基於訊息傳遞)。
機器學習的分散式模式,又分為資料分散式和模型分散式。資料分散式是指將訓練資料切成很多份,不同的機器處理一部分資料。但對於一些較大的模型,單機可能沒有辦法完成整個模型的運算,於是把模型切成很多份,不同機器計算模型的不同部分。在實際應用過程中,根據不同的場景需要,二者一般是並存的。
資料分散式和模型分散式
最常見的就是分散式資料流計算模型。資料流模型是一種資料驅動的平行計算執行模型。資料流計算邏輯基於資料流圖表達。 使用者通過描述一個計算流圖來完成計算,對計算流圖中的計算節點進行定義,使用者一般不需要指定具體執行流程。資料流圖內部不同資料的計算一般是非同步完成的,其中的計算節點只要上游ready就可以執行計算邏輯。目前主流的ETL(Extract-Transform-Load)資料處理框架比如Hadoop、Spark、Flink等都是基於資料流計算模型。但是機器學習計算任務有一個共享的不斷被擦寫的中間狀態:模型引數,計算過程會不斷的讀寫中間狀態。資料流的計算模型在執行過程中一般是非同步的,所以很難對共享中間狀態——模型引數,進行很好的一致性控制。所以基於資料流計算模型的一致性模型一般都是同步的,在資料流內部保證強一致性,但是基於同步的系統執行效能取決於最慢的計算節點,計算效率比較低。
資料流計算模型中的模型引數困惑
另一個常見的分散式平行計算模型就是基於引數伺服器的分散式計算模型。引數伺服器就是對機器學習模型訓練計算中的共享狀態——模型引數管理的一種直觀的抽象,對模型引數的讀寫由統一的引數伺服器管理,引數伺服器本質上就是一個支援多種一致性模型的高效能Key-Value儲存服務。基於引數伺服器可以實現不同的一致性模型,一個極端就是BSP(Bulk Synchronous Parallel,同步並行),所有的計算節點在計算過程中都獲取一致的模型引數,對於演算法實現而言有一致性的保障,但是代價是同步造成的資源浪費;另一個極端是ASP(Asynchronous Parallel,非同步並行),所有的計算節點在計算過程中彼此之間的模型引數沒有任何的一致性保證,計算節點之間完全非同步執行,這種一致性模型計算效率很高,但是模型引數沒有一致性保證,不同節點獲取到的是不同版本的模型,訓練過程不穩定,影響演算法效果;CMU的Erix Xing教授提出了介於BSP和ASP兩者之間的SSP(Stale Synchronous Parallel),通過限制最大不一致的引數版本數來控制整體的同步節奏,這樣既能緩解由於同步帶來的執行效率問題,又使得演算法相對於ASP在收斂性質上有更好的保證。基於不同的一致性模型可以很好地在執行速度和演算法效果上進行權衡。
其實,資料流計算模型和引數伺服器計算模型刻畫了機器學習模型訓練計算過程的不同方面,機器學習的樣本資料的流動用資料流來描述就很自然,模型訓練過程中的中間狀態可以被引數伺服器計算模型自然的描述。因此,這兩者進行結合是整體的發展趨勢:在資料流中對引數伺服器進行讀寫操作,比如Intel就開發了Spark上的引數伺服器。但是資料流計算模型和引數伺服器計算模型的一致性模型不盡相同,引數伺服器的一致性模型比如BSP或者SSP都會打破資料流原有的非同步計算邏輯。引數伺服器和資料流結合的災備策略和一致性管理策略需要仔細的設計才能很好地統一和融合。
資料流和引數伺服器結合的架構
程式設計正規化可以分為兩種,命令式與宣告式。指令式程式設計通過顯式指定具體執行流程來進行程式設計,常見的命令式語言是C/C++等;與指令式程式設計不同,宣告式程式設計不顯示指定具體執行流程,只定義描述計算任務目標,將具體執行交由底層計算框架決定。指令式程式設計由於顯式指定具體執行流程會顯得更加靈活,而宣告式程式設計底層計算框架可以針對執行流程進行更深入的優化從而可能會更加高效。在實際的機器學習模型訓練計算框架中,兩者其實一般是並存的,比如MxNet、Tensorflow等。
求和運算的命令式實現和宣告式實現比較
為了兼顧執行效率和易用性,機器學習模型訓練計算框架的程式語言的選擇一般採用前後端分離的方式:以C/C++、Java/Scala等作為後端以保證系統執行效率,使用Python、R等作為前端以提供更為易用的程式設計介面。對於後端語言的選擇上,主流的就是Java和C++,這兩者各有優劣:
在生態上,Java由於易於開發使得其生態要遠遠好於C++,很多大資料計算框架都基於Java或者類Java語言開發;
在可移植性上,由於JVM遮蔽了很多底層差異性,所以Java要優於C++;
在記憶體管理上,基於GC的Java在大資料、同步分散式並行的情況下,效率要遠低於優化過的C++的效率,因為大資料情況下,GC的概率會很高,而一旦一臺伺服器開始GC其計算能力將受很大影響,整體叢集尤其在同步情況下的計算效率也會大打折扣,而機器數增加的情況下,在一定時刻觸發GC的概率也會大大增加;
在語言抽象上,C++的模板機制在編譯時刻進行展開,可以做更多的編譯優化,在實際執行時除了產生的程式檔案更大一些之外,整體執行效率非常高,而與之對應的Java泛型採用型別擦除方式實現,在實際執行時做資料型別cast,會帶來很多額外的開銷,使得其整體執行效率受到很大影響。
在實際機器學習模型訓練系統的設計上,具體的選擇取決於框架設計者的偏好和實際問題(比如系統部署要求、開發代價等)的需求。
執行效率優化方面主要舉例分享計算、儲存、通訊、容錯四個方面的優化。
在計算方面,最重要的優化點就是均衡。均衡不僅包括不同的機器、不同的計算執行緒之間的負載均衡,還包括算術邏輯運算資源、儲存資源、通訊資源等等各種與計算有關資源之間的均衡,其最終目的是最大化所有計算資源的利用率。在實際的優化過程中,需要仔細地對程式進行Profiling,然後找出可能的效能瓶頸,針對效能瓶頸進行優化,解決瓶頸問題,但是這時候效能瓶頸可能會轉移,就要繼續迭代:Profiling→發現瓶頸→解決瓶頸。
典型的計算效能優化迴圈
CPU和GPU的架構對比
分散式計算是有代價的,比如序列化代價、網路通訊代價等等,並不是所有的任務都需要分散式執行,有些情況下任務或者任務的某些部分可以很好地被單機執行,不要為了分散式而分散式。為了得到更好的計算效能,需要對單機和分散式進行分離優化。
CPU、GPU、FPGA等不同硬體有各自的優勢,比如CPU適合複雜指令,有分支預測,大快取,適合任務並行;GPU有大量的算術邏輯運算單元,但快取較小,沒有分支預測,適合粗粒度資料並行,但不適合複雜指令執行,可以用來加速比如矩陣運算等粗粒度並行的計算任務;FPGA對於特定的計算任務,比如深度學習預測,經過優化後有著介於CPU和GPU之間的峰值,同時功耗遠低於GPU晶片。針對機器學習任務需要進行合理的任務排程,充分發揮不同計算硬體的優勢,提升計算硬體的利用率。
近些年CPU、GPU等計算硬體的效率提升速度遠高於主存效能的提升速度,所以計算和儲存上的效能差距在不斷擴大,形成了“儲存牆”(Memory Wall),因此在很多問題上,儲存優化更為重要。在儲存方面,從CPU的暫存器到L1、L2等快取記憶體,再到CPU本地記憶體,再到其他CPU記憶體,還有外存等有著複雜的儲存結構和不同的儲存硬體,訪問效率也有著量級的差距。Jeff Dean建議程式設計人員牢記不同儲存硬體的效能資料。
儲存層級架構、效能資料和儲存牆
針對儲存的層次結構和各個層級儲存硬體的效能特性,可以採取資料本地化及訪存模式等儲存優化的策略。因為機器學習是迭代的,可以將一些訓練資料或者一些中間計算結果放在本地,再次訓練時,無需請求遠端的資料;另外在單機情況下,也可以嘗試不同的記憶體分配策略,調整計算模式,增強資料本地化。在訪存模式優化方面,也可以進行很多優化:資料訪問重新排序,比如GPU中紋理渲染和矩陣乘法運算中常見的Z秩序曲線優化;調整資料佈局,比如可以採用更緊緻的資料結構,提升順序訪存的快取命中率,同時,在多執行緒場景下,儘量避免執行緒之間頻繁競爭申請釋放記憶體,會競爭同一把鎖。除此之外還可以將冷熱資料進行分離,提升快取命中率;資料預取,比如可以用另外一根執行緒提前預取資料到更快的儲存中,提升後續計算的訪存效率。
通訊是分散式機器學習計算系統中至關重要的部分。通訊包括點對點通訊和組通訊(如AllReduce、AllGather等)。可通過軟體優化、硬體優化的形式提高執行效率。
在軟體優化方面,可以通過比如序列化框架優化、通訊壓縮、應用層優化的方式進行優化:
通訊依賴於序列化,通用序列化框架比如ProtoBuffer、Thrift等,為了通用性、一些前後相容性和跨語言考慮等會犧牲一定的效率,針對特定的通訊場景可以設計更加簡單的序列化框架,提升序列化效率。
在頻寬成為瓶頸時,可以考慮使用CPU兌換頻寬的方式,比如利用壓縮技術來降低頻寬壓力。
更重要的優化來自於考慮應用層通訊模式,可以做更多的優化:比如引數伺服器的客戶端,可以將同一臺機器中多個執行緒的請求進行請求合併,因為同一次機器學習訓練過程中,不同執行緒之間大概率會有很多重複的模型引數請求;或者根據引數伺服器不同的一致性模型,可以做請求快取,提升查詢效率,降低頻寬;或者對於不同的網路拓撲,可以採取不同的組通訊實現方式。
除了軟體優化之外,通訊架構需要充分利用硬體特性,利用硬體來提升網路吞吐、降低網路延遲,比如可以配置多網路卡建立冗餘鏈路提升網路吞吐,或者部署 Infiniband提升網路吞吐、降低網路延遲等。
在容錯方面,對於不同的系統,容錯策略之間核心的區別就在於選擇最適合的Tradeoff。這裡的Tradeoff是指每次失敗後恢復任務所需要付出的代價和為了降低這個代價所付出的overhead之間的權衡。在選擇機器學習模型訓練系統的容錯策略時,需要考慮機器學習模型訓練任務的特點:首先機器學習模型訓練是一個迭代式的計算任務,中間狀態較多;其次機器學習模型訓練系統中模型引數是最重要的狀態;最後,機器學習模型訓練不一定需要強一致性。
在業界常見的有Data Lineage和Checkpointing兩種機器學習訓練任務災備方案。Data Lineage通過記錄資料的來源,簡化了對資料來源的追蹤,一旦資料發生錯誤或者丟失,可以根據Data Lineage找到之前的資料利用重複計算進行資料恢復,常見的開源專案Spark就使用這種災備方案。Data Lineage的粒度可大可小,同時需要一個比較可靠的維護Data Lineage的服務,總體overhead較大,對於機器學習模型訓練中的共享狀態——模型引數不一定是很好的災備方式,因為模型引數是共享的有著非常多的中間狀態,每個中間狀態都依賴於之前版本的模型引數和中間所有資料的計算;與Data Lineage不同,機器學習模型訓練系統中的Checkpointing策略,一般會重點關注對機器學習模型引數的災備,由於機器學習是迭代式的,可以利用這一點,在滿足機器學習一致性模型的情況下,在單次或多次迭代之間或者迭代內對機器學習模型引數以及訓練進度進行災備,這樣在發生故障的情況下,可以從上一次迭代的模型checkpoint開始,進行下一輪迭代。相比於Data Lineage,機器學習模型訓練系統對模型引數和模型訓練進度進行Checkpointing災備是更加自然和合適的,所以目前主流的專門針對機器學習設計的計算框架比如Tensorflow、Mxnet等都是採用Checkpointing災備策略。
除了上述的容錯方式之外,還可以使用傳統災備常用的部署冗餘系統來進行災備,根據災備系統的線上情況,可以分為冷、溫和熱備份方式,實際應用中可以根據實際的資源和計算效能要求選擇最合適實際問題的冗餘容錯方式。
在實際的機器學習應用中,經常會遇到一些容易被忽視的陷阱。這裡舉例分享一些常見的陷阱:一致性、開放世界、依賴管理、可理解性/可除錯性。
一致性陷阱是最常見的容易被忽視的陷阱。
首先訓練/預估一致性問題是最常見的,其中包括特徵表達不一致以及目標含義不一致。特徵表達不一致較為常見,起因也有很多:表達方式不一是比較常見的,比如在訓練資料中0代表男,1代表女,可是在預估資料中1代表女,0代表男;訓練和評估特徵提取中,某一方或者兩方都出現了邏輯錯誤,會導致不一致;有一種比較隱祕的不一致叫“穿越”,尤其在時序資料上特別容易發生,“穿越”就是指特徵裡包含了違反時序或者因果邏輯的資訊,比如有特徵是在整個訓練資料集中取該特徵時正負例的個數/比例,這裡其實隱含使用到了樣本的標註資訊,但是實際在預估過程中是不可能提前拿到標註資訊的(否則就不需要預估了);又比如某些特徵使用了當前樣本時間點之後的資訊,但是這在實際的預估中是做不到的,因為目前還無法穿越到未來。還有一種不一致性是目標含義的不一致性,比如目標是優化搜尋結果的使用者滿意度,但是卻用使用者點選作為機器學習的目標,使用者點選了某個搜尋結果不代表使用者對這個結果滿意。
另外一種容易被忽視的一致性是欄位含義會隨著時間的推移會發生變化。
在實際應用中需要重點關注一致性測試,留意特徵的具體物理含義,避免出現特徵表達不一致、目標含義不一致、隨時間變化的不一致的問題。
2、開放世界陷阱
機器學習系統被應用到實際業務中去時,面對的就是一個開放世界,機器學習系統不再是一個靜態孤立的系統,而是需要跟外部世界打交道,這裡就有很多的陷阱。其中有一個非常著名的倖存者偏差問題,因為當前的模型會影響下一次模型的訓練資料,如果不做干涉,那麼訓練資料是有偏差的。這個偏差最著名的起源來自二戰期間,科學家團隊研究如何對飛機加固來提升飛機在戰場的存活率,他們找來了戰場上存活下來的飛機上的彈孔進行分析,最後得出結論:腹部中彈最多,所以需要在腹部進行加固,可提高存活率。但是,統計學家Abraham Wald指出他們忽略了那些被摧毀的飛機,因為它們被擊中了機翼、引擎等關鍵部位,所以可能更好地保護機翼、引擎等關鍵部位才能提升飛機在戰場上的存活率。在推薦系統、搜尋引擎等系統中這樣的問題是非常常見的,使用者看到的結果是基於機器學習模型推薦出來的,而這些結果又會成為下一次機器學習模型訓練的資料,但是這些資料是有模型偏置的。本質上這是一個Exploitation和Exploration上權衡的問題,需要以長期效果為目標,解決這樣的問題可以參考強化學習中的解決方案。除了倖存者偏差陷阱之外,機器學習系統在實際業務系統中也可能會與其他系統進行配合,機器學習系統的輸出會隨著資料而發生變化,但是如果與之配合的系統中依賴機器學習系統輸出的引數比如閾值等卻固定不變,就可能會影響整個系統的效果。實際應用中需要監控機器學習系統的輸出分佈和對其他系統的影響,可採取比如預估分佈矯正等策略。
3、依賴陷阱
不謹慎的依賴容易導致非常災難性的結果,但是在實際應用中往往會被忽視。常見的依賴有:
資料依賴:與傳統軟體系統不同,機器學習系統的表現依賴於外部資料。而資料依賴相比於程式碼依賴會更加可怕,因為很多情況下是隱式的很難察覺或分析。
在大公司中經常發生的情況是模型之間的依賴,在解決某個業務問題時,建立了機器學習模型B,為了圖快,依賴了其他團隊模型A的輸出,但是如果依賴的團隊升級了模型A,那麼對於B而言將會是災難性的。
除了資料依賴和模型之間的依賴之外,更難被察覺的是隱性依賴,可能會有一些特徵欄位會被模型自己改變,比如推薦系統中“使用者點選推薦文章的次數”這個特徵會隨著推薦模型的升級而發生改變。
實際應用中要密切關注資料依賴,儘量避免產生模型之間的依賴,避免出現隱性依賴。
4、可理解性/可除錯性陷阱
可理解性/可除錯性最容易被大家忽略。在實際的業務應用中,經常為了追求效果可能會採用非常複雜的模型,然後這個模型可能很難理解,也很難除錯。
對於一些業務,比如醫療應用、銀行審計等都會需要模型的可理解性。對於可理解性,一種常見的解決方法是說做模型轉換,比如說像周志華教授提出的Twice Learning方法,可以把一個非常複雜的應用模型,通過Twice Learning的方式轉換成一個效能相近的決策樹模型,而決策樹模型是一個比較容易理解的模型。還有一種做法就是對模型的預測結果給出解釋,比如最新的工作LIME借用類似Twice Learning的思想,在區域性區域內用可理解模型對複雜模型進行解釋。
可除錯性對於實際應用是非常重要的,因為模型幾乎不可能100%正確,而為了追求業務效果,容易採用非常複雜的特徵和模型,但是在複雜模型和特徵情況下沒發生了bad case,或者想要提升模型效能,會很難分析,導致模型很難提升,不利於後續的發展,所以在實際的業務中需要選擇適合實際問題和團隊能力的特徵、模型複雜度。
[Twice Learnig和LIME]
總結
機器學習利用資料改善系統效能,是一種資料驅動的實現人工智慧的方式,已經被廣泛應用在各行各業。隨著實際業務資料量和資料維度的增長,計算能力的不斷提升,機器學習演算法的持續優化,工業應用中的機器學習正在從早期的簡單模型巨集觀特徵轉變到現在的複雜模型微觀特徵,這樣的轉變為機器學習訓練系統的設計與優化帶來了新的挑戰。
機器學習應用的核心繫統包括資料收集、資料預處理、模型訓練和模型服務,每個系統對計算、儲存、通訊和一致性的要求都不一樣。對於模型訓練系統而言,由於摩爾定律失效,實際業務整體的資料量和資料維度持續不斷的增長,機器學習演算法的No Free Lunch定理,實際建模過程中頻繁嘗試的需要,計算框架的No Free Lunch,實際的機器學習系統需要一個專門針對機器學習設計的兼顧開發效率和執行效率的分散式平行計算框架。這次分享首先對解決開發效率中的計算和程式設計模型的選擇,程式語言的選擇做了介紹,開發者需要根據自己實際的應用場景、開發成本和團隊能力等去做權衡和選擇。然後又舉例介紹瞭解決執行效率中涉及到的計算、儲存、通訊和容錯的設計和優化。持續Profiling,迭代消除瓶頸,均衡利用好各種計算資源,儘可能最大化各類計算資源的利用率,從而提升整體執行效率。
機器學習被應用到實際的業務中會有很多容易被忽視的陷阱。這次分享對其中常見的各種型別的一致性陷阱、機器學習面對開放世界中的陷阱、機器學習系統中各種依賴的陷阱以及容易被忽視的模型可理解性和可除錯性做了簡單的介紹,同時給出了一些可供參考的解決方案。在實際的機器學習應用中需要儘量避免踏入這些陷阱。