近來(lái),有不少關(guān)于代碼補(bǔ)全工具的消息爆出,例如,來(lái)自美國(guó)的 Kite,來(lái)自加拿大的 TabNine 等,一時(shí)間獲得了不少程序員的關(guān)注。

從程序員到數(shù)據(jù)工程師,編寫(xiě)程序代碼是一項(xiàng)基本功,但是編寫(xiě)冗長(zhǎng)代碼的過(guò)程也極大地消耗了開(kāi)發(fā)者的耐心。近來(lái),有不少關(guān)于代碼補(bǔ)全工具的消息爆出,例如,來(lái)自美國(guó)的 Kite,來(lái)自加拿大的 TabNine 等,一時(shí)間獲得了不少程序員的關(guān)注。但其實(shí)很多人還并不知道,在這些國(guó)外產(chǎn)品不斷被媒體推送的背后,有一款能力更為強(qiáng)大、更早將深度學(xué)習(xí)應(yīng)用于代碼補(bǔ)全的產(chǎn)品,一款源自中國(guó)的工具——aiXcoder,它的研發(fā)者們來(lái)自于北京大學(xué)。
在本文中,機(jī)器之心采訪了項(xiàng)目總負(fù)責(zé)人北京大學(xué)計(jì)算機(jī)科學(xué)技術(shù)系副教授李戈,請(qǐng)他為讀者朋友解讀自動(dòng)代碼補(bǔ)全背后的技術(shù),以及 aiXcoder 背后的技術(shù)特性和優(yōu)勢(shì)。aiXcoder 官網(wǎng):https://www.aixcoder.com/#/
aiXcoder 的代碼補(bǔ)全效果
我們先看看寫(xiě) TensorFlow 時(shí)的代碼補(bǔ)全效果:
如上所示,aiXcoder 在 TensorFlow 的代碼環(huán)境下能夠直接「猜測(cè)」到模型建立后的一系列代碼流程。例如,在定義了 loss 之后需要定義 optimizer,之后需要 train_op、init 方法,然后最終定義模型的保存方式 saver,以及開(kāi)始運(yùn)行計(jì)算圖。這樣一個(gè)流程基本上是深度學(xué)習(xí)開(kāi)發(fā)者所知曉的,但是按照流程寫(xiě)下來(lái)非常繁瑣。在 aiXcoder 的提示下,開(kāi)發(fā)速度得到了提升。
aiXcoder 支持 Java、C++/C、Python、PHP、JavaScript 等語(yǔ)言,以插件的方式集成到現(xiàn)有的 IDE 中,如 Pycharm、Android Studio、VS Code、Eclipse、Webstorm、Sublime 等,插件的背后是一個(gè)強(qiáng)大的云端深度學(xué)習(xí)引擎。
針對(duì)開(kāi)發(fā)者,該產(chǎn)品目前分為社區(qū)版、專業(yè)版和企業(yè)版。社區(qū)版是完全免費(fèi)的,專業(yè)版也可以通過(guò)分享而免費(fèi)獲得。它們間的不同之處在于模型會(huì)不會(huì)繼續(xù)學(xué)習(xí),社區(qū)版主要利用事先訓(xùn)練好的公用模型做預(yù)測(cè),而專業(yè)版則會(huì)根據(jù)用戶的代碼習(xí)慣及結(jié)構(gòu)作進(jìn)一步的調(diào)整。
企業(yè)版是 aiXcoder 功能最為強(qiáng)大的版本,它能夠在企業(yè)內(nèi)部的私有云中進(jìn)行部署,并能夠利用企業(yè)自己的代碼來(lái)進(jìn)行模型的優(yōu)化訓(xùn)練,從而具有更高的準(zhǔn)確率和運(yùn)行性能。
aiXcoder 用起來(lái)怎么樣
百聞不如一見(jiàn),機(jī)器之心也對(duì) aiXocder 進(jìn)行了使用測(cè)試。
機(jī)器之心在 Pycharm 上試用了社區(qū)版/專業(yè)版,它們都是需要在線推斷。不同的地方在于專業(yè)版還需要額外的內(nèi)存,因?yàn)槊恳粋€(gè) Pro 用戶都需要額外的緩沖區(qū)來(lái)儲(chǔ)存模型「學(xué)到的」用戶習(xí)慣。當(dāng)然,Pro 用戶的緩沖區(qū)是是只有該插件能訪問(wèn)的。
一般而言,當(dāng)我們選擇 Python 和 PyCharm 時(shí),代碼補(bǔ)全就自然用 IDE 自帶的工具。使用 aiXcoder 第一個(gè)感受是它比自帶的補(bǔ)全工具靈活得多,因?yàn)橐郧暗难a(bǔ)全主要體現(xiàn)在 Python 函數(shù)或其它包的 API,而 aiXcoder 還會(huì)預(yù)測(cè)變量名是什么、運(yùn)算是什么、想調(diào)用的函數(shù)又是什么。
雖然代碼補(bǔ)全的推斷過(guò)程全是在云端完成的,但在我們的使用中,一般網(wǎng)絡(luò)環(huán)境甚至 4G 都能有實(shí)時(shí)的反饋,所以補(bǔ)全速度上基本和 Pycharm 自帶的工具差不多。李戈教授表示,目前 aiXcoder 絕大多數(shù)都能在 200ms 左右得到反饋,有部分地區(qū)的用戶由于網(wǎng)絡(luò)延遲問(wèn)題可能會(huì)感覺(jué)到卡頓,aiXcoder 正在全國(guó)各個(gè)主要城市部署服務(wù)器,以提升用戶體驗(yàn)。同時(shí),aiXcoder 團(tuán)隊(duì)也特別關(guān)注模型壓縮技術(shù),希望把基于 CPU 的推理運(yùn)算時(shí)間壓縮到可接受的程度,從而推出能夠在 CPU 上運(yùn)行的本地版。
總體而言,aiXcoder 提供的補(bǔ)全功能在預(yù)測(cè)變量名、函數(shù)名或關(guān)鍵字等效果上確實(shí)非常靈活,而且它還會(huì)學(xué)習(xí)開(kāi)發(fā)者的代碼風(fēng)格與編程模式,因此效果還是挺好的。
如下是自動(dòng)補(bǔ)全的一些候選,一些函數(shù)名稱可能是開(kāi)發(fā)者之間經(jīng)常使用的,因此得到了推薦:
對(duì)于一些變量,aiXcoder 可根據(jù)變量類型提出該變量可能的操作,比如,對(duì)于下圖的變量「m」,aiXcoder 提出了一個(gè)對(duì)字符串進(jìn)行增加的代碼:
aiXcoder 官方也將產(chǎn)品和其他代碼補(bǔ)全工具進(jìn)行了對(duì)比,包括 Kite 和 TabNine 等。
在對(duì)比過(guò)程中,aiXcoder 會(huì)使用 Kite 或 TabNine 官方提供的示例代碼,并測(cè)試完成這段代碼到底需要多少次按鍵。結(jié)果表明,aiXcoder 較其他插件在效率上提升 1.5 倍以上。
aiXcoder 是如何打造的
能夠?qū)崿F(xiàn)高效代碼補(bǔ)全的 aiXcoder,背后有著強(qiáng)大的技術(shù)支撐。據(jù)李戈教授介紹,aiXcoder 很早就試過(guò)了語(yǔ)言模型,將代碼視為一種語(yǔ)言從而直接建模,這就和 Deep TabNine 一樣。但是研究者很快發(fā)現(xiàn),只有語(yǔ)言模型是行不通的,它總會(huì)提出一些毫無(wú)意義、很不科學(xué)的補(bǔ)全建議。為此,aiXcoder 融合了基于序列的程序代碼語(yǔ)言模型、基于抽象語(yǔ)法樹(shù)和程序邏輯關(guān)系的圖神經(jīng)網(wǎng)絡(luò)等方法,共同打造一個(gè)完整的系統(tǒng)。
如果深度學(xué)習(xí)模型能根據(jù)開(kāi)發(fā)者的意圖,以端到端的方式直接生成對(duì)應(yīng)的代碼,那么這樣的模型會(huì)很「優(yōu)雅」。但是經(jīng)過(guò)研究發(fā)現(xiàn),這樣的任務(wù)需求是很難實(shí)現(xiàn)的,這和任務(wù)本身所依賴的數(shù)據(jù)的性質(zhì)有關(guān)系。
李戈教授從機(jī)器學(xué)習(xí)所依賴的數(shù)據(jù)性質(zhì)的角度,對(duì)代碼生成任務(wù)和傳統(tǒng)的圖像處理任務(wù)、自然語(yǔ)言處理任務(wù)的不同,給出一種較為形象化的解釋。
對(duì)于圖像識(shí)別或圖像分類任務(wù)而言,機(jī)器學(xué)習(xí)的目標(biāo)是建立一個(gè)連續(xù)的數(shù)據(jù)集(圖像數(shù)據(jù))到一個(gè)近乎連續(xù)的、有著接近清晰邊界的數(shù)據(jù)集(標(biāo)簽)之間的映射關(guān)系。
這樣一來(lái),由于圖像數(shù)據(jù)異常的稠密,而標(biāo)簽集又有足夠清晰的邊界,那么這就相當(dāng)于一個(gè)標(biāo)簽擁有大量的數(shù)據(jù)可以學(xué)習(xí)。這樣的映射關(guān)系是比較容易建立的,這也是機(jī)器學(xué)習(xí)中和圖像相關(guān)的任務(wù)相對(duì)較為容易完成的原因。
對(duì)于自然語(yǔ)言處理任務(wù)而言,機(jī)器學(xué)習(xí)需要從一個(gè)較為連續(xù)的(離散度高于圖像)、有著較清晰邊界的數(shù)據(jù)集建立與另一個(gè)較為連續(xù)的、有著較清晰的邊界的數(shù)據(jù)集之間的映射關(guān)系。
而由于自然語(yǔ)言處理中的文本數(shù)據(jù)相比圖像數(shù)據(jù)更為稀疏,因此自然語(yǔ)言處理相關(guān)的任務(wù)更難取得較好的模型性能。
但是在代碼生成方面,從編程者的意圖(intent)生成程序代碼的問(wèn)題,可以看做是「程序員意圖空間」到「程序代碼空間」的映射,其中意圖可以是由自然語(yǔ)言描述的信息。如上圖所示,這是從一個(gè)較為連續(xù)的、有著較清晰邊界的數(shù)據(jù)集,向一個(gè)更加離散而沒(méi)有清晰邊界的數(shù)據(jù)集進(jìn)行映射。
換句話說(shuō),盡管代碼生成的意圖較為清楚,但是實(shí)現(xiàn)該意圖的代碼數(shù)據(jù)卻比較稀疏,而且即便對(duì)于相同的意圖,其對(duì)應(yīng)的實(shí)現(xiàn)代碼之間仍存在較大差距,因此這樣的任務(wù)是非常難學(xué)習(xí)的。
為此,在 aiXcoder 的實(shí)際實(shí)現(xiàn)中,對(duì)不同應(yīng)用領(lǐng)域的代碼都采用了特定的模型,它們僅使用該領(lǐng)域的數(shù)據(jù)進(jìn)行訓(xùn)練。例如,對(duì) TensorFlow 或 PyTorch 等框架也有其特定的代碼補(bǔ)全模型。這樣做的主要目的就是加強(qiáng)程序分布的稠密性,在特定領(lǐng)域下,代碼分布更加接近連續(xù)性。可見(jiàn),根據(jù)編程者的「意圖」來(lái)「直接」生成完整代碼是非常困難的,但李戈教授表示,可以用類似的技術(shù)來(lái)輔助人類程序員來(lái)編寫(xiě)代碼,我們可以從程序員已經(jīng)寫(xiě)下的代碼中獲取程序員的「編程意圖」,然后綜合分析代碼,的結(jié)構(gòu)信息、變量引用信息、API 序列信息、繼承關(guān)系信息等等,以自動(dòng)生成后續(xù)代碼。然而,在這個(gè)過(guò)程中,只有語(yǔ)言模型是遠(yuǎn)遠(yuǎn)不夠的,還需要對(duì)很多其它代碼特征進(jìn)行分析,才能做好生成式的代碼補(bǔ)全。單純的預(yù)訓(xùn)練語(yǔ)言模型又怎么樣?
提起代碼補(bǔ)全,有些人可能會(huì)下意識(shí)的認(rèn)為這僅僅是一個(gè)普通的語(yǔ)言建模任務(wù),模型只需要根據(jù)開(kāi)發(fā)者之前寫(xiě)的代碼預(yù)測(cè)之后的代碼即可。因此使用最先進(jìn)的預(yù)訓(xùn)練語(yǔ)言模型,再在代碼數(shù)據(jù)上進(jìn)行微調(diào)說(shuō)不定是一種好方法。
但是李戈教授表示,這樣的想法是遠(yuǎn)遠(yuǎn)不夠的。預(yù)訓(xùn)練語(yǔ)言模型在代碼補(bǔ)全任務(wù)中效果不佳,主要是因?yàn)榇a補(bǔ)全任務(wù)本身存在諸多不同于自然語(yǔ)言分析任務(wù)的挑戰(zhàn)。
首先是代碼文本中存在的語(yǔ)義抽象性問(wèn)題。代碼的語(yǔ)義(功能語(yǔ)義)與其字面表示之間存在更大的差距。我們無(wú)法根據(jù)字面確定代碼的準(zhǔn)確語(yǔ)義。例如,在代碼中,只改變一個(gè)字符就有可能完全改變整行代碼的功能,因此處理代碼的語(yǔ)言并準(zhǔn)確提取其含義相比自然語(yǔ)言處理任務(wù)更棘手。
- f = open('word_ids.txt','r')f = open('word_ids.txt','w')
上圖所示,在 Python 代碼中,打開(kāi)某個(gè)文件時(shí)使用「r」和「w」會(huì)實(shí)現(xiàn)完全不同的功能。
此外,代碼的功能語(yǔ)義難以進(jìn)行具體的表達(dá)和刻畫(huà),而且代碼功能語(yǔ)義的表達(dá)方式多種多樣。例如,有多種代碼的形式文本用于實(shí)現(xiàn)某個(gè)功能,不能說(shuō)某一種代碼是對(duì)的而另一種是錯(cuò)的。
- list_a = [] for i in items: result = test(i) list_a.append(result) list_a = [test(i) for i in items]
如圖所示,實(shí)現(xiàn) list_a 的代碼可以是多種多樣的,但語(yǔ)言模型會(huì)將它們學(xué)習(xí)為完全不同的表征。
同時(shí),代碼文本本身的結(jié)構(gòu)非常復(fù)雜。例如,代碼的語(yǔ)義與代碼結(jié)構(gòu)(如行與行的縮進(jìn))之間存在較大的關(guān)聯(lián)性,代碼語(yǔ)義依賴于代碼結(jié)構(gòu)進(jìn)行表達(dá)。這是預(yù)訓(xùn)練語(yǔ)言模型難以表示的特征。
最后,代碼具有演化性的特征。代碼較自然語(yǔ)言的迭代速度更快,因此預(yù)訓(xùn)練語(yǔ)言模型不能夠及時(shí)捕捉演化特征。
考慮到代碼語(yǔ)言中的諸多特性,單純的預(yù)訓(xùn)練語(yǔ)言模型無(wú)法得到非常好的效果。
既然單獨(dú)的語(yǔ)言模型不行,那么 aiXcoder 又結(jié)合了哪些技術(shù),它又是靠什么來(lái)補(bǔ)全代碼的?總體而言,aiXcoder 主要依賴于其特有的對(duì)程序代碼進(jìn)行學(xué)習(xí)的深度神經(jīng)網(wǎng)絡(luò)模型,該模型能夠?qū)Τ绦虻娜缦聨最愄卣鬟M(jìn)行分析:
1. 程序的結(jié)構(gòu)語(yǔ)義特征:程序語(yǔ)言是一種結(jié)構(gòu)性很強(qiáng)的語(yǔ)言,程序的結(jié)構(gòu)信息也體現(xiàn)著程序的語(yǔ)義。例如,抽象語(yǔ)法樹(shù)是對(duì)代碼進(jìn)行解析的一種較為通用的結(jié)構(gòu),它體現(xiàn)了代碼的語(yǔ)義特征,aiXcoder 便充分利用了抽象語(yǔ)法樹(shù),對(duì)程序員已經(jīng)寫(xiě)下的代碼的語(yǔ)義進(jìn)行解讀。
2. 程序元素間的邏輯關(guān)系:程序代碼的不同元素之間存在著不同的關(guān)系,例如程序變量之間的引用關(guān)系、類之間的繼承關(guān)系、方法與參數(shù)之間的調(diào)用關(guān)系等等。程序本身又可以表示為多種圖,例如控制流圖、數(shù)據(jù)流圖、調(diào)用關(guān)系圖等等。aiXcoder 借助圖神經(jīng)網(wǎng)絡(luò)能夠?qū)Τ绦蛟刂g的多種關(guān)系進(jìn)行建模,從而能夠?qū)Τ绦蛟刂g的復(fù)雜關(guān)系進(jìn)行分析和推理。
- 3. 程序語(yǔ)言序列模型:當(dāng)然,程序語(yǔ)言也具有與自然語(yǔ)言相似的一面,因此可以利用程序標(biāo)識(shí)符之間的序列關(guān)系建立程序語(yǔ)言模型。aiXcoder 也使用了最新的深度學(xué)習(xí)語(yǔ)言模型對(duì)程序中的序列信息進(jìn)行建模。
在獲得程序代碼的各種特征之后,就該把這些特征輸入深度神經(jīng)網(wǎng)絡(luò)進(jìn)行分析了,但這并不容易,因?yàn)樵谳斎肷窠?jīng)網(wǎng)絡(luò)之前需要把這些特征進(jìn)行向量化表示。在研究過(guò)程中,北京大學(xué)提出了一系列解決程序語(yǔ)言成分相量化的辦法,并且在國(guó)際上最早發(fā)表了相關(guān)的論文,這些都為 aiXcoder 的構(gòu)造打下了基礎(chǔ)。