NLP - 如何使用 BERT 實現相關文章功能

當你的部落格文章越來越多時,你可能會想要增加一個相關文章的功能,以便讀者可以更方便地閱讀你的其他文章。

然而,你可能會發現每當新增一篇新文章,就需要建立新的相關性關係,這樣一來,你就得花費大量時間整理文章之間的相關性。

幸運的是,我們可以利用自然語言處理(NLP)技術來實現相關文章功能。

本文將介紹如何使用 BERT 模型,自動生成每篇文章的相關文章列表,使你可以更專注於撰寫文章本身。

BERT

到底什麼是 BERT,以下是維基百科對於 BERT 的定義:

基於變換器的雙向編碼器表示技術(英語:Bidirectional Encoder Representations from Transformers,BERT)是用於自然語言處理(NLP)的預訓練技術,由Google提出。
2018年,雅各布·德夫林和同事建立並發布了 BERT。Google正在利用BERT來更好地理解使用者搜尋語句的語意。
2020年的一項文獻調查得出結論:”在一年多一點的時間裡,BERT已經成為NLP實驗中無處不在的基線”,算上分析和改進模型的研究出版物超過150篇。

- BERT - 維基百科,自由的百科全書

簡單來說 BERT 具有許多自然語言方面的應用。下面列舉一下常見用途:

  • 文字分類:BERT可以用於文字分類任務,例如情感分析、垃圾郵件偵測、主題分類等。它可以從給定的文本中學習特徵並預測文本的類別。
  • 命名實體識別(NER):BERT能夠辨識文本中的命名實體,例如人名、地名、組織名等。這對於資訊提取、問答系統等任務非常有用。
  • 問答系統:BERT可用於建立問答系統,能夠根據問題生成相關的答案。它能理解問題的語境並根據語意進行準確回答。
  • 文本生成:BERT能夠生成連貫、具有上下文意義的文本。這對於自動摘要、對話生成、文章生成等應用非常有價值。
  • 語言翻譯:BERT可以用於機器翻譯任務,將一種語言的文本轉換為另一種語言。它能夠學習不同語言之間的關係並生成準確的翻譯結果。
  • 文本相似度計算:BERT能夠比較兩段文本之間的相似度,這對於資訊檢索、相似問題檢測等任務非常有用。

而其中,文本相似度計算就是我們今天要介紹的主題。

安裝

首先我們來安裝相關套件 sentence-transformers

1
pip install -U sentence-transformers

這預設會安裝 CPU 版本,如果你想了解更多詳細的安裝方式,可以參考 sentence-transformers - PyPI

預先訓練模型

好,現在我們來召喚模型。

1
2
3
from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')

其中,paraphrase-multilingual-MiniLM-L12-v2 是一個已經預先訓練好的通用 BERT 模型,支援 50 種以上的語言。
如果是簡單的用途,用這個就可以了,接下來的例子也會以這個模型為範例。

如果你想要了解模型特性或其他更多模型,可以參考 Pretrained Models

計算向量

接下來我們要把文章輸入到模型裡來計算每篇文章的向量。

1
2
3
4
5
6
7
8
sentences = [
'今天我想去吃咖哩飯,你要一起嗎?',
'今天我想去吃咖哩飯,要不要找大家一起?',
'明天我出門吃飯,已經找了很多人。',
'明天我自己一個人出去玩。'
]

embedding = model.encode(sentences, convert_to_tensor=False)

現在我們已經得到每篇文章的向量,接下來可以用時下最流行的 cosine similarity 演算法來計算任兩篇文章之間的相似度。
最後再排序一下找出最相似的兩個文章(句子)。

求知若渴的你,可以在這裡找到支援的所有演算法 util
而計算的範例程式碼,你也可以在 BERT 的官網找到 Sentence Embeddings

1
2
3
4
5
6
7
8
9
10
11
12
13
cosine_scores = util.cos_sim(embedding, embedding)

d = {}
for i, v1 in enumerate(sentences):
for j, v2 in enumerate(sentences):
if i >= j:
continue
d[v1 + ' vs. ' + v2] = cosine_scores[i][j].item()

# sort by score
d_sorted = dict(sorted(d.items(), key=lambda x: x[1], reverse=True))

print(json.dumps(d_sorted, indent=4, ensure_ascii=False))

輸出結果如下

1
2
3
4
5
6
7
8
{
"今天我想去吃咖哩飯,你要一起嗎? vs. 今天我想去吃咖哩飯,要不要找大家一起?": 0.9814210534095764,
"今天我想去吃咖哩飯,要不要找大家一起? vs. 明天我出門吃飯,已經找了很多人。": 0.718012273311615,
"今天我想去吃咖哩飯,你要一起嗎? vs. 明天我出門吃飯,已經找了很多人。": 0.6594694256782532,
"明天我出門吃飯,已經找了很多人。 vs. 明天我自己一個人出去玩。": 0.5195528864860535,
"今天我想去吃咖哩飯,要不要找大家一起? vs. 明天我自己一個人出去玩。": 0.41144296526908875,
"今天我想去吃咖哩飯,你要一起嗎? vs. 明天我自己一個人出去玩。": 0.3922367990016937
}

看起來還蠻符合直覺的(自己講

以下為剛剛示範的完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import json
from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')

sentences = [
'今天我想去吃咖哩飯,你要一起嗎?',
'今天我想去吃咖哩飯,要不要找大家一起?',
'明天我出門吃飯,已經找了很多人。',
'明天我自己一個人出去玩。'
]

embedding = model.encode(sentences, convert_to_tensor=False)

cosine_scores = util.cos_sim(embedding, embedding)

d = {}
for i, v1 in enumerate(sentences):
for j, v2 in enumerate(sentences):
if i >= j:
continue
d[v1 + ' vs. ' + v2] = cosine_scores[i][j].item()

# sort by score
d_sorted = dict(sorted(d.items(), key=lambda x: x[1], reverse=True))

print(json.dumps(d_sorted, indent=4, ensure_ascii=False))

實際成果

雖說因為文章量還不夠多,有些文章老實說沒辦法找出那麼有相關的文章。

但大家可以看看這篇文章 如何使用 One Time Password 加強 Ubuntu 伺服器 SSH 安全性 的相關文章,就成功整理出安全相關的文章擺在一起。

LeetCode 相關的文章,也可以成功找出類似的題目。
例如 Paint House 裡面就成功計算出 Paint House II 為相關文章。

我實際用來計算相關文章的程式碼在這,有興趣的朋友可以參考看看。
https://gist.github.com/PttCodingMan/7cdb47d857c0cad987ade593ae86a765

並且整合到 GitHub Action,每次發布文章時,就會自動計算相關文章,放在文章的最下面。
https://gist.github.com/PttCodingMan/e303178da7886baa6f2e08f2ae979658

祝各位都可以簡單上手 BERT!

相關文章