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