奧推網

選單
科技

吳軍親述程式設計生涯:不用低效率的演算法做事情

作者 | 吳軍 責編 | 田瑋靖出品 | 《新程式設計師》編輯部

世界上總有一些IT難題,需要有經驗的人解決。如今已55歲的吳軍,認為年齡與能力共同成長才是應對“35歲危機”之道,在過去的職業生涯中,吳軍正是這樣做的。本文,吳軍透過講述從大學學習計算機課程到留校做科研,到出國深造,再到入職Google的經歷,分享了他的工作經驗與技術感悟。

吳軍:博士,著名自然語言處理專家、作家、投資人,曾在Google研究院、騰訊等公司任職,現為豐元資本(Amino Capital)合夥人。著有《數學之美》《浪潮之巔》《計算之魂》《文明之光》《見識》《態度》等暢銷書。

初識計算機

和如今大城市裡的學生不同的是,我在進大學校園之前完全不會程式設計,因為那時計算機是個稀罕物。

我第一次見到計算機是在小學二三年級的時候,在父親任教的學校裡有一個計算機房,裡面的計算機有十幾個冰箱大。而那麼大的計算機一秒鐘也就執行10萬次左右,不及如今手機速度的萬分之一。我對計算機是如何工作的毫無瞭解,那時的計算機不僅非常精貴,而且操作複雜,會使用計算機的都是專家。而如此精貴的計算機也只能用於解決國家非常重要的問題,一般的工程問題只能拉計算尺計算。到了20世紀70年代末,我父親的實驗室裡有了一臺機械的計算器,今天想來大概相當於萊布尼茨的計算器加上打字機吧,能做運算,但無法程式設計。即便是電子計算器在中國出現,也是改革開放後的事情。

雖然我在上大學前沒有碰過計算機,但還是選了那個專業,主要是那時受了剛開始的資訊革命的影響。比我低一年級的學弟學妹們,就有機會在中學接觸計算機了,他們中間有四個人因為參加計算機競賽得了獎,被直接保送到清華大學(以下簡稱“清華”)和北京大學(以下簡稱“北大”)了。不過這四個人後來也沒有再從事和計算機相關的工作。

我正式接觸計算機程式設計是從一個可程式設計的計算器開始,那個計算器帶有BASIC的直譯器,可以用BASIC編寫一些很簡單的小程式。當時我雖然在清華大學計算機系,但是第一年並沒有學任何計算機的課程,學的都是基礎課,因此我在寒假拿了一本書自學BASIC,假期做了些統計、整理全班平均分之類的小事情。再往後,我有時到父親的實驗室,幫他編寫小程式,那時IBM PC已經進入了科研單位。

特別要指出的是,自學一門程式語言不是難事,因為計算機的語言比人的語言容易多了。但是,如果完全靠自學來掌握計算機的技能,最多成為一個“二把刀”,要想在計算機領域走得遠,就必須系統性地學習。而且,如果要獲得較深的領悟,光看書是不夠的,甚至向一些水平不高的老師學習也會有欠缺。

本文節選自《新程式設計師》004

與程式設計結緣

我正式系統地學習計算機是在大學二年級,有一門程式設計的課程教授Pascal語言。這種語言今天已經沒有人使用了,甚至當時在工業界用得也不多,主要是它的執行效率並不高,而且業餘人士學起來也比較麻煩。

在任何時代,使用頻率最高的程式語言都不是最好的程式語言,而是最容易學,最容易實現當時各種應用軟體所需要的最基本功能的語言。不過,如果學習計算機從那些語言入手,最後通常是“二把刀”,因為他們會對整個計算機的世界有一種片面看法。Pascal語言在過去對系統學習計算機的人來講是一門好的程式語言,它的結構和描述計算機演算法所用的虛擬碼是一致的,可以幫助大家培養起計算機的思維方式,改掉人一些不好的思維方式。比如,幫助大家從跳來跳去的思維,變成模組式思維。當我在大二學的這門課時,程式語言只是內容的很小一部分,主要內容是演算法,懂演算法才能解決那些比較難的問題。

在隨後的一年裡,我大約又學習了四門程式語言,包括FORTRAN、LISP、PROLOG和C,雖然這些語言今天都不用了,但在歷史上它們有存在的理由,如LISP和PROLOG是為了處理人工智慧問題。我列舉這些語言是為了說明學習程式語言真的很容易,在工作中如果需要學習一門新的程式語言,自學就好,學會了也沒有什麼可喜的,因為它們並不是很難。我後來用到的C++、Perl、Java、Python等,都是自學的。對於從事計算機專業的人來講,重要的是資料結構、演算法和計算機系統結構基礎,其次是對計算技術的全面瞭解。會寫程式碼,就如同建築工人會砌磚頭,生產線上的工人會插元器件一樣,不是什麼了不得的事情。

到了大三,我學習了資料結構。這門課很重要,有了對資料結構的全面瞭解,編寫出來的程式才是專業水平。這不僅是因為建立在好的資料結構基礎上的程式執行效率高,不容易有Bug,而且這樣的程式碼也容易被讀懂,能夠重複使用。

與編寫程式碼看似無關,但卻非常重要的兩門課是系統結構和編譯原理。

系統結構可以讓大家對計算機從處理器到系統有全面瞭解,做產品深入後,不瞭解它們是做不好的。缺乏這方面的知識,成不了系統級的工程師,更成為不了架構師。通常在美國,系統結構被分為計算機原理和系統結構兩門課,這裡面還包括了機器語言和組合語言。我在清華上學時,這門課被拆成了三門課。編譯原理不僅讓大家知道為什麼程式可以在計算機上執行,而且可以清楚如何寫程式能讓執行效率更高。不瞭解編譯的原理,靠工作經驗改進自己寫程式的效率幾乎是不可能的,因為世界上各種各樣的問題如果一點點總結經驗,是總結不過來的。

編譯原理通常不好學習,它完全建立在巢狀和遞迴基礎之上,學習這門課要求人完全從常人的思維徹底轉到計算機思維上,而且學好它還需要大量的練習。因 此,很多學校計算機專業並不要求所有人都學這門課。不過,如果想做一個超越“碼農”的計算機工程師,我還是建議學好這門課。我在清華的畢業設計,做的就是一個PROLOG編譯器。真正做過一個編譯器,就知道計算機的程式如何執行,寫程式時也知道該如何提高效率了。

相比美國計算機系的學生,中國學生平時的作業不少,但在學校裡使用計算機做工程專案的訓練卻少很多。如果美國的學生一門課工程專案的負擔是10分,清華的學生可能只有3~4分。這帶來的結果是中國的大學畢業生如果沒有參加過實習工作,很難一畢業就上手工作。

我在騰訊時,就發現很多成績不錯的畢業生需要再培訓才能正式工作。我在學校裡並不屬於最喜歡寫程式的學生,但如果有時間,還是想在計算機上做點事情。於是從大三開始,我就在教研組參加一些科研工作。當時即便是教研組的實驗室,晚上10點多鐘也要熄燈。所幸,學校有一個通宵機房,我們可以在那裡工作到零點,這是夏天最令我高興的事情。回宿舍時天氣已經涼快下來,在寧靜的夜裡騎車是一件很舒服的事情。後來讀到高德納和比爾·羅伊(太陽公司創始人,Solaris的發明人)等人的經歷,發現他們在大學時對計算機的興趣比我濃多了,他們經常通宵工作。對於一份職業,如果一個人不是感興趣而是為了餬口,成績永遠做不到前5%。

到了大四暑假時,我和十來位同學在寧波實習,為當地的工廠開發了一個財務管理系統。這次實習有件事讓我此生難忘。同組的一個同學當時因為偷懶,在對賬的程式中用了平方複雜度的演算法,結果當這家企業積攢的資料越來越多後,對賬變得越來越慢。這個問題其實有一個很簡單的線性複雜度演算法,但那位同學一時偷懶就忽略了,而測試時並沒有多少資料,看不出效能上的差異。修補這個Bug只需要寫十幾行程式碼,但為此我們不得不派一位學生出差去專門解決這個問題。此後,我非常注重演算法質量,從來不去用那些低效率的演算法做事情。

做到領域內最好

畢業後,我的第一份工作極其無聊,就是透過反彙編把機器語言改成組合語言,然後把微軟原本只支援英文的作業系統漢化。這並不難,只要把處理鍵盤的中斷程式和處理顯示的終端程式修改為支援漢字即可。但當時的作業系統都沒有原始碼,只能透過Debug把二進位制的機器程式碼逆向還原出組合語言的程式,然後修改那些程式。這項工作既枯燥,又侵犯智慧財產權,於是,完成任務後,我就再也不願意做這種事了,即使這讓我後來Debug的水平非常高。

工作兩年後,我發現在中國做計算機相關的技術工作很難開展,畢竟像作業系統、資料庫系統以及常用的軟體都是世界上少數幾家大型跨國公司在做。由於做計算機生意,我接觸了大量使用者,發現有很多和具體產業相關的問題得不到解決,甚至在資訊、影象和語音處理等方面,計算機都使用得不好。遺憾的是,我並沒有資訊處理的經驗,這些錢自然就掙不到。

我在本科畢業時已被保送研究生,這個資格在幾年內有效。因此當我工作兩年覺得錢掙夠之後,就決定回清華讀研究生了。在離開學校之前,有兩個系都給了我研究生的資格,除了我原先學習的計算機系,電子工程系(當時叫無線電系)也錄取了我。但我並不知道該選哪個,只是圖新鮮,覺得該換個系讀一讀,選擇了電子工程系。等到我研究生入學的時候,又有兩個研究方向很相近,課題組都希望我去,一個做影象識別,一個做語音識別,我也必須二選一。影象識別比較直觀,我在本科時也有一些這方面的經驗,因此對我更有吸引力。不過最後分配的結果是讓我做語音識別,而我對此一無所知。但考慮到很多基礎的技術都是相通的,便安心在這個課題組學習和做研究了。當時並沒有想到,後來的我居然很喜歡這個領域的研究,而且做了大半輩子。

別看從計算機系轉到電子工程系,兩個專業似乎很相近,但在中國跨專業並不是易事,因為國內本科生選課的方向都比較窄。我在讀研究生前,對訊號處理和語音技術毫無瞭解,因為計算機系幾乎沒有這方面的課程。因此讀研究生的前半年,我花了很多時間補課,那是我從本科到博士10年中最辛苦的半年。我同組的同學本科就是讀訊號處理的,因此他們上手比我快。不過,半年後我就趕上來了。

第一學期結束後,我就可以讀文獻了,很快完成了選題和開題。到暑假結束時,我已經完成了一半研究工作,最後從學習基礎課,到完成碩士論文,我只花了一年半時間。當然學校沒有慣例讓研究生那麼快畢業,於是第二年的後半年我就到一家清華的合作單位工作了。我周圍的老師和同學其實很驚訝於我讀書和做研究的速度。實際上,這得益於我的計算機專業基礎比較好。當時的計算機都很慢,資源也有限,今天看似很容易做的事情,如統計大文字中常見的二元組(bigram)頻率,在當時幾乎做不了。而在電子系的研究生中,我可能是唯一個本科讀計算機專業的人,因此,在他人看來非常難處理的問題,我卻能找到適合的演算法解決方案。

由於我在研究生期間發表了一些論文,我自覺喜歡做科研,加上工作時攢了一筆錢,當時並沒有生活壓力,便決定留在清華當老師。那時大學老師的薪酬很低,大多數人希望出校門掙大錢。而當老師最大的好處是時間靈活,這對我很重要。我每週都會到圖書館看文獻,白天做研究,晚上整理資料、寫論文。我在清華當了三年老師,語音識別和資訊處理的水平大有提升,也發表了很多論文,在我所研究的領域,我已經做到最好了。不過計算機水平並沒有提高。

他山之石,潛心攻玉

關於我到約翰霍普金斯大學讀博士這個選擇,對今天很多人來講是難以想象的,因為要放棄很多既得利益。不過我那時還年輕,眼睛是往前看的,知道要想從國內一流做到世界一流,去世界頂級的實驗室學習是捷徑。

我所在的約翰霍普金斯大學語言與語言處理中心(CLSP)是世界上規模最大的語言識別和自然語言處理實驗室之一,這是一個跨了很多學科的大實驗室,教授來自電子工程系、計算機系、認知科學、生物醫學等學科。博士生必須選擇一個系,在我申請學校時,教授們根據我的材料把我劃分到了計算機系。而由於我的研究經費來自電子工程系,因此在約翰霍普金斯,我就在電子工程系做研究,從計算機係獲得學位。這種跨系拿學位的做法,也為我帶來了麻煩。

為了取得計算機系的學位,我就要學習足夠多計算機系的課程。我挑選了一些在國內沒有開設的課程,如計算機演算法課、資訊和網路搜尋課,還大量選修了自己感興趣的課程,像平行計算這種當時不知道有什麼用的課程,後來在Google做雲計算時,就派上了用場。在美國讀書,每一門課的工程量都非常大。例如,作業系統課,幾個工程專案拼到一起,就是一個可以執行的作業系統。類似地,學完語音處理課後就能搭建一個語音識別系統。我在到Google之前,關於網頁搜尋的全部經驗就來自資訊和網路搜尋課的工程專案,加上我暑假在AT&T實習的經歷。這些經驗已經足以讓我應付Google的各種工作了。

語音識別的研究人員,通常來自電子工程(特別是通訊)和計算機兩個領域。通常前者的系統準確率高但不講究執行效率,後者的系統速度快但準確率要差一個百分點。在21世紀初,世界上語音識別做得最好的兩家公司是IBM和AT&T,前者是計算機公司,後者是通訊公司。它們的系統就有上述特點。能否兼顧準確性和效率呢?通常很難,因為不同領域的研究人員都受限於自己的專業。通訊和資訊處理出身的人,會把注意力放到改進資訊處理的方法上,而計算機出身的人會更重視系統的效率。

我的研究是在電子工程系做的,從導師到周圍的同學,每天都在討論如何改進哪怕是一點點的準確性。到了第四年,我已經完全達到了畢業要求,當時我做的語言模型在NIST(美國國家標準與技術局)的評測中取得了最好的成績,也獲得了世界上最有影響力的語音大會的最佳論文獎。可以說我已經達成了去美國的目的——做到世界第一。當時校報還專門報道了我的研究工作。在此之前我的師兄、師姐們平均畢業時間是6年。不過當我提出論文答辯請求時,委員會里大部分教授認為我在計算機領域的貢獻不足,畢竟我是要計算機的學位,而不是電子工程的學位。於是,他們建議我再花一年時間在計算機演算法上做出點成績。顯然他們覺得我離學生畢業的平均時間還早,再用一年時間,可以把論文做得更好。當時,我內心非常不舒服,因為我覺得靠自己省下了一年時間,卻不得不接受這個現實。

在接下來的幾個月,我的工作狀態就是一沓紙,一支筆,推導數學公式,試圖減少計算量,提高速度。彼時機器學習的演算法訓練稍微複雜一點的模型要幾個月的時間,我希望找到一種至少能把計算量降低一到兩個數量級的演算法。如今,絕大多數演算法已經被最佳化,不太可能像快速排序的發明人託尼·霍爾那樣把一種演算法的複雜度降低很多。不過,依然有三個可以降低演算法複雜度的方向:一是透過數學變換,用等價的計算替代原有的計算;二是透過儲存一些中間計算結果,確保沒有任何重複性的計算;三是用抽樣近似的方法,避免對全部資料進行計算。我在改進機器學習演算法的過程中,這三種方法都採用了。

終於有一天,我和我的導師講,我發現了新的、非常快的演算法。然後我在白板上推導公式。我寫完一屏時,他就用掃描器列印一幕,就這樣我寫了十幾屏,他打印出了一沓紙。我放下筆說,計算量至少可以降低幾百倍。他盯著白板又思考了一會兒說,“好像是對的,不過我得回去仔細檢查一下。”幾天後,他告訴我,我的推導無誤,我成功了,我把那種機器學習演算法的執行時間降低了兩到三個數量級。然後我又用這種方法把之前很多人想做卻做不了的工作一一完成了。後來想起來,這一年時間讓我在演算法領域有了一點點貢獻。因為有了足夠讓人信服的成果,接下來的論文是很好寫的,不過這項額外的工作讓我晚畢業一年,而這也改變了我對工作的選擇。如果是早一年,我會選擇在IBM或AT&T做研究。因為晚了一年,讓我無意中考慮了Google的機會。因此,很多時候早一點晚一點,還真不知道是福是禍。

在Google的兩件事

到了Google,工作的性質就變了。當時的Google非常小,沒有做研究的可能性,每天要處理大量的工程問題。思路與做研究時差距極大。例如,對於演算法的複雜度,我們通常認為在大O概念下相同的演算法沒有再改進的必要,因為提高一倍速度在計算機科學中毫無意義。不過,在工程領域,這還是有意義的。比如提供一項服務需要1000臺伺服器,它一年的執行成本是100萬美元,如果能降低10%的計算量,這就意味著一年節省10萬美元的執行成本。10萬美元對Google這樣的公司當然不是大錢,但如果所有的服務成本都降低10%,那就不單單是成本問題,而是競爭力的問題。試想一下,如果有兩家公司,提供的服務質量完全相同,其中一家公司的報價低10%,那麼它將獲得更大的市場份額,並透過市場優勢逐漸確立它的市場壟斷地位。當時同樣的服務成本,Google的價格大約是雅虎的1/3、微軟的1/10以 下,因此Google可以用價格優勢完全限制另兩家公司的發展。後來亞馬遜也用同樣的方法確立了在雲計算方面的壟斷地位。在Google,我寫的最常用的一批程式碼被用在幾百個專案中,這些程式碼如果沒有最佳化到極致,很多服務的效率都會受到影響。在Google有兩件事可以舉例給大家參考。

第一件事是關於工作節奏。我到Google時,Google還是一家小公司,人少工作多,我幾乎沒有在零點之前回家的印象,然後第二天早上9點多就到了辦公室,週末也需要加一天班。我從來不贊同公司逼著員工“996”,不 過,如果想做成偉大的公司,996是遠遠不夠的。當時大家在Google,每週至少工作80小時。在美國的公司中,提交程式程式碼之前都需要進行程式碼審查,在Google程式碼寫得最規範,對Google的貢獻也最大的工程師是Craig Silverstein(斯爾福斯坦),他是Google第一位員工,一個人完成了第一版Google的幾乎全部程式碼,而且Google的程式碼規範就是斯爾福斯坦制定的。由於我的程式碼絕大部分是系統程式碼,因此按照Google的慣例,一定要由斯爾福斯坦稽核批准。斯爾福斯坦工作很忙,一般在零點之後稽核我的程式碼。因此,我們在零點之後透過郵件討論程式碼的問題,直到他批准了,我提交了,我才回家。我到Google一年後,貢獻了程式碼庫中0。5%的程式碼,當時Google已經有300個工程師了,而且很多人工作了好幾年,因此我的產出還是比較高的。當時,斯爾福斯坦一個人貢獻了Google多達8%的程式碼。我的朋友朱會燦貢獻了1%,是貢獻最多者之一。

第二件事是關於專業人士和業餘工作者的區別。到了Google幾年後,我放慢了工作節奏,很多專案都交了出去,開始做顧問。當時中國的工程團隊有一個專案,第一個版本做得很不好,引起了大家的批評,於是,公司讓我去重新做那個專案。

據我瞭解,該專案的所有問題出自一個根源,就是事情做得粗製濫造。比如,它佔用了很多儲存和計算資源,卻沒有給使用者提供足夠好的效能,顯然專案組的態度就是應付差事。我接手這個專案後,估算產品應該能夠節省80%的儲存資源和2/3的計算資源,提供至少2~3倍的體驗。當我提出要求時,得到的反饋是,“這怎麼可能呢?”我說:“從你們使用的資訊總量上來講,就需要這麼多的儲存容量,用多了就是沒設計好。另外,由於有幾個子程式非常佔用計算時間,因此它們必須最佳化,最佳化好了就能加速好幾倍。如果這些問題不難,還要你們這些學計算機的幹什麼?我們從大街上找幾個人培訓一下也能寫程式碼,計算機科班出身的,就是要解決別人解決不了的問題。”李開復在一旁聽著,插嘴說, “你說得對,比爾·蓋茨也經常講類似的話。”隨後我又講,“作為一個計算機的工程師,就要有信心做到世界最好,你們按我說的思路去做,做到我要求的效能指標,我向公司申請,送你們去夏威夷度假。”後來大家真做到了,而且獲得了去夏威夷度假的獎勵。專業人士和業餘人士都能實現基本功能,但既然是專業人士,就要努力做出世界級的產品。

2008年之後,我在Google的工作比較輕鬆,有時間重新開始做研究。我把一件原來在約翰霍普金斯大學想做而沒做的事情給做了——寫一個自然語言的語法分析器,能夠分析並初步理解各種英語的句子和段落。我做這件事時並沒有什麼商業目的,只是出於興趣。

幾年後我再回到Google,發現Google的人用這種方法把網路上能找到的所有英語文章都分析了一遍。Google後來的計算機自動問答系統就是在這項工作的基礎上實現的。很多時候,做研究不能太功利,做研究需要為大家解決一些基礎性的問題。我很喜歡3M公司的一個說法“科學就是把錢變成知識,技術是把知識變成錢,兩者不是一回事”。

結語

人們通常會覺得從事IT行業生命週期很短,主要是因為一些人的能力沒有和年齡同步增長。世界上總有一些IT難題,需要有經驗的人解決。我在Google時,湯普森(UNIX的發明人,圖靈獎獲得者)和我都在研究部門,他每日的工作就是和大家閒聊,看看有什麼問題需要解決,然後能碰撞出什麼想法。一段時間之後,他和兩位IT老兵發明了Go這種新的程式語言,當時他已經60多歲了。

很多人會問我現在還寫不寫程式碼。我現在偶爾還會寫,但只是出於興趣,不會寫任何產品的程式碼了。這就如同一個老兵,後來當上了將軍,不用親自扛槍上戰場了,但還會出於興趣玩槍,還會去靶場射擊一樣。實際上,圖靈獎獲得者都有這個習慣,在矽谷的圖靈獎獲得者甚至還自己發起編碼比賽。過去,通常冠軍都是高德納。