日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

BERT Word Embeddings Tutorial

發布時間:2025/3/21 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 BERT Word Embeddings Tutorial 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文譯自?BERT Word Emebddings Tutorial,我將其中部分內容進行了精簡。轉載請注明出處

1. Loading Pre-Trained BERT

通過 Hugging Face 安裝 BERT 的 PyTorch 接口,該庫還包含其它預訓練語言模型的接口,如 OpenAI 的 GPT 和 GPT-2

如果您在 Google Colab 上運行此代碼,每次重新連接時都必須安裝此庫

  • !pip install transformers

BERT 是由 Google 發布的預訓練模型,該模型使用 Wikipedia 和?Book Corpus?數據進行訓練(Book Corpus 是一個包含不同類型的 10000 + 本書的數據集)。Google 發布了一系列 BERT 的變體,但我們在這里使用的是兩種可用尺寸("base" 和 "large")中較小的一種,并且我們設置忽略單詞大小寫

transformers?提供了許多應用于不同任務的 BERT 模型。在這里,我們使用最基本的?BertModel,這個接口的輸出不針對任何特定任務,因此用它提取 embeddings 是個不錯的選擇

現在讓我們導入 PyTorch,預訓練 BERT 模型以及 BERT tokenizer

  • import torch

  • from transformers import BertTokenizer, BertModel

  • ?
  • # 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')

2. Input Formatting

由于 BERT 是一個預訓練模型,需要輸入特定格式的數據,因此我們需要:

  • A?special token,?[SEP],?to mark the end of a sentence, or the separation between two sentences
  • A?special token,?[CLS],?at the beginning of our text. This token is used for classification tasks, but BERT expects it no matter what your application is.
  • Tokens that conform with the fixed vocabulary used in BERT
  • The?Token IDs?for the tokens, 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
  • 幸運的是,使用?tokenizer.encode_plus?這個函數可以幫我們處理好一切。但是,由于這只是使用 BERT 的介紹,因此我們將主要以手動方式執行這些步驟

    有關?tokenizer.encode_plus?這個函數的使用示例,可以這篇文章

    2.1 Special Tokens

    BERT 可以將一個或兩個句子作為輸入。如果是兩個句子,則使用?[SEP]?將它們分隔,并且?[CLS]?標記總是出現在文本的開頭;如果是一個句子,也始終需要兩個標記,此時?[SEP]?表示句子的結束。舉個例子

    2 個句子的輸入:

    [CLS] The man went to the store. [SEP] He bought a gallon of milk.

    1 個句子的輸入:

    [CLS] The man went to the store. [SEP]

    2.2 Tokenization

    BERT 提供了?tokenize?方法,下面我們看看它是如何處理句子的

    • text = "Here is the sentence I want embeddings for."

    • marked_text = "[CLS] " + text + " [SEP]"

    • ?
    • # Tokenize our sentence with the BERT tokenizer.

    • tokenized_text = tokenizer.tokenize(marked_text)

    • ?
    • # Print out the tokens.

    • print (tokenized_text)

    • # ['[CLS]', 'here', 'is', 'the', 'sentence', 'i', 'want', 'em', '##bed', '##ding', '##s', 'for', '.', '[SEP]']

    注意 "embeddings" 這個詞是如何表示的:['em', '##bed', '##ding', '##s']

    原始單詞已被拆分為較小的子詞和字符。這些子詞中前面兩個##哈希符號表示該子詞或字符是較大字的一部分。因此,例如 '##bed' 和 'bed' 這兩個 token 不相同;第一個用于子詞 "bed" 出現在較大詞中時,第二個是獨立的 token

    為什么會這樣?因為 BERT 的 tokenizer 是使用 WordPiece 模型創建的。這個模型貪婪地創建了一個固定大小的詞匯表,其中包含了最適合我們語言的固定數量的字符、子詞和單詞。由于我們 BERT 模型的 tokenizer 限制詞匯量為 30000,因此 WordPiece 模型生成的詞匯表包含所有英文字符以及該模型所訓練英語預料庫中找到的約 30000 個最常見的單詞和子詞。該詞匯表包含四類東西:

  • 整個詞
  • 出現在單詞開頭或單獨出現的子詞("embddings" 中的 "em" 與 "go get em" 中的 "em" 向量相同)
  • 不在單詞開頭的子詞,前面會添加上 "##"
  • 單個字符
  • 具體來說,tokenzier 首先檢查整個單詞是否在詞匯表中,如果不在,它會嘗試將單詞分解為詞匯表中最大可能的子詞,如果子詞也沒有,它就會將整個單詞分解為單個字符。所以我們至少可以將一個單詞分解為單子字符的集合。基于此,不在詞匯表中的單詞不會分配給 "UNK" 這種萬能的標記,而是分解為子詞和字符標記

    因此,即使 "embeddings" 這個詞不在詞匯表中,我們也不會將這個詞視為未知詞匯,而是將其分為子詞 tokens ['em', '##bed', '##ding', '##s'],這將保留單詞的一些上下文含義。我們甚至可以平均這些子詞的嵌入向量以生成原始單詞的近似向量。有關 WordPeice 的更多信息,請參考原論文

    下面是我們詞匯表中的一些示例

    • 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']

    將文本分解為標記后,我們必須將句子轉換為詞匯索引列表。從這開始,我們將使用下面的例句,其中兩個句子都包含 "bank" 這個詞,且它們的含義不同

    • # Define a new example sentence with multiple meanings of the word "bank"

    • text = "After stealing money from the bank vault, the bank robber was seen " \

    • "fishing on the Mississippi river bank."

    • ?
    • # Add the special tokens.

    • marked_text = "[CLS] " + text + " [SEP]"

    • ?
    • # Split the sentence into tokens.

    • tokenized_text = tokenizer.tokenize(marked_text)

    • ?
    • # Map the token strings to their vocabulary indeces.

    • indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)

    • ?
    • # Display the words with their indeces.

    • for tup in zip(tokenized_text, indexed_tokens):

    • print('{:<12} {:>6,}'.format(tup[0], tup[1]))

    • [CLS] 101

    • after 2,044

    • stealing 11,065

    • money 2,769

    • from 2,013

    • the 1,996

    • bank 2,924

    • vault 11,632

    • , 1,010

    • the 1,996

    • bank 2,924

    • robber 27,307

    • was 2,001

    • seen 2,464

    • fishing 5,645

    • on 2,006

    • the 1,996

    • mississippi 5,900

    • river 2,314

    • bank 2,924

    • . 1,012

    • [SEP] 102

    2.3 Segment ID

    BERT 希望用 0 和 1 區分兩個句子。也就是說,對于?tokenized_text?中的每個 token,我們必須指明它屬于哪個句子。如果是單句,只需要輸入一系列 1;如果是兩個句子,請將第一個句子中的每個單詞(包括 [SEP])指定為 0,第二個句子指定為 1

    • # Mark each of the 22 tokens as belonging to sentence "1".

    • segments_ids = [1] * len(tokenized_text)

    3. Extracting Embeddings

    3.1 Running BERT on our text

    接下來,我們需要將數據轉換為 PyTorch tensor 類型

    • # Convert inputs to PyTorch tensors

    • tokens_tensor = torch.tensor([indexed_tokens])

    • segments_tensors = torch.tensor([segments_ids])

    調用?from_pretrained?函數將從互聯網上獲取模型。當我們加載?bert-base-uncased?時,我們會在 logging 記錄中看到模型的定義。該模型是一個具有 12 層的深度神經網絡,解釋每層的功能不在本文的范圍內,您可以查看我博客之前的內容來學習相關信息

    model.eval()?會使得我們的模型處于測試模式,而不是訓練模式。在測試模式下,模型將會關閉 dropout regularization

    • # Load pre-trained model (weights)

    • model = BertModel.from_pretrained('bert-base-uncased',

    • output_hidden_states = True, # Whether the model returns all hidden-states.

    • )

    • ?
    • # Put the model in "evaluation" mode, meaning feed-forward operation.

    • model.eval()

    接下來,讓我們把示例文本傳入模型,并獲取網絡的隱藏狀態

    torch.no_grad()?告訴 PyTorch 在前向傳播的過程中不構造計算圖(因為我們不會在這里反向傳播),這有助于減少內存消耗并加快運行速度

    • # Run the text through BERT, and collect all of the hidden states produced

    • # from all 12 layers.

    • with torch.no_grad():

    • ?
    • outputs = model(tokens_tensor, segments_tensors)

    • ?
    • # Evaluating the model will return a different number of objects based on

    • # how it's configured in the `from_pretrained` call earlier. In this case,

    • # becase we set `output_hidden_states = True`, the third item will be the

    • # hidden states from all layers. See the documentation for more details:

    • # https://huggingface.co/transformers/model_doc/bert.html#bertmodel

    • hidden_states = outputs[2]

    3.2 Understanding the Output

    hidden_states?包含的信息有點復雜,該變量有四個維度,分別是:

  • The Layer number(13 layers)
  • The batch number(1 sentence)
  • The word / token number(22 tokens in our sentence)
  • The hidden unit / feature number(768 features)
  • ちょっと待って,13 層?前面不是說 BERT 只有 12 層嗎?因為最前面的一層是 Word Embedding 層,剩下的是 12 個 Encoder Layer

    第二個維度(batch size)是一次向模型提交多個句子時使用的;不過,在這里我們只有一個句子

    • print ("Number of layers:", len(hidden_states), " (initial embeddings + 12 BERT layers)")

    • ?
    • layer_i = 0

    • print ("Number of batches:", len(hidden_states[layer_i]))

    • ?
    • batch_i = 0

    • print ("Number of tokens:", len(hidden_states[layer_i][batch_i]))

    • ?
    • token_i = 0

    • print ("Number of hidden units:", len(hidden_states[layer_i][batch_i][token_i]))

    • Number of layers: 13 (initial embeddings + 12 BERT layers)

    • Number of batches: 1

    • Number of tokens: 22

    • Number of hidden units: 768

    通過快速瀏覽指定 token 和網絡層的數值范圍,您會發現其中大部分值介于 [-2, 2],少數在 - 12 附近

    • # For the 5th token in our sentence, select its feature values from layer 5.

    • token_i = 5

    • layer_i = 5

    • vec = hidden_states[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 進行分組

    當前的維度:[layers, batchs, tokens, features]

    期望的維度:[tokens, layers, features]

    幸運的是,PyTorch 的?permute?函數可以輕松的重新排列維度。但是目前?hidden_states?第一個維度是 list,所以我們要先結合各層,使其成為一個 tensor

    • # Concatenate the tensors for all layers. We use `stack` here to

    • # create a new dimension in the tensor.

    • token_embeddings = torch.stack(hidden_states, dim=0)

    • ?
    • token_embeddings.size()

    • # torch.Size([13, 1, 22, 768])

    接著我們消掉 "batch" 維度,因為我們不需要它

    • # Remove dimension 1, the "batches".

    • token_embeddings = token_embeddings.squeeze(dim=1)

    • ?
    • token_embeddings.size()

    • # torch.Size([13, 22, 768])

    最后,我們使用?permute?函數來交換維度

    • # Swap dimensions 0 and 1.

    • token_embeddings = token_embeddings.permute(1,0,2)

    • ?
    • token_embeddings.size()

    • # torch.Size([22, 13, 768])

    3.3 Creating word and sentence vectors from hidden states

    我們希望為每個詞獲取單獨的向量,或者為整個句子獲取單獨的向量。但是對于輸入的每個詞,我們有 13 個向量,每個向量的長度為 768。為了獲得單個向量,我們需要將一些層的向量組合起來。但是,哪個層或組合哪些層比較好?

    Word Vectors

    我們用兩種方式創建詞向量。第一種方式是拼接最后四層,則每個單詞的向量長度為?4*768=3072

    • # Stores the token vectors, with shape [22 x 3,072]

    • token_vecs_cat = []

    • ?
    • # `token_embeddings` is a [22 x 12 x 768] tensor.

    • ?
    • # For each token in the sentence...

    • for token in token_embeddings:

    • ?
    • # `token` is a [12 x 768] tensor

    • ?
    • # Concatenate the vectors (that is, append them together) from

    • # the last four layers.

    • # Each layer vector is 768 values, so `cat_vec` is length 3072.

    • cat_vec = torch.cat((token[-1], token[-2], token[-3], token[-4]), dim=0)

    • ?
    • # Use `cat_vec` to represent `token`.

    • token_vecs_cat.append(cat_vec)

    • ?
    • print ('Shape is: %d x %d' % (len(token_vecs_cat), len(token_vecs_cat[0])))

    • # Shape is: 22 x 3072

    第二種方式是將最后四層相加

    • # Stores the token vectors, with shape [22 x 768]

    • token_vecs_sum = []

    • ?
    • # `token_embeddings` is a [22 x 12 x 768] tensor.

    • ?
    • # For each token in the sentence...

    • for token in token_embeddings:

    • ?
    • # `token` is a [12 x 768] tensor

    • ?
    • # Sum the vectors from the last four layers.

    • sum_vec = torch.sum(token[-4:], dim=0)

    • ?
    • # Use `sum_vec` to represent `token`.

    • token_vecs_sum.append(sum_vec)

    • ?
    • print ('Shape is: %d x %d' % (len(token_vecs_sum), len(token_vecs_sum[0])))

    • # Shape is: 22 x 768

    Sentence Vectors

    有很多種策略可以獲得一個句子的單個向量表示,其中一種簡單的方法是將倒數第 2 層所有 token 的向量求平均

    • # `hidden_states` has shape [13 x 1 x 22 x 768]

    • ?
    • # `token_vecs` is a tensor with shape [22 x 768]

    • token_vecs = hidden_states[-2][0]

    • ?
    • # Calculate the average of all 22 token vectors.

    • sentence_embedding = torch.mean(token_vecs, dim=0)

    • ?
    • print("Our final sentence embedding vector of shape:", sentence_embedding.size())

    • # Our final sentence embedding vector of shape: torch.Size([768])

    3.4 Confirming contextually dependent vectors

    為了確認這些向量的值是上下文相關的,我們可以檢查一下例句中 "bank" 這個詞的向量

    “After stealing money from the?bank?vault, the?bank?robber was seen fishing on the Mississippi river?bank.”

    • for i, token_str in enumerate(tokenized_text):

    • print(i, token_str)

    • 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 5 vector values for each instance of "bank".')

    • print('')

    • print("bank vault ", str(token_vecs_sum[6][:5]))

    • print("bank robber ", str(token_vecs_sum[10][:5]))

    • print("river bank ", str(token_vecs_sum[19][:5]))

    • First 5 vector values for each instance of "bank".

    • ?
    • bank vault tensor([ 3.3596, -2.9805, -1.5421, 0.7065, ...])

    • bank robber tensor([ 2.7359, -2.5577, -1.3094, 0.6797, ...])

    • river bank tensor([ 1.5266, -0.8895, -0.5152, -0.9298, ...])

    很明顯值不同,但是通過計算向量之間的余弦相似度可以更精確的進行比較

    • from scipy.spatial.distance import cosine

    • ?
    • # Calculate the cosine similarity between the word bank

    • # in "bank robber" vs "bank vault" (same meaning).

    • same_bank = 1 - cosine(token_vecs_sum[10], token_vecs_sum[6])

    • ?
    • # Calculate the cosine similarity between the word bank

    • # in "bank robber" vs "river bank" (different meanings).

    • diff_bank = 1 - cosine(token_vecs_sum[10], token_vecs_sum[19])

    • ?
    • print('Vector similarity for *similar* meanings: %.2f' % same_bank) # 0.94

    • print('Vector similarity for *different* meanings: %.2f' % diff_bank) # 0.69

    3.5 Pooling Strategy & Layer Choice

    BERT Authors

    BERT 作者通過將不同的向量組合作為輸入特征提供給 NER 任務,并觀察所得的 F1 分數

    雖然最后四層拼接在此特定任務上產生了最佳結果,但許多其他方法效果也不差,通常建議針對特定應用測試不同版本,結果可能會有所不同

    Han Xiao's BERT-as-service

    肖涵在 Github 上創建了一個名為?bert-as-service?的開源項目,該項目旨在使用 BERT 為您的文本創建單詞嵌入。他嘗試了各種方法來組合這些嵌入,并在項目的?FAQ?頁面上分享了一些結論和基本原理

    肖涵的觀點認為:

  • 第一層是嵌入層,由于它沒有上下文信息,因此同一個詞在不同語境下的向量是相同的
  • 隨著進入網絡的更深層次,單詞嵌入從每一層中獲得了越來越多的上下文信息
  • 但是,當您接近最后一層時,詞嵌入將開始獲取 BERT 特定預訓練任務的信息(MLM 和 NSP)
  • 倒數第二層的詞嵌入比較合理
  • 總結

    以上是生活随笔為你收集整理的BERT Word Embeddings Tutorial的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。