1. 什么是Word2Vec和Embeddings?
Word2Vec是從大量文本語料中以無監(jiān)督的方式學(xué)習(xí)語義知識的一種模型,它被大量地用在自然語言處理(NLP)中。那么它是如何幫助我們做自然語言處理呢?Word2Vec其實就是通過學(xué)習(xí)文本來用詞向量的方式表征詞的語義信息,即通過一個嵌入空間使得語義上相似的單詞在該空間內(nèi)距離很近。Embedding其實就是一個映射,將單詞從原先所屬的空間映射到新的多維空間中,也就是把原先詞所在空間嵌入到一個新的空間中去。
我們從直觀角度上來理解一下,cat這個單詞和kitten屬于語義上很相近的詞,而dog和kitten則不是那么相近,iphone這個單詞和kitten的語義就差的更遠了。通過對詞匯表中單詞進行這種數(shù)值表示方式的學(xué)習(xí)(也就是將單詞轉(zhuǎn)換為詞向量),能夠讓我們基于這樣的數(shù)值進行向量化的操作從而得到一些有趣的結(jié)論。比如說,如果我們對詞向量kitten、cat以及dog執(zhí)行這樣的操作:kitten - cat + dog,那么最終得到的嵌入向量(embedded vector)將與puppy這個詞向量十分相近。
2. 模型
Word2Vec模型中,主要有Skip-Gram和CBOW兩種模型,從直觀上理解,Skip-Gram是給定input word來預(yù)測上下文。而CBOW是給定上下文,來預(yù)測input word。本篇文章僅講解Skip-Gram模型。

Skip-Gram模型的基礎(chǔ)形式非常簡單,為了更清楚地解釋模型,我們先從最一般的基礎(chǔ)模型來看Word2Vec(下文中所有的Word2Vec都是指Skip-Gram模型)。
Word2Vec模型實際上分為了兩個部分,第一部分為建立模型,第二部分是通過模型獲取嵌入詞向量。Word2Vec的整個建模過程實際上與自編碼器(auto-encoder)的思想很相似,即先基于訓(xùn)練數(shù)據(jù)構(gòu)建一個神經(jīng)網(wǎng)絡(luò),當(dāng)這個模型訓(xùn)練好以后,我們并不會用這個訓(xùn)練好的模型處理新的任務(wù),我們真正需要的是這個模型通過訓(xùn)練數(shù)據(jù)所學(xué)得的參數(shù),例如隱層的權(quán)重矩陣——后面我們將會看到這些權(quán)重在Word2Vec中實際上就是我們試圖去學(xué)習(xí)的“word vectors”?;谟?xùn)練數(shù)據(jù)建模的過程,我們給它一個名字叫“Fake Task”,意味著建模并不是我們最終的目的。
上面提到的這種方法實際上會在無監(jiān)督特征學(xué)習(xí)(unsupervised feature learning)中見到,最常見的就是自編碼器(auto-encoder):通過在隱層將輸入進行編碼壓縮,繼而在輸出層將數(shù)據(jù)解碼恢復(fù)初始狀態(tài),訓(xùn)練完成后,我們會將輸出層“砍掉”,僅保留隱層。
2.1.The Fake Task
我們在上面提到,訓(xùn)練模型的真正目的是獲得模型基于訓(xùn)練數(shù)據(jù)學(xué)得的隱層權(quán)重。為了得到這些權(quán)重,我們首先要構(gòu)建一個完整的神經(jīng)網(wǎng)絡(luò)作為我們的“Fake Task”,后面再返回來看通過“Fake Task”我們?nèi)绾伍g接地得到這些詞向量。
接下來我們來看看如何訓(xùn)練我們的神經(jīng)網(wǎng)絡(luò)。假如我們有一個句子“The dog barked at the mailman”。
首先我們選句子中間的一個詞作為我們的輸入詞,例如我們選取“dog”作為input word;
有了input word以后,我們再定義一個叫做skip_window的參數(shù),它代表著我們從當(dāng)前input word的一側(cè)(左邊或右邊)選取詞的數(shù)量。如果我們設(shè)置skip_window=2,那么我們最終獲得窗口中的詞(包括input word在內(nèi))就是['The', 'dog','barked', 'at']。skip_window=2代表著選取左input word左側(cè)2個詞和右側(cè)2個詞進入我們的窗口,所以整個窗口大小span=2x2=4。另一個參數(shù)叫num_skips,它代表著我們從整個窗口中選取多少個不同的詞作為我們的output word,當(dāng)skip_window=2,num_skips=2時,我們將會得到兩組 (input word, output word) 形式的訓(xùn)練數(shù)據(jù),即 ('dog', 'barked'),('dog', 'the')。
神經(jīng)網(wǎng)絡(luò)基于這些訓(xùn)練數(shù)據(jù)將會輸出一個概率分布,這個概率代表著我們的詞典中的每個詞是output word的可能性。這句話有點繞,我們來看個栗子。第二步中我們在設(shè)置skip_window=2和num_skips=2的情況下獲得了兩組訓(xùn)練數(shù)據(jù)。假如我們先拿一組數(shù)據(jù) ('dog', 'barked') 來訓(xùn)練神經(jīng)網(wǎng)絡(luò),那么模型通過學(xué)習(xí)這個訓(xùn)練樣本,會告訴我們詞匯表中每個單詞是“barked”的概率大小。
模型的輸出概率代表著到我們詞典中每個詞有多大可能性跟input word同時出現(xiàn)。舉個栗子,如果我們向神經(jīng)網(wǎng)絡(luò)模型中輸入一個單詞“Soviet“,那么最終模型的輸出概率中,像“Union”, ”Russia“這種相關(guān)詞的概率將遠高于像”watermelon“,”kangaroo“非相關(guān)詞的概率。因為”Union“,”Russia“在文本中更大可能在”Soviet“的窗口中出現(xiàn)。
我們將通過給神經(jīng)網(wǎng)絡(luò)輸入文本中成對的單詞來訓(xùn)練它完成上面所說的概率計算。下面的圖中給出了一些我們的訓(xùn)練樣本的例子。我們選定句子“The quick brown fox jumps over lazy dog”,設(shè)定我們的窗口大小為2(window_size=2),也就是說我們僅選輸入詞前后各兩個詞和輸入詞進行組合。下圖中,藍色代表input word,方框內(nèi)代表位于窗口內(nèi)的單詞。

我們的模型將會從每對單詞出現(xiàn)的次數(shù)中習(xí)得統(tǒng)計結(jié)果。例如,我們的神經(jīng)網(wǎng)絡(luò)可能會得到更多類似(“Soviet“,”Union“)這樣的訓(xùn)練樣本對,而對于(”Soviet“,”Sasquatch“)這樣的組合卻看到的很少。因此,當(dāng)我們的模型完成訓(xùn)練后,給定一個單詞”Soviet“作為輸入,輸出的結(jié)果中”Union“或者”Russia“要比”Sasquatch“被賦予更高的概率。
2.2.模型細(xì)節(jié)
我們?nèi)绾蝸肀硎具@些單詞呢?
首先,我們都知道神經(jīng)網(wǎng)絡(luò)只能接受數(shù)值輸入,我們不可能把一個單詞字符串作為輸入,因此我們得想個辦法來表示這些單詞。最常用的辦法就是基于訓(xùn)練文檔來構(gòu)建我們自己的詞匯表(vocabulary)再對單詞進行one-hot編碼。
假設(shè)從我們的訓(xùn)練文檔中抽取出10000個唯一不重復(fù)的單詞組成詞匯表。我們對這10000個單詞進行one-hot編碼,得到的每個單詞都是一個10000維的向量,向量每個維度的值只有0或者1,假如單詞ants在詞匯表中的出現(xiàn)位置為第3個,那么ants的向量就是一個第三維度取值為1,其他維都為0的10000維的向量(ants=[0,0,1,0,...,0]T)。
還是上面的例子,“The dog barked at the mailman”,那么我們基于這個句子,可以構(gòu)建一個大小為5的詞匯表(忽略大小寫和標(biāo)點符號):("the", "dog", "barked", "at", "mailman"),我們對這個詞匯表的單詞進行編號0-4。那么”dog“就可以被表示為一個5維向量[0, 1, 0, 0, 0]。
模型的輸入如果為一個10000維的向量,那么輸出也是一個10000維度(詞匯表的大小)的向量,它包含了10000個概率,每一個概率代表著當(dāng)前詞是輸入樣本中output word的概率大小。
下圖是我們神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu):

隱層沒有使用任何激活函數(shù),但是輸出層使用了sotfmax。
我們基于成對的單詞來對神經(jīng)網(wǎng)絡(luò)進行訓(xùn)練,訓(xùn)練樣本是 ( input word, output word ) 這樣的單詞對,input word和output word都是one-hot編碼的向量。最終模型的輸出是一個概率分布。
隱層
說完單詞的編碼和訓(xùn)練樣本的選取,我們來看下我們的隱層。如果我們現(xiàn)在想用300個特征來表示一個單詞(即每個詞可以被表示為300維的向量)。那么隱層的權(quán)重矩陣應(yīng)該為10000行,300列(隱層有300個結(jié)點)。
Google在最新發(fā)布的基于Google news數(shù)據(jù)集訓(xùn)練的模型中使用的就是300個特征的詞向量。詞向量的維度是一個可以調(diào)節(jié)的超參數(shù)(在Python的gensim包中封裝的Word2Vec接口默認(rèn)的詞向量大小為100, window_size為5)。
看下面的圖片,左右兩張圖分別從不同角度代表了輸入層-隱層的權(quán)重矩陣。左圖中每一列代表一個10000維的詞向量和隱層單個神經(jīng)元連接的權(quán)重向量。從右邊的圖來看,每一行實際上代表了每個單詞的詞向量。

所以我們最終的目標(biāo)就是學(xué)習(xí)這個隱層的權(quán)重矩陣。
我們現(xiàn)在回來接著通過模型的定義來訓(xùn)練我們的這個模型。
上面我們提到,input word和output word都會被我們進行one-hot編碼。仔細(xì)想一下,我們的輸入被one-hot編碼以后大多數(shù)維度上都是0(實際上僅有一個位置為1),所以這個向量相當(dāng)稀疏,那么會造成什么結(jié)果呢。如果我們將一個1 x 10000的向量和10000 x 300的矩陣相乘,它會消耗相當(dāng)大的計算資源,為了高效計算,它僅僅會選擇矩陣中對應(yīng)的向量中維度值為1的索引行(這句話很繞),看圖就明白。

我們來看一下上圖中的矩陣運算,左邊分別是1 x 5和5 x 3的矩陣,結(jié)果應(yīng)該是1 x 3的矩陣,按照矩陣乘法的規(guī)則,結(jié)果的第一行第一列元素為0x17+0x23+0x4+1x10+0x11=10,同理可得其余兩個元素為12,19。如果10000個維度的矩陣采用這樣的計算方式是十分低效的。
為了有效地進行計算,這種稀疏狀態(tài)下不會進行矩陣乘法計算,可以看到矩陣的計算的結(jié)果實際上是矩陣對應(yīng)的向量中值為1的索引,上面的例子中,左邊向量中取值為1的對應(yīng)維度為3(下標(biāo)從0開始),那么計算結(jié)果就是矩陣的第3行(下標(biāo)從0開始)—— [10, 12, 19],這樣模型中的隱層權(quán)重矩陣便成了一個”查找表“(lookup table),進行矩陣計算時,直接去查輸入向量中取值為1的維度下對應(yīng)的那些權(quán)重值。隱層的輸出就是每個輸入單詞的“嵌入詞向量”。
輸出層
經(jīng)過神經(jīng)網(wǎng)絡(luò)隱層的計算,ants這個詞會從一個1 x 10000的向量變成1 x 300的向量,再被輸入到輸出層。輸出層是一個softmax回歸分類器,它的每個結(jié)點將會輸出一個0-1之間的值(概率),這些所有輸出層神經(jīng)元結(jié)點的概率之和為1。
下面是一個例子,訓(xùn)練樣本為 (input word: “ants”, output word: “car”) 的計算示意圖。

直覺上的理解
下面我們將通過直覺來進行一些思考。
如果兩個不同的單詞有著非常相似的“上下文”(也就是窗口單詞很相似,比如“Kitty climbed the tree”和“Cat climbed the tree”),那么通過我們的模型訓(xùn)練,這兩個單詞的嵌入向量將非常相似。
那么兩個單詞擁有相似的“上下文”到底是什么含義呢?比如對于同義詞“intelligent”和“smart”,我們覺得這兩個單詞應(yīng)該擁有相同的“上下文”。而例如”engine“和”transmission“這樣相關(guān)的詞語,可能也擁有著相似的上下文。
實際上,這種方法實際上也可以幫助你進行詞干化(stemming),例如,神經(jīng)網(wǎng)絡(luò)對”ant“和”ants”兩個單詞會習(xí)得相似的詞向量。
詞干化(stemming)就是去除詞綴得到詞根的過程。
3.skip-gram訓(xùn)練技巧
第一部分我們了解skip-gram的輸入層、隱層、輸出層。在第二部分,會繼續(xù)深入講如何在skip-gram模型上進行高效的訓(xùn)練。
在第一部分講解完成后,我們會發(fā)現(xiàn)Word2Vec模型是一個超級大的神經(jīng)網(wǎng)絡(luò)(權(quán)重矩陣規(guī)模非常大)。
舉個栗子,我們擁有10000個單詞的詞匯表,我們?nèi)绻肭度?00維的詞向量,那么我們的輸入-隱層權(quán)重矩陣和隱層-輸出層的權(quán)重矩陣都會有 10000 x 300 = 300萬個權(quán)重,在如此龐大的神經(jīng)網(wǎng)絡(luò)中進行梯度下降是相當(dāng)慢的。更糟糕的是,你需要大量的訓(xùn)練數(shù)據(jù)來調(diào)整這些權(quán)重并且避免過擬合。百萬數(shù)量級的權(quán)重矩陣和億萬數(shù)量級的訓(xùn)練樣本意味著訓(xùn)練這個模型將會是個災(zāi)難(太兇殘了)。
Word2Vec的作者在它的第二篇論文中強調(diào)了這些問題,下面是作者在第二篇論文中的三個創(chuàng)新:
將常見的單詞組合(word pairs)或者詞組作為單個“words”來處理。
對高頻次單詞進行抽樣來減少訓(xùn)練樣本的個數(shù)。
對優(yōu)化目標(biāo)采用“negative sampling”方法,這樣每個訓(xùn)練樣本的訓(xùn)練只會更新一小部分的模型權(quán)重,從而降低計算負(fù)擔(dān)。
事實證明,對常用詞抽樣并且對優(yōu)化目標(biāo)采用“negative sampling”不僅降低了訓(xùn)練過程中的計算負(fù)擔(dān),還提高了訓(xùn)練的詞向量的質(zhì)量。
3.1.Word pairs and "phases"
論文的作者指出,一些單詞組合(或者詞組)的含義和拆開以后具有完全不同的意義。比如“Boston Globe”是一種報刊的名字,而單獨的“Boston”和“Globe”這樣單個的單詞卻表達不出這樣的含義。因此,在文章中只要出現(xiàn)“Boston Globe”,我們就應(yīng)該把它作為一個單獨的詞來生成其詞向量,而不是將其拆開。同樣的例子還有“New York”,“United Stated”等。
在Google發(fā)布的模型中,它本身的訓(xùn)練樣本中有來自Google News數(shù)據(jù)集中的1000億的單詞,但是除了單個單詞以外,單詞組合(或詞組)又有3百萬之多。
如果你對模型的詞匯表感興趣,可以點擊這里,你還可以直接瀏覽這個詞匯表。
如果想了解這個模型如何進行文檔中的詞組抽取,可以看論文中“Learning Phrases”這一章,對應(yīng)的代碼word2phrase.c被發(fā)布在這里。
3.2.對高頻詞抽樣
在第一部分的講解中,我們展示了訓(xùn)練樣本是如何從原始文檔中生成出來的,這里我再重復(fù)一次。我們的原始文本為“The quick brown fox jumps over the laze dog”,如果我使用大小為2的窗口,那么我們可以得到圖中展示的那些訓(xùn)練樣本。

但是對于“the”這種常用高頻單詞,這樣的處理方式會存在下面兩個問題:
當(dāng)我們得到成對的單詞訓(xùn)練樣本時,("fox", "the") 這樣的訓(xùn)練樣本并不會給我們提供關(guān)于“fox”更多的語義信息,因為“the”在每個單詞的上下文中幾乎都會出現(xiàn)。
由于在文本中“the”這樣的常用詞出現(xiàn)概率很大,因此我們將會有大量的(”the“,...)這樣的訓(xùn)練樣本,而這些樣本數(shù)量遠遠超過了我們學(xué)習(xí)“the”這個詞向量所需的訓(xùn)練樣本數(shù)。
Word2Vec通過“抽樣”模式來解決這種高頻詞問題。它的基本思想如下:對于我們在訓(xùn)練原始文本中遇到的每一個單詞,它們都有一定概率被我們從文本中刪掉,而這個被刪除的概率與單詞的頻率有關(guān)。
如果我們設(shè)置窗口大小span=10(即skip_window=5),并且從我們的文本中刪除所有的“the”,那么會有下面的結(jié)果:
由于我們刪除了文本中所有的“the”,那么在我們的訓(xùn)練樣本中,“the”這個詞永遠也不會出現(xiàn)在我們的上下文窗口中。
當(dāng)“the”作為input word時,我們的訓(xùn)練樣本數(shù)至少會減少10個。
這句話應(yīng)該這么理解,假如我們的文本中僅出現(xiàn)了一個“the”,那么當(dāng)這個“the”作為input word時,我們設(shè)置span=10,此時會得到10個訓(xùn)練樣本 ("the", ...) ,如果刪掉這個“the”,我們就會減少10個訓(xùn)練樣本。實際中我們的文本中不止一個“the”,因此當(dāng)“the”作為input word的時候,至少會減少10個訓(xùn)練樣本。
上面提到的這兩個影響結(jié)果實際上就幫助我們解決了高頻詞帶來的問題。
抽樣率
word2vec的C語言代碼實現(xiàn)了一個計算在詞匯表中保留某個詞概率的公式。

wi是一個單詞,z(wi)是wi這個單詞在所有語料中出現(xiàn)的頻次。舉個栗子,如果單詞“peanut”在10億規(guī)模大小的語料中出現(xiàn)了1000次,那么

。
在代碼中還有一個參數(shù)叫“sample”,這個參數(shù)代表一個閾值,默認(rèn)值為0.001(在gensim包中的Word2Vec類說明中,這個參數(shù)默認(rèn)為0.001,文檔中對這個參數(shù)的解釋為“ threshold for configuring which higher-frequency words are randomly downsampled”)。這個值越小意味著這個單詞被保留下來的概率越?。从性酱蟮母怕时晃覀儎h除)。
P(wi)代表著保留某個單詞的概率:


圖中x軸代表著Z(wi),即單詞wi在語料中出現(xiàn)頻率,y軸代表某個單詞被保留的概率。對于一個龐大的語料來說,單個單詞的出現(xiàn)頻率不會很大,即使是常用詞,也不可能特別大。
從這個圖中,我們可以看到,隨著單詞出現(xiàn)頻率的增高,它被采樣保留的概率越來越小,我們還可以看到一些有趣的結(jié)論:
當(dāng)Z(wi)<=0.0026時,P(wi)=1.0。當(dāng)單詞在語料中出現(xiàn)的頻率小于0.0026時,它是100%被保留的,這意味著只有那些在語料中出現(xiàn)頻率超過0.26%的單詞才會被采樣。
當(dāng)Z(wi)=0.00746時,P(wi)=0.5,意味著這一部分的單詞有50%的概率被保留。
當(dāng)Z(wi)=1時,P(wi)=0.033,意味著這部分單詞以3.3%的概率被保留。
如果你去看那篇論文的話,你會發(fā)現(xiàn)作者在論文中對函數(shù)公式的定義和在C語言代碼的實現(xiàn)上有一些差別,但我認(rèn)為C語言代碼的公式實現(xiàn)是更權(quán)威的一個版本。
3.3.負(fù)采樣(negative sampling)
訓(xùn)練一個神經(jīng)網(wǎng)絡(luò)意味著要輸入訓(xùn)練樣本并且不斷調(diào)整神經(jīng)元的權(quán)重,從而不斷提高對目標(biāo)的準(zhǔn)確預(yù)測。每當(dāng)神經(jīng)網(wǎng)絡(luò)經(jīng)過一個訓(xùn)練樣本的訓(xùn)練,它的權(quán)重就會進行一次調(diào)整。
正如我們上面所討論的,vocabulary的大小決定了我們的Skip-Gram神經(jīng)網(wǎng)絡(luò)將會擁有大規(guī)模的權(quán)重矩陣,所有的這些權(quán)重需要通過我們數(shù)以億計的訓(xùn)練樣本來進行調(diào)整,這是非常消耗計算資源的,并且實際中訓(xùn)練起來會非常慢。
負(fù)采樣(negative sampling)解決了這個問題,它是用來提高訓(xùn)練速度并且改善所得到詞向量的質(zhì)量的一種方法。不同于原本每個訓(xùn)練樣本更新所有的權(quán)重,負(fù)采樣每次讓一個訓(xùn)練樣本僅僅更新一小部分的權(quán)重,這樣就會降低梯度下降過程中的計算量。
當(dāng)我們用訓(xùn)練樣本 ( input word: "fox",output word: "quick") 來訓(xùn)練我們的神經(jīng)網(wǎng)絡(luò)時,“ fox”和“quick”都是經(jīng)過one-hot編碼的。如果我們的vocabulary大小為10000時,在輸出層,我們期望對應(yīng)“quick”單詞的那個神經(jīng)元結(jié)點輸出1,其余9999個都應(yīng)該輸出0。在這里,這9999個我們期望輸出為0的神經(jīng)元結(jié)點所對應(yīng)的單詞我們稱為“negative” word。
當(dāng)使用負(fù)采樣時,我們將隨機選擇一小部分的negative words(比如選5個negative words)來更新對應(yīng)的權(quán)重。我們也會對我們的“positive” word進行權(quán)重更新(在我們上面的例子中,這個單詞指的是”quick“)。
在論文中,作者指出指出對于小規(guī)模數(shù)據(jù)集,選擇5-20個negative words會比較好,對于大規(guī)模數(shù)據(jù)集可以僅選擇2-5個negative words。
回憶一下我們的隱層-輸出層擁有300 x 10000的權(quán)重矩陣。如果使用了負(fù)采樣的方法我們僅僅去更新我們的positive word-“quick”的和我們選擇的其他5個negative words的結(jié)點對應(yīng)的權(quán)重,共計6個輸出神經(jīng)元,相當(dāng)于每次只更新300x6=1800個權(quán)重。對于3百萬的權(quán)重來說,相當(dāng)于只計算了0.06%的權(quán)重,這樣計算效率就大幅度提高。
如何選擇negative words
我們使用“一元模型分布(unigram distribution)”來選擇“negative words”。
要注意的一點是,一個單詞被選作negative sample的概率跟它出現(xiàn)的頻次有關(guān),出現(xiàn)頻次越高的單詞越容易被選作negative words。
在word2vec的C語言實現(xiàn)中,你可以看到對于這個概率的實現(xiàn)公式。每個單詞被選為“negative words”的概率計算公式與其出現(xiàn)的頻次有關(guān)。
代碼中的公式實現(xiàn)如下:

每個單詞被賦予一個權(quán)重,即f(wi), 它代表著單詞出現(xiàn)的頻次。
公式中開3/4的根號完全是基于經(jīng)驗的,論文中提到這個公式的效果要比其它公式更加出色。你可以在google的搜索欄中輸入“plot y = x^(3/4) and y = x”,然后看到這兩幅圖(如下圖),仔細(xì)觀察x在[0,1]區(qū)間內(nèi)時y的取值,x3/4有一小段弧形,取值在y=x函數(shù)之上。

負(fù)采樣的C語言實現(xiàn)非常的有趣。unigram table有一個包含了一億個元素的數(shù)組,這個數(shù)組是由詞匯表中每個單詞的索引號填充的,并且這個數(shù)組中有重復(fù),也就是說有些單詞會出現(xiàn)多次。那么每個單詞的索引在這個數(shù)組中出現(xiàn)的次數(shù)該如何決定呢,有公式P(wi)table_size,也就是說計算出的負(fù)采樣概率1億=單詞在表中出現(xiàn)的次數(shù)**。
有了這張表以后,每次去我們進行負(fù)采樣時,只需要在0-1億范圍內(nèi)生成一個隨機數(shù),然后選擇表中索引號為這個隨機數(shù)的那個單詞作為我們的negative word即可。一個單詞的負(fù)采樣概率越大,那么它在這個表中出現(xiàn)的次數(shù)就越多,它被選中的概率就越大。
到目前為止,Word2Vec中的Skip-Gram模型就講完了,對于里面具體的數(shù)學(xué)公式推導(dǎo)細(xì)節(jié)這里并沒有深入。這篇文章只是對于實現(xiàn)細(xì)節(jié)上的一些思想進行了闡述。