AIによるチャットボットの作り方>チャットボット用AIの作り方>One-hotベクトルによる文章表現 - 更新

One-hotベクトルによる文章表現概要

   One-hotとはデジタル回路に由来する用語であり、1ビットだけ1であり、他のビットが0であるようなビット列を意味しますが、 自然言語処理の分野では文章の表現手法としてOne-hotベクトル表現が使用されることが多いようです。 One-hotベクトル表現は、データ量が増加するというデメリットもありますが、表現がシンプルであり行列計算に適していますので本サイトでもこの表現を利用します。

このページの目次

  1. One-hotベクトルによる文章表現概要
  2. MeCabによる形態素解析
  3. 単語IDベクトルによるコーパスの表現
  4. One-hotベクトルによるコーパスの表現
  5. One-hotベクトルのメリット - Embedding層

MeCabによる形態素解析

   日本語の文章は英語のそれとは異なり、文章中に空白がないため、まず、文章を形態素解析(morphological analysis:意味を持つ最小限の単位(形態素(Morpheme))に分解して品詞等を判別すること) をする必要があります。 形態素解析ツールとしてはMeCabJUMAN などがありますが、本サイトでは、速度の点で圧倒的に優れていることに加えて、Pythonから比較的簡単に利用できるオープンソースの形態素解析エンジンであるMeCabを使用します。

MeCabの実行環境

   AIチャットボット環境の作り方 で示す通り、AIチャットボットはWindows10、CentOS7.6、Ubuntu18.04という3種類のOS上で開発やサービスを実施しているため、 PythonやMeCabもこれらの3種類の環境で動作するよう構築しておく必要があります。
Windows10やCentOS7.6にPythonをインストールする場合には、NumPyなどの数学ライブラリやWindowsのAnaconda promptが使用できるAnaconda3 からインストールするのが便利です。ただし、Ubuntu18.04では最初からPythonがインストールされているため、プリインストールされているPythonを最新版に更新して利用する方が簡単でしょう。

Encoder-Decoderモデルによる新たな文章の生成

   AIチャットボットでは、Encoder-Decoderモデルとも呼ばれるseq2seq(Sequence to Sequence)技術を使用しますので、以下Encoder-Decoderモデル用のデータを用いて解説して行きます。 ここで、Encoder-Decoderモデルとは、Encoderによりコード化された時系列入力データが、Decoderにより別の時系列データとして出力されるようなモデルであり、 翻訳やチャットなどの基本モデルとなっています。
例えば、チャット具体例でのEncoder-Decoderモデルを考えてみましょう。 「今日は晴れですね。」というEncoderの入力に対して、「はい、今日は晴れていて気持ちがいいですね。」というDecoderの出力を返すようなケースにおいて、 Encoder-Decoderモデルは、まず、入力文章の形態素解析を行って単語IDベクトルに変換後、ゲート付きRNNの一つである LSTM によりコード化します。そして、コード化した中間層の状態をDecoderモデルに引き渡すことによって新たな文章を生み出すのですが、 Decoderモデルの入力も単語IDベクトルで与えますし、出力単語IDベクトルで得ることになります。
上記の説明では「はい、今日は晴れていて気持ちがいいですね。」をDecoderの出力と記述しましたが、これはDecoderの入力でもあるのです。 即ち、Decoderが単語IDを出力する度に、その単語IDをDecoderの時系列入力として与えて行って順次出力を得ることにより、新たな時系列データである文章を生み出して行きます。 このように、日本語をEncoder-Decoderモデルで処理する場合には、まず形態素解析を行って単語IDベクトルで表す必要があります。
なお、自然言語処理の分野では、構造化した大規模の文章をコーパス(Corpus)と呼びますが、 本サイトでは、Encoderの入力文章をEncoderコーパス、Decoderの入出力文章をDecoderコーパスと呼ぶことにします。

MeCabによる形態素解析

   それでは、

「今日は晴れですね。」→「はい、今日は晴れていて気持ちがいいですね。」

というチャットの応答(「Encoderコーパス」→「Decoderコーパス」)の形態素解析例を見てみましょう。 まず、Windows10のAnaconda promptでPythonを起動してMeCabのインスタンス'm'を作成します。 ここで、'>'はAnaconda promptのプロンプト(入力を促す表示)であり、'>>>'はPythonのプロンプトです。
> python ・・・ >>> import sys >>> import MeCab >>> m = MeCab.Tagger ("-Ochasen")
下記のようにMeCabで形態素解析を行うと、

「今日/は/晴れ/です/ね/。」→「はい/、/今日/は/晴れ/て/い/て/気持ち/が/いい/です/ね/。」

Encoderコーパスは6個の形態素に、Decoderコーパスは14個の形態素に分解されることが分かります。
>>> print(m.parse("今日は晴れですね。")) 今日 キョウ 今日 名詞-副詞可能 は ハ は 助詞-係助詞 晴れ ハレ 晴れ 名詞-一般 です デス です 助動詞 特殊・デス 基本形 ね ネ ね 助詞-終助詞 。 。 。 記号-句点 EOS
>>> print(m.parse("はい、今日は晴れていて気持ちがいいですね。")) はい ハイ はい 感動詞 、 、 、 記号-読点 今日 キョウ 今日 名詞-副詞可能 は ハ は 助詞-係助詞 晴れ ハレ 晴れる 動詞-自立 一段 連用形 て テ て 助詞-接続助詞 い イ いる 動詞-非自立 一段 連用形 て テ て 助詞-接続助詞 気持ち キモチ 気持ち 名詞-一般 が ガ が 助詞-格助詞-一般 いい イイ いい 形容詞-自立 形容詞・イイ 基本形 です デス です 助動詞 特殊・デス 基本形 ね ネ ね 助詞-終助詞 。 。 。 記号-句点 EOS

単語IDベクトルによるコーパスの表現

   次に、MeCabの形態素解析結果を使用してEncoder、Decoderの入出力を単語IDベクトルで表示してみましょう。 Encoder入力文章→Decoderの入出力文章の順にスキャンするとして、出現した形態素ごとに単語ID(正確には形態素ID)として登録し、その単語IDベクトルを使用して文章を表示します。 この際に、本サイトでは、あらかじめ特殊語として全角スペース' 'を単語ID=0、'<eos>'を単語ID=1としてそれぞれ登録していますので、 一般の文章の現れる形態素の単語IDは2から始めることにします。

「今日(2)/は(3)/晴れ(4)/です(5)/ね(6)/。(7)」→「はい(8)/、(9)/今日(2)/は(3)/晴れ(10)/て(11)/い(12)/て(11)/気持ち(13)/が(14)/いい(15)/です(5)/ね(6)/。(7)」

( )中の番号が単語IDです。 ここで注意して頂きたいのは、「晴れ」という単語が2回現れていますが、1回目は名詞として現れ、2回目は動詞として現れています。 品詞が異なれば、文章中でその出現の仕方も異なりますので、これらは別単語として登録します。 多数の文章がある場合にはスキャンする順序により単語IDが異なりますが、気にしないで新たに出現した単語と品詞を調べて、新たな形態素であれば新単語として登録して行けば良いだけです。
このような処理により、EncoderコーパスおよびDecoderコーパスは(1)式および(2)式のような6次元および14次元の単語IDベクトルとして表現されます。 \[ \begin{pmatrix} 2 & 3 & 4 & 5 & 6 & 7 \end{pmatrix} \tag{1} \] \[ \begin{pmatrix} 8 & 9 & 2 & 3 & 10 & 11 & 12 & 11 & 13 & 14 & 15 & 5 & 6 & 7 \end{pmatrix} \tag{2} \] そして、単語IDから単語や品詞などの形態素を探し出したり、逆に、単語から単語IDを探し出したりできるよう、Pythonの辞書変数としてまとめておくと良いでしょう。

word_to_id = {' ':0, '<eos>':1, '今日':2, 'は':3, '晴れ':4,・・・,'_晴れ':10, 'て':11, 'い':12, '気持ち':13, 'が':14, 'いい':15}
word_to_class = {' ':'記号', '<eos>':'記号', '今日':'名詞', 'は':'助詞', 晴れ:'名詞',・・・,'_晴れ':'動詞','て':'助詞', 'い':'動詞', '気持ち':'名詞', 'が':'助詞', 'いい':'形容詞'}
id_to_word = {0:' ', 1:'<eos>', 2:'今日', 3:'は', 4:'晴れ',・・・,10:'_晴れ', 11:'て', 12:'い', 13:'気持ち', 14:'が', 15:'いい'}

ここで、二回目に出て来た品詞が異なる同単語には先頭に'_'を付けて辞書登録しています。 そして、Decoderの出力を文章として表現する際には先頭の'_'を除去して表示すれば良いだけです。

One-hotベクトルによるコーパスの表現

   EncoderコーパスおよびDecoderコーパスは、One-hotベクトル表記すると(3)式および(4)式のような6行16列および14行16列の行列として表示されます。 ここで、行数はEncoderコーパスおよびDecoderコーパスの形態素数であり、(1)式および(2)式の単語IDベクトルの次元数と一致し、列数は語彙数になります。 時系列情報が学習できるRNN では、\(t=0\) ~\(T-1\)の時系列情報を扱いますが、このモデルの時間のスパン\(T\)がEncoderコーパスやDeecoderコーパスの形態素数に対応しています。 \[ \begin{pmatrix} 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \end{pmatrix} \tag{3} \] \[ \begin{pmatrix} 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \end{pmatrix} \tag{4} \]
このOne-hotベクトル(One-hot行列)はスパース(sparse:スカスカ)な行列ですが、語彙数が増えるに伴って膨大な行列になってしまいます。 しかし、実際に内部でこのようなOne-hot行列の形式でEncoderコーパスやDecoderコーパスを保持している訳ではなく、 このような表現を使用することにより内部の演算がとても単純化されることから言語処理の分野ではOne-hotベクトルを使用することが多いようです。

One-hotベクトルのメリット - Embedding層

   自然言語処理用AIの歴史 で述べた通り、CBOWモデルを始めとするword2vec技術では単語の分散表現を行い、単語は意味を的確に表す多次元ベクトルで表現されます。 自然言語処理において多次元ベクトルで単語を表現することをEmbedding(埋め込み)と呼びますが、その処理は入力コーパス\(x_i\)と重み\(w_{ij}\)の積\(x_i w_{ij}\)で表現されます。
時系列情報が学習できるRNNで解説する通り、RNNやLSTMでは、時系列の層状態を表す行列 と重み行列の積が使用されます。 ここでは、EncoderコーパスやDecoderコーパスなどの時系列入力のOne-hot行列\(x_{ti}\)と重み\(w_{ij}\)との積を求めるEmbedding層の処理について考えてみましょう。 Embedding層の処理は次式で表されます。 \[ h_{tj} = x_{ti} w_{ij} \tag{5} \] ここで、入力層であるコーパス行列\(x_{ti}\)は\(T\)x\(V\)の行列、\(w_{ij}\)は\(V\)x\(D\)の行列ですので、これらの積である単語ベクトル層の状態\(h_{tj}\)は\(T\)x\(D\)の行列になります。 \(T\)は時系列情報の長さ、即ちEncoderコーパスやDecoderコーパスの単語IDベクトルの次元数であり、\(V\)は入力ベクトルの次元数(語彙数)、\(D\)は単語の意味表す単語ベクトルの次元数です。 そして、図1の例では、コーパス行列の\(t\)行は\(V\)列のみ1で他は0ですので、\(h_{tj}\)の\(t\)行は重み\(w_{ij}\)の\(V\)行を抽出するだけで良いことが分かります。
即ち、Embedding層では、単語ベクトル層の状態\(h_{tj}\)の\(t\)行は、コーパス行列\(x_{ti}\)の\(t\)行の中で\(i\)列の要素が1であれば、 重み\(w_{ij}\)の\(i\)行の行ベクトルを抽出するという処理に帰着されますので、コンピュータメモリーも計算時間も大幅に節約できることが分かります。 コーパス行列\(x_{ti}\)の\(t\)行の\(i\)列の要素が1であるということは、コーパスの\(t\)番目の単語IDが\(i\)であるを意味しますので、 この単語IDに該当する重み\(w_{ij}\)の\(i\)行を抽出すれば良いだけになるのです。
Embedding層の処理
図1 Embedding層の処理
   なお、Embedding層を実装するためにはEmbedding層の誤差逆伝播を知る必要があります。 Embedding層の誤差逆伝播の詳細は、 Embedding層の誤差逆伝播 をご覧ください。


ページトップへ

運営会社情報 | プライバシー・ポリシー
Twitter Twitter Facebook Mail Line

* 無断転載禁止。
* どのページでもご自由にリンクして下さい。
* ご意見・ご質問等がございましたら こちらからメールをご送付下さい。 無料SEO対策 -153.121.92.88。