【NLP】用code2vec、glow和spaCy進(jìn)行詞嵌入
編譯 | VK?
來源 | Towards Data Science
改進(jìn)機(jī)器學(xué)習(xí)模型的一個(gè)有效方法是使用詞嵌入。使用詞嵌入,你可以捕獲文檔中單詞的上下文,然后找到語義和語法上的相似之處。
在這篇文章中,我們將討論詞嵌入技術(shù)的一個(gè)不尋常的應(yīng)用。我們將嘗試使用OpenAPI的規(guī)范作為數(shù)據(jù)集在其中找到最好的詞嵌入技術(shù)。作為OpenAPI規(guī)范的一個(gè)例子,我們將使用OpenAPI specifications(https://swagger.io/specification/)提供的OpenAPI規(guī)范數(shù)據(jù)集。
最大的挑戰(zhàn)是,OpenAPI規(guī)范既不是自然語言,也不是代碼。但這也意味著我們可以自由使用任何可用的嵌入模型。在這個(gè)實(shí)驗(yàn)中,我們將研究三種可能可行的候選方案:code2vec、glow和spaCy。
code2vec是一個(gè)神經(jīng)模型,可以學(xué)習(xí)與源代碼相關(guān)的類比。該模型是在Java代碼數(shù)據(jù)庫上訓(xùn)練的,但是你可以將其應(yīng)用于任何代碼。
還有GloVe。GloVe是一種常用的自然語言處理算法。它是在維基百科和Gigawords上訓(xùn)練的。
最后,我們有了spaCy。雖然spaCy是最近才發(fā)展起來的,但該算法已經(jīng)以世界上最快的詞嵌入而聞名。
讓我們看看這些算法中哪一種更適合OpenAPI數(shù)據(jù)集,哪種算法對于OpenAPI規(guī)范的運(yùn)行速度更快. 我把這篇文章分為六個(gè)部分,每個(gè)部分都包含代碼示例和一些將來使用的提示,外加一個(gè)結(jié)論。
下載數(shù)據(jù)集
下載詞匯表
提取字段名稱
標(biāo)識化
創(chuàng)建字段名稱的數(shù)據(jù)集
測試嵌入
結(jié)論
現(xiàn)在,我們可以開始了。
1.下載數(shù)據(jù)集
首先,我們需要下載整個(gè)apis-guru數(shù)據(jù)庫:https://apis.guru/。
你會(huì)注意到,大多數(shù)apis-guru規(guī)范都是Swagger 2.0格式。但是,OpenAPI規(guī)范的最新版本是OpenAPI 3.0。
因此,讓我們使用Unmock腳本將整個(gè)數(shù)據(jù)集轉(zhuǎn)換為這種格式!你可以按照Unmock openapi腳本的README文件中的說明完成此操作:https://github.com/meeshkan/unmock-openapi-scripts/blob/master/README.md。
這可能需要一段時(shí)間,最后,你將得到一個(gè)大數(shù)據(jù)集。
2.下載詞匯表
「code2vec」
從code2vec GitHub頁面下載模型,按照快速入門部分中的說明進(jìn)行操作。
使用gensim庫加載。
model?=?word2vec.load_word2vec_format(vectors_text_path,?binary=False)
「GloVe」
從網(wǎng)站下載一個(gè)GloVe詞匯表。我們選了最大的一個(gè),因?yàn)檫@樣它就更有可能找到我們所有的單詞。你可以選擇下載它的位置,但為了方便起見,最好將其存儲(chǔ)在工作目錄中。
手動(dòng)加載GloVe詞匯表。
embeddings_dict?=?{}
with?open("../glove/glove.6B.300d.txt",?'r',?encoding="utf-8")?as?f:
????for?line?in?f:
????????values?=?line.split()
????????word?=?values[0]
????????vector?=?np.asarray(values[1:],?"float32")
????????embeddings_dict[word]?=?vector
「spaCy」
加載spaCy的詞匯表:
nlp?=?spacy.load(‘en_core_web_lg’).
3.提取字段名
OpenAPI規(guī)范名稱的整個(gè)列表可以從scripts/fetch-list.sh文件或使用以下函數(shù)(對于Windows)獲?。?/p>
def?getListOfFiles(dirName):
????listOfFile?=?os.listdir(dirName)
????allFiles?=?list()
????for?entry?in?listOfFile:
????????fullPath?=?posixpath.join(dirName,?entry)
????????if?posixpath.isdir(fullPath):
????????????allFiles?=?allFiles?+?getListOfFiles(fullPath)
????????else:
????????????allFiles.append(fullPath)
????????????????
????return?allFiles
另一個(gè)大問題是從我們的OpenAPI規(guī)范中獲取字段名。為此,我們將使用openapi-typed庫。讓我們定義一個(gè)get_fields函數(shù),該函數(shù)接受OpenAPI規(guī)范并返回字段名列表:
def?get_fields_from_schema(o:?Schema)?->?Sequence[str]:
????return?[
????????*(o['properties'].keys()?if?('properties'?in?o)?and?(type(o['properties'])?==?type({}))?else?[]),
????????*(sum([
????????????get_fields_from_schema(schema)?for?schema?in?o['properties'].values()?if?not?('$ref'?in?schema)?and?type(schema)?==?type({})],?[])?if?('properties'?in?o)?and?($????????*(get_fields_from_schema(o['additionalProperties'])?if?('additionalProperties'?in?o)?and?(type(o['additionalProperties'])?==?type({}))?else?[]),
????????*(get_fields_from_schema(o['items'])?if?('items'?in?o)?and??(type(o['items']?==?type({})))?else?[]),
????]
def?get_fields_from_schemas(o:?Mapping[str,?Union[Schema,?Reference]])?->?Sequence[str]:
????return?sum([get_fields_from_schema(cast(Schema,?maybe_schema))?for?maybe_schema?in?o.values()?if?not?('$ref'?in?maybe_schema)?and?(type(maybe_schema)?==?type({}))],?[])
def?get_fields_from_components(o:?Components)?->?Sequence[str]:
????return?[
????????*(get_fields_from_schemas(o['schemas'])?if?'schemas'?in?o?else?[]),
????????????]???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
def?get_fields(o:?OpenAPIObject)?->?Sequence[str]:
????return?[
????????*(get_fields_from_components(o['components'])?if?'components'?in?o?else?[]),
????]
恭喜!現(xiàn)在我們的數(shù)據(jù)集準(zhǔn)備好了。
4.標(biāo)識化
字段名可能包含標(biāo)點(diǎn)符號,如_和-符號,或大小寫為駝峰的單詞。我們可以把這些單詞切分,稱為標(biāo)識。
下面的camel_case函數(shù)檢查駝峰命名。首先,它檢查是否有標(biāo)點(diǎn)符號。如果是,那就不是駝峰命名。然后,它檢查單詞內(nèi)部是否有大寫字母(不包括第一個(gè)和最后一個(gè)字符)。
def?camel_case(example):??????
????if??any(x?in?example?for?x??in?string.punctuation)==True:
????????return?False
????else:
????????if?any(list(map(str.isupper,?example[1:-1])))==True:
????????????return?True
????????else:
????????????return?False
下一個(gè)函數(shù)camel_case_split將駝峰單詞拆分為多個(gè)部分。為此,我們應(yīng)該識別大寫字母并標(biāo)記大小寫更改的位置。函數(shù)返回拆分后的單詞列表。例如,字段名BodyAsJson轉(zhuǎn)換為一個(gè)列表['Body','As','Json']。
def?camel_case_split(word):
????idx?=?list(map(str.isupper,?word))
????case_change?=?[0]
????for?(i,?(x,?y))?in?enumerate(zip(idx,?idx[1:])):
????????if?x?and?not?y:??
????????????case_change.append(i)
????????elif?not?x?and?y:??
????????????case_change.append(i+1)
????case_change.append(len(word))
????return?[word[x:y]?for?x,?y?in?zip(case_change,?case_change[1:])?if?x?這個(gè)camel_case_split函數(shù)隨后用于以下標(biāo)記化算法。在這里,我們首先檢查單詞中是否有標(biāo)點(diǎn)符號。然后,我們把這個(gè)詞分成幾部分。這些詞有可能是駝峰詞。如果是這樣的話,我們可以把它分成小塊。最后,拆分每個(gè)元素后,整個(gè)列表將轉(zhuǎn)換為小寫。
def?tokenizer(mylist):
????tokenized_list=[]
????for?word?in?mylist:
????????if?'_'??in?word:
????????????splitted_word=word.split('_')
????????????for?elem?in?splitted_word:
????????????????if?camel_case(elem):
????????????????????elem=camel_case_split(elem)
????????????????????for?el1?in?elem:
????????????????????????tokenized_list.append(el1.lower())
????????????????else:????
????????????????????tokenized_list.append(elem.lower())
????????elif?'-'?in?word:
????????????hyp_word=word.split('-')
????????????for?i?in?hyp_word:
????????????????if?camel_case(i):
????????????????????i=camel_case_split(i)
????????????????????for?el2?in?i:
????????????????????????tokenized_list.append(el2.lower())
????????????????else:?
????????????????????tokenized_list.append(i.lower())
????????elif?camel_case(word):
????????????word=camel_case_split(word)
????????????for?el?in?word:
????????????????tokenized_list.append(el.lower())
????????else:
????????????tokenized_list.append(word.lower())
????return(tokenized_list)
tokenizer(my_word)
5.創(chuàng)建字段名的數(shù)據(jù)集
現(xiàn)在,讓我們用所有規(guī)范中的字段名創(chuàng)建一個(gè)大數(shù)據(jù)集。
下面的dict_dataset函數(shù)獲取文件名和路徑的列表,并打開每個(gè)規(guī)范文件。對于每個(gè)文件,get_field函數(shù)返回字段名的列表。某些字段名稱可能在一個(gè)規(guī)范中重復(fù)。為了避免這種重復(fù),讓我們使用list(dict.fromkeys(col))將列表中的字段名列表轉(zhuǎn)換為字典,然后再返回。然后我們可以將列表標(biāo)識化。最后,我們創(chuàng)建一個(gè)以文件名為鍵,以字段名列表為值的字典。
def?dict_dataset(datasets):
????dataset_dict={}
????for?i?in?datasets:
????????with?open(i,?'r')?as?foo:
????????????col=algo.get_fields(yaml.safe_load(foo.read()))
????????????if?col:
????????????????mylist?=?list(dict.fromkeys(col))
????????????????tokenized_list=tokenizer(mylist)
????????????????dataset_dict.update({i:?tokenized_list})
????????????else:
????????????????continue
????return?(dataset_dict)
6.測試嵌入
「code2vec和GloVe」
現(xiàn)在我們可以找出詞匯表外的單詞(未識別的單詞)并計(jì)算這些單詞在code2vec詞匯表中所占的百分比。以下代碼也適用于GloVe。
not_identified_c2v=[]
count_not_indent=[]
total_number=[]
for?ds?in?test1:
????count=0
????for?i?in?data[ds]:
????????if?not?i?in?model:
????????????not_identified_c2v.append(i)
????????????count+=1
????count_not_indent.append(count)
????total_number.append(len(data[ds]))
total_code2vec=sum(count_not_indent)/sum(total_number)*100
「spaCy」
spaCy詞匯表不同,因此我們需要相應(yīng)地修改代碼:
not_identified_sp=[]
count_not_indent=[]
total_number=[]
for?ds?in?test1:
????count=0
????for?i?in?data[ds]:
????????f?not?i?in?nlp.vocab:
????????????????count+=1
????????????????not_identified_sp.append(i)
????count_not_indent.append(count)
????total_number.append(len(data[ds]))
????????
total_spacy=sum(count_not_indent)/sum(total_number)*100
對于code2vec、glow和spaCy,未識別單詞的百分比分別為3.39、2.33和2.09。由于每個(gè)算法的百分比相對較小且相似,因此我們可以進(jìn)行另一個(gè)測試。
首先,讓我們創(chuàng)建一個(gè)測試字典,其中的單詞應(yīng)該在所有API規(guī)范中都是相似的:
test_dictionary={'host':?'server',
'pragma':?'cache',
'id':?'uuid',
'user':?'client',
'limit':?'control',
'balance':?'amount',
'published':?'date',
'limit':?'dailylimit',
'ratelimit':?'rate',
'start':?'display',
'data':?'categories'}
對于GloVe和code2vec,我們可以使用gensim庫提供的similar_by_vector方法。spaCy還沒有實(shí)現(xiàn)這個(gè)方法,但是通過這個(gè)我們可以自己找到最相似的單詞。
為此,我們需要格式化輸入向量,以便在距離函數(shù)中使用。我們將在字典中創(chuàng)建每個(gè)鍵,并檢查對應(yīng)的值是否在100個(gè)最相似的單詞中。
首先,我們將格式化詞匯表以便使用distance.cdist函數(shù)。這個(gè)函數(shù)計(jì)算詞匯表中每對向量之間的距離。然后,我們將從最小距離到最大距離對列表進(jìn)行排序,并取前100個(gè)單詞。
from?scipy.spatial?import?distance
for?k,?v?in?test_dictionary.items():
????input_word?=?k
????p?=?np.array([nlp.vocab[input_word].vector])
????closest_index?=?distance.cdist(p,?vectors)[0].argsort()[::-1][-100:]
????word_id?=?[ids[closest_ind]?for?closest_ind?in?closest_index]
????output_word?=?[nlp.vocab[i].text?for?i?in?word_id]
????#output_word
????list1=[j.lower()?for?j?in?output_word]
????mylist?=?list(dict.fromkeys(list1))[:50]
????count=0
????if?test_dictionary[k]?in?mylist:
????????count+=1
????????print(k,count,?'yes')
????else:
????????print(k,?'no')
下表總結(jié)了結(jié)果。spaCy顯示單詞“client”位于單詞“user”的前100個(gè)最相似的單詞中。它對幾乎所有的OpenAPI規(guī)范都是有用的,并且可以用于將來OpenAPI規(guī)范相似性的分析。單詞“balance”的向量接近單詞“amount”的向量。我們發(fā)現(xiàn)它對支付API特別有用。

結(jié)論
我們已經(jīng)為OpenAPI規(guī)范嘗試了三種不同的詞嵌入算法。盡管這三個(gè)詞在這個(gè)數(shù)據(jù)集上都表現(xiàn)得很好,但是對最相似的單詞進(jìn)行額外的比較表明spaCy對我們的情況更好。
spaCy比其他算法更快。spaCy詞匯表的讀取速度比glow或code2vec詞匯表快5倍。然而,在使用該算法時(shí),缺少內(nèi)置函數(shù)(如similar_by_vector和similar_word)是一個(gè)障礙。
另外,spaCy與我們的數(shù)據(jù)集很好地工作,這并不意味著spaCy對世界上的每個(gè)數(shù)據(jù)集都會(huì)更好。所以,請隨意嘗試為你自己的數(shù)據(jù)集嵌入不同的單詞,感謝你的閱讀!
原文鏈接:https://towardsdatascience.com/word-embeddings-with-code2vec-glove-and-spacy-5b26420bf632
往期精彩回顧
獲取一折本站知識星球優(yōu)惠券,復(fù)制鏈接直接打開:
https://t.zsxq.com/662nyZF
本站qq群1003271085。
加入微信群請掃碼進(jìn)群(如果是博士或者準(zhǔn)備讀博士請說明):
