本篇文章譯自 Chris McCormick
的BERT Word Embeddings Tutorial
在這篇文章,我深入研究了由Google的Bert生成的word embeddings享怀,并向您展示了如何通過生成自己的word embeddings來開始Bert趟咆。
這篇文章有兩種形式——一種是博客文章梅屉,另一種是colab的notebook坯汤。
介紹
歷史
2018年是NLP取得突破性進展的一年搀愧。遷移學習,特別是像Allen-AI的ELMO咱筛、OpenAI的Open-GPT和Google的BERT這樣的模型,使研究人員能夠以最小的特定于任務(wù)的微調(diào)來粉碎多個基準點溉愁,并為NLP社區(qū)的其他人提供更容易(使用更少的數(shù)據(jù)和更短的計算時間)微調(diào)的預(yù)訓練模型。進行微調(diào)并實現(xiàn)拐揭,以產(chǎn)生最先進的結(jié)果投队。不幸的是爵川,對于許多在NLP起步的人,甚至對一些有經(jīng)驗的實踐者來說寝贡,這些強大的模型的理論和實際應(yīng)用還沒有被很好地理解。
什么是BERT?
BERT(Transformers的雙向編碼器表示)于2018年底發(fā)布碟案,是我們將在本教程中使用的模型价说,旨在為讀者更好地理解和指導(dǎo)在NLP中使用遷移學習模型风秤。BERT是一種預(yù)訓練語言表示的方法鳖目,是NLP實踐者可以免費下載和使用的模型。您可以使用這些模型從文本數(shù)據(jù)中提取高質(zhì)量的語言功能缤弦,也可以在特定任務(wù)(分類领迈、實體識別、問題解答等)上使用自己的數(shù)據(jù)對這些模型進行微調(diào),以生成最新的預(yù)測狸捅。
為什么是BERT?
在本教程中衷蜓,我們將使用bert從文本數(shù)據(jù)中提取特征,即單詞和句子embedding vectors(嵌入向量)尘喝。我們可以用這些單詞和句子的嵌入向量做什么?首先扯夭,這些嵌入對于關(guān)鍵字/搜索擴展鞍匾、語義搜索和信息檢索很有用橡淑。例如梁棠,如果您希望匹配客戶問題或針對已回答問題或有良好文檔記錄的搜索符糊,這些表示將幫助您準確地檢索與客戶意圖和上下文含義匹配的結(jié)果呛凶,即使沒有關(guān)鍵字或短語重疊漾稀。
其次崭捍,也許更重要的是,這些向量被用作下游模型的高質(zhì)量特征輸入实夹。像lstms或cnns這樣的nlp模型需要以數(shù)字向量的形式輸入亮航,這通常意味著將詞匯和部分演講等特征轉(zhuǎn)換為數(shù)字表示塞赂。在過去宴猾,單詞要么被表示為唯一的索引值(獨熱編碼),要么更有用地表示為神經(jīng)單詞嵌入沦辙,其中詞匯單詞與固定長度的特征嵌入相匹配油讯,這是由Word2Vec或FastText等模型產(chǎn)生的延欠。Bert比Word2vec等模型更具優(yōu)勢由捎,因為每個單詞在Word2vec下都有一個固定的表示狞玛,而不管單詞出現(xiàn)在什么上下文中心肪,Bert都會生成由它們周圍的單詞動態(tài)inform的單詞表示。例如慧瘤,給出兩個句子:
“The man was accused of robbing a bank.”
“The man went fishing by the bank of the river.”
word2vec將在兩個句子中為單詞“bank”生成相同的詞嵌入碑隆,而在BERT 下上煤,“bank”的單詞嵌入對于每個句子都是不同的劫狠。除了捕獲明顯的差異(如多義)永部,上下文通知的單詞嵌入還捕獲其他形式的信息苔埋,這些信息會導(dǎo)致更精確的特征表示,進而導(dǎo)致更好的模型性能罚随。
安裝和導(dǎo)入
通過Hugging Face為BERT安裝pytorch接口淘菩。(該庫包含其他預(yù)訓練語言模型的接口,如OpenAI的GPT和GPT-2)我們選擇了pytorch接口汇在,因為它在高級API(易于使用但不提供具體如何工作)和tensorflow代碼(其中包含許多細節(jié)趾疚,但經(jīng)常會讓我們陷入關(guān)于張量流的課程,當這里的目的是BERT時)之間取得了很好的平衡 丛肮。
如果您在Google Colab上運行此代碼宝与,則每次重新連接時都必須安裝此庫; 以下單元格將為您處理。
! pip install pytorch-pretrained-bert
現(xiàn)在讓我們導(dǎo)入pytorch嚼隘,預(yù)訓練的BERT model和BERT tokenizer飞蛹。 我們將在后面的教程中詳細解釋BERT模型卧檐,這是由Google發(fā)布的預(yù)訓練模型,該模型在維基百科和Book Corpus上運行了許多小時闪唆,這是一個包含不同類型的+10,000本書的數(shù)據(jù)集。 該模型(稍作修改)在一系列任務(wù)中擊敗NLP各項基準。 Google發(fā)布了一些BERT型號的變體贷帮,但我們在這里使用的是兩種可用尺寸(“base” 和 “l(fā)arge”))中較小的一種并且忽略了 casing,因此是 “uncased.”
import torch
from pytorch_pretrained_bert import BertTokenizer, BertModel, BertForMaskedLM
# OPTIONAL: if you want to have more information on what's happening, activate the logger as follows
import logging
#logging.basicConfig(level=logging.INFO)
import matplotlib.pyplot as plt
% matplotlib inline
# Load pre-trained model tokenizer (vocabulary)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
100%|██████████| 231508/231508 [00:00<00:00, 2386266.84B/s]
輸入格式
由于bert是一個預(yù)訓練模型,它期望輸入數(shù)據(jù)采用特定格式沃但,因此我們需要:
- special tokens to mark the beginning ([CLS]) and separation/end of sentences ([SEP])
- tokens that conforms with the fixed vocabulary used in BERT
- token IDs from BERT’s tokenizer
- mask IDs to indicate which elements in the sequence are tokens and which are padding elements
- segment IDs used to distinguish different sentences
- positional embeddings used to show token position within the sequence
幸運的是维雇,這個接口為我們處理了一些輸入規(guī)范逸贾,因此我們只需要手動創(chuàng)建其中的一些(我們將在另一個教程中重新訪問其他輸入)。
特殊的Tokens
BERT可以將一個或兩個句子作為輸入,并期望特殊tokens標記每個句子的開頭和結(jié)尾:
2個句子的輸入:
[CLS] the man went to the store [SEP] he bought a gallon of milk [SEP]
1個句子的輸入:
[CLS] the man went to the store [SEP]
text = "Here is the sentence I want embeddings for."
text = "After stealing money from the bank vault, the bank robber was seen fishing on the Mississippi river bank."
marked_text = "[CLS] " + text + " [SEP]"
print (marked_text)
[CLS] After stealing money from the bank vault, the bank robber was seen fishing on the Mississippi river bank. [SEP]
我們已經(jīng)引入一個BERT指定的tokenizer庫猾警,讓我們看一眼輸出:
Tokenization
tokenized_text = tokenizer.tokenize(marked_text)
print (tokenized_text)
['[CLS]', 'after', 'stealing', 'money', 'from', 'the', 'bank', 'vault', ',', 'the', 'bank', 'robber', 'was', 'seen', 'fishing', 'on', 'the', 'mississippi', 'river', 'bank', '.', '[SEP]']
注意“embeddings”這個詞是如何表示的:
[‘em’, ‘##bed’, ‘##ding’, ‘##s’]
原始單詞已被拆分為較小的子詞和字符拂蝎。這些子字之前的兩個哈希符號只是我們的tokenizer表示該子字或字符是較大字的一部分并且前面是另一個子字的方式温自。因此悼泌,例如‘##bed’ 這個token和 ‘bed’ 這個token不同,第一個被使用每當子詞'bed'出現(xiàn)在一個較大的單詞中時丙者,第二個被明確用于當獨立token'你睡覺的東西'出現(xiàn)時。
為什么看起來像這樣侣集?是因為BERT的tokenizer是使用WordPiece模型創(chuàng)建的世分。這個模型貪婪地創(chuàng)建了一個最適合我們的語言數(shù)據(jù)的固定數(shù)量的單個字符踪央,子詞和單詞的詞匯表,由于我們的BERT標記器模型的詞匯限制大小為30,000累贤,因此WordPiece模型生成的詞匯表包含所有英文字符以及在模型訓練的英語語料庫中找到的~30,000個最常見的單詞和子詞。 這個詞匯表包含四件事:
- 整個詞
- 在單詞的前面或單獨出現(xiàn)的子詞(在“embeddings”中的“em”被賦予與獨立的“em”字符序列相同的向量,如“go get em”中所示)
- 不在單詞前面的子詞始鱼,前面有'##'表示這種情況
- 單個字符
要在此模型下對單詞進行tokenize,tokenizer 首先檢查整個單詞是否在詞匯表中。 如果沒有,它會嘗試將單詞分解為詞匯表中包含的最大可能子詞焙矛,并作為最后的手段將單詞分解為單個字符。 請注意,正因為如此峰档,我們總是可以將一個單詞表示為其各個字符的集合。
因此,不是將不在詞匯表中的單詞分配給像'OOV'或'UNK'這樣的全能標記捉蚤,而是將不在詞匯表中的單詞分解為子字和字符標記特石,然后我們可以為其生成嵌入姆蘸。
因此,我們不是將“embeddings”和每一個不在詞匯表中其他詞匯分配一個重載的未知詞匯表token推捐,而是將其分為子詞token['em'痊乾,'##bed'蛾魄,'##ding','## s' ]這將保留原始單詞的一些上下文含義。我們甚至可以平均這些子字嵌入向量以生成原始單詞的近似向量恋日。
以下是我們詞匯表中包含的令token的一些示例誓竿。 以兩個哈希開頭的標記是子字或單個字符筷屡。
list(tokenizer.vocab.keys())[5000:5020]
['knight',
'lap',
'survey',
'ma',
'##ow',
'noise',
'billy',
'##ium',
'shooting',
'guide',
'bedroom',
'priest',
'resistance',
'motor',
'homes',
'sounded',
'giant',
'##mer',
'150',
'scenes']
接下來扼倘,我們需要調(diào)用tokenizer來將token與tokenizer詞匯表中的索引進行匹配:
indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)
for tup in zip(tokenized_text, indexed_tokens):
print (tup)
('[CLS]', 101)
('after', 2044)
('stealing', 11065)
('money', 2769)
('from', 2013)
('the', 1996)
('bank', 2924)
('vault', 11632)
(',', 1010)
('the', 1996)
('bank', 2924)
('robber', 27307)
('was', 2001)
('seen', 2464)
('fishing', 5645)
('on', 2006)
('the', 1996)
('mississippi', 5900)
('river', 2314)
('bank', 2924)
('.', 1012)
('[SEP]', 102)
Segment ID
BERT接受過句子對的訓練,期望使用1和0來區(qū)分這兩個句子。也就是說侦鹏,對于“tokenized_text”中的每個標token,我們必須指定它屬于哪個句子:句子0(一系列0)或句子1(一系列1)。出于我們的目的驶赏,單句輸入只需要一系列1嘱蛋,因此我們將為輸入句中的每個token創(chuàng)建一個1的向量龄恋。如果要處理兩個句子,請將第一個句子中的每個單詞再加上'[SEP]'token分配為0扳肛,將第二個句子的所有token分配為1挖息。
segments_ids = [1] * len(tokenized_text)
print (segments_ids)
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
運行我們的示例
接下來沉迹,我們需要將數(shù)據(jù)轉(zhuǎn)換為torch張量并調(diào)用BERT模型。 BERT PyTorch接口要求數(shù)據(jù)在torch張量而不是Python列表中,因此我們在此處轉(zhuǎn)換列表-這并不會改變形狀或數(shù)據(jù)腋么。
model.eval()將我們的模型置于評估模式而不是訓練模式费变。 在這種情況下,評估模式關(guān)閉在訓練中使用的dropout正則化。
調(diào)用from_pretrained將從互聯(lián)網(wǎng)上獲取模型帮匾。當我們加載bert-base-uncased時肠缔,我們會看到在logging記錄中打印的模型的定義。該模型是一個12層的深度神經(jīng)網(wǎng)絡(luò)!解釋網(wǎng)絡(luò)層及其作用超出了本文的范圍,您現(xiàn)在可以跳過此輸出。
# Convert inputs to PyTorch tensors
tokens_tensor = torch.tensor([indexed_tokens])
segments_tensors = torch.tensor([segments_ids])
# Load pre-trained model (weights)
model = BertModel.from_pretrained('bert-base-uncased')
# Put the model in "evaluation" mode, meaning feed-forward operation.
model.eval()
output:
100%|██████████| 407873900/407873900 [00:06<00:00, 61266351.38B/s]
BertModel(
(embeddings): BertEmbeddings(
(word_embeddings): Embedding(30522, 768, padding_idx=0)
(position_embeddings): Embedding(512, 768)
(token_type_embeddings): Embedding(2, 768)
(LayerNorm): BertLayerNorm()
(dropout): Dropout(p=0.1)
)
(encoder): BertEncoder(
(layer): ModuleList(
...
省略output
...
(output): BertOutput(
(dense): Linear(in_features=3072, out_features=768, bias=True)
(LayerNorm): BertLayerNorm()
(dropout): Dropout(p=0.1)
)
)
)
)
(pooler): BertPooler(
(dense): Linear(in_features=768, out_features=768, bias=True)
(activation): Tanh()
)
)
接下來灌砖,讓我們獲取網(wǎng)絡(luò)的隱藏狀態(tài)。
Torch.no_Grad關(guān)閉梯度計算库继,節(jié)省內(nèi)存酱虎,并加快計算速度(我們不需要梯度或反向傳播聊记,因為我們只是向前傳播)
# Predict hidden states features for each layer
with torch.no_grad():
encoded_layers, _ = model(tokens_tensor, segments_tensors)
輸出
存儲在對象encoded_layers中的該模型的完整隱藏狀態(tài)有點令人眼花繚亂杰捂。 此對象具有四個維度谷暮,順序如下:
- The layer number (12 layers)
- The batch number (1 sentence)
- The word / token number (22 tokens in our sentence)
- The hidden unit / feature number (768 features)
這是202,752個唯一值湿弦,只是為了代表我們的一句話班利!第二個維度,即批量大小,在一次向模型提交多個句子時使用; 但是,在這里鼓黔,我們只有一個例句。
print ("Number of layers:", len(encoded_layers))
layer_i = 0
print ("Number of batches:", len(encoded_layers[layer_i]))
batch_i = 0
print ("Number of tokens:", len(encoded_layers[layer_i][batch_i]))
token_i = 0
print ("Number of hidden units:", len(encoded_layers[layer_i][batch_i][token_i]))
Number of layers: 12
Number of batches: 1
Number of tokens: 22
Number of hidden units: 768
讓我們快速瀏覽一下給定網(wǎng)絡(luò)層和token的值范圍灶似。
您會發(fā)現(xiàn),對于所有層和token砌创,范圍都相當相似,其中大部分值介于-2、2之間竟纳,少量值在-10左右。
# For the 5th token in our sentence, select its feature values from layer 5.
token_i = 5
layer_i = 5
vec = encoded_layers[layer_i][batch_i][token_i]
# Plot the values as a histogram to show their distribution.
plt.figure(figsize=(10,10))
plt.hist(vec, bins=200)
plt.show()
按層對值進行分組對于模型來說是有意義的际歼,但是為了我們的目的,我們希望它按token進行分組。
下面的代碼只是調(diào)整值的形狀输枯,以便我們可以將其保存在表單中:
[# tokens, # layers, # features]
# Convert the hidden state embeddings into single token vectors
# Holds the list of 12 layer embeddings for each token
# Will have the shape: [# tokens, # layers, # features]
token_embeddings = []
# For each token in the sentence...
for token_i in range(len(tokenized_text)):
# Holds 12 layers of hidden states for each token
hidden_layers = []
# For each of the 12 layers...
for layer_i in range(len(encoded_layers)):
# Lookup the vector for `token_i` in `layer_i`
vec = encoded_layers[layer_i][batch_i][token_i]
hidden_layers.append(vec)
token_embeddings.append(hidden_layers)
# Sanity check the dimensions:
print ("Number of tokens in sequence:", len(token_embeddings))
print ("Number of layers per token:", len(token_embeddings[0]))
Number of tokens in sequence: 22
Number of layers per token: 12
從隱藏狀態(tài)創(chuàng)建單詞和句子向量
現(xiàn)在,我們?nèi)绾翁幚磉@些隱藏的狀態(tài)缎讼? 我們希望為每個token獲取單獨的向量,或者可能是整個句子的單個向量表示,但是對于我們輸入的每個token茅姜,我們有12個單獨的向量锄开,每個向量的長度為768
為了獲得單個向量,我們需要組合一些層向量......但哪個層或?qū)咏M合提供了最佳表示计维? BERT作者通過將不同的向量組合作為輸入特征輸送到用于命名實體識別任務(wù)的BiLSTM并觀察得到的F1分數(shù)來測試這一點嗅蔬。
雖然最后四層的連接在這個特定任務(wù)上產(chǎn)生了最好的結(jié)果艺蝴,但許多其他方法緊隨其后盒延,一般來說胯盯,建議為您的特定應(yīng)用測試不同的版本:結(jié)果可能會有所不同。通過注意BERT的不同層編碼非常不同類型的信息來部分地證明這一點泞边,因此適當?shù)某鼗呗詫⒏鶕?jù)應(yīng)用而改變烟具,因為不同的層編碼不同類型的信息。
注意到bert的不同層編碼的信息種類非常不同,因此適當?shù)某鼗呗詫⒏鶕?jù)應(yīng)用而改變,因為不同層編碼的信息種類不同。韓曉對這一主題的討論是相關(guān)的,他們的實驗也是如此翩瓜,他們的實驗研究了在新聞數(shù)據(jù)集上訓練的不同層次的PCA可視化坟桅,并從不同的池化策略中觀察四類分離的差異:
結(jié)果是建蹄,正確的池化策略(平均值,最大值,連接數(shù)等)和使用的層(最后四個焦人,全部矿辽,最后一層等)取決于應(yīng)用。 這種對池化策略的討論既適用于整個句子嵌入批狐,也適用于類似ELMO的token嵌入华弓。
詞向量
為了給您提供一些示例慌洪,讓我們使用最后四層的串聯(lián)和求和來創(chuàng)建單詞向量:
concatenated_last_4_layers = [torch.cat((layer[-1], layer[-2], layer[-3], layer[-4]), 0) for layer in token_embeddings] # [number_of_tokens, 3072]
summed_last_4_layers = [torch.sum(torch.stack(layer)[-4:], 0) for layer in token_embeddings] # [number_of_tokens, 768]
句向量
為了獲得整個句子的單個向量欧引,我們有多個依賴于應(yīng)用的策略,但是一個簡單的方法是平均每個token的倒數(shù)第二層,產(chǎn)生一個768長度向量谭企。
sentence_embedding = torch.mean(encoded_layers[11], 1)
print ("Our final sentence embedding vector of shape:"), sentence_embedding[0].shape[0]
Our final sentence embedding vector of shape:
(None, 768)
確認上下文相關(guān)的向量
為了確認這些向量的值實際上是上下文相關(guān)的盹廷,讓我們來看下一句話的輸出(如果您想嘗試這個方法剥汤,您必須從頂部單獨運行這個例子暮芭,用下面的句子替換原始句子):
print (text)
After stealing money from the bank vault, the bank robber was seen fishing on the Mississippi river bank.
for i,x in enumerate(tokenized_text):
print (i,x)
0 [CLS]
1 after
2 stealing
3 money
4 from
5 the
6 bank
7 vault
8 ,
9 the
10 bank
11 robber
12 was
13 seen
14 fishing
15 on
16 the
17 mississippi
18 river
19 bank
20 .
21 [SEP]
print ("First fifteen values of 'bank' as in 'bank robber':")
summed_last_4_layers[10][:15]
First fifteen values of 'bank' as in 'bank robber':
tensor([ 1.1868, -1.5298, -1.3770, 1.0648, 3.1446, 1.4003, -4.2407, 1.3946,-0.1170, -1.8777, 0.1091, -0.3862, 0.6744, 2.1924, -4.5306])
print ("First fifteen values of 'bank' as in 'bank vault':")
summed_last_4_layers[6][:15]
First fifteen values of 'bank' as in 'bank vault':
tensor([ 2.1319, -2.1413, -1.6260, 0.8638, 3.3173, 0.1796, -4.4853, 3.1215, -0.9740, -3.1780, 0.1046, -1.5481, 0.4758, 1.1703, -4.4859])
print ("First fifteen values of 'bank' as in 'river bank':")
summed_last_4_layers[19][:15]
First fifteen values of 'bank' as in 'river bank':
tensor([ 1.1295, -1.4725, -0.7296, -0.0901, 2.4970, 0.5330, 0.9742, 5.1834, -1.0692, -1.5941, 1.9261, 0.7119, -0.9809, 1.2127, -2.9812])
正如我們所看到的,這些都是不同的向量。雖然“bank”這個詞是相同的峭范,但在我們句子的每一個案例中,它都有不同的含義,有時意義卻截然不同。在這句話中我們有三種不同的“銀行”用法期犬,其中兩種應(yīng)該幾乎相同鲤妥。 讓我們檢查余弦相似度贡耽,看看是否是這種情況:
from sklearn.metrics.pairwise import cosine_similarity
# Compare "bank" as in "bank robber" to "bank" as in "river bank"
different_bank = cosine_similarity(summed_last_4_layers[10].reshape(1,-1), summed_last_4_layers[19].reshape(1,-1))[0][0]
# Compare "bank" as in "bank robber" to "bank" as in "bank vault"
same_bank = cosine_similarity(summed_last_4_layers[10].reshape(1,-1), summed_last_4_layers[6].reshape(1,-1))[0][0]
print ("Similarity of 'bank' as in 'bank robber' to 'bank' as in 'bank vault':", same_bank)
Similarity of 'bank' as in 'bank robber' to 'bank' as in 'bank vault': 0.94567525
print ("Similarity of 'bank' as in 'bank robber' to 'bank' as in 'river bank':", different_bank)
Similarity of 'bank' as in 'bank robber' to 'bank' as in 'river bank': 0.6797334
其他:特殊token,OOV詞和相似性指標
特殊的tokens
應(yīng)該注意的是,盡管“[CLS]”充當分類任務(wù)的“聚合表示”是尖,但這不是高質(zhì)量句子嵌入向量的最佳選擇兜辞。 根據(jù)BERT作者Jacob Devlin的說法:
“我不確定這些向量是什么凶硅,因為BERT不會生成有意義的句子向量韩脑。 似乎這是在對單詞標記進行平均匯總以獲得句子向量首量,但我們從未建議這將產(chǎn)生有意義的句子表示拣宏∷寻桑“
(但是,如果對模型進行了微調(diào),則[CLS]token確實有意義,其中此token的最后一個隱藏層用作序列分類的“句子向量”张咳。)
詞匯表外的詞
對于由多個句子和字符級embeddings組成的詞匯外單詞葱峡,關(guān)于如何最好地恢復(fù)此embeddings军援,還有一個更進一步的問題漓糙。 對embeddings進行平均是最直接的解決方案(在類似的embeddings模型中依賴的一個醉鳖,具有子字詞匯表北发,如fasttext)瞭恰,但是對于子詞embeddings的和以及簡單地采用最后一個token的embeddings(請記住向量是上下文敏感的)是可接受的替代策略颜启。
相似度量
值得注意的是,單詞級的相似性比較不適合于bert embeddings形葬,因為這些嵌入是上下文相關(guān)的冻辩,這意味著單詞向量會根據(jù)它出現(xiàn)的句子而變化咙咽。這就允許了一些奇妙的事情蜡豹,比如說,你的表示編碼河流“bank”而不是一個金融機構(gòu)“bank”,使得直接的字詞相似度比較不那么有價值。但是伸刃,對于句子embeddings的相似性比較仍然有效,例如,可以針對其他句子的數(shù)據(jù)集查詢單個句子,以便找到最相似的句子。根據(jù)所使用的相似度度量铺浇,由于許多相似度度量對向量空間(例如,等量加權(quán)維)進行假設(shè),而這些向量空間不適用于768維,因此產(chǎn)生的相似度值將比相似度輸出的相對排名信息更少。
實現(xiàn)
您可以使用此notebook中的代碼作為您自己的應(yīng)用程序的基礎(chǔ),從文本中提取BERT功能。 然而砌些,官方的tensorflow和備受好評的pytorch實現(xiàn)已經(jīng)存在,為您做到這一點。 此外,bert-as-a-service是專為高性能運行此任務(wù)而設(shè)計的出色工具难裆,也是我推薦用于生產(chǎn)應(yīng)用程序的工具。 作者在該工具的實現(xiàn)中非常謹慎,并提供了優(yōu)秀的文檔(其中一些用于幫助創(chuàng)建本教程),以幫助用戶了解用戶面臨的更細微的細節(jié)逛球,如資源管理和池化策略祟身。