1. sonoisa

    No comment

    sonoisa
Changes in body
Source | HTML | Preview
@@ -1,335 +1,339 @@
## 要点
* Sentence-BERT([論文](https://arxiv.org/abs/1908.10084)、[実装](https://github.com/UKPLab/sentence-transformers))の日本語モデルを作り、公開しました。
* この日本語モデルを使うことで、誰でも簡単に高品質な文ベクトルを作れるようになります。ご活用ください。
* Sentence-BERTとは、事前学習された[BERT](https://arxiv.org/abs/1810.04805)モデルと[Siamese Network](https://qiita.com/hkambe/items/f6405a21e5fcea96fcc1)を使い、高精度な文ベクトルを作る手法です。
* 英語版の精度評価([STSbenchmark](http://ixa2.si.ehu.es/stswiki/index.php/STSbenchmark)を用いたコサイン類似度と正解ラベルのspearmanの順位相関係数。1に近いほど良い)では、素朴な単語ベクトル(GloVe)の平均を用いる方法では0.58、今回作った規模相当のモデルでは0.85前後です([論文](https://arxiv.org/abs/1908.10084)のTable 2参照)。
* なお、素のBERTのCLSベクトルを用いると精度は0.17、BERTの埋め込みの平均を用いると0.46となり、単語ベクトルの平均の結果(0.58)よりも悪い結果になります([論文](https://arxiv.org/abs/1908.10084)のTable 1と2参照)。BERTの原論文にも書かれているとおり、これらを文ベクトルとして使うことは適切ではありません。
イメージ
<img width="700" alt="UMAP.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26062/ef61887a-f5a9-5410-c322-dda35c5c8ebc.png">
## はじめに
(日本語において)品質の高い、つまり、字面ではなく文脈を踏まえた文の意味が近いほど近いベクトルになるという性質を持ったベクトルを作る話です。
趣味や実務で意味が近い文を探すなどの目的で、文ベクトルを作るということをしている人は一定数いると思います。
しかし、単語ベクトルの平均でも悪くはないけれども、いまいちな場面が出てきて、気がつくとヒューリスティクスという名の迷路に迷い込んだりしていないでしょうか(特定の単語や品詞をストップワードにしてみたり、謎の重み付けを加えてみたり。え、根拠は?と)。
他にも、単語ベクトルの平均では多義語が扱えずに困ったり、文脈が考慮されないため「そこじゃない」という単語に着目した類似文検索になったり。
一方、Universal Sentence Encoderのような既存の深層ニューラルネットワークを用いた文ベクトルモデルの学習は計算コストが高すぎて、自分で学習させるのは辛かったりも。
こういう悩みを抱えている同志向けに、[論文 Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks](https://arxiv.org/abs/1908.10084)で提案された手法を使い高品質な日本語文ベクトルモデルを作り、公開します。
## 前提知識
- 現代的な自然言語処理の基礎知識
- BERTとは何か
- 文ベクトル(文の分散表現)とは何か
- Google Colaboratoryの使い方
- 文ベクトルを実際に使った経験がある
## 日本語文ベクトルモデルの使用例
Sentence-BERTの技術的な解説の前に、使い方から説明します。簡単です。
例では次の2つを行います。これらの例はGoogle Colaboratoryで実際に試せます。
1. 与えられたクエリ文に意味が近い文を検索する。
2. タイトル文の潜在意味空間をUMAPで可視化する。
* Colaboratory Notebook: https://colab.research.google.com/github/sonoisa/sentence-transformers/blob/master/sentence_transformers_ja.ipynb
※GPUが必要です。TensorBoardを使用するためChromeで開いてください。
### セットアップ
関連するライブラリをインストールし、日本語モデルをダウンロードします。
詳細な説明は不要でしょう。
Colaboratoryで簡単に試用する都合により、今回は日本語sentence-transformersをインストールする代わりに、そのソースコードのあるディレクトリに移動していますが、本番利用では、コメントアウトされているIn[2]のようにsetup.pyでインストールする方がいいでしょう。
```bash:In[1]
!git clone https://github.com/sonoisa/sentence-transformers
!cd sentence-transformers; pip install -r requirements.txt
```
```bash:In[2]
#!cd sentence-transformers; python setup.py install
```
```bash:In[3]
!wget -O sonobe-datasets-sentence-transformers-model.tar "https://www.floydhub.com/api/v1/resources/JLTtbaaK5dprnxoJtUbBbi?content=true&download=true&rename=sonobe-datasets-sentence-transformers-model-2"
!tar -xvf sonobe-datasets-sentence-transformers-model.tar
```
tarを解凍することで、モデルを格納したディレクトリtraining_bert_japaneseが作られます。
```python:In[4]
%cd sentence-transformers
```
### 日本語モデルの読み込み
日本語モデルの置かれたディレクトリへのパス /content/training_bert_japanese を引数にSentenceTransformerインスタンスを生成すれば、モデルの読み込み完了です。
```python:In[5]
%tensorflow_version 2.x
from sentence_transformers import SentenceTransformer
import numpy as np
model_path = "/content/training_bert_japanese"
model = SentenceTransformer(model_path, show_progress_bar=False)
```
### 文ベクトルの計算
文ベクトルを計算します。ただ、model.encode(文のリスト)を呼び出すだけです。
この例では文として、[別の記事](https://qiita.com/sonoisa/items/775ac4c7871ced6ed4c3)で公開している「いらすとや」さんのタイトル(を少し加工したもの)を用いることにします。
(本当はもう数倍は長い文章の方が効果を示すのにいいのですが、すぐ用意できなかったため、ひとまずタイトルで。もっと適切な文章を用意できたら説明を追加します)
```python:In[6]
# 出典: https://qiita.com/sonoisa/items/775ac4c7871ced6ed4c3 で公開されている「いらすとや」さんの画像タイトル抜粋(「のイラスト」「のマーク」「のキャラクター」という文言を削った)
sentences = ["お辞儀をしている男性会社員", "笑い袋", "テクニカルエバンジェリスト(女性)", "戦うAI", "笑う男性(5段階)",
...
"お金を見つめてニヤけている男性", "「ありがとう」と言っている人", "定年(女性)", "テクニカルエバンジェリスト(男性)", "スタンディングオベーション"]
```
```python:In[7]
sentence_vectors = model.encode(sentences)
```
文ベクトルの計算はこれだけです。
<!--
### 意味が近い文をクラスタリングする
```python:In[8]
from sklearn.cluster import KMeans
num_clusters = 8
clustering_model = KMeans(n_clusters=num_clusters)
clustering_model.fit(sentence_vectors)
cluster_assignment = clustering_model.labels_
clustered_sentences = [[] for i in range(num_clusters)]
for sentence_id, cluster_id in enumerate(cluster_assignment):
clustered_sentences[cluster_id].append(sentences[sentence_id])
for i, cluster in enumerate(clustered_sentences):
print("Cluster ", i+1)
print(cluster)
print("")
```
```python:Out[8]
Cluster 1
['テクニカルエバンジェリスト(女性)', 'いろいろな漫符', '雛人形「仕丁・三人上戸」', '定年(男性)', '運動会「徒競走・白組」', '技術書', '茅の輪くぐり', '技術書(バラバラ)', 'いろいろな映画の「つづく」', 'ハリセン', 'ピコピコハンマー', 'シンギュラリティ', '英語のアルファベット', 'テクニカルエバンジェリスト(男性)']
Cluster 2
['笑う男性(5段階)', '笑う女性(5段階)', 'いろいろな表情の酔っぱらい(男性)', '拍手している男の子', '徹夜明けの笑顔(男性)', '鏡を見る人(笑顔の男性)']
Cluster 3
['戦うAI', 'AIの家族', '武器を持つAI', 'AIと仲良くなる人間', '心を持ったAI', 'ON AIRランプ', '画像認識をするAI', '人工知能・AI']
Cluster 4
['苦笑いをする女性', '笑いをこらえる人(女性)', 'ダンス「踊る女性」', '縄跳びを飛んでいる女性', '拍手している女の子', 'ピースサインを出す人(女性)', '啓示を受けた人(女性)', '一輪車に乗る女の子', '走る猫(笑顔)', '遠足「お弁当・男の子・女の子」', 'プレゼントをもらって喜ぶ女の子', 'いろいろな表情の酔っぱらい(女性)', '徹夜明けの笑顔(女性)', 'バンザイをしているお婆さん', '料理「女性」', '定年(女性)']
Cluster 5
['笑い袋', '福笑いをしている人', '福笑い(女性)', '拍手をしている人', '福笑いのおたふく', '愛想笑い', '福笑い(ひょっとこ)', '苦笑いをする男性', 'ありがた迷惑', '福笑い(男性)', '福笑い(おかめ)', '表情', '拍手している人(棒人間)', '笑いをこらえる人(男性)', 'お金を見つめてニヤけている男性', '「ありがとう」と言っている人', 'スタンディングオベーション']
Cluster 6
['成長する人工知能', '人工知能と喧嘩をする人', '人工知能', '人工知能とメールをする人(男性)', '作曲する人工知能', '人工知能とメールをする人(女性)', '人工知能と戦う囲碁の棋士', '検索する人工知能', '仕事をする人工知能', '人工知能と戦う将棋の棋士', '仕事を奪う人工知能', '文章を書く人工知能', '絵を描く人工知能', '人工知能と仲良くする人たち', '人工知能に仕事を任せる人', 'スマートスピーカー', '学ぶ人工知能']
Cluster 7
['お辞儀をしている男性会社員', 'お辞儀をしている医者(女性)', 'お辞儀をしている薬剤師', 'お辞儀をしている犬', 'お辞儀をしている医者', 'お辞儀をしている看護師(男性)', 'お辞儀をしているクマ', 'お辞儀をしている猫', 'お辞儀をしているウサギ', 'お辞儀をしている女性会社員']
Cluster 8
['漫才師', 'コント師', 'ダンス「踊る男性」', 'ものまね芸人', 'お笑い芸人「漫才師」', '芸人の男の子(将来の夢)']
```
-->
### 意味が近い文を検索する
計算した文ベクトルを用いて、意味が近い文(いらすとのタイトル)を検索してみます。
文ベクトルのコサイン距離が小さいものを探します。
```python:In[9]
import scipy.spatial
queries = ['暴走したAI', '暴走した人工知能', 'いらすとやさんに感謝', 'つづく']
query_embeddings = model.encode(queries)
closest_n = 5
for query, query_embedding in zip(queries, query_embeddings):
distances = scipy.spatial.distance.cdist([query_embedding], sentence_vectors, metric="cosine")[0]
results = zip(range(len(distances)), distances)
results = sorted(results, key=lambda x: x[1])
print("\n\n======================\n\n")
print("Query:", query)
print("\nTop 5 most similar sentences in corpus:")
for idx, distance in results[0:closest_n]:
print(sentences[idx].strip(), "(Score: %.4f)" % (distance / 2))
```
以下、出力結果です。
暴走 ≒ 戦い、武器と捉えられるので、自然な結果になっていると思います。
心を持つと確かに暴走しうるとも捉えられますね。
```bash:Out[9]
======================
Query: 暴走したAI
Top 5 most similar sentences in corpus:
戦うAI (Score: 0.1521)
心を持ったAI (Score: 0.1666)
武器を持つAI (Score: 0.1994)
人工知能・AI (Score: 0.2130)
画像認識をするAI (Score: 0.2306)
```
AIを人工知能に言い換えました。結果が変わりました。
表記が近いものの方が上位にくる傾向はあるようです。
```bash:Out[9]
======================
Query: 暴走した人工知能
Top 5 most similar sentences in corpus:
仕事を奪う人工知能 (Score: 0.1210)
人工知能と喧嘩をする人 (Score: 0.1389)
人工知能 (Score: 0.1411)
成長する人工知能 (Score: 0.1482)
人工知能・AI (Score: 0.1629)
```
感謝 =「ありがとう」ということを分かっていますね。
```bash:Out[9]
======================
Query: いらすとやさんに感謝
Top 5 most similar sentences in corpus:
「ありがとう」と言っている人 (Score: 0.1381)
福笑いのおたふく (Score: 0.1693)
福笑い(ひょっとこ) (Score: 0.1715)
福笑い(おかめ) (Score: 0.1743)
笑いをこらえる人(男性) (Score: 0.1789)
```
文ではなく、単一の単語でも近い文を探せています。
```bash:Out[9]
======================
Query: つづく
Top 5 most similar sentences in corpus:
いろいろな映画の「つづく」 (Score: 0.1878)
シンギュラリティ (Score: 0.2703)
愛想笑い (Score: 0.2811)
ありがた迷惑 (Score: 0.2881)
ハリセン (Score: 0.2931)
```
### TensorBoardで潜在意味空間を可視化する
以下のコードを実行し、ColaboratoryのTensorBoard拡張を使って、文ベクトルの空間を低次元空間にマップして可視化してみます。
※Chromeでないと表示されないことがあります。
```python:In[10]
%load_ext tensorboard
import os
logs_base_dir = "runs"
os.makedirs(logs_base_dir, exist_ok=True)
```
```python:In[11]
import torch
from torch.utils.tensorboard import SummaryWriter
import tensorflow as tf
import tensorboard as tb
tf.io.gfile = tb.compat.tensorflow_stub.io.gfile
summary_writer = SummaryWriter()
summary_writer.add_embedding(mat=np.array(sentence_vectors), metadata=sentences)
```
```python:In[12]
%tensorboard --logdir {logs_base_dir}
```
- TensorBoardが起動したら、右上のメニューからPROJECTORを選択してください。
- 可視化アルゴリズム(TensorBoardの左下ペイン)はUMAPの2D、neighbors(TensorBoardの右ペイン)は10に設定すると見やすいでしょう。
うまくいけば文ベクトルの空間が次のように可視化されます。
左上に「人工知能」系がありそのすぐ右に「AI」系の塊が見えます。
左下には「お辞儀」系、中央はその他少数の雑多なもの、右には「女性」系、下には「男性」系の塊が見えます。
<img width="700" alt="TensorBoard.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26062/33ecd130-5461-237b-2caf-d13086555aad.png">
-## 日本語版ソースコードとモデルのダウンロード
-
-上記の例でも用いた、日本語版のソースコードとモデルは以下からダウンロードすることができます。
-
-* [日本語版sentence-transformersのソースコード](https://github.com/sonoisa/sentence-transformers)
-* [日本語モデル](https://www.floydhub.com/api/v1/resources/JLTtbaaK5dprnxoJtUbBbi?content=true&download=true&rename=sonobe-datasets-sentence-transformers-model-2) (442.8MB)
-tarファイルには次のファイルが含まれています。
- * training_bert_japanese/0_BERTJapanese/added_tokens.json
- * training_bert_japanese/0_BERTJapanese/config.json
- * training_bert_japanese/0_BERTJapanese/pytorch_model.bin
- * training_bert_japanese/0_BERTJapanese/sentence_bert_config.json
- * training_bert_japanese/0_BERTJapanese/special_tokens_map.json
- * training_bert_japanese/0_BERTJapanese/tokenizer_config.json
- * training_bert_japanese/0_BERTJapanese/vocab.txt
- * training_bert_japanese/1_Pooling/config.json
- * training_bert_japanese/config.json
- * training_bert_japanese/modules.json
-
-
## Sentence-BERTの概要
(とても読みやすい論文ですので、解説不要な気がしますが)
+- 論文: [Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks](https://arxiv.org/abs/1908.10084)
+- オリジナル実装: [UKPLab/sentence-transformers](https://github.com/UKPLab/sentence-transformers) (※オリジナル実装では日本語版モデルは動かないので注意)
+
一言で言えば、BERTでトークンを埋め込み、mean poolingすることで作る文ベクトルをSiamese Networkを使い距離学習(finetune)させるというものです。シンプルですね。
以下に引用した論文のFig.1と2を見れば大体分かると思います。
<img width="700" alt="SBERT_architecture.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26062/4dc3f534-5ab5-89f3-fb6a-02db2d07ddc9.png">
[論文](https://arxiv.org/abs/1908.10084)では他の方法も色々実験されていますが、日本語モデル構築では、最高性能だったこのネットワーク構造を採用しています。
以下に引用したTable 2のように、英語版の精度評価([STSbenchmark](http://ixa2.si.ehu.es/stswiki/index.php/STSbenchmark)を用いたコサイン類似度と正解ラベルのspearmanの順位相関係数。1に近いほど良い)では、素朴な単語ベクトル(GloVe)の平均を用いる方法では0.58、今回作った規模相当のモデルでは0.85前後です。
<img width="400" alt="Performance.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26062/19019fba-38e3-1b6c-f62f-1143ac2871dc.png">
また、[論文](https://arxiv.org/abs/1908.10084)のTable 1によれば、素のBERTのCLSベクトルを用いると精度は0.17、BERTの埋め込みの平均を用いると0.46となり、単語ベクトルの平均の結果(0.58)よりも悪い結果になります。BERTの原論文にも書かれているとおり、これらを文ベクトルとして使うことは適切ではないことが分かります(想像以上に悪いですね)。
日本語版のモデルの作成において、[huggingface/transformers](https://github.com/huggingface/transformers)(モデルは[東北大学 乾・鈴木研究室様作](https://github.com/cl-tohoku/bert-japanese))の日本語版BERTモデルを用いています。
そして今回の日本語モデルの学習方法(学習に用いたデータセット、精度評価方法)ですが、、、すみませんが諸般の事情により秘密です。結果としては、英語版と遜色ない、文脈を加味した文ベクトルを作れているようにみえています。
+
+## 日本語版ソースコードとモデルのダウンロード
+
+上記の例でも用いた、日本語版のソースコードとモデルは以下からダウンロードすることができます。
+
+* [日本語版sentence-transformersのソースコード](https://github.com/sonoisa/sentence-transformers)
+* [日本語モデル](https://www.floydhub.com/api/v1/resources/JLTtbaaK5dprnxoJtUbBbi?content=true&download=true&rename=sonobe-datasets-sentence-transformers-model-2) (442.8MB)
+tarファイルには次のファイルが含まれています。
+ * training_bert_japanese/0_BERTJapanese/added_tokens.json
+ * training_bert_japanese/0_BERTJapanese/config.json
+ * training_bert_japanese/0_BERTJapanese/pytorch_model.bin
+ * training_bert_japanese/0_BERTJapanese/sentence_bert_config.json
+ * training_bert_japanese/0_BERTJapanese/special_tokens_map.json
+ * training_bert_japanese/0_BERTJapanese/tokenizer_config.json
+ * training_bert_japanese/0_BERTJapanese/vocab.txt
+ * training_bert_japanese/1_Pooling/config.json
+ * training_bert_japanese/config.json
+ * training_bert_japanese/modules.json
+
+
## 免責事項
著者は本シリーズ記事を掲載するにあたって、その内容、機能等について細心の注意を払っておりますが、内容が正確であるかどうか、安全なものであるか等について保証をするものではなく、何らの責任を負うものではありません。
本記事内容のご利用により、万一、ご利用者様に何らかの不都合や損害が発生したとしても、著者や著者の所属組織(日鉄ソリューションズ株式会社(NSSOL、旧新日鉄住金ソリューションズ株式会社))は何らの責任を負うものではありません。
## まとめ
Sentence-BERTの日本語版コードとモデルを作りました。
これで誰でも簡単に高品質な文ベクトルを作れるようになります。
ご活用ください。