Watson NLPのEmbedding（埋め込み）を試してみた

[Watson NLP (Natural Language Processing)](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp.html) ライブラリーは、エンティティ抽出、テキスト分類、感情分類、キーワード抽出などのさまざまな自然言語処理のモデルを提供しています。
多くの機能は [Watson NLU](https://www.ibm.com/jp-ja/cloud/watson-natural-language-understanding)と似ていますが、ここではWatson NLUには存在しないユニークな機能として**Embedding (埋め込み)** を紹介します。


# 目次

[1. 埋め込みモデル](#1-埋め込みモデル)
[2. 埋め込みモデルの利用](#2-埋め込みモデルの利用)
[3. 独自モデルの作成と利用](#3-独自モデルの作成と利用)

## 1. 埋め込みモデル
マニュアルでは埋め込みの説明として以下のように書かれています。
埋め込み: 個々の単語またはより大きなテキスト・スニペットをベクトル・スペースにマップします。
これだと何ができるのかわかりにくいですが、単語を与えると学習済みの大規模モデルから関連する語句を返してくれるものになります。
単語間、文、段落、または短い文書の類似性を計算するのに便利なツールです。埋め込みは、分類および抽出タスクのためのディープラーニングおよびトランスフォーマーベースのアルゴリズムで一般的に使用され、意味的類似性の計算やクラスタリングなどのタスクにも役立ちます。

## 2. 埋め込みモデルの利用

[Watson Studio](https://www.ibm.com/jp-ja/cloud/watson-studio) のNotebookでPythonを使用したプロジェクトを作成します。

GloVe (Global Vectors) によって単語の取得します。Watson NLPではWikipediaコーパスで学習したGloVeモデルが利用できるようになっています。
最初にWatson NLPで提供されている埋め込みモデルとして**GloVeモデル**をロードします。

```py
import watson_nlp
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
# word embedding モデルをダウンロードして読み込みます
# CP4Dオンプレ環境では watson_nlp.load('embedding_glove_ja_stock') のようにします
glove_model = watson_nlp.load(watson_nlp.download('embedding_glove_ja_stock'))
```
試しに **"人工知能"** という語句を与えて、関連する語句を10個取得してみます。
```py
# 検索語句
search_term = '人工知能'
# 表示する語句の数
num_display = 10

# 検索語がモデルの語彙に含まれていることを確認
try:
    term_index = glove_model.embedding.vocab_to_idx[search_term]
except KeyError:
    raise KeyError('「{}」 はモデルの語彙にありません'.format(search_term))

term_vector = glove_model.embedding.data[term_index]
term_mat = np.expand_dims(term_vector, axis=0)
vocab_sim = cosine_similarity(glove_model.embedding.data, term_mat).squeeze()
word_ranks = np.argsort(-vocab_sim)

# 語句とスコアを表示
idx_to_vocab = {v:k for k,v in glove_model.embedding.vocab_to_idx.items()}
for idx, rank in enumerate(word_ranks):
    if idx == 0:
        print('語句：{0} '.format(idx_to_vocab[rank]))
    else:
        print('{0}位： {1} 　, Score: {2:.2f}'.format(idx, idx_to_vocab[rank], vocab_sim[rank]))
    if idx > num_display:
        break
```
語句：人工知能 
1位： ai 　, Score: 0.61
2位： mit 　, Score: 0.59
3位： 科学 　, Score: 0.49
4位： 研究者 　, Score: 0.49
5位： コンピューター 　, Score: 0.48
6位： 研究 　, Score: 0.48
7位： マサチューセッツ工科大学 　, Score: 0.48
8位： 計算機 　, Score: 0.48
9位： コンピュータ 　, Score: 0.48
10位： プログラミング言語 　, Score: 0.46
11位： ロボット 　, Score: 0.45

:::note info
**人工知能**に関連する語句をスコアと一緒に表示したところ、確かに人工知能に関連しそうな言葉が取得できました。wikipediaで学習したモデルではこのような語句と距離が近いのがわかります。
:::

## 3. 独自モデルの作成と利用
次に独自モデルを作成してみます。現在、Watson NLP を使用した GloVe 埋め込みトレーニングは、Linuxベースのシステムでのみサポートされています。

### 3.1. Linux環境でモデル作成
独自モデルの作成には大量の文書が必要となっていますが、今回は小規模な数百ページの製品マニュアルで学習をさせてどのような結果が得られるのかを試してみます。
今回使用したデータは [Watson Discoveryのマニュアル(PDF)](https://cloud.ibm.com/media/docs/pdf/discovery-data/nl/ja/discovery-data.pdf)のテキストを学習用データとして取り込んでいます。

Discoveryのマニュアルからテキストを抽出してファイル（discovery_manual.txt）に保存したものを学習用データとして利用します。

```py
import watson_nlp
from watson_nlp.toolkit.embedding import train_glove_utils
from watson_nlp.blocks.embedding import GloVe

# Discoveryのマニュアルからテキストを抽出した入力ファイル（discovery_manual.txt）
glove_corpus = "discovery_manual.txt"
# 形態素解析(tokenize)した結果を保存するファイル(discovery_manual.tok)
glove_tokenized_corpus = "discovery_manual.tok"
language = "ja"
train_glove_utils.prepare_data_for_glove_train(glove_corpus, glove_tokenized_corpus, language)

# 作成したファイル（形態素解析した語句群）を使用して独自モデルを作成します。
corpus = "discovery_manual.tok"
my_model = GloVe.train(corpus=corpus)
```
BUILDING VOCABULARY
Processed 248619 tokens.
Counted 6955 unique words.
Truncating vocabulary at min count 5.
Using vocabulary of size 2389.

:::note info
モデル作成時の情報が表示されます。今回の学習用データには248,619個の語句があり、6,955個がユニークな語句として存在。その中で5回以上出現した2,389個がモデル作成に利用されています。
:::
作成したモデルをローカルフォルダに保存します。
```py
# 作成したモデルをローカル環境に保存（フォルダが作成される）
my_model.save('./my_model')
```
モデルを他の環境で利用できるように、my_model フォルダをzipで圧縮ファイル形式にします。
```sh
zip -r my_model.zip my_model/
```

### 3.2. 独自モデルの利用
作成したモデル(my_model.zip)をWatson Studioのプロジェクトにアップロードした後、次のように読み込みます。
```py
import watson_nlp
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
# linux環境で作成したモデル(zip形式)をプロジェクトにコピーしておき、以下のコマンドで読み込みます
# CP4Dオンプレ環境では watson_nlp.load(wslib.load_data("my_model.zip")) とします
my_model = watson_nlp.load(project.get_file('my_model.zip'))
glove_model = my_model
```

:::note info
CP4DaaS環境の場合は、先に[カスタムライブラリのインストール](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/install-cust-lib.html?locale=ja&audience=wdp)を参考にしてプロジェクトトークンの挿入を記述しておきます。
:::


Discoveryのマニュアルに存在しそうな"プラン"という語句を与えてみます。
```py
search_term = 'プラン'
num_display = 10

# 検索語がモデルの語彙に含まれていることを確認
try:
    term_index = glove_model.embedding.vocab_to_idx[search_term]
except KeyError:
    raise KeyError('「{}」 はモデルの語彙にありません'.format(search_term))

term_vector = glove_model.embedding.data[term_index]
term_mat = np.expand_dims(term_vector, axis=0)
vocab_sim = cosine_similarity(glove_model.embedding.data, term_mat).squeeze()
word_ranks = np.argsort(-vocab_sim)

# 語句とスコアを表示
idx_to_vocab = {v:k for k,v in glove_model.embedding.vocab_to_idx.items()}
for idx, rank in enumerate(word_ranks):
    if idx == 0:
        print('語句：{0} '.format(idx_to_vocab[rank]))
    else:
        print('{0}位： {1} 　, Score: {2:.2f}'.format(idx, idx_to_vocab[rank], vocab_sim[rank]))
    if idx > num_display:
        break
```

語句： プラン
1位： ライト 　, Score: 0.89
2位： エンタープライズ 　, Score: 0.89
3位： マイニング 　, Score: 0.88
4位： ティー 　, Score: 0.87
5位： プラス 　, Score: 0.85
6位： プレミアム 　, Score: 0.85
7位： コンテンツ 　, Score: 0.84
8位： ストア 　, Score: 0.82
9位： グ 　, Score: 0.81
10位： アプリケーション 　, Score: 0.81

:::note info
Discoveryには "ライト", "エンタープライズ", "プラス", "プレミアム" といったプランが存在するので、プランに関連する語句が取得できているのがわかります。少ない文書で学習したモデルですが、期待に近い結果が返ってきました。
:::

wikipediaで作成したGloVeモデルと今回作成した独自モデルの結果を比較してみます。

|語句|GloVeモデル(wikipedia)|独自モデル|
|:--|:--|:--|
|discovery|1位： neighbor<br>2位： frontier<br>3位： vanguard<br>4位： fair<br>5位： record<br>6位： cruise<br>7位： rumble<br>8位： address<br>9位： pirates<br>10位： village|1位： watson<br>2位： v<br>3位： サービス<br>4位： インスタンス<br>5位： assistant<br>6位： 2<br>7位： ®<br>8位： クラスター<br>9位： jdbc:db<br>10位： api|
|プラン|1位： シュリーフェン<br>2位： 定額<br>3位： モーゲンソー<br>4位： ウィルコム<br>5位： ゴールドプラン<br>6位： タン<br>7位： イエナ<br>8位： 料金<br>9位： マル<br>10位： 基本|1位： ライト<br>2位： エンタープライズ<br>3位： マイニング<br>4位： ティー<br>5位： プラス<br>6位： プレミアム<br>7位： コンテンツ<br>8位： ストア<br>9位： グ<br>10位： アプリケーション|
|エンリッチメント|1位： 環境<br>2位： 高湿度<br>3位： マルチブート<br>4位： 無重量<br>5位： 微小重力<br>6位： 多湿<br>7位： 決定論<br>8位： ラベリング<br>9位： オペレーティング<br>10位： 苛酷|1位： 適⽤<br>2位： および<br>3位： テキスト<br>4位： エンリッチ<br>5位： only<br>6位： 辞書<br>7位： または<br>8位： 作成<br>9位： 設定<br>10位： エンティティー|

この結果を見ると、独自モデルでは学習に利用した文書から近しい言葉を取得できていますが、助詞や接続詞など不要な語句はストップワードとして除外する必要はありそうです。wikipediaから学習したモデルでは、一般的な言葉でも漏れなく関連する語句を取得できているのがわかります。
この**埋め込み**の用途としては、社内で使われている文書から関連する語句を取得したいことがあります。例えば検索やテキスト分析で使用する辞書の作成は社内で利用されている語句が有効です。また、検索クエリーに関連する語句を表示することで利用者に気づきを与えることもあります。
今回のように独自のモデルは簡単に作成できるので、埋め込みモデルを検索語句の拡張や辞書作成に利用することは試す価値がありそうです。

---
**Watson NLP (Natural Language Processing) ライブラリ**
https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp.html