Fast AI 2018 Lesson 4 後半

Fast aiのライブラリによる自然言語処理

タスク

  • 映画レビューのセンチメント分析
    • 入力:映画のレビュー文章
    • 出力:文章がPositiveかNegativeか

データセット

  • IMDBの映画レビュー
    • サイズ
      • 50000件
    • Attribute
      • レビューの文章
      • 1-10のスコア(4以下で否定的なレビュー、それ以上で肯定的なレビューとみなす)

モデリングの流れ

  1. 英語を理解する言語モデルを作成
  2. 上記のモデルをファインチューニングし、(入力:レビュー文章、出力:ラベル)となるようなモデルを作成

なぜ直接本題である2番目のタスクを直接解かずに1を経由するのか?

→ こちらのほうが効率的だから、とのこと。言語もわからないままある文字列について感情分析するよりも、言語を理解してから感情分析をするほうが遥かに早いということ。

さて、上記のステップ1, 2をpythonコードで示す。

1. 英語を理解する言語モデルを作成

テキストの前処理の方法を記述

import torchtext
from torchtext import vocab, data
from torchtext.datasets import language_modeling

# lower=true テキストをLowerCaseにする
# tokenize="spacy" トークナイザの指定
TEXT = data.Field(lower=True, tokenize="spacy")

ModelDataの作成

PATH = 'data/aclImdb/'
TRN_PATH = 'train/all/'
VAL_PATH = 'test/all/'
TRN = f'{PATH}{TRN_PATH}'
VAL = f'{PATH}{VAL_PATH}'

# バッチサイズ    
bs=64 
# Back prop through time
bptt=70 
FILES = dict(train=TRN_PATH, validation=VAL_PATH, test=VAL_PATH)
    
md = LanguageModelData.from_text_files(PATH, TEXT, **FILES, bs=bs, 
                                           bptt=bptt, min_freq=10)

ModelDataの作成後、TEXT変数にはテキスト内のユニークな単語の集合が格納される。また各単語は整数値へとマッピングされる。

各種パラメタの設定

em_sz = 200  # 各単語のEmbeddingのベクトルの長さ
nh = 500     # レイヤー毎の隠れ層の数
nl = 3       # レイヤー数

テーブルデータのカテゴリー値の学習時と同様に、単語についてEmbedding行列を作る。テーブルデータ時の学習時にはEmbeddingベクトルの長さの最大は50としていたが、今回はそれよりも大きな200としている。これは、Sundayのようなカテゴリー値よりも単語はずっと多くのニュアンスを含んでいるため。 通常、50-600のサイズを選ぶことが多い。

最適化関数の指定

opt_fn = partial(optim.Adam, betas=(0.7, 0.99))

NLPのタスクでは最適化関数にAdamを用いることが多く、今回もそれに則る。 betasに指定しているパラメータはモメンタムと呼ばれる。NLPのタスクではデフォルトの値(0.9)よりも小さい値でうまくいくことが多いそう。 これらについては次回の講義で説明される。

Learnerオブジェクトを作成・フィッティング

learner = md.get_model(opt_fn, em_sz, nh, nl, dropouti=0.05,
                           dropout=0.05, wdrop=0.1, dropoute=0.02, 
                           dropouth=0.05)
learner.reg_fn = partial(seq2seq_reg, alpha=2, beta=1)
learner.clip=0.3
  • 上記のドロップアウトに関するパラメータがいくつもあるが、詳しい説明は後半の講義で語るということで割愛されていた。過学習気味ならばこれらの値をまとめて大きく、反対に未学習傾向にあるならこれらの値を小さくすればよい、とのこと。
  • reg_fnも同様の理由で詳しい説明はないが、今の段階では過学習を抑えるためのおまじない、という認識でよいとのこと。
  • clipは一度に更新される重みの最大値を指定している。これにより一度の更新で重みが大幅に更新されて重みが収束しなくなるのを防ぐことができる。
learner.fit(3e-3, 4, wds=1e-6, cycle_len=1, cycle_mult=2)
learner.save_encoder('adam1_enc')
learner.fit(3e-3, 4, wds=1e-6, cycle_len=10, 
            cycle_save_name='adam3_10')
learner.save_encoder('adam3_10_enc')
learner.fit(3e-3, 1, wds=1e-6, cycle_len=20, 
            cycle_save_name='adam3_20')
learner.load_cycle('adam3_20',0)

learner.save_encoder('adam3_20_enc')
learner.load_encoder('adam3_20_enc')

いつもと同じようにSGDRでの最適化を行う。

2. 1のモデルを今回のタスク用にファインチューニングする

データの前処理・読み込み

# 元のモデルと同じテキストの集合・整数マッピングを用いる
TEXT = pickle.load(open(f'{PATH}models/TEXT.pkl','rb'))
# 
IMDB_LABEL = data.Field(sequential=False)

splits = torchtext.datasets.IMDB.splits(TEXT, IMDB_LABEL, 'data/')
md2 = TextData.from_splits(PATH, splits, bs)
  • splits pytorchの訓練データ
  • TextData.from_splitsfast aiのライブラリでsplitからそのままDataModelオブジェクトを生成できる

ファインチューニング

Differential Learning Rateを指定したり、最後の層以外を固定し学習→仕上げに全層を学習、と今までと同様の方法で行う。

m3.clip=25.
lrs=np.array([1e-4,1e-3,1e-2])
m3.freeze_to(-1)
m3.fit(lrs/2, 1, metrics=[accuracy])
m3.unfreeze()
m3.fit(lrs, 1, metrics=[accuracy], cycle_len=1)

結果

m3.fit(lrs, 7, metrics=[accuracy], cycle_len=2, 
       cycle_save_name='imdb2')
m3.load_cycle('imdb2', 4)
accuracy(*m3.predict_with_targs())
=> 0.94310897435897434