TransformerをPython・Kerasで実装したレビュー分類を行うサンプルコードを解説(2)

以下の記事で紹介したTransformerのサンプルコードについて解説します。
https://nine-num-98.blogspot.com/2023/04/imdb-classification-01.html

各プログラム等の概要

GitHub公開プログラム
https://github.com/kotetsu99/imdb_classification

(1)01-imdb_train.py: レビュー学習プログラム
・IMDBのレビューを読み込み、高評価/低評価の分類をAIに学習させる。
・AIモデルは、TransformerのEncoder部分を主に使用

(2)02-imdb_test.py: レビュー分類プログラム
・(1)で学習させたAIモデルを使って、IMDBレビューの分類(高評価/低評価)の分類を行う。

今回は(2)のサンプルコードについて解説致します。

レビュー分類プログラム: ライブラリインポート、各設定

 1: import tensorflow as tf
 2: from tensorflow import keras
 3: from tensorflow.keras import layers
 4: import sys, os
 5: 
 6: # imdbデータセット読み込みの設定
 7: 
 8: # 出現数上位ワード数の設定(単語インデックスは出現数ランキング番号に対応)
 9: vocab_size = 20000
10: # 各レビューで学習の対象とする最初のワード数の上限
11: maxlen = 200
12: 
13: # レビュー文最初の単語のインデックス番号
14: start_char = 1
15: # レビュー文の上位ワード数(vocab_size)に入っていない単語の
16: # インデックス番号はoov_charで置換
17: oov_char = 2
18: # 単語インデックスは以下の番号以降が振られる
19: # ここではstart_char,oov_charを除く4以降が割り振り
20: index_from = 3

tensorflow等のライブラリのインポートを行ったあとレビュー文解析の設定を行っています。imdbレビューで解析の対象とする単語数を、出現数の多い上位20000件(vocab_size)に設定。さらにレビュー1件の文の長さを制限200単語(maxlen)に制限しています。

その後、レビュー文に登場する単語のインデックス(番号)の定義を行っています。文頭は「1」、上位ワード数圏外の単語は「2」と表現します。登場する単語のインデックスは「4」以降を割り振ります。

レビュー分類プログラム: main関数

main関数について、見ていきます。 

23: # main関数
24: def main():
25: 
26:     # 学習済モデル読み込み。引数に学習済みモデルがなければ強制終了
27:     if not len(sys.argv)==2:
28:         print('使用法: python 02-imdb_test.py 学習済ファイル名')
29:         sys.exit()
30:     savefile = sys.argv[1]
31:     model = keras.models.load_model(savefile)
32: 
33:     # トークンデータ生成
34:     x_train, inverted_word_index = generate_token()
35: 
36:     # レビュー評価プログラムスタート(Ctrl + C が押下されるまで繰り返し)
37:     while True:
38: 
39:         try:
40:             # レビュー番号を入力
41:             input_key = input("\nレビュー番号を入力してください(1~25000の番号):")
42:             # レビュー番号を変数に退避
43:             d_index = int(input_key) - 1
44:             if not (0 <= d_index < 25000):
45:                 raise ValueError
46: 
47:             # レビュー文の単語をインデックスから引いて組み立て。単語間はスペースでつなげる
48:             decoded_sequence = " ".join(inverted_word_index[i] for i in x_train[d_index])
49:             # レビュー番号に該当するレビュー文を表示する
50:             print(decoded_sequence)
51: 
52:             # 上記のレビューについて、レビュースコア(最高100、最低0)の計算を行う、
53:             x_test = keras.preprocessing.sequence.pad_sequences([x_train[d_index]], maxlen=maxlen)
54:             res = model(x_test)
55:             rev_score = res.numpy()[0][0] * 100
56: 
57:             # レビュースコア50以上であれば高評価、50未満であれば低評価とする、
58:             if rev_score >= 50 :
59:                 print("スコア="+ str(rev_score) + ":高評価をつけたレビューです。")
60:             else:
61:                 print("スコア="+ str(rev_score) + ":低評価をつけたレビューです。")
62: 
63:         except ValueError:
64:             print("1~25,000の整数を入力してください")
65:             continue
66: 
67:         # 強制終了コマンドが入った場合終了
68:         except KeyboardInterrupt:
69:             print("\nCtrl + C により強制終了")
70:             break

main関数は、実行コマンドに学習済モデルが含まれているがをまずチェックします。

python 02-imdb_test.py imdb_model

のようにコマンドが実行されていれば、末尾の引数に指定されている学習済モデルを読み込みます。引数にモデルが指定されていなければ、プログラムを終了します。

次に、レビュー文(トークン)のデータを生成する関数を実行します。generate_token()という関数は後述に定義されていますので、後ほど解説します。この関数によって、
 x_train, inverted_word_index
という2つの変数に、レビュー文の情報が格納されます。

x_trainは、数値化された単語(インデックス)で表現された、レビュー文が格納されており、inverted_word_indexは、このインデックスから元の単語を引き出すための単語-インデックスの対応表が格納されています。

その後、Whileループ処理に入り、ここから
(1)レビュー文番号を入力
(2)レビュー文を表示
(3)レビュー文のスコア(作品評価)を計算、表示
(4)以後、(1)~(3)繰り返し

という、レビュー評価プログラムが実行されます。

全体の構造としては、まずtry~exceptによる例外処理を実装しています。
・レビュー文番号に、数値以外のものや、指定範囲外の番号が入力されたら、再入力を求める。
・Ctrl + C による強制終了コマンドが入った場合に、プログラムを終了する。
という作りになっています。

tryの中にある処理が、レビューの評価・表示を行うメインの部分になります。

40:             # レビュー番号を入力
41:             input_key = input("\nレビュー番号を入力してください(1~25000の番号):")
42:             # レビュー番号を変数に退避
43:             d_index = int(input_key) - 1
44:             if not (0 <= d_index < 25000):
45:                 raise ValueError

ユーザーにレビュー番号入力を求め、入力された番号を保存します。指定範囲外の番号が入力されたら例外ValueErrorを発生させ、前述の例外処理に移ります。

47:             # レビュー文の単語をインデックスから引いて組み立て。単語間はスペースでつなげる
48:             decoded_sequence = " ".join(inverted_word_index[i] for i in x_train[d_index])
49:             # レビュー番号に該当するレビュー文を表示する
50:             print(decoded_sequence)

レビュー文章を表示させるための処理です。単語が数値(インデックス)表現されたレビュー文(x_train)から、インデックスを順次取り出し対応する元の単語文字列を、inverted_word_indexから引いて行きます。引いた単語を半角スペースを挟んで連結させていき、元のレビュー文章を組み立てていきます。

この処理は、以下のサイトを参考に作成しました。
https://keras.io/api/datasets/imdb/

52:             # 上記のレビューについて、レビュースコア(最高100、最低0)の計算を行う、
53:             x_test = keras.preprocessing.sequence.pad_sequences([x_train[d_index]], maxlen=maxlen)
54:             res = model(x_test)
55:             rev_score = res.numpy()[0][0] * 100
56: 
57:             # レビュースコア50以上であれば高評価、50未満であれば低評価とする、
58:             if rev_score >= 50 :
59:                 print("スコア="+ str(rev_score) + ":高評価をつけたレビューです。")
60:             else:
61:                 print("スコア="+ str(rev_score) + ":低評価をつけたレビューです。")

計算されたレビュースコアに対し、50以上なら高評価、50未満であれば低評価という判定を行いそれをスコアと共に表示します。

以上が、main関数の説明になります。上記のmain関数の実行は、ブログラムの終盤で命令されています。 

94: # main関数実行
95: if __name__ == '__main__':
96:     main() 

レビュー分類プログラム: トークンデータ生成

73: # imdbレビューのトークンデータ生成
74: def generate_token():
75:     # imdbレビューから学習用データを取得.
76:     (x_train, _), _ = keras.datasets.imdb.load_data(
77:         start_char=start_char, oov_char=oov_char, index_from=index_from
78:     ,num_words=vocab_size)
79:     # 単語とそれに対応するインデックスを取得
80:     word_index = keras.datasets.imdb.get_word_index()
81:     # 単語-インデックスをインデックス-単語にしてマッピング。新しくディクショナリを作る
82:     # `index_from` を引数にしてインデックスを `x_train` のものに合わせる
83:     inverted_word_index = dict(
84:         (i + index_from, word) for (word, i) in word_index.items()
85:     )
86:     # 開始インデックス:開始文字 (1:[START])
87:     inverted_word_index[start_char] = "[START]"
88:     # 除外文字インデックス:除外文字 (2:[OOV])を追加
89:     inverted_word_index[oov_char] = "[OOV]"
90: 
91:     return x_train, inverted_word_index

imdbレビューのトークン(単語)データを生成する関数です。この処理は、以下のサイトを参考に作成しました。
https://keras.io/api/datasets/imdb/

処理を順に見ていきます。

75:     # imdbレビューから学習用データを取得.
76:     (x_train, _), _ = keras.datasets.imdb.load_data(
77:         start_char=start_char, oov_char=oov_char, index_from=index_from
78:     ,num_words=vocab_size)

まず、kerasにデフォルトで実装されている、keras.datasets.imdb.load_dataという関数を使って、レビュー文のデータを取得、x_trainに保存します。このデータは、数値(インデックス)表現された単語からなるレビュー文の集合です。

開始文字列はstart_char(=1)、vocab_size(=20000)圏外の単語は、oov_char(=2)というインデックスが振られます。そのため、レビュー文に登場する単語のインデックスはindex_from(=3)より大きい番号が振られます。

例えば、レビュー文の出現頻度が多い順に次の単語があった場合

the = 4
and = 5
a = 6
of = 7

といった感じです。

79:     # 単語とそれに対応するインデックスを取得
80:     word_index = keras.datasets.imdb.get_word_index()
81:     # 単語-インデックスをインデックス-単語にしてマッピング。新しくディクショナリを作る
82:     # `index_from` を引数にしてインデックスを `x_train` のものに合わせる
83:     inverted_word_index = dict(
84:         (i + index_from, word) for (word, i) in word_index.items()
85:     )

keras.datasets.imdb.get_word_index() という関数を用いてimdbレビューの単語-インデックスの対応が保存されている対応表データを取得します。インデックスは単語の出現数の多い順番に、1,2,3と振られており

the = 1
and = 2
a = 3
of = 4

という状態で保存されています。これをインデックス-単語の対応表に逆転させる処理を行います。このとき、インデックス1は文頭文字、2は除外文字とされているためindex_from=3より大きい(4以上)のインデックス番号から、レビュー文単語を対応させていきます。

4 : the
5 : and
6 : a
7 : of

86:     # 開始インデックス:開始文字 (1:[START])
87:     inverted_word_index[start_char] = "[START]"
88:     # 除外文字インデックス:除外文字 (2:[OOV])を追加
89:     inverted_word_index[oov_char] = "[OOV]"

インデックス-単語の対応表inverted_word_indexにおいて、インデックス1,2について、対応する単語を登録します。

・インデックス1:[START] (文頭を表す)
・インデックス2:[OOV] (出現数上位20000件圏外の文字を表す)

これにより、inverted_word_index は以下のようなインデックス-単語の対応表データが格納されます。

1 : [START]
2 : [OOV]
3 :
4 : the
5 : and
6 : a
7 : of

こうしてできた inverted_word_index を main関数側に返します。
#3番目が何も入っていない状態なのが気になりますが、index_from = 3としているためにこうなっています。

以上が、レビュー分類プログラムの解説になります。

スポンサーリンク