BERTによる文章ベクトルを視覚化する

はじめに

前回の記事「BERTを使用した文章ベクトル作成」では日本語BERT学習済みモデルを使い、日本語の文章ベクトル作成についてご紹介しました。文章ベクトルとは自然言語の文章の特徴を数値化したもので、文章の分類や、機械学習アプリケーションへの入力として使うなど、色々な自然言語処理に応用することができます。似た文章は近い文章ベクトルになるという説明をしましたが、文章ベクトルはたくさんの数字の羅列で、見ても近いのか遠いのかイメージがつきにくいものでした。今回はそのベクトルを視覚化して似た文章同士の文章ベクトルが近くなることを実感してみましょう。今回もGoogle Colaboratoryを使ってプログラムを動かしてみます。

BERTのモデルを準備する

まずはBERTのモデルの準備が必要となります。前回記事で紹介した文章ベクトルを求めるプログラムをすでに動かしていれば、同じモデルを使用するので準備は不要です。まだ準備をしていない方は前回記事の「BERTのモデルを準備する」の節を参照し準備をしてください。

文章ベクトルの視覚化

前回紹介した方法で作ったBERTの文章ベクトルは768次元になります。我々は3次元の世界に住んでいるのでこのような高次元の数値を直接実感することができません。視覚化して実感するにはこの高い次元を次元圧縮という方法で低い次元に変換する必要があります。このためにt-SNEという高次元ベクトルを視覚化するアルゴリズムを使って行います。詳細な説明はリンク先の参考文献を参照いただくことにして、簡単に言うと高次元データを可視化のため2次元または3次元の低次元空間へ次元圧縮を行うことができるアルゴリズムです。2次元にしてしまえば x-y のグラフ上にプロットすることで文章ベクトル同士の近さが直感的に感じられるようになります。今回は文章ベクトルの768次元を2次元に変換し平面上にプロットして視覚化することにします。

では、早速始めてみましょう。こちらからプログラム本体である以下のファイルをダウンロードしてGoogle Driveの任意の場所にアップロードします。

似た文章のデータが必要となりますが今回はCSVファイルから読み込むこととします。同じ場所にある以下ファイルをダウンロードして、Google Driveのマイドライブ直下のbertフォルダに保存します。

このファイルはcategoryとinputの列を持つCSVファイルで、類似の文章にはcategoryに同じ番号が振られています。飲食店によくある質問文を9個のcategoryに分け合計80文を使用します。

category,input
1,お弁当の配達はお願いできますか?
1,弁当を配達してもらえる?
1,お弁当はやってますか
… 省略 …
2,ペットを連れて利用できますか?
2,ペット同伴してもいいですか
2,ペットは一緒に入れる
… 省略 …

先にアップロードしたプログラム本体をGoogle Colaboratoryで開きます。開いたらメニューのRuntimeRun all を選択(または[Ctrl]+[F9])して実行すると、コードが順に走ります。

途中一か所「Google Driveをパス/content/driveにマウントする」という部分で以下図のように表示されコードの入力待ちになります。Google Driveのファイルを参照するために認証を通す必要があるため、”Go to this URL in a browser”で表示されたリンクをクリックし、画面の指示に従ってGoogle Driveへのアクセス許可を与え、最後に表示されたコードを、“Enter your authorization code” に入れると先に進みます。

最後のコードブロックが実行されると、input1.csv質問文を文章ベクトルに変換、それを2次元に変換しcategoryで色分けして平面上にプロットします。正常に実行できると最後に下図のようなプロットが表示されると思います。

input1.csvの文章ベクトルのプロット

色が同じ点はinput1.csv中で同じcategoryを持つ文章、すなわち類似の文章を示しています。同じ色が近い場所にあれば、類似の文章はベクトル的に近い場所にあることがいえると思いますがどうでしょうか?多少のばらつきはありますが、同じ色同士で比較的固まっているように見えるかと思います。

コード説明

ここからはプログラムの中身に興味がある人向けの内容になります。今回動かしたコードbert_sentencevector_test2.ipynbの説明をかいつまんでしていきたいと思います。

引数のテキストから文章ベクトルを求めて返す関数text2vectorの定義までは前回記事と同じコードとなりますので説明は省略します。

今回はCSVファイルから質問文を順に読み込んで関数text2vectorで文章ベクトルを順に計算しています。それを実施している部分が以下のコードブロックとなります。各文の文章ベクトルは変数svに格納しています。すべての文章ベクトルの計算が終わるとsvは80文の768次元のベクトルが格納された (80, 768)の行列となっています。

# 質問リストのCSVを読み込む。
# 質問種別,質問文、の形式になっている。
# index_col=Falseを付けないと1列目の数字を正しく読まない
features_df = pd.read_csv(bertPath + 'input1.csv', encoding="cp932", index_col=False, dtype=str)
categories = features_df['category'].astype('category').cat.codes.values.tolist()
catnum = max(categories)+1
print('corpus size:',features_df['input'].size)
print('categories:',catnum)

# 質問リストの先頭を表示
print(features_df.head())

# すべての質問文に対して文章ベクトルを求める
sv = np.empty((0,768), np.float32)
for sentence in features_df['input']:
  _sv = text2vector(sentence)
  if _sv is not None:
    sv = np.append(sv, _sv, axis=0)
print('vector size:',sv.shape)

最後のコードブロックでsklearnの関数TSNE でt-SNEによる768次元から2次元への次元圧縮を行っています。変数 sv に格納されている80文、768次元の文章ベクトル、(80, 768)の行列は、関数TSNEを通すとsv_reduced に(80, 2)の行列で格納されます。これを質問種別ごとに色分けしてプロットしています。matplotの関数cm.jetは0~1の間の数値を色分けするのに使えますので0~1の範囲をカテゴリの数に等分して色分けするようにしました。

# 768次元→2次元に圧縮する
sv_reduced = TSNE(n_components=2, random_state=0).fit_transform(sv)
print('reduced vector size:',sv_reduced.shape)

# 質問種別(category)ごとに色分けして表示
colors = [cm.jet(i/(catnum-1)) for i in categories]
plt.figure(figsize=(10, 10))
plt.scatter(sv_reduced[:, 0], sv_reduced[:, 1], c=colors)

コードはこれで終了になります。

おわりに

本記事がAI・機械学習(Machine Learning)を学ぶ人の参考になれば幸いです。

当社では機械学習とオントロジー技術によるハイブリッド会話AI「ENOKI」を開発しています。自然言語処理を応用した音声会話アプリケーションやチャットボットでの顧客接点にご利用いただける製品です。詳細はぜひこちらのページをご覧ください。

参考文献

本記事はQiitaに投稿した以下記事に加筆・修正をしたものです。

t-SNEについて説明やアルゴリズムの参考資料です。