Universal Sentence Encoderを日本語で試す

# Universal Sentence Encoderとは
その名の通り、文をエンコード、すなわち文をベクトル化する手法です。
Googleの研究者達が開発したもので、2018年に[Tensorflow Hub](https://tfhub.dev/google/universal-sentence-encoder-multilingual/1)で公開されました。
公開当初は英語のみの対応でしたが、2019年9月現在では日本語、中国語、ドイツ語など16言語に対応しています。

これまで文をベクトル化する手法としては、

- 単語の分散表現を利用したもの
    - 具体的な方法としては[この記事](https://qiita.com/kenta1984/items/93b64768494f971edf86)が分かりやすいです。
- Sentence2Vec
- Skip-thought

など様々な手法が提案されてきましたが、これといった決定打に欠ける状況です。
ただ、Universal Sentence Encoderは[STSbenchmark](http://ixa2.si.ehu.es/stswiki/index.php/STSbenchmark)で高い精度を達成しており、また言語の違いを意識する必要がない（≒異なる言語でも同じベクトル空間上にマップされる）ので、今後は文のベクトル化のデファクトスタンダードになるかもしれません。

# Universal Sentence Encoderのモデル
下記の2つのモデルを提案しています。

1. Transformer
2. Deep Averaging Network(DAN)

Transformのモデルは精度が高いもののモデルが複雑で多くのリソースを使うのに対して、DANのモデルは精度が少し落ちるもののシンプルなモデルでリソースの消費量が少なく済みます。
アルゴリズムの詳細を知りたい方は[元の論文]()をご覧下さい。
なお、[Tensorflow Hub](https://tfhub.dev/google/universal-sentence-encoder-multilingual/1)で公開されているものは、DANのモデルが使われています。

# Universal Sentence Encoder（Version 1）の使い方
*2020/04/19時点ではVersion 3が最新版になります。Version 3の使い方は[こちら](https://qiita.com/kenta1984/items/9613da23766a2578a27a#universal-sentence-encoderversion-3%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9)をご覧下さい。*

使い方は非常に簡単です。最新バージョンの`tensorflow`、`tensorflow_hub`、`numpy`、`tf_sentencepiece`を`pip`でインストールすれば、わずか十数行で書けます。
下記のプログラムでは、文をベクトル化した上でコサイン類似度で文同士の類似度を計算しています。
ベクトル化したい文をリストに格納しますが、異なる言語の文を同じリストのオブジェクトに格納しても問題ありません。

``` python
import tensorflow as tf
import tensorflow_hub as hub
import numpy as np
import tf_sentencepiece

def cos_sim(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

if __name__ == "__main__":
    # Graph set up.
    g = tf.Graph()
    with g.as_default():
      text_input = tf.placeholder(dtype=tf.string, shape=[None])
      # どちらを使っても大丈夫
      embed = hub.Module("https://tfhub.dev/google/universal-sentence-encoder-multilingual-large/1")
      # embed = hub.Module("https://tfhub.dev/google/universal-sentence-encoder-multilingual/1")
      embedded_text = embed(text_input)
      init_op = tf.group([tf.global_variables_initializer(), tf.tables_initializer()])
    g.finalize()

    # Initialize session.
    session = tf.Session(graph=g)
    session.run(init_op)

    # Make sentence vectors.
    texts = ["昨日、お笑い番組を見た。", "昨夜、テレビで漫才をやっていた。", "昨日、公園に行った。", "I saw a comedy show last night.", "Yesterday, I went to the park."]
    vectors = session.run(embedded_text, feed_dict={text_input: texts})

    print(texts[0], texts[1], cos_sim(vectors[0], vectors[1]), sep="\t")
    print(texts[0], texts[2], cos_sim(vectors[0], vectors[2]), sep="\t")
    print(texts[0], texts[3], cos_sim(vectors[0], vectors[3]), sep="\t")
    print(texts[0], texts[4], cos_sim(vectors[0], vectors[4]), sep="\t")
```

結果は以下のようになります。
日本語同士で似た意味の文の類似度が高くなっているだけでなく、日本語と英語でも似た意味の文の類似度が高くなっていることが分かります。

```
昨日、お笑い番組を見た。	昨夜、テレビで漫才をやっていた。	0.80579656
昨日、お笑い番組を見た。	昨日、公園に行った。	0.32864782
昨日、お笑い番組を見た。	I saw a comedy show last night.	0.7474545
昨日、お笑い番組を見た。	Yesterday, I went to the park.	0.26472652
```

# Universal Sentence Encoder（Version 3）の使い方
Version3ではさらに簡単に使えるようになりました。

``` python
import tensorflow_hub as hub
import numpy as np
import tensorflow_text

# for avoiding error
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

def cos_sim(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

embed = hub.load("https://tfhub.dev/google/universal-sentence-encoder-multilingual/3")

texts = ["昨日、お笑い番組を見た。", "昨夜、テレビで漫才をやっていた。", "昨日、公園に行った。", "I saw a comedy show last night.", "Yesterday, I went to the park."]
vectors = embed(texts)

print(texts[0], texts[1], cos_sim(vectors[0], vectors[1]), sep="\t")
print(texts[0], texts[2], cos_sim(vectors[0], vectors[2]), sep="\t")
print(texts[0], texts[3], cos_sim(vectors[0], vectors[3]), sep="\t")
print(texts[0], texts[4], cos_sim(vectors[0], vectors[4]), sep="\t")
```

結果は以下のようになります。

```
昨日、お笑い番組を見た。	昨夜、テレビで漫才をやっていた。	0.59764516
昨日、お笑い番組を見た。	昨日、公園に行った。	0.43056923
昨日、お笑い番組を見た。	I saw a comedy show last night.	0.71707606
昨日、お笑い番組を見た。	Yesterday, I went to the park.	0.3018708
```

# Universal Sentence Encoderの応用方法
ここまで文のベクトル化ということで紹介してきましたが、単語、フレーズ、複文であってもベクトル化が可能です。
ですので、口コミのポジネガ判定やアンケート回答のクラスタリングなどの文書分類に落とし込める問題では、Universal Sentence Encoderを活用できると思います。
さらに、言語の違いを意識する必要がないので、日本語のクエリ文から英語の類似文を抽出するというような処理も簡単にできるようになっています。

Universal Sentence Encoderは、精度だけでなく異なる言語でも同じベクトル空間上で表現できるというのが非常に大きな強みだと思います。
是非、自分たちの研究や業務ではどのような場面に活かせそうかという事を考えてみて下さい。