【導(dǎo)語】:作者在上一篇文章《基于內(nèi)容的推薦算法》中介紹了基于內(nèi)容的推薦算法的實(shí)現(xiàn)原理。在本篇文章中作者會(huì)介紹一個(gè)具體的基于內(nèi)容的推薦算法的實(shí)現(xiàn)案例。該案例是作者在2015年基于Erlang語言開發(fā)的相似視頻推薦系統(tǒng),從開發(fā)完成就一直在公司多個(gè)產(chǎn)品線中使用,該算法目前已經(jīng)使用了四年。
本文會(huì)從視頻相似推薦系統(tǒng)簡(jiǎn)介、算法原理及實(shí)現(xiàn)細(xì)節(jié)、問題與難點(diǎn)、為什么用Erlang語言開發(fā)、系統(tǒng)架構(gòu)與工程實(shí)現(xiàn)、核心亮點(diǎn)、未來優(yōu)化方向、個(gè)人收獲與感悟等8個(gè)方面來講解。
通過學(xué)習(xí)本文讀者可以深入了解基于向量空間模型的相似推薦算法原理及實(shí)現(xiàn)細(xì)節(jié)、對(duì)Erlang語言特性也會(huì)有基本的了解、同時(shí)對(duì)實(shí)現(xiàn)一個(gè)簡(jiǎn)單高效的Master/Slaver架構(gòu)的分布式計(jì)算框架的原理和工程細(xì)節(jié)有基本的概念。
視頻相似推薦系統(tǒng)簡(jiǎn)介
作者所在公司從事智能電視/智能機(jī)頂盒上的視頻業(yè)務(wù)(主要產(chǎn)品是電視貓),由于智能電視上主要依賴遙控器操作,所以操控不是很方便,產(chǎn)品對(duì)推薦系統(tǒng)的依賴會(huì)更大。而相似推薦就是為每個(gè)視頻關(guān)聯(lián)一組相似(或者有一定關(guān)聯(lián)關(guān)系)的視頻。
具體產(chǎn)品形態(tài)見下圖1和圖2:
圖1:電視貓電影詳情頁的相似影片
圖2:電視貓奇趣短視頻的關(guān)聯(lián)推薦
我們的產(chǎn)品包括長(zhǎng)視頻(電影、電視劇、動(dòng)漫、少兒、紀(jì)錄片、綜藝6種)和短視頻(資訊、奇趣、游戲、體育、戲曲、音樂6種)兩大類,我們需要為每類視頻類型的節(jié)目關(guān)聯(lián)一組相似推薦(在同一類別中做推薦,電影的相關(guān)推薦只能是電影、體育的關(guān)聯(lián)推薦是體育等)。
具體怎么計(jì)算相似呢?我們是基于視頻的metadata信息來計(jì)算兩個(gè)視頻之間的相似度,利用相似度從高到低來排序,獲取某個(gè)視頻最相似的topN作為關(guān)聯(lián)或者相似推薦。
這里提一下,雖然長(zhǎng)視頻和短視頻采用同一套算法體系,但是由于視頻類型不一樣,前端的產(chǎn)品形態(tài)是不一樣的。由于短視頻單片時(shí)長(zhǎng)較短,一般幾分鐘就播完了,所以短視頻的關(guān)聯(lián)推薦采用的是信息流的方式,播放原視頻,它關(guān)聯(lián)的視頻會(huì)作為信息流播放,這樣整體用戶體驗(yàn)會(huì)好很多(這就是上面圖1和圖2雖然都是相似推薦但是產(chǎn)品形態(tài)不一樣的原因)。
相信通過上面的介紹,大家對(duì)視頻相似推薦的產(chǎn)品形態(tài)比較清楚了。在下面一節(jié),我們來講解具體的算法實(shí)現(xiàn)細(xì)節(jié)。
算法原理及具體實(shí)現(xiàn)細(xì)節(jié)
視頻行業(yè)一般是具備結(jié)構(gòu)化信息的,一般視頻公司會(huì)有CMS(Content Management System,內(nèi)容管理系統(tǒng)),該系統(tǒng)一般會(huì)包含媒資庫,媒資庫中針對(duì)每個(gè)節(jié)目會(huì)有標(biāo)題、演職員、導(dǎo)演、標(biāo)簽、評(píng)分、地域等維度數(shù)據(jù),這類數(shù)據(jù)一般存放在關(guān)系型數(shù)據(jù)庫(如MySQL)中。
這類數(shù)據(jù),我們可以將一個(gè)字段(也是一個(gè)特征)作為向量的一個(gè)維度,這時(shí)用向量表示視頻,每個(gè)維度的值不一定是數(shù)值,但是形式還是向量化的形式,即所謂的向量空間模型(Vector Space Model,簡(jiǎn)稱VSM)。這時(shí)我們可以通過如下的方式計(jì)算兩個(gè)視頻之間的相似度。
假設(shè)兩個(gè)視頻的向量表示分別為:
?? ??
?? ? ?
這時(shí)這兩個(gè)視頻的相似度可以采用如下公式計(jì)算:
其中代表的是向量的兩個(gè)分量?
之間的相似度。可以采用Jacard相似度等各種方法計(jì)算兩個(gè)分量之間的相似度。上面公式中還可以針對(duì)不同的分量采用不同的權(quán)重策略,見下面公式,其中?
是第t個(gè)分量(特征)的權(quán)重,具體權(quán)重的數(shù)值可以根據(jù)對(duì)業(yè)務(wù)的理解來人工設(shè)置,或者利用機(jī)器學(xué)習(xí)算法來訓(xùn)練學(xué)習(xí)得到。
有了上面的計(jì)算公式,我們就知道該怎么計(jì)算兩個(gè)視頻的相似度了。但是上面公式中未解決的問題是,對(duì)于某一個(gè)具體的維度,我們?cè)撛趺从?jì)算相似度呢?
上式中的?、?
??分別代表兩個(gè)節(jié)目第 i 個(gè)維度的值,可以是數(shù)值、字符串等。下面我們列舉一下針對(duì)不同的分量怎么計(jì)算分量之間的相似度。
年代
假設(shè)兩個(gè)視頻???的年代分別是?
??和?
??,計(jì)算在年代這個(gè)維度的相似度可以采用如下分段函數(shù)表示:
其中(1)、(2)是剔除掉無效的??值,(3)是給出的當(dāng)?
??在0到2020年之間的一個(gè)計(jì)算公式,
??值越大,最終的相似度越大。這里相似度與?
?無關(guān)(所以嚴(yán)格來說,不叫做相似度,而是貢獻(xiàn)度,下面類似,不再說明,直接用貢獻(xiàn)度來描述)在其他條件相同的情況下,越是近期拍攝的視頻權(quán)重越大。
標(biāo)題
假設(shè)兩個(gè)視頻?, ?
?和?
?分別是這兩個(gè)視頻的標(biāo)題,首先我們可以通過分詞或者關(guān)鍵詞提取算法將 ?
??和?
?提取關(guān)鍵詞得到對(duì)應(yīng)的關(guān)鍵詞,假設(shè)如下:
?? ? ?
?? ? ?
其中?分別是
?提取關(guān)鍵詞后的集合,那么我們可以用如下Jacard相似度來計(jì)算?
?之間的相似度:
?? ? ?
這里涉及到很多NLP方面的技術(shù)和領(lǐng)域知識(shí)。首先我們需要有視頻行業(yè)相關(guān)語料,才能夠保證分詞準(zhǔn)確,另外標(biāo)題可能是雜亂無章的,比如很多電影標(biāo)題中包含粵語版、國(guó)語版、新傳、第三季等對(duì)相似度價(jià)值不大的詞或者詞組,這些都需要借助規(guī)則或者NLP技術(shù)做預(yù)處理,才能得到較好的關(guān)鍵詞提取效果,最終才會(huì)有較好的相似度計(jì)算效果。
標(biāo)簽
對(duì)于標(biāo)簽,可以采用跟上面標(biāo)題一樣的計(jì)算方法,因?yàn)闃?biāo)題提取關(guān)鍵詞后的每個(gè)關(guān)鍵詞可以看成是一個(gè)標(biāo)簽。這里不再細(xì)說怎么計(jì)算了。
地域
假設(shè)兩個(gè)視頻??, ?
?和?
?分別是這兩個(gè)視頻的出品地,我們可以用如下公式來計(jì)算這兩個(gè)視頻在地域維度的相似度。
這個(gè)公式可以考慮得更復(fù)雜一點(diǎn),將地區(qū)按照語言、地域等分成幾類,比如北美、東歐、東南亞等,當(dāng)兩個(gè)視頻的地域完全一樣時(shí)相似度為1,當(dāng)兩個(gè)視頻在同一個(gè)地區(qū)分類內(nèi),相似度為0.5(或者其他大于0小于1的值,根據(jù)業(yè)務(wù)經(jīng)驗(yàn)開發(fā)人員自己定義),否則為0,這樣會(huì)更加精確合理。
豆瓣評(píng)分
假設(shè)兩個(gè)視頻?,?
?和?
?分別是這兩個(gè)視頻的豆瓣評(píng)分(豆瓣評(píng)分是0到10之間),下面公式給出視頻?
? 在豆瓣評(píng)分這個(gè)維度的貢獻(xiàn)度,評(píng)分越高,貢獻(xiàn)度越大。
是否獲獎(jiǎng)
假設(shè)兩個(gè)視頻?,?
? 和?
?分別是這兩個(gè)視頻所獲的獎(jiǎng)項(xiàng),那么可以簡(jiǎn)單用下面公式來計(jì)算視頻?
? 在獲獎(jiǎng)這個(gè)維度上的貢獻(xiàn),當(dāng)然計(jì)算公式可以更加復(fù)雜,對(duì)不同獎(jiǎng)項(xiàng)區(qū)別對(duì)待,級(jí)別更高的獎(jiǎng)項(xiàng)可以給出更高的貢獻(xiàn)度。
導(dǎo)演與演員
通常情況下,視頻一般只有一個(gè)導(dǎo)演,導(dǎo)演的相似度可以類似地域一樣的方法計(jì)算。而演職員可以采用跟標(biāo)簽類似的方法計(jì)算相似度。
上面給出了幾個(gè)內(nèi)容維度怎么計(jì)算兩個(gè)視頻在該維度的相似度(或者貢獻(xiàn)度),我們?cè)趯?shí)際項(xiàng)目中用到的維度比這個(gè)更多,但是計(jì)算原理類似,這里不再一一列舉。另外,具體的計(jì)算和處理邏輯也會(huì)更復(fù)雜,比如對(duì)于標(biāo)簽相似度,標(biāo)簽之間是有相似關(guān)系的,比如驚悚和恐怖,它們是相似的。上面方法沒有考慮到標(biāo)簽之間的這種相似關(guān)系而是將他們看成不同的標(biāo)簽,計(jì)算相似性多少有點(diǎn)簡(jiǎn)單。
實(shí)際上,我們項(xiàng)目中用到了數(shù)學(xué)中等價(jià)類的思想,將很相似的標(biāo)簽看成是同一類的,在同一類的標(biāo)簽之間是有相似度的,這樣如果一個(gè)電影的標(biāo)簽是恐怖,另外一個(gè)電影的標(biāo)簽是驚悚,按照上面的計(jì)算方法相似度是0,而按照我們等價(jià)類的思路,相似度是大于0的。同時(shí),對(duì)于某些類別的視頻我們加了很多規(guī)則性的東西,更好地適應(yīng)不同類別內(nèi)容的相似計(jì)算。像這類提升效果的處理還有很多,這里不再細(xì)說。
問題與難點(diǎn)
該項(xiàng)目最早(2012年底)是采用Java來開發(fā)的,寫一個(gè)單機(jī)程序,當(dāng)時(shí)視頻量還比較少,也沒有這么多視頻類別,基本可以支撐,當(dāng)后面加入越來越多的視頻類別,每類視頻數(shù)量也越來越多時(shí),單機(jī)計(jì)算的性能就出現(xiàn)瓶頸了。
當(dāng)時(shí)采用的應(yīng)對(duì)方案是將視頻按照類別分成幾組,每一組采用一個(gè)Java線程計(jì)算,雖然某種程度上可以做到并行計(jì)算,但是每個(gè)視頻類型的數(shù)量及增長(zhǎng)速度是不一樣的,人工按照類型拆開分布不夠均勻,問題比較多。
回顧老的設(shè)計(jì)面臨的問題,我們來總結(jié)一下,基于該算法的相似視頻推薦主要難點(diǎn)有如下幾個(gè):
數(shù)據(jù)量大,增速快
前面講到我們長(zhǎng)短視頻加起來大概有12個(gè)類別,類別多,總量有幾百萬條視頻。對(duì)于短視頻來說,特別是資訊、體育等,每天都有大量(萬級(jí))新增的節(jié)目。
第一次全量計(jì)算所有節(jié)目的相似度時(shí),由于需要計(jì)算的節(jié)目太多,必須采用分布式計(jì)算,否則計(jì)算太慢,單機(jī)可能要花幾個(gè)月時(shí)間才能完全算完。
需要實(shí)時(shí)計(jì)算
在我們的APP上,短視頻一般按照時(shí)間排列,新的短視頻放在最前面,用戶更容易看到。所以對(duì)于新增的視頻節(jié)目,我們需要實(shí)時(shí)計(jì)算出相似的視頻,否則只能用默認(rèn)推薦頂替相似推薦,效果肯定不會(huì)太好。
計(jì)算與某個(gè)視頻最相似的視頻需要遍歷所有視頻
我們一般只關(guān)聯(lián)推薦同一大類的視頻,但是各個(gè)類別數(shù)量是極不均衡的,電影1-2萬部,資訊大概有上百萬。在我們?yōu)槟硞€(gè)資訊計(jì)算與它最相似的N個(gè)資訊時(shí),我們需要遍歷所有其他的資訊才能找到與它最相似的N個(gè),怎么設(shè)計(jì)這個(gè)遍歷過程對(duì)計(jì)算時(shí)間有很大影響。
更新已經(jīng)計(jì)算的視頻的相似度
對(duì)于新入庫的視頻A,我們需要計(jì)算它的關(guān)聯(lián)推薦,同時(shí),對(duì)于某個(gè)已經(jīng)計(jì)算好的視頻B,如果新加入的視頻A與B的相似度高于B原來計(jì)算好的topN相似的某個(gè)視頻的相似度,這時(shí)需要更新B的相似度列表,將A添加進(jìn)去,同時(shí)刪除原來B的相似列表中相似度最低的視頻。每個(gè)新視頻的加入都可能影響很多已經(jīng)計(jì)算過相似度的視頻,怎么在短時(shí)間內(nèi)快捷地查找出這類需要更新相似度列表的視頻也是一個(gè)挑戰(zhàn)。
上面我們對(duì)相似視頻推薦的算法原理及難點(diǎn)做了比較細(xì)致的講解。為了實(shí)現(xiàn)這些算法和克服難點(diǎn),我們基于Erlang語言完美地解決了這些問題,在講怎么利用Erlang語言來從工程上實(shí)現(xiàn)上面的算法之前,我們先簡(jiǎn)單對(duì)Erlang語言做一個(gè)初略的介紹,方便讀者更好地理解后續(xù)算法的架構(gòu)和工程實(shí)現(xiàn)原理。
為什么要用Erlang語言開發(fā)
Erlang語言簡(jiǎn)介
Erlang是一種通用的面向并發(fā)的編程語言,它由瑞典電信設(shè)備制造商愛立信所轄的CS-Lab開發(fā),目的是創(chuàng)造一種可以應(yīng)對(duì)大規(guī)模并發(fā)事件的編程語言和運(yùn)行環(huán)境。Erlang問世于1987年,經(jīng)過十年的發(fā)展,于1998年發(fā)布開源版本。Erlang是運(yùn)行于虛擬機(jī)的解釋性語言。在編程范型上,Erlang屬于多重范型編程語言,涵蓋函數(shù)式、并發(fā)式及分布式。
Erlang是一個(gè)結(jié)構(gòu)化、動(dòng)態(tài)類型編程語言,內(nèi)建并行計(jì)算支持。最初是由愛立信專門為通信應(yīng)用設(shè)計(jì)的,比如控制交換機(jī)或者變換協(xié)議等,因此非常適合于構(gòu)建分布式、實(shí)時(shí)并行計(jì)算系統(tǒng)。使用Erlang編寫出的應(yīng)用程序運(yùn)行時(shí)通常由成千上萬個(gè)輕量級(jí)進(jìn)程組成,并通過消息傳遞相互通訊。進(jìn)程間上下文切換對(duì)于Erlang來說非常簡(jiǎn)單,比起C程序的線程切換要高效得多。
Erlang語言的特性
Erlang語言雖然開發(fā)于上世紀(jì)80年代,但是很多思想是非常超前的,在當(dāng)前云計(jì)算時(shí)代具有非常實(shí)用的價(jià)值,算是在多如牛毛的編程語言大軍中獨(dú)樹一幟。這些特性也是我選擇利用Erlang語言開發(fā)相似視頻推薦系統(tǒng)的主要原因之一。
下面我們列舉一些Erlang語言的主要特性:
(1) 函數(shù)式編程及部分語法特性
Erlang是一個(gè)函數(shù)式編程語言,即可以將函數(shù)作為參數(shù)傳入別的的函數(shù),并且可以作為函數(shù)的返回值。
Erlang語法也比較特殊,通過遞歸來實(shí)現(xiàn)迭代邏輯,沒有其他語言的while和for循環(huán)結(jié)構(gòu)。Erlang的變量跟數(shù)學(xué)中類似,只能單次賦值,不可重復(fù)賦不同值。Erlang的模式匹配能力也非常強(qiáng)大。Erlang內(nèi)置了很多數(shù)據(jù)類型及操作函數(shù),輔助更好地進(jìn)行函數(shù)式編程。
(2) 并發(fā)模型
Erlang是一個(gè)高并發(fā)語言,天生支持高并發(fā),Erlang基于Actor的并發(fā)編程模型,進(jìn)程間通信通過消息傳遞進(jìn)行,高效自然可靠。Erlang語言將并發(fā)模式作為自己的核心特性,非常方便構(gòu)建分布式處理邏輯,從一開始設(shè)計(jì)之初就充分利用多核處理器性能,非常適合在現(xiàn)代服務(wù)器上構(gòu)建分布式應(yīng)用。
(3) 跨平臺(tái)
Erlang語言與java類似,采用虛擬機(jī)來解釋執(zhí)行代碼,Erlang的beam虛擬機(jī)負(fù)責(zé)對(duì)代碼進(jìn)行解釋執(zhí)行,因此具備跨平臺(tái)特性,一次編譯到處運(yùn)行。
(3) 錯(cuò)誤處理
Erlang是一個(gè)高容錯(cuò)的編程框架,它對(duì)錯(cuò)誤處理有兩個(gè)設(shè)計(jì)哲學(xué):讓另外一個(gè)程序來解決錯(cuò)誤,如果出錯(cuò)就讓程序崩潰并重新啟動(dòng)。第一個(gè)設(shè)計(jì)哲學(xué)將錯(cuò)誤”外包“給另外一個(gè)專門的程序監(jiān)控和處理,這樣原來的程序?qū)⒑诵姆诺教幚磉壿嬌希@個(gè)監(jiān)控程序可以放在另外一臺(tái)機(jī)器上,如果原來程序所在機(jī)器掛了,監(jiān)控程序也可以發(fā)現(xiàn)問題。
基于第二個(gè)設(shè)計(jì)哲學(xué),既然處理邏輯和處理錯(cuò)誤的程序分離了,如果處理邏輯的程序掛了(一般也是遇到偶發(fā)的情況或者傳入非法參數(shù)等原因掛掉),處理錯(cuò)誤的程序就可以讓它重新啟動(dòng),這樣系統(tǒng)又可以正常運(yùn)行了。這個(gè)哲學(xué)跟我們熟知的重啟可以解決90%以上問題不謀而合。
(4) OTP框架
OTP 是包裝在Erlang中的一組庫程序。OTP構(gòu)成Erlang的行為機(jī)制(behaviors),用于編寫服務(wù)器、有限狀態(tài)機(jī)、事件管理器。不僅如此,OTP的應(yīng)用行為(the application behavior)允許程序員把寫好的Erlang代碼打包成一個(gè)單獨(dú)的應(yīng)用程序;監(jiān)測(cè)行為(the supervisor behavior )允許程序員創(chuàng)建樹狀結(jié)構(gòu)的進(jìn)程依賴鏈,使得某個(gè)進(jìn)程死后,它的監(jiān)控進(jìn)程(父進(jìn)程)會(huì)重新啟動(dòng)它而復(fù)活。
OTP提供大量通用的庫程序,可以輕松創(chuàng)建具有高度容錯(cuò)、熱機(jī)換碼等功能的高質(zhì)量高效的程序。你可以通過OTP獲得如下好處:
a 通用服務(wù)器、有限狀態(tài)機(jī)、事件管理器;
b 標(biāo)準(zhǔn)化應(yīng)用程序結(jié)構(gòu);
c 代碼熱機(jī)更換;
d 監(jiān)測(cè)樹行為機(jī)制,讓你的進(jìn)程永不”罷工“。
OTP是在Erlang之上構(gòu)建系統(tǒng)平臺(tái)的標(biāo)準(zhǔn)方式。大型Erlang項(xiàng)目,如ejabberd, CouchDB等,都是基于OTP開發(fā)的。我們的視頻推薦系統(tǒng)也大量利用OTP的各種行為機(jī)制,這樣只需要實(shí)現(xiàn)核心的接口,進(jìn)程間的調(diào)用、監(jiān)控這些能力行為機(jī)制很容易幫我們做到。
(5) 內(nèi)嵌的Mnesia數(shù)據(jù)庫
Mnesia是內(nèi)嵌入Erlang的一款容錯(cuò)的、分布式可拓展的交易型數(shù)據(jù)庫,數(shù)據(jù)按照表來組織,類似于關(guān)系型數(shù)據(jù)庫,數(shù)據(jù)可以選擇存在內(nèi)存或者磁盤中,并且有一套自己的非常方便的查詢語言,可以對(duì)數(shù)據(jù)進(jìn)行方便快捷的讀寫查詢等操作。
為什么選擇Erlang語言來開發(fā)相似視頻推薦系統(tǒng)
有了上面對(duì)Erlang語言的簡(jiǎn)單介紹,我們?cè)谶@里簡(jiǎn)單介紹一下該項(xiàng)目采用Erlang語言來開發(fā)的主要原因:
(1) Erlang語言有比較牛的互聯(lián)網(wǎng)應(yīng)用
大家耳熟能詳?shù)幕ヂ?lián)網(wǎng)軟件,如CouchBase、CouchDB(apache基金會(huì)上的一款文檔型數(shù)據(jù)庫,類似MongoDB),RabbitMQ(消息隊(duì)列中間件),還有基于XMPP協(xié)議的IM開源軟件ejabberd等非常流行的軟件都是基于Erlang語言開發(fā)的,它們?cè)诠I(yè)界有大量應(yīng)用案例。
另外大家可能知道whatsApp背后只有50多位工程師支撐近5億的月活用戶規(guī)模,在2014年被Facebook以190億美元收購。而該公司背后用到的編程語言正是Erlang。當(dāng)時(shí),作者看到這個(gè)消息時(shí)是非常震驚的,對(duì)Erlang語言越發(fā)佩服了。
(2) Erlang語言的幾個(gè)特性非常適合該項(xiàng)目
前面對(duì)Erlang語言的特性做了簡(jiǎn)單描述,這些特性是構(gòu)建相似視頻推薦系統(tǒng)的核心基礎(chǔ),我們?cè)谶@里重點(diǎn)講一下對(duì)我們開發(fā)該系統(tǒng)非常重要的一些特性。
Erlang屏蔽了跨服務(wù)器交互的細(xì)節(jié),內(nèi)嵌了跨服務(wù)器訪問的rpc及網(wǎng)絡(luò)交換函數(shù),跨服務(wù)器交互跟與本地交互基本一樣,所以非常適合開發(fā)分布式程序,能夠快速擴(kuò)展。
Erlang自帶非常多的數(shù)據(jù)處理函數(shù),方便對(duì)Set、List、Map、字符串等結(jié)構(gòu)的各類操作。Erlang包含ETS、DETS等key-value的分布式數(shù)據(jù)結(jié)構(gòu)以及嵌入式數(shù)據(jù)庫Mnesia,非常方便對(duì)數(shù)據(jù)進(jìn)行讀寫等操作。
Erlang的OTP框架和錯(cuò)誤處理機(jī)制也是非常的強(qiáng)大,適合開發(fā)穩(wěn)定高效的應(yīng)用程序。
正是Erlang有這些成功的軟件產(chǎn)品、優(yōu)秀的應(yīng)用案例及非常有意思的特性,讓作者對(duì)Erlang崇拜不已,躍躍欲試,最終決定采用Erlang來開發(fā)相似視頻推薦系統(tǒng)。對(duì)Erlang語言有興趣的讀者可以看參考文獻(xiàn)2,Erlang的作者寫的一本全面介紹Erlang編程的書,非常值得一讀。
系統(tǒng)架構(gòu)與工程實(shí)現(xiàn)
前面對(duì)相似視頻的算法實(shí)現(xiàn)細(xì)節(jié)及Erlang的特性做了完整的介紹,在本節(jié)我們就來詳細(xì)講解怎么基于Erlang的一些特性從工程上實(shí)現(xiàn)一個(gè)高效的分布式的Master/Slaver架構(gòu)的相似視頻推薦系統(tǒng)。
首先我們給出相似視頻推薦的架構(gòu)圖(見下面圖3),再針對(duì)每個(gè)模塊詳細(xì)說明實(shí)現(xiàn)的細(xì)節(jié)。
圖3:基于Erlang語言的相似視頻推薦架構(gòu)圖
另外,整個(gè)項(xiàng)目的工程目錄如下圖,這里簡(jiǎn)單解釋一下:
conf是配置文件相關(guān)目錄,doc是文檔相關(guān)目錄,ebin是編譯文件目錄,log是日志目錄,Mnesia.helios@Platform-recommended-couchbase11是Mnesia數(shù)據(jù)存儲(chǔ)目錄,out是輸出相關(guān)目錄,RelevanceRecommend.* 、similarity_computing.app是工程啟動(dòng)及配置相關(guān)文件,src是源碼。
圖4:基于Erlang語言的相似視頻推薦工程目錄結(jié)構(gòu)
相似視頻推薦采用主流的Master/Slaver架構(gòu),主要包括Master、Slaver、Riak Cluster、Cowboy server 4個(gè)核心部分,其中Master、Slaver是整個(gè)相似視頻推薦算法的核心。Master主要負(fù)責(zé)任務(wù)的分配、跟Slaver保持聯(lián)系、并且從MySQL中將metadata同步到Mnesia中,而Slaver主要負(fù)責(zé)相似度計(jì)算,計(jì)算完后將推薦結(jié)果插入Riak集群中。我們?cè)谙旅娣謩e介紹這4個(gè)模塊的核心功能和實(shí)現(xiàn)細(xì)節(jié)。
Master節(jié)點(diǎn)模塊與功能
Master節(jié)點(diǎn)主要負(fù)責(zé)任務(wù)分派,數(shù)據(jù)同步,處理節(jié)點(diǎn)加入及退出等異常情況。Master包含4個(gè)主要組件,如上圖,各個(gè)組件的功能如下:
(1) data sync模塊
該模塊負(fù)責(zé)將需要計(jì)算相似性的視頻從MySQL(媒資庫)同步到Slaver的Mnesia集群中,Slaver計(jì)算時(shí)直接從本地Mnesia讀取數(shù)據(jù)來進(jìn)行相似計(jì)算。由于需要參與計(jì)算的字段是較少的(媒資庫字段很多,我們只選擇同步對(duì)計(jì)算相似度有價(jià)值的字段),這里我們采用Mnesia的內(nèi)存存儲(chǔ),將所有數(shù)據(jù)存在內(nèi)存中,方便計(jì)算程序更快地從Mnesia讀取需要參與計(jì)算的視頻metadata,提升計(jì)算速度。
該模塊不光可以具備批量讀取MySQL所有數(shù)據(jù)的能力(項(xiàng)目第一次跑的時(shí)候需要全量計(jì)算),同時(shí)還需要實(shí)時(shí)監(jiān)控媒資庫的變化,如有新視頻加入,馬上(在秒級(jí)內(nèi))將新視頻同步到Mnesia中。
(2) add node模塊
當(dāng)加入新節(jié)點(diǎn)時(shí),通過重啟Master節(jié)點(diǎn),Master節(jié)點(diǎn)與新節(jié)點(diǎn)建立聯(lián)系,識(shí)別出新節(jié)點(diǎn),并把新節(jié)點(diǎn)加入計(jì)算集群,同時(shí)將Mnesia上的數(shù)據(jù)均勻分配到所有節(jié)點(diǎn)(包括新加入的節(jié)點(diǎn))、給新節(jié)點(diǎn)分派計(jì)算任務(wù)(如果當(dāng)前還有未計(jì)算過視頻相似度的視頻的話)。
(3) heartbeat模塊
Master節(jié)點(diǎn)定期(幾秒鐘)向所有Slaver節(jié)點(diǎn)發(fā)送心跳信號(hào),通過該信號(hào)探測(cè)Slaver是否活著,如果一段時(shí)間后Slaver無任何響應(yīng),Master會(huì)認(rèn)為該Slaver掛掉了,這時(shí)會(huì)將該掛掉的Slaver從計(jì)算列表中刪除,后續(xù)新的計(jì)算任務(wù)不再分配給該Slaver。
(4) Task allocation模塊
Slaver節(jié)點(diǎn)上啟動(dòng)多個(gè)(一般可以設(shè)置為該服務(wù)器核數(shù)的1-2倍,如4核機(jī)器,可以啟動(dòng)4-8個(gè)進(jìn)程進(jìn)行計(jì)算,有效利用多核計(jì)算能力)進(jìn)程進(jìn)行計(jì)算,等待Master節(jié)點(diǎn)來分配任務(wù)。Master節(jié)點(diǎn)定期跟Slaver節(jié)點(diǎn)通訊,輪詢各個(gè)Slaver節(jié)點(diǎn),了解Slaver節(jié)點(diǎn)是否空閑,如有空閑并且現(xiàn)在還有未完成的計(jì)算任務(wù),那么Master將新的計(jì)算任務(wù)分配給該slaver進(jìn)行計(jì)算。
(5) similarity update模塊
如果新加入的視頻A比視頻B相似列表中相似度最低的視頻相似度更大,這時(shí)我們就需要更新視頻B的相似列表,將A添加進(jìn)去,同時(shí)將B原來相似列表中相似度最低的剔除掉(見下面圖5)。
圖5:如果新視頻相似度大于某個(gè)視頻的相似列表,需要更新相似列表
那么怎么更新老視頻的相似推薦列表呢?一般來說,我們可以采用如下的方法,該方法也非常簡(jiǎn)單,容易理解。
在Master節(jié)點(diǎn)維護(hù)一張視頻id和它的相似列表最小相似度的表(見下表,表中的視頻都是已經(jīng)計(jì)算完相似推薦的視頻)。新加入的視頻A計(jì)算完相似視頻后(在計(jì)算topN相似度時(shí),保存A跟每個(gè)其他視頻的相似度),將A視頻與每個(gè)其他視頻的相似度與下表比對(duì),假設(shè)A與id_1的相似度大于s_1,那么就需要更新id_1的相似推薦列表了。
視頻id | 推薦列表最小的相似度 |
id_1 | s_1 |
id_2 | s_2 |
...... | ...... |
id_k | s_k |
實(shí)際上可以做很多簡(jiǎn)化,比如我們可以先求出上表中第二列的最小值(見下面公式),我們只保留A與其他視頻相似度大于?
的視頻,其他視頻直接丟棄(其實(shí)很多視頻可以丟棄,畢竟很多視頻跟A是沒有任何相似度的),而不會(huì)影響更新計(jì)算邏輯,這些相似度大于?
?的視頻才是可能需要更新推薦列表的視頻。
? ? ??
Master節(jié)點(diǎn)除了上面5個(gè)核心模塊外,還維護(hù)兩個(gè)關(guān)鍵的數(shù)據(jù)結(jié)構(gòu):
A :living_nodes:記錄集群目前可用的Slaver節(jié)點(diǎn),如有有節(jié)點(diǎn)加入或者掛掉會(huì)更新該數(shù)據(jù)。
B:need_computing_id: 記錄哪些視頻還沒有計(jì)算相關(guān)視頻推薦列表,針對(duì)這些視頻在任務(wù)分配模塊中分派給Slaver節(jié)點(diǎn)計(jì)算,分配出去后將該視頻從待計(jì)算列表中刪除,避免重復(fù)計(jì)算。如果有新視頻加入,新視頻的id會(huì)寫入該列表。
Slaver:主要負(fù)責(zé)計(jì)算任務(wù)
Slaver節(jié)點(diǎn)只有一個(gè)核心模塊,即計(jì)算模塊,負(fù)責(zé)根據(jù)Master節(jié)點(diǎn)指派的任務(wù)進(jìn)行相似計(jì)算。當(dāng)Master將某個(gè)視頻的計(jì)算任務(wù)分配到Slaver時(shí),Slaver從Mnesia讀取這個(gè)視頻的metadata信息,并計(jì)算該視頻與該視頻所在group(如電影組)的所有其他視頻的相似度,將相似度最大的TopN存到Riak集群中。
這里我們對(duì)核心的計(jì)算過程進(jìn)行更詳細(xì)的講解,讓大家知道我們是怎么解決TopN最相似視頻的計(jì)算問題的。在下面圖6中每個(gè)Slaver節(jié)點(diǎn)中有4個(gè)worker(工作進(jìn)程,負(fù)責(zé)進(jìn)行相似計(jì)算),每個(gè)worker維護(hù)一個(gè)最大堆(最大堆中保留的元素個(gè)數(shù)就是我們需要計(jì)算的TopN的相似視頻數(shù)),最大堆負(fù)責(zé)保留最相似的N個(gè)視頻及相似度。
當(dāng)Master將某個(gè)視頻A分配給worker1時(shí),worker1先從Mnesia集群中將所有與A在同一類別(如電影)中的所有視頻取出來,循環(huán)計(jì)算與A的相似度,計(jì)算完一個(gè)就丟給最大堆,當(dāng)所有的視頻與A的相似度都計(jì)算完后,這些相似視頻都丟給了worker1維護(hù)的最大堆,根據(jù)最大堆的性質(zhì),最終最大堆中留下的視頻(及相似度)就是與A最相似的N個(gè)相似視頻了。
圖6:Slaver中進(jìn)行相似計(jì)算過程與邏輯
上面的最大堆是基于Erlang的ETS數(shù)據(jù)結(jié)構(gòu)及相關(guān)操作構(gòu)建的,它非常高效,也是我們整個(gè)計(jì)算引擎的核心子模塊,可以自動(dòng)對(duì)丟入的{(videoid(視頻Id),similarity_score(相似度))}結(jié)構(gòu)進(jìn)行排序,保留最相似的N個(gè)。上面提到worker中包含的計(jì)算部分,即是基于我們?cè)诘诙糠种械墓竭M(jìn)行計(jì)算的。當(dāng)某個(gè)視頻最相似的TopN計(jì)算完成后,worker會(huì)將推薦列表插入Riak集群,供前端接口調(diào)用。
這里面也是有很多可優(yōu)化點(diǎn)的,比如對(duì)于新聞或者體育等時(shí)效性很強(qiáng)的視頻,我們可以只從Mnesia中選取一段時(shí)間內(nèi)(比如過去1天)的視頻來計(jì)算相似度,這樣就不需要計(jì)算相似度時(shí)跟Mnesia中同一組中的所有其他視頻計(jì)算相似度,這樣大大節(jié)省了計(jì)算時(shí)間。再比如,如果計(jì)算相似度中標(biāo)簽的權(quán)重最大,我們計(jì)算視頻A與其他視頻相似度時(shí),如果A與其他視頻的標(biāo)簽相似度為0(或者很小),我們就沒必要計(jì)算它的其他維度的相似度了,直接將該視頻丟棄計(jì)算下一個(gè)。這些優(yōu)化點(diǎn)還很多,這里不一一描述。
Riak集群:負(fù)責(zé)最終相似推薦結(jié)果的存儲(chǔ)
Riak是基于Erlang語言開發(fā)的一個(gè)分布式的key-value存儲(chǔ)系統(tǒng),可以非常容易地水平擴(kuò)展,非常適合大規(guī)模的數(shù)據(jù)存儲(chǔ),是整個(gè)相似視頻推薦系統(tǒng)的最終計(jì)算結(jié)果的存儲(chǔ)模塊,所有視頻的相似推薦列表都存在Riak集群中。Slaver的worker計(jì)算完一個(gè)視頻的相似度后會(huì)直接插入Riak。
相應(yīng)請(qǐng)求模塊:基于客戶端用戶請(qǐng)求,給出推薦結(jié)果
當(dāng)用戶在客戶端訪問某個(gè)視頻的詳情頁時(shí),客戶端向服務(wù)端發(fā)送請(qǐng)求,請(qǐng)求響應(yīng)模塊根據(jù)用戶請(qǐng)求從Riak集群中將該節(jié)目的相似列表取出來,并將其他需要的信息(如標(biāo)題、演職員、海報(bào)圖等在前端展示需要用到的節(jié)目metadata信息,這些信息我們存在Redis集群中)填充完整后返回給用戶。
請(qǐng)求響應(yīng)模塊是基于Cowboy (一款基于Erlang開發(fā)的高性能輕量級(jí)接口服務(wù)器)來開發(fā)的。從前面的介紹中可以知道,Cowboy除了從Riak中獲取推薦列表外,還需要從Redis中獲取節(jié)目的metadata信息做填充。
核心亮點(diǎn)
到此為止,我們基本講完了相似視頻推薦的核心算法原理與基于Erlang實(shí)現(xiàn)的工程架構(gòu),該系統(tǒng)是作者在15年開發(fā)的,一直在作者公司的兩個(gè)產(chǎn)品中使用到現(xiàn)在,其中一個(gè)產(chǎn)品目前還是用的該算法(另外一個(gè)產(chǎn)品基于Spark平臺(tái)做了重構(gòu),整體效果更好),該算法在服務(wù)公司業(yè)務(wù)的4年中,雖然視頻種類和數(shù)量有了非常大的增長(zhǎng),但是系統(tǒng)一直比較穩(wěn)定,也能夠很好地應(yīng)對(duì)視頻量的增長(zhǎng),并且效果還不錯(cuò),這得益于Erlang良好的容錯(cuò)機(jī)制及該系統(tǒng)較好的分布式擴(kuò)展能力。
在這里我們回顧一下該系統(tǒng)的亮點(diǎn),也讓我們可以更好地理解它的價(jià)值。
分布式可拓展能力
該系統(tǒng)采用Master/Slaver架構(gòu),可以通過水平地增加服務(wù)器來拓展該系統(tǒng)的計(jì)算能力,同時(shí)當(dāng)全量計(jì)算完后,后面就是增量計(jì)算了,增量計(jì)算相對(duì)沒有那么大的計(jì)算量,不需要這么多計(jì)算資源,我們可以縮減部分服務(wù)器節(jié)省開支。
可以實(shí)時(shí)對(duì)新增加的視頻做計(jì)算
Master的Data sync模塊近實(shí)時(shí)監(jiān)控媒資庫MySQL,如果有新視頻加入,馬上將該視頻同步到Mnesia中,并分派給Slaver進(jìn)行計(jì)算,在分鐘級(jí)內(nèi)新視頻就可以完成計(jì)算,這樣基本可以有效避免新入庫視頻的相似推薦冷啟動(dòng)問題。
系統(tǒng)很穩(wěn)定
該系統(tǒng)一般很少出問題,這得益于Erlang的OTP框架,我們的Master和Slaver服務(wù)都是基于OTP框架來實(shí)現(xiàn)的,每個(gè)進(jìn)程都有一個(gè)supervisor進(jìn)程,當(dāng)進(jìn)程掛掉后,supervisor會(huì)重啟該進(jìn)程,避免了因?yàn)榕紶柕墓收匣蛘弋惓?shù)據(jù)導(dǎo)致的系統(tǒng)崩潰,最終讓我們的系統(tǒng)非常穩(wěn)定。
分將計(jì)算過程解耦合,抽象為最大堆來維護(hù)topN最相似的視頻列表
將計(jì)算相似性計(jì)算抽象為一個(gè)計(jì)算模塊,每個(gè)worker通過維護(hù)一個(gè)最大堆,可以非常有效地解決最相似的topN計(jì)算問題。同時(shí),針對(duì)新聞、體育等時(shí)效性強(qiáng)的視頻類型,我們還可以只取最近一段時(shí)間內(nèi)的視頻來計(jì)算相似度,進(jìn)一步減少計(jì)算量。
充分利用多核能力
每個(gè)Slaver可以啟動(dòng)多個(gè)worker進(jìn)行計(jì)算,充分利用現(xiàn)代服務(wù)器多核的能力,大大加速了計(jì)算過程。
除了上述優(yōu)點(diǎn)外,我們通過配置文件來定義各種參數(shù)(比如一個(gè)Slaver可以啟動(dòng)多少個(gè)worker),可以方便地對(duì)參數(shù)進(jìn)行調(diào)整,系統(tǒng)啟動(dòng)時(shí)會(huì)首先解析這些參數(shù)。我們也可以一鍵啟動(dòng)Master節(jié)點(diǎn)和所有的Slaver節(jié)點(diǎn),整個(gè)配置和啟動(dòng)過程跟Spark比較類似。該系統(tǒng)可以看成基于Erlang語言開發(fā)的具備特定功能的類Spark的小型分布式計(jì)算平臺(tái)。
未來優(yōu)化方向
雖然該系統(tǒng)有很多優(yōu)點(diǎn),但由于個(gè)人能力及精力有限,并且對(duì)Erlang的了解還沒有達(dá)到爐火純青的階段,該系統(tǒng)還有非常多的地方是可以做優(yōu)化的。下面對(duì)可能的優(yōu)化點(diǎn)進(jìn)行說明,作為后面努力的方向。
算法本身的優(yōu)化
該系統(tǒng)基于視頻內(nèi)容的簡(jiǎn)單向量空間模型來計(jì)算相似度,雖然算法原理簡(jiǎn)單,但是由于視頻的metadata比較雜亂,相似性效果受到數(shù)據(jù)質(zhì)量好壞的嚴(yán)重影響。同時(shí),向量空間模型是一個(gè)比較簡(jiǎn)單的模型,無法獲得更復(fù)雜的特征表示??尚械膬?yōu)化點(diǎn)是,我們可以基于metadata數(shù)據(jù)或者用戶行為數(shù)據(jù)做嵌入,為每個(gè)視頻構(gòu)建一個(gè)稠密的特征向量表示,該系統(tǒng)可以通過稠密向量的相似度來計(jì)算視頻的相似性。其他各類計(jì)算視頻相似度的方法都是可以使用的。
目前我們的計(jì)算相似度算法和整個(gè)系統(tǒng)還是耦合比較緊密的,通過優(yōu)化是可以將計(jì)算相似性做成可插拔的組件的,這樣就可以方便更換計(jì)算引擎。
另外,我們的向量空間模型各個(gè)維度的權(quán)重是根據(jù)人工經(jīng)驗(yàn)自定義的,比較主觀,其實(shí)是可以
利用用戶點(diǎn)擊反饋機(jī)制自動(dòng)化學(xué)習(xí)最優(yōu)參數(shù)的,這樣可能效果會(huì)更好。
調(diào)度策略的優(yōu)化
當(dāng)前該框架的調(diào)度策略還非常粗暴,對(duì)于每個(gè)需要計(jì)算相似推薦的視頻,直接從所有Slaver中先過濾出有空余資源的worker,將任務(wù)分配給第一個(gè)空閑的worker。針對(duì)每個(gè)視頻都要從頭過濾一遍,效率很低。
更好的方式是每個(gè)Slaver節(jié)點(diǎn)維護(hù)一個(gè)待計(jì)算相似度的固定長(zhǎng)度的視頻隊(duì)列,當(dāng)隊(duì)列中待計(jì)算的視頻都計(jì)算完了相似度,Slaver主動(dòng)向Master申請(qǐng)待計(jì)算的視頻。這樣將主動(dòng)權(quán)放到Slaver上,減少原來分配方案中毫無意義的輪詢,同時(shí)也減輕了Master節(jié)點(diǎn)的壓力。
部署方式的優(yōu)化
目前的系統(tǒng)雖然部署非常容易,只要在每臺(tái)服務(wù)器上安裝Erlang,將該項(xiàng)目編譯好,將編譯后的工程代碼分發(fā)到每臺(tái)服務(wù)器上統(tǒng)一的目錄下,修改每臺(tái)服務(wù)器上的配置文件(實(shí)際上所有Slaver上配置是一樣的,跟Master上略有不同)就可以啟動(dòng)了。
更好的方式可以利用docker將工程構(gòu)建在容器之上,利用mesos或者kubernetes等來管理工程運(yùn)行,可以更好地做到集群監(jiān)控和資源彈性伸縮。
錯(cuò)誤監(jiān)控與問題排查的優(yōu)化
目前該項(xiàng)目運(yùn)行過程中會(huì)打少量的日志記錄,但對(duì)于各個(gè)模塊中可能存在的錯(cuò)誤信息并未捕獲并記錄下來,對(duì)于問題的發(fā)現(xiàn)和排查不是很友好。雖然Erlang很穩(wěn)定,但是偶爾出一些問題是在所難免的,這一塊的優(yōu)化也是未來可行的方向之一。
數(shù)據(jù)同步的優(yōu)化
目前是由Master節(jié)點(diǎn)的data sync模塊直接監(jiān)控MySQL(媒資庫),從中將數(shù)據(jù)同步到Mnesia集群的。這一塊是可以直接采用消息隊(duì)列(如RabbitMQ)解耦的,data sync只需要監(jiān)控消息隊(duì)列中某個(gè)topic是否有新節(jié)目進(jìn)來,有新的話就同步到Mnesia中,這比直接監(jiān)控MySQL高效得多。
后面作者會(huì)考慮基于上面幾點(diǎn)優(yōu)化思路對(duì)該項(xiàng)目進(jìn)行優(yōu)化,同時(shí)對(duì)代碼結(jié)構(gòu)進(jìn)行調(diào)整,讓代碼看起來更加優(yōu)雅,如果有機(jī)會(huì)的話是希望可以將該項(xiàng)目開源的。
個(gè)人收獲與感悟
到此為止,關(guān)于利用Erlang語言開發(fā)分布式視頻相似推薦系統(tǒng)的介紹就講完了。在最后作者簡(jiǎn)單說下自己做這個(gè)項(xiàng)目后的收獲和感悟。
作者在2015年花了近半年時(shí)間,算是一邊學(xué)習(xí)Erlang語言(在這之前看過Erlang,但是看得不太懂)一邊開發(fā)了該項(xiàng)目。該項(xiàng)目一共5000行左右代碼,雖然不是很多,但是對(duì)于像Erlang這類簡(jiǎn)潔的語言來說,也不算少(如果用Java實(shí)現(xiàn),估計(jì)要幾萬行,還很難實(shí)現(xiàn)分布式計(jì)算)。在整個(gè)開發(fā)過程中,最大的收獲有如下3點(diǎn):
新學(xué)習(xí)了一門比較有意思的函數(shù)式編程語言,對(duì)Erlang的特性有了比較深入的了解;
對(duì)于分布式計(jì)算有了更深刻的認(rèn)識(shí),這個(gè)項(xiàng)目相當(dāng)于獨(dú)立實(shí)現(xiàn)了一個(gè)小型的分布式計(jì)算引擎,對(duì)于深刻認(rèn)識(shí)Spark、Hadoop的原理是非常有幫助的;
獨(dú)立完成了一個(gè)較大的工程,并且實(shí)現(xiàn)了一個(gè)基于內(nèi)容的相似視頻推薦系統(tǒng)。
做完該項(xiàng)目對(duì)個(gè)人來說確實(shí)是非常有幫助的。但是從整個(gè)團(tuán)隊(duì)來說,這樣做未必是好事,利用一個(gè)很小眾的語言來開發(fā)一整套系統(tǒng),為以后埋下了很大的隱患,如果人員離職,很難招聘到Erlang的開發(fā)人員,新人很難獨(dú)立維護(hù)這套系統(tǒng),風(fēng)險(xiǎn)極大。作為團(tuán)隊(duì)管理者,應(yīng)該避免這種情況發(fā)生,最好還是利用主流的技術(shù)棧,避免留下無窮后患。
當(dāng)時(shí)作者管理經(jīng)驗(yàn)還不足、對(duì)技術(shù)的理解還不夠深入,所以自告奮勇的親自開發(fā)了該系統(tǒng)。經(jīng)過這幾年的積累和成長(zhǎng),對(duì)技術(shù)和管理有了更深的感悟。如果作為一名技術(shù)管理者,讓我再一次選擇,我可能不會(huì)用Erlang來開發(fā)該系統(tǒng),而會(huì)采用Spark流式計(jì)算引擎來開發(fā)。
-
視頻
+關(guān)注
關(guān)注
6文章
1972瀏覽量
73950 -
算法
+關(guān)注
關(guān)注
23文章
4711瀏覽量
95432 -
erlang
+關(guān)注
關(guān)注
0文章
16瀏覽量
5806
原文標(biāo)題:基于Erlang語言的視頻相似推薦系統(tǒng) | 深度
文章出處:【微信號(hào):rgznai100,微信公眾號(hào):rgznai100】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
CPLD系統(tǒng)設(shè)計(jì)及VHDL語言的視頻教程
適用于Java的嵌入式腳本語言是什么
基于語義相似度的主觀題閱卷系統(tǒng)模型設(shè)計(jì)
分布式的Erlang程序:陷阱和對(duì)策
優(yōu)化相似度計(jì)算在推薦系統(tǒng)中的應(yīng)用
erlang如何自定義_ERLANG環(huán)境搭建

Erlang到底好在哪里_Erlang的優(yōu)勢(shì)與缺陷
Erlang是什么_erlang適合做什么_olang與erlang的比較

Erlang與java的內(nèi)存架構(gòu)比較_erlang與java構(gòu)建的節(jié)點(diǎn)通訊
erlang編程語言特點(diǎn)詳細(xì)解析
Go和Python,Erlang的語言對(duì)比分析和Go編程示例概述

評(píng)論