はじめに
「the cat perched on the mat」と「the cat sat on the mat」のような類義語による言い換えを見つける方法をまとめました。
WordNetを用いる方法
調べていると、人手で作った概念辞書であるwordnetを用いる方法が見つかりました。
次のように類義語を見つけることができます。
import nltk
nltk.download('wordnet')
from nltk.corpus import wordnet
def wordnet_sim_word(word):
synonyms = []
antonyms = []
for syn in wordnet.synsets(word):
for l in syn.lemmas():
if "_" not in l.name() and "-" not in l.name():
synonyms.append(l.name())
return list(set(synonyms))
word = 'have'
synonym = wordnet_sim_word(word)
print(synonym)
# 出力結果
# ['let', 'birth', 'possess', 'stimulate', 'have', 'ingest', 'deliver', 'hold', 'throw', 'accept', 'give', 'bear', 'suffer', 'make', 'get', 'experience', 'cause', 'induce', 'feature', 'own', 'sustain', 'consume', 'receive', 'take']
しかしこの方法では、得られる複数の類義語から最も適切な類義語を選択することが難しいという問題がありました。
WordNet独自の類似度計算法があるようですが十分ではなく、文脈にあうような類義語を得るためには人手でルールを設定する必要があるようでした。
BERTを用いる方法
そこで、文脈を加味するといえばBERTだろうと思い調べたところ、以下の論文の方法で類義語を見つけられることがわかりました。BERTは穴埋め問題を解いているため、その仕組みを応用して周辺の単語の情報から類義語を見つけることができます。面白い点は、単に[MASK]のある1文を入力するのではなく、答えのある1文と[MASK]のある1文をセットで入力していることです。こうすることで、予測される中で最も確率の高い単語がもとの単語と一致するようになります。
実際に次のように類義語を求めることができます。
!pip install --quiet pytorch-transformers
!pip install --quiet pytorch-nlp
from collections import Counter
from tqdm import tqdm
import numpy as np
import torch
from pytorch_transformers import (
BertTokenizer,
BertForMaskedLM,
)
import matplotlib.pyplot as plt
# Build model
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForMaskedLM.from_pretrained('bert-base-uncased')
def get_synonym_with_bert(text, masked_idx):
text_length = len(text.split())
masked_idx = masked_idx + text_length + 2
segments_ids = [0] * (text_length + 2) + [1] * (text_length + 1)
text = " ".join(["[CLS]", text, "[SEP]", text, "[SEP]"])
# Tokenize a text
# tokenized_text = tokenizer.tokenize(text)
tokenized_text = text.split()
# Mask a complex token which should be substituted
complex_word = tokenized_text[masked_idx]
tokenized_text[masked_idx] = '[MASK]'
# Convert inputs to PyTorch tensors
tokens_ids = tokenizer.convert_tokens_to_ids(tokenized_text)
tokens_tensor = torch.tensor([tokens_ids])
segments_tensors = torch.tensor([segments_ids])
# Predict a masked token
model.eval()
if torch.cuda.is_available():
tokens_tensor = tokens_tensor.to('cuda')
segments_tensors = segments_tensors.to('cuda')
model.to('cuda')
with torch.no_grad():
outputs = model(tokens_tensor, token_type_ids=segments_tensors)
predictions = outputs[0]
# Output top 10 of candidates
topk_score, topk_index = torch.topk(predictions[0, masked_idx], 10)
topk_tokens = tokenizer.convert_ids_to_tokens(topk_index.tolist())
# Visualize output probabilities
plt.bar(topk_tokens, torch.softmax(topk_score, 0).tolist())
plt.xticks(rotation=70)
plt.ylabel('Probability')
plt.show()
return topk_tokens
text = "the cat perched on the mat"
for i in range(6):
get_synonym_with_bert(text=text , masked_idx = i)
「the cat perched on the mat」という文の各単語に対する類義語の予測確率の分布は以下のようになります。元の単語の確率が高くなっており、2番目以降から類義語を選択します。いい感じの類義語が取れていることがわかります。
おわりに
WordNetやBERTを用いて類義語を見つけました。
BERTによる類義語探しに関して次のことが気になりました。
・「cat」などの名詞は「dog」などの全く別のものを指してしまう
・文の最後はコンマや?だろうという先入観をモデルが持っている
論文ではBERTだけでなく、n-gram言語モデルとFastText、単語の出現頻度を加味して類義語を高精度で見つけられると言っています。
次はそれらも試してみようと思います。