VS Code release 1.86 から最低システム要件が変更になった影響で、非対応となったOS(例:CentOS 7、Ubuntu 18.04)を搭載しているサーバへの接続が不可になりました。とはいえ、システム管理者でもない限り接続先サーバのアップデートは困難なので、クライアント側での対処手順を備忘録程度にまとめておきます。
Apple Siliconなら以下からダウンロードできます。
私は $HOME/Applications
以下に配置しました。
現行の環境と共存させるための作業を行います。
Visual Studio Code.app
が配置されているディレクトリに code-portable-data
を作成
cd $HOME/Applications
mkdir code-portable-data
xattr -dr com.apple.quarantine Visual\ Studio\ Code.app
アプリ起動後、左下歯車⚙️アイコンをクリック→検索欄に”update”と入力
空っぽの状態なので、再びRemote SSHをインストール & その他環境を整えて完了です。
argparse.ArgumentParser
を使ってコマンドライン引数を受け取るようにしていた。TrainingArguments
で管理し、わざわざargparse.ArgumentParser
に引数を追加するようなことはしたくない。下記のように、argparse.ArgumentParser
を継承したCustomArgumentParser
を定義し、parse_args()
内でargs, extras = self.parser.parse_known_args()
を呼ぶ所がキモです。
extras
にself.parser
に定義されていない引数のリストが格納されているので、これをself.hf_parser
に渡せば万事解決となります。
import argparse
from transformers import HfArgumentParser, TrainingArguments
class CustomArgumentParser(argparse.ArgumentParser):
def __init__(self):
self.parser = argparse.ArgumentParser()
self.hf_parser = HfArgumentParser(TrainingArguments)
# Define any custom arguments using argparse
self.parser.add_argument(
"--dataset_path",
type=str,
required=True,
help="Path to the dataset."
)
self.parser.add_argument(
"--tokenizer_name_or_path",
type=str,
required=True,
help="Path to the tokenizer."
)
self.parser.add_argument(
"--model_name_or_path",
type=str,
required=True,
help="Path to the model."
)
self.parser.add_argument(
"--cache_dir",
type=str,
default=None,
help="Path to the cache directory."
)
def parse_args(self):
args, extras = self.parser.parse_known_args()
training_args = self.hf_parser.parse_args_into_dataclasses(extras)[0]
return args, training_args
免責事項
本稿は他の記事と同じく個人的な経験・分析を基に執筆されています.本稿の記載内容を参考に執筆された学会論文やDissertation,レポート等の文法のミスが指摘されても一切の責任を負いかねます.くれぐれも自己責任でご利用ください.
分野によってかなりしきたりが異なります.本稿で想定しているのは自然言語処理や機械学習系の論文です.
少なくとも機械学習や自然言語処理分野では「現在形」で書かれることが多い.
先行研究がやったことは「過去形」
「最近やられたぞ」ということを強調したいときは現在完了も使う.(イギリスではアメリカよりも何かにつけて現在完了を使うことが多いように感じます.)
問題設定とかは「現在形」 提案手法の説明も「現在形」
実験設定・操作の説明は過去形で書く.
過去形を使う派と現在形を使う派の二通りの流儀があるように思います.おそらく前者は実験は過去に行われたものなので,その得られた結果も過去のものという認識で過去形にしているのだと思います.後者は実験手順には再現性があるので,その操作を行うと論文に記載の結果が必ず得られるという説明の意味で現在形にしているのだと思います.いずれにせよどちらかに統一して書くのが無難だと思います.
論文中の”その場”で分析している体なので現在形で書くものと思われます.
説明文なので現在形になります.
論文を書き出す前の出来事(先行研究等):過去形
論文中で取り組んだこと(結果の分析等):現在完了形や場合によっては過去形
例: we have introduced ~~~
なお現在完了形が作るthat節の中は時制の一致を引き起こしません.
単に強調するだけならダブルコーテーションよりもイタリック(斜体)にしておくのが無難です.
文中のセミコロン「:」は強調や言い換えの意味で使われます.
時制の一致は従属節に適応されます.なので”that S + V”のような典型的なケースだけでなく,一応関係代名詞等も含まれます.
冠詞をつけても問題はありませんが,大抵の場合は省略されます.ちなみに順番は,both + (冠詞) + 名詞 です.
all + 冠詞 + 名詞の順番です.
時と場合によるので,冠詞をつける場合とつけない場合でググってヒット件数の比較をすることをお勧めしますが,基本的なルールは下記の通りです.
一つの単語として発音するものには冠詞は不要
例:COVID-19
アルファベットで発音するものには冠詞をつける
例:TPP (Trans-Pacific Partnership)
冠詞を省略する,telegraphicに書く流派があるように思います.したがって,投稿する学会の傾向に合わせて冠詞を省略するか決めると良いでしょう.
イギリスで学位論文等を執筆する際はくれぐれもイギリス英語で書くようにしましょう.アメリカ英語で書くと査読する方の心象を損ねる可能性があります.
基本的にネイティブでない人がどれだけ言い回しが自然かどうかを自分で判断する術は,ググる意外に手段がないと思います.気になる言い回しが出たら,ダブルコーテーションで囲って検索にかけましょう.
“Courtesy”を常に忘れないで対応する.
]]>参考:COLING 2018 - PC chairs report back: On the effectiveness of author response
機械学習を使った研究や実応用で避けて通れないのが、不均衡なデータセットを扱うことです.具体例としては,機械学習全般で言えば異常検知や,自然言語処理分野ではフェイクニュースの検知,音声処理分野では音声区間検出等,数え始めたらキリのないほどありそうな気がしてきます.
不均衡データを用いて機械学習モデルを学習させること自体は,通常の均衡データを用いるときとほぼ変わりない手順で実行できると思います.問題は「分析」です.モデルの学習がうまくいっているか,学習させたモデルが検証用データ・テストデータに対して有効であるかどうか,この二つをどのように確認するかが不均衡データを扱うときの鍵になってきます.そこで本稿の前半部分では,よく論文等で目にするメジャーな評価手法を掻い摘んで紹介します.後半部分では,不均衡データを扱う分類問題に実際に取り組み,その結果を前半部分で扱う評価手法に基づいて評価・分析することで,各指標についての理解を深めることを目指します.
定量的な数値で評価するパターンとしては,精度 (Accuracy)・適合率 (Precision)・再現率 (Recall)・F1値 が代表的なものとして挙げられます.それに付随して,混同行列 (Confusion matrix) が用いられる場合もあります.何度も目にするこれらの評価指標ですが,念のため以下で定義とともに抑えておきます.
Accuracy は精度(正解した割合)なので,正解した数を全体数で割ることで求められます.
TP・TN・FP・FN の説明も以下に載せておきます.
後ほど実験でも確認しますが,Accuracy は不均衡データの分析には向いていません.というのも,データセットのラベル分布が偏っていると,分子の正解数が多数を占めるクラスの影響を強く受けてしまうため,評価値が良くなってしまう傾向にあるからです.例えば,データセットのうち 80% が Positive で, 20% が Negative なラベルを持つとき,常に Positive を返す分類器の精度は 80% になります.一見すると,それなりに良いスコアに見えてしまいますね.
Precision は予測結果が Positive であったデータのうち,どの程度のデータが本当に Positive であるかを示す指標です.
Recall は Positive なラベルを持つ正解データのうち,どの程度モデルが Positive と判定できたかを示す指標です.True Positive Rate (TPR) とも呼ばれます.
F1値は Precision と Recall の調和平均 (harmonic mean) で求められます.Precision と Recall はトレードオフの関係にあると言われているので,その二つの調和平均を取ることで統一的にモデルの性能を評価しようという指標です.
F値には,$F_\beta$ という重みつきの派生版もありますが,ここでは扱いません.
混同行列(Confusion Matrix)は,上述の TP・TN・FP・FN の4つの分類を表を使ってわかりやすく表現したものです.実際には表ではなくヒートマップが使われることがほとんどかと思います.混同行列の数値の見せ方には二通りあり,「サンプル数をそのまま表示するパターン」と,「TP と FN,TN と FP の組ごとに正規化をして表示するパターン」があります.どちらを使うかは,タスク次第といった所でしょうか.なお,以下の実験では両方とも出力しています.
続いて,曲線の形状やその曲線の作る面積の大きさで評価するパターンの紹介に移ります.代表的な指標としては,受信者動作特性曲線(Receiver Operating Characteristic Curve)と,Precision-Recall 曲線,Detection Error Tradeoff 曲線の3つがあります.
受信者動作特性曲線(ROC曲線)は,横軸に False Positive Ratio (FPR): $\frac{FP}{TN + FP}$ をとり,縦軸に True Positive Ratio (TPR): $\frac{TP}{TP + FN}$ をとる曲線です.予測結果が Positive か Negative かどうかを判断する閾値 (threshold) を $[0, 1]$ の範囲で少しずつずらして,各閾値における FPR と TPR を求めることで描画できます.ここで,閾値よりも大きい値を持つ予測結果は Positive とみなし,小さい値を持つ予測結果は Negative とみなします.
閾値が 0 に近いときは,ほとんどの予測結果が Positive であると判断されるので,TP と FP が大きくなり,TPR と FPR ともに 1 に近づきます.一方で,閾値が 1 に近いときは,ほとんどの予測結果が Negative であると判断されるので,TP と FP が小さくなり,TPR と FPR は 0 に近づきます.
閾値の範囲は,$[0, 1]$ に限りませんが,深層学習においてはシグモイド関数やソフトマックス関数を出力の関数として使うことが多く,これらの関数の値域は $(0, 1)$ であることから,ここでは $[0, 1]$ としています.
ROC曲線と FPR (横軸) で囲まれた部分の面積を Are Under Curve (AUC) と言い,モデルの良し悪しを評価するのに用いられます.AUC の最大値は 1 で 最小値は 0.5 になります.AUC は 1 に近づけば近づくほど良く,0.5 はランダム分類を意味します.
ROC曲線はモデルがベースライン(ランダム分類)よりも有意に分類ができているかどうかを直感的に確認するのに適していますが,次に紹介する Precision-Recall 曲線 (PR曲線)よりも不均衡データへの反応が鈍い傾向にあります.つまり,あまり正確に分類できなかったケースでも,AUC がそれなりに大きくなることがあります.この場合,モデル間比較が難しくなってしまうので,PR-AUC も併用した方が良いです.
Precision-Recall Curve (PR曲線) は,横軸に Recall を取り,縦軸に Precision をとった曲線です.描画の手順はROC曲線と全く同じです.閾値が 0 に近いと FN の数が減り,TP と FP の数が増加するので,Precision はノイズ (FP) が増えるため悪化し,Recall はノイズ (FN) が減るので改善します.他方,閾値が 1 に近づくと FP は小さくなり,FN が大きくなることから,Precision は改善し,Recall は 悪化します.
ROC曲線と同様に曲線の作り出す面積が1に近いほど良いモデルと言えます.ただし下限は 0.5 ではなく,0です.なお実験では AUC の計算に,Average Precision (AP) を活用しています.
参考: (AP - Wikipedia)
Detection Error Tradeoff Curve (DET曲線) は,どの程度モデルが誤検知・未検知したかを分析するのに向いている指標です.若干マイナーな曲線ですが,音声処理等の分野で活用されています.DET曲線は横軸に False Positive Rate (FPR): $\frac{FP}{TN + FP}$ をとり,縦軸に False Negative Rate (FNR): $\frac{FN}{TP + FN}$ を取ります.FPR は False Alarm Rate (FAR) とも呼ばれ,FNR は Miss Rate と表記されていることもあります.なお描画の仕方は上述の2曲線と同じく,閾値を少しずつずらして対応する FPR と FNR をプロットしていく形です.
誤検出率:FPR も, 未検出率:FNR もできる限り小さくするのが目標となるので,DET曲線は原点に近づけば近づくほど良い傾向であるといえます.
Equal Error Rate (EER) は,FPR と FNR が等しくなる点 (値) を指します.EER は小さければ小さいほど良いモデルであると言える指標です.
誤検出も未検出も同程度重要視している場合には,EER に対応する閾値をモデルの閾値として使うという手もありかもしれません.しかし DET曲線 と, EER はデータの偏りを考慮していないので,不均衡データを扱う際にEERをベースに閾値を決めるのは注意が必要です.この場合,FPR と FNR の各重要度を考慮したコスト関数を設定すれば,ベストな閾値を求めることができると思います.
もしそのまま EER から逆算した閾値を使うと,Precision か Recall のいずれかが極端に良くなり,もう片方が極端に悪くなるという現象が発生すると予想されます.
一通り評価指標を見てきたところで,簡単な分類問題を扱って各指標の見え方の違いについて確認していきます.
今回は MNIST データセットを活用して,入力画像(数字)が偶数か奇数かを判定するタスクに取り組みます.前処理としてデータセットに付与されているラベル (0-9) を偶数奇数 (偶数: 1,奇数: 0)に張り替え,わざと偶数ラベルを持つデータを減らすことで不均衡データセットに仕立て上げます.
モデルは隠れ層を2層(256・128次元)持つ,シンプルな順伝播型ニューラルネット (FFNN) にしました.実装には,PyTorch をメインで使い,評価指標等の計算には一部 scikit-learn を使っています.実装は,GitHub にて公開してあります.
単に不均衡データセットを分類する Toy Problem だと面白味に欠けるので,Binary cross-entropy loss の Positive クラスに対応する項に重み付けをして,その振る舞いを実験で観察することにします.具体的には次の式(5)のような損失関数になります.
$N$: サンプル数,$y_i$: $i$ 番目のサンプルの正解ラベル,$x_i$: $i$ 番目のサンプルの入力データ,$p(x_i)$: 入力データ $x_i$ が モデルによって Positive であると判断される確率 (= シグモイド関数の出力),$w_{\rm pos}$: Positive クラスの重み.
重み $w_{\rm pos}$ は,Negative ラベルを持つデータ数 $n_{\rm neg}$ を Positive ラベルを持つデータ数 $n_{\rm pos}$ で割った値: $\frac{n_{\rm neg}}{n_{\rm pos}}$ として求められます.
今回のタスクの場合,重み付けがない状態だと,偶数データの分類にミスをしてもデータ数が少ないため,損失関数にそこまで大きな影響を及ぼしません.一方で重み付けをした状態で偶数データの分類にミスをすると,奇数データの分類にミスをしたときよりも大きなペナルティを被るので,モデルが不均衡データの性質をより意識できるようになると期待されます.
実験結果は,均衡状態での結果,不均衡状態での結果,不均衡状態で損失関数に重み付けを行ったときの結果の3つに分けて紹介します.
均衡状態での MNIST データセットを使った偶数奇数の分類性能は,次の表のようになりました.軒並み良い数値を出しているのを見ると,簡単なタスクだったようです.
データ | Accuracy | Precision | Recall | F1 | AUC | AP | EER |
---|---|---|---|---|---|---|---|
Validation | 0.983 | 0.986 | 0.980 | 0.983 | 0.998 | 0.997 | 0.016 |
Test | 0.987 | 0.989 | 0.984 | 0.986 | 0.998 | 0.998 | 0.014 |
続いて偶数ラベルを持つデータを元の5%まで減らしたときの結果を見ていきます.比較用に訓練用データのラベル分布に基づいてランダムに分類を行う分類器の結果を Random として載せました.
データ / モデル | Accuracy | Precision | Recall | F1 | AUC | AP | EER |
---|---|---|---|---|---|---|---|
Validation | 0.985 | 0.946 | 0.719 | 0.817 | 0.996 | 0.933 | 0.024 |
Test | 0.988 | 0.955 | 0.780 | 0.859 | 0.996 | 0.958 | 0.020 |
Random | 0.915 | 0.056 | 0.053 | 0.054 | 0.505 | 0.047 | NA |
偶数データの減量前の結果と比較すると主に次の3点が読み取れます.
Accuracy と AUC があまり変化していない.
これは評価指標の説明時に紹介した傾向がそのまま現れたものです.Accuracy と AUC は,多数派を占める Negative(奇数)ラベルを持つデータの分類結果に強く影響されがちなので,奇数データの分類が上手く行っていればそれなりに高いスコアがでてしまいます.特に Accuracy はランダム分類でも 0.915 とかなり高いスコアが出てしまっています.
減量後の Recall と F1 が悪化している.
混同行列 (Confusion Matrix) を見ればわかりやすいのですが,この実験では,偶数ラベルを持つデータの分類にかなり失敗しています.(28% の偶数データが誤分類されています.)そのため,偶数ラベルが付与されているデータの正解率を示す,Recall の値も悪化したと考えられます.また F1 は Precision と Recall の調和平均なので,どちらかが悪化するとそれに伴って悪化します.
最後に損失関数を式(5)に置き換えたときの分類結果を見ていきます.明らかな違いとしては,Precision が悪化した代わりに,Recall が大幅に改善したことが挙げられます.ただし,その他の指標は同程度か若干悪化しています.したがって,モデルが偶数ラベルを持つデータをより意識できるようになったものの,その分ノイズ (誤検出) が増えてしまったと言えます.
更に言えば,閾値を調整すれば損失関数に重み付けをしなくても類似の結果を再現することができると思われるので,果たしてこのタスクにおいて損失関数への重み付けに意味があるのかどうかは謎です.
データ | Accuracy | Precision | Recall | F1 | AUC | AP | EER |
---|---|---|---|---|---|---|---|
Validation | 0.985 | 0.781 | 0.942 | 0.854 | 0.996 | 0.938 | 0.024 |
Test | 0.983 | 0.777 | 0.890 | 0.830 | 0.991 | 0.914 | 0.049 |
本稿では種々の評価指標について一通り確認し,簡単な分類問題を通して各指標の振る舞いの違いを観察しました.実装は GitHub にて公開してあります.お気軽にご利用ください.
]]>チェックポイントのセーブは行わない仕様になっているので,研究等に活用する際はくれぐれもご注意ください.
著者: Cadilhac et al.
所属: IRIT Univ. Toulouse
出典: EMNLP 2013 (https://www.aclweb.org/anthology/D13-1035/
交渉ゲームにおいてプレイヤーの行動を予測する手法を提案した論文であり,「Settlers of Catan ゲーム」の交渉ログをアノテーションしたデータを構築して検証を行った.
アノテーション粒度が既存研究よりも細かい
単に Accept や Reject だけをアノテーションするのではなく,dialogue act にどのような物品をやりとりしたかを示す属性: Resource を設けている.
交渉対話のアノテーションについておそらく初めて扱った論文
なお,論文からだけではデータが公開されているかどうかは不明.
3つのフェーズに分けてアノテーションした.コーパスの規模は 511 dialogues と各交渉ログの長さを勘案すると若干少なめ.
交渉ダイアログを「ターン:EDU」に分割
各ターンを Elementary Discourse Unit(EDU)と呼ばれる単位に分割する.EDU には発言者が予め付与される.
Dialogue Act Annotation
交渉対話なので,Dialogue Act は “offer, counter offer, accept, refusal” に加えて, “other” からなる.各EDUごとに Dialogue Act のアノテーションがされている.
other は 交渉とはあまり関係のない行動について付与するもの.
Dialogue Act については,SLP3 の26章を参照するとよい.
Resource Type Annotation
各EDUに付与した Dialogue Act の具体的な内容をアノテーションしており,交渉でやり取りする物品についての内容と取引の属性をまとめている.具体的には,Givable・Not Givable・Receivable・Not Receivable の4つがある.
カタンゲームが多者間交渉であることから,取引先の関係性を明示するため(照応解析)に Anaphora Link という属性もアノテーション対象に含まれている.
以上のフェーズを考慮してアノテーションを行った結果が以下の表となっている.
Dialogue act と resource の予測は3つのフェーズごとにモデリングした.
Dialogue Act の特定を行う.
あるEDUはそれより前のEDUと依存関係がある:(例)Accept や Reject は Offer や Counter Offer のあとに続くことが多い.
→ 系列ラベリングとして考えられるので, Conditional Random Field (CRF) が Dialogue Act の特定に使えるという仮説.
Resource の範囲を特定する.
交渉の最中にやりとりした物品の内容を特定するために必要なフェーズ.単一カテゴリの交渉であるためやり取りする内容が決まっていることから,予め決めた辞書に語句が含まれているかどうかだけを検知する.
Resource の属性を特定する.(つまり前述の Givable・Not Givable・Receivable・Not Receivable を CRF を使って推定.)
プレーヤーの行動の予測には CP-net を活用した.CP-net はグラフィカルモデルの一種.
F値(マクロ平均)と精度が主に使われている.CP-netについては,混同行列の値も求めている.
基本的に各手法はベースラインとして比較されている手法を上回っている.
]]>前回は交差検証について紹介をしました。今回は、ゼロからKerasシリーズの総まとめとして、ハイパーパラメータチューニングについて紹介します。実装例としては、Keras Tuner と呼ばれる、Keras用のハイパーパラメータ自動最適化ツールを活用した実装を紹介します。
ハイパーパラメータとは、最適化アルゴリズムによって最適化できないパラメータのことを指します。例えば、学習エポック数やバッチサイズ・隠れ層の次元数、学習率などがあたります。
最適化アルゴリズムでは最適化できないハイパーパラメータを最適化するには、人手ではなく、専用のツールを使うのが便利です。専用のツールはたくさん種類があるので、フレームワーク等の状況に応じたものを取捨選択すると良いです。
例によって、画像データセット: CIFAR10 の分類実験を題材にハイパーパラメータチューニングを実施してみます。基にするソースコードは前回の交差検証で用いた、cnn.py
とします。冒頭で述べたように、Keras Tuner を活用して実装を行います。
参照: (GitHub: cnn.py)
Keras Tuner は、その名の通り Keras 用に開発中のハイパーパラメータ最適化ツールです。現時点で対応している最適化手法は、「ランダムサーチ」と Hyperband
になります。今回はランダムサーチを適用してチューニングを行います。ランダムサーチは決められた範囲内からパラメータをランダムに選択し、試していく手法です。
なお、Keras Tuner は、TensorFlow 2.0 以降のTensorFlowに統合されたKeras (tf.keras
) に対応していることが明記されているので、これまで本ブログで扱ってきたいわゆる無印Keras とは少し異なります。そのため、本記事では、無印Kerasユーザの方でも、tf.keras
を使って最低限動かせるような構成にしてあります。
まずは、Keras Tunerをインストールします。Python 3.6〜 と TensorFlow 2.0 が Requirementsとして指定されています。
git clone https://github.com/keras-team/keras-tuner.git
cd keras-tuner
pip install .
これまで活用してきた、cnn.py
は tf.keras
に互換性がないので、tf.keras
向けに書き換えます。
ライブラリ読み出し部分は至って簡単に移植できます。今までのソースコードに、tensorflow.
を追加するだけです。
from tensorflow.keras import Model
from tensorflow.keras.layers import Input, Dense, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.utils import to_categorical
import numpy as np
Keras Tuner のライブラリも読み込ませます。
from kerastuner.tuners import RandomSearch
モデルの定義部分は、Keras Tuner 向けに書き換える必要が出てきます。具体的には、次の2点について改造を施します。
hp
: ハイパーパラメータ を取らせるhp.Range
に置き換える。hp.Range
の使い方は次の表の通りです。
引数 | 説明 |
---|---|
min_value |
チューニングしたいパラメータの最小値を指定します。 |
max_value |
チューニングしたいパラメータの最大値を指定します。 |
step |
インクリメントしていく値(幅)を指定します。 |
以下が、Keras Tunerに対応させたソースコードとなります。
def build_model(hp) -> Model:
# モデル定義
_input = Input(shape=(32, 32, 3))
_hidden = Conv2D(filters=hp.Range('filters', min_value=10,
max_value=40, step=10),
kernel_size=hp.Range('kernel_size', min_value=2,
max_value=5, step=1),
strides=(1, 1), padding='valid', activation='relu')(_input)
_hidden = MaxPooling2D(pool_size=(2, 2))(_hidden)
_hidden = Flatten()(_hidden)
_hidden = Dense(units=hp.Range('units', min_value=50,
max_value=200, step=50),
activation='relu')(_hidden)
_output = Dense(10, activation='softmax')(_hidden)
model = Model(_input, _output)
model.compile(optimizer=Adam(hp.Choice('learning_rate', values=[1e-2, 1e-3, 1e-4])),
loss='categorical_crossentropy', metrics=['accuracy'])
return model
データセットの読み込み部は一切変更する必要はありません。無印のKeras用に実装したソースコードをそのまま活用できます。
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train = x_train.astype('float') / 255.
x_test = x_test.astype('float') / 255.
y_train = to_categorical(y_train, num_classes=10)
y_test = to_categorical(y_test, num_classes=10)
ここからが本題になります。まずは、ランダムサーチを行うためのインスタンス tuner
を生成します。
tuner = RandomSearch(build_model, objective='val_accuracy',
max_trials=5, executions_per_trial=1, directory='tuning', project_name='log')
RandomSearch クラスの主な引数は次の表の通りです。
引数 | 説明 |
---|---|
hypermodel |
HyperModel クラスのインスタンスか、ハイパーパラメータを引数にとる、Model インスタンスを返す関数を与えます。 |
objective |
何を基準に最適化を行うかを指定します。 |
max_trials |
最大何回探索を行うかを指定します。 |
executions_per_trial |
一回の探索で何回学習を繰り返すかを指定できます。 複数回指定すると、結果を安定させる効果があります。 |
directory |
ログの保存先ディレクトリを指定します。 |
project_name |
ログの保存先ディレクトリ2を指定します。 つまり、 directory /project_name 下にログファイルが保存されます。 |
RandomSearch クラスのインスタンスを生成したら、ランダムサーチを実行できます。その前に、search_space_summary()
メソッドを使って、探索候補を確認することができます。
tuner.search_space_summary()
実行結果:
[Search space summary]
|-Default search space size: 4
> filters (Range)
|-default: None
|-max_value: 40
|-min_value: 10
|-step: 10
> kernel_size (Range)
|-default: None
|-max_value: 5
|-min_value: 2
|-step: 1
> units (Range)
|-default: None
|-max_value: 200
|-min_value: 50
|-step: 50
> learning_rate (Choice)
|-default: 0.01
|-values: [0.01, 0.001, 0.0001]
探索候補の範囲が定義した通りに表示されていますね。
では、本題の探索に移っていきましょう。探索は、search()
メソッドを使います。このメソッドは、model.fit()
に対応しています。
tuner.search(x_train, y_train, epochs=10, validation_data=(x_test, y_test))
チューニングの結果は、results_summary()
で確認できます。
tuner.results_summary()
実行結果:
[Results summary]
|-Results in tuning/log
|-Ran 5 trials
|-Ran 5 executions (1 per trial)
|-Best val_accuracy: 0.6094
実行結果から、最高精度を記録したパラメータの設定は下記の設定のときでした。
今回は5回しか探索をしていないので、最適なパラメータが得られたとは言い難いです。実データでパラメータチューニングを行う際は、探索空間の大きさに応じて探索回数を適度に増やすのが無難です。
"values": {"filters": 10, "kernel_size": 3, "units": 50, "learning_rate": 0.001}
今回は使いませんでしたが、
get_best_models()
で最良の結果を残したモデルをピックアップできます。(このメソッドを使わなくても、重みファイルは自動で保存されます。)
今回は、Keras Tunerを活用したハイパーパラメータチューニングの方法について紹介をしました。簡単に使えるので便利ですね。
他にも便利なチューニングツール(例: Optuna)が数多く公開されているので、確認してみると良いかもしれません。
ソースコードは、GitHubにて公開してあります。
]]>機械学習を活用した研究に取り組んでいると,論文中に “k-fold CV” や “cross-validation”,「交差検証」 といった表記を見かけるときが多々あると思います.また実際に機械学習モデルを構築してその性能を評価したいときに,より厳密に性能を測定するには,「交差検証」の適用は避けられません.
そこで,本稿では,「ゼロから作るDeep Learningとともに学ぶフレームワーク」で用いられていたソースコードを基に,交差検証の基本的な考え方とその適用方法について紹介します.ソースコードとともに本稿の内容を理解することで,交差検証に関する基礎的な知識が身につくものと想定されます.
それでは交差検証の解説から始めていきましょう.
交差検証とはデータセットを細切れに分割して,異なる組合せで複数回機械学習モデルを学習させて,それらの平均をとることによりモデルの性能を測る手法のことを指します.
交差検証を使わない場合,データセットを学習用 (training set)・検証用 (validation/development set)・テスト用 (test set) の3つに分割して,モデルの性能を評価することが多いと思われます.しかしながらテスト・検証用セットを設けることで,実際に学習に使えるデータ数が減少してしまうというデメリットがあります.また最終的な評価結果もデータセットのサンプリング方法によって左右される恐れもあります.
交差検証を用いることで検証用セットだけのためのデータを確保する必要がなくなるため,データセットを存分に活用することができ,異なる組合せで複数回学習させた結果の平均をとるので,より正確な性能評価が可能となります.
交差検証の主な種類としては,以下の二つが挙げられます.
K分割交差検証 (k-fold cross validation)
データセットを $k$ 個に分割をし,$k-1$ 個のデータのかたまりで機械学習モデルを学習させて,残りの$1$ 個の未知データでモデルの検証(テスト)を行います.この流れを異なる組合せで $k$ 回行い,最終的な結果はそれぞれの組合せでの実験結果の平均として得られます.
Leave One Out 交差検証
Leave One Out 交差検証はその名の通りデータを一個だけ残して交差検証を行うことを指します.つまり,K分割交差検証で,$K = n$ ($n$ はデータ数)とすることを意味しています.しかしこの手法はかなり計算コストが高いので,あまり深層学習を適用している研究分野で使われている例を見たことがありません…
念の為確認しておくと,機械学習モデルの学習・評価の流れとしては,まず学習用セットでモデルを学習させて,検証用セットで学習したモデルを評価(early stopping の適用 や ハイパーパラメータの最適化など)します.最後に検証用セットで良好な結果を残したモデルについて,最終的な評価を下すためにテストセットを一度だけ適用します.(テストセットはある学習済みモデルにとって未知(unseen)であることが求められます.)
K分割交差検証の例として,データセットを3分割にしたときの,3分割交差検証の流れを見ていきましょう.
流れとしては,まずデータ①と②で学習させたモデルをデータ③で性能評価し,その後データ①と③で学習させたモデルをデータ②でテストし,最後にデータ②と③で学習させたモデルをデータ①で評価します.最終的な結果は各性能評価の平均となります.
より厳密にモデルの評価を行う場合はあらかじめデータセットを2分割にして,うち片方をテストセットとしてキープしておきます.(ホールドアウトともいう.)最終的な性能評価はテストセットで行います.
ここまで厳密にやるのは,イメージ的にはコンペ系のタスクが多いのかなと感じます.この場合,テストセットはリークを防ぐために公開されていないケースが多いです.
これまで見てきた交差検証の例はデータセットのクラスの偏りについて考慮していませんでした.分類問題に対して交差検証を適用する場合には,データセット中の各クラスの出現確率に注意を払う必要がでてきます.具体的にはデータセットが不均衡となっている場合に,通常のK分割交差検証ではなく,「層化K分割交差検証」(stratified k-fold CV)を適用する必要があります.
層化K分割交差検証は,各fold(データのかたまり)での各クラスの出現確率が分割前のデータでの出現確率とほぼ同一になるようにデータを分割して,K分割交差検証を適用する手法です.これによってあるfoldにデータが偏ったり,全く存在しないといった事象を防ぐことができ,より正確に精度の計測を行うことができます.
「ゼロから作るDeep Learningとともに学ぶフレームワーク」で紹介したCIFAR10の分類モデルのソースコードを,k分割交差検証を適用できるように改造していきましょう.
K分割交差検証の適用方法は簡単で,scikit-learnに含まれている,model_selection.KFold
クラスを使えば良いだけです.層化K分割交差検証の場合は,model_selection.StratifiedKFold
クラスになります.
まず,KFold
クラスのインスタンスを生成します.
from sklearn.model_selection import KFold
kf = KFold(n_splits=5, random_state=1234)
KFold
の引数は次の表のようになっています.
引数 | 説明 |
---|---|
n_splits |
データセットを何分割するかを指定します. |
shuffle |
データセットを分割前にシャッフルするかを指定します. デフォルトは False になっています. |
random_state |
データをランダム選択する際のシード値を指定できます. |
次に,KFold
クラスのメソッドである,split
を使ってデータのインデックスを受け取るだけです.擬似コードは以下のようになります.
for train_index, val_index in kf.split(x_train, y_train):
model = build_model()
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(x=x_train[train_index], y=_y_train[train_index], batch_size=100, epochs=10, verbose=1)
_history.append(model.evaluate(x=x_train[val_index], y=_y_train[val_index], batch_size=100))
split
はジェネレータになっているので,各foldでデータのインデックスを提供してくれます.したがって,与えられたインデックスをそのままデータセットのNumPy配列に与えれば,自動的にデータが選別されます.
以上の一連の流れをまとめたソースコードは,cross-validationディレクトリ下に run.py
として置いてあります.基になっているソースコードは,ゼロから作るDeep Learningとともに学ぶフレームワークで実装した cnn.py
です.
参照: (GitHub: run.py)
run.py
を用いた学習のさせ方は簡単で,python run.py
で実行できます.出力としてはコマンドライン上に学習の経過が表示されるようになっており,最後に全foldでの結果の平均と標準偏差を出力させるようになっています.出力例は下記の通りです.
loss: 1.0935191447734833 ± 0.05668393814738748
acc: 0.6322800013422967 ± 0.012688641313732466
(注意) 学習にはそれなりの時間を要するので,ノートパソコン等で動作させる際には,n_splits
を小さい値: 3などに設定してご利用ください.
run.py
で層化K分割交差検証を適用したい場合には,main関数の引数 stratified
に True
を与えれば適用可能となります.ただしCIFAR10データセットは不均衡データセットではないため,実行結果への影響は軽微なものです.
今回は交差検証について簡単に紹介しました.ゼロからKerasシリーズとの連携を想定して作成したので,GitHubの実装例もぜひ合わせて確認することをおすすめします.
]]>
著者: H. He et al.
所属: Computer Science Department, Stanford University
出典: EMNLP 2018
自然言語で交渉可能なエージェントを作成する際に,交渉戦略とその実行部(発話機構)を切り分けて考えることで,従来手法よりもタスクの合意成功率を向上させ,発話に見られる人間らしさを向上させることに成功した.
単にend-to-endでモデルを学習させるのではなく,「Parser・Manager・Generator」の三つにモデルを分割して捉えた点.(従来手法は,seq2seqに基づいて単に学習させるだけだった.)
戦略を制御する部分は,従来の単語ベースではなく,coarse-dialogue acts:粗い(要約)対話情報に依存しているため,戦略の制御がしやすいというメリットがある.
図引用: Decoupling Strategy and Generation in Negotiation Dialogues
モジュール型のモデルを提案.戦略と生成を切り離して学習させることで,戦略の制御しやすさを維持しながらも,発話の人間らしさが低下しないようにできている.
クラウドソーシングサービスである,Amazon Mechanical Turk (AMT)を活用.クレイグスリストから交渉シナリオをスクレイピングして,AMTでそのシナリオに基づいた二者間の価格交渉を行わせることで,品物売買データセットを構築.
Deal or no deal データセットと,著者らが作成した,Craigslist Bargain データセットにより実験を行なっている.人間らしさを5点満点でAMT上で評価.また,task-specificな値(効用,合意率,公平さ,対話の長さ)も計算.
教師あり学習時は,著者の提案するモジュール型のモデルを使うとよいことが示されている.また,強化学習時も,wordベースではなくdialogue actベースにすると,報酬を最適化しつつ,発話の自然さも維持できることが示されている.
交渉エージェントは以下の二つの点をうまく実行できる必要がある.
先行研究は「戦略」に着目したものが多かった.また,近年では,end-to-endに交渉戦略と言語生成の両方を同時に,人間同士の交渉を扱ったコーパスからニューラルネットワークベースのモデルにより学習を行う研究がでてきている (Lewis 2017, He 2017).
end-to-endに学習を行うモデルの問題点として,以下が挙げられる.
そこで,著者らは戦略と生成を分離する手法の提案を行なっている.これにより,同じ生成器を用いていても,戦略を変更することができる.(例:効用を最大化する・公平な合意案を導く.)
提案手法のフレームワークは三つのモジュールからなる.
Parser
交渉相手の発言の意図やその変数:(価格)を解析
Manager
交渉エージェントの次の戦略(行動)を生成
Generator
戦略と発話履歴を基に返答文を生成
図引用: Decoupling Strategy and Generation in Negotiation Dialogues
先行研究で用いられているデータセットは,クローズドなドメイン:物の山分け交渉のみを扱っている.こうした設定は,実社会(real world)とは程遠い.そのため,craigslistと呼ばれる,クラシファイドコミュニティサイトから,物の売り買いに関するポスティングを抽出して,交渉シナリオを作成した.この交渉シナリオを基に,Amazon Mechanical Turk(AMT)を活用して,二者間での物の価格交渉を実施した.この交渉ログが本論文で用いられている,Craigslist Bargain データセットである.
獲得効用 & 人間らしさで評価.
→ AMTを活用して,A/Bテストを行なって評価.
Settlers of catan データセットや,Deal or no deal データセットは,ゲーム形式の交渉対話データセットになっている.このため,対話が直接的(オファー内容をそのまま伝えてしまう)であった.本来の実社会の交渉では,「説得」や「情報収集」が入るので,先行研究のデータセットはあまり現実的でない.
クレイグスリストデータセットでは,売り手と買い手になりきって,商品の売買を行う.より自然な状況設定なので,より現実的になるという主張.
交渉シナリオの生成は,クレイグスリストの6カテゴリを選択した.(housing,furniture,cars,bikes,phones,electronics).買い手の目標価格は,リスティング価格の0.5,0.7,0.9倍で設定されている.
交渉ログの例とデータセットの統計は以下の表の通り.
図引用: Decoupling Strategy and Generation in Negotiation Dialogues
先行研究とのデータセットの比較結果は次の通り.多ジャンルのデータセットなので,語彙数が多くなっている.さらに,発話あたりの単語数も多い.
図引用: Decoupling Strategy and Generation in Negotiation Dialogues
dialogue agentの役割:
入力:発話履歴 $x_1, x_2, \dots, x_{t-1}$ と交渉シナリオ $c$
出力:返答 $x_t$ の確率分布
coarse dialogue act:
$x_t$に対して,coarse dialogue act $z_t$ が設けられている.
例:$x_t$→ “I am willing to pay $15.” $z_t$→ “propose(price=15)”
parser
入力: $x_{t-1}$, 対話履歴 $x_{<t}$, $z_{<t}$, 交渉シナリオ $c$
出力: $z_{t-1}$
$x_{<t}$ はcoarse dialogue actの決定には影響しない
ルールベースのマッチングで,価格や物品に関する情報を抽出する.具体的には,正規表現とif文でマッチングをしているらしい.
図引用: Decoupling Strategy and Generation in Negotiation Dialogues
dialogue managerは,各タイムステップ $t$ において,過去のcoarse dialogue acts 履歴 $z_{<t}$ と,交渉シナリオ $c$ から,次に取る行動 $z_t$ を策定する.
Managerの学習手法は,教師あり学習,強化学習,ハイブリッド方式の三つが用いられている.
人間の振る舞いをモデリングするのに最適な学習手法.
入力: 各学習データは,各対話のcoarse dialogue acts $z_1, \dots. z_T$ からなる.
出力: $p_\theta (z_t | z_{<t}, c)$
学習は学習データの尤度を最大化することで行う. モデルは,通常のseq2seqモデルに注意機構を付加したものである.各dialogue actは,通常のトークンとして入力される.例:offer 150.この場合,語彙数はかなり少なくなる.
報酬 $R(z_{1:T})$ をcoarse dialogue actsのひとまとまりに対して適用.3つの報酬関数により実験を行う.
Utility
クレイグスリストデータセットでは,ゼロサムゲームとして与える.それ以外のFBのような二者間山分け交渉の場合は,総和となる.
クレイグスリストでは,目標価格で購入 or 販売できたときにのみ,効用として,1を獲得でき,それ以外の場合には,0を獲得する
Fairness
二者間の効用になるべく差がなくなるようにする.平等重視.計算方法としては,二者間の効用の差で表される.
Length
長く会話させるための指標.
合意が形成されなかった場合,報酬は一律に$-1$ である. 最適化には,policy gradient(方策勾配法)を用いる.パラメータは以下の式(1)に基づいて更新される.
ただし,$\eta$ は学習率,$b$ は出力の平均から推定されるベースライン.$a_i$ は生成されたトークン(policyが取る行動)を意味しており,$z_{1:T}$に対応している.
- 方策勾配法について
方策勾配法は価値関数 $Q^{\pi_\theta}(s, a)$ を実際に得られた報酬の合計で近似するもの.
ベースラインを設けるのは,期待値の分散を減らすため(variance reduction). これによって,モデルの学習を成功させやすくなるらしい.
coarse dialogue actsが与えられたとき,ドメインに関する知識があれば,ルールベースのmanagerを作成できる.
例:$z_{t-1} = {\rm greet}$ のとき,$z_{t}$ も $\rm greet$ とする.
実用的には,学習済みのmanagerを用いて,意図(行動)を決定させて,それに関する変数は,ルールベースで決定するものである.
generatorは,coarse dialogue actと対話履歴の両方に基づいて,検索ベースにより発話内容を決定する.
検索対象の候補はタプルとして保存されている:$(d(x_{t-1}), z_{t-1}, d(x_t), z_t)$
$d$はテンプレート抽出器:”How about $150?”という文があったら,”How about [price]?”と置き換えられる.[price] 部分は生成時に穴埋めされる.
テスト時には,$z_t$ が与えられたら,まず,$z_t$ と $z_{t-1}$ と同じ意図を持つ候補を検索する.候補はテンプレートと現在の対話のコンテキストの類似度で評価される.具体的には,テンプレート $d(x_{t-1})$ はTF-IDFで重み付けされた,BoWベクトルであり,類似度は二つのコンテキストベクトル間の内積で得られる.
まず,教師あり学習によってモデルを学習させる.このとき,2種類のモデルを比較する.
SL(word): seq2seq + attention
ベクトルはCBoWで埋め込み.
SL(act): モジュール型のモデル
ルールベースのパーサー,学習済みのmanager,検索ベースのgeneratorからなる.
クレイグスリストデータセットには様々な価格帯があるので,値段を正規化して扱う.(target priceが1,bottomline priceが0.)売り手のbottomlineは,listing priceの0.7倍.買い手のbottomlineはlisting price.
教師あり学習で学習させたモデルを用いて,強化学習でfine-tuneする.モデルの詳細は以下の表6の通り.
図引用: Decoupling Strategy and Generation in Negotiation Dialogues
SL(word)
3個前までの発言をattentionの対象にする.交渉シナリオは,CBoWで埋め込み.
SL(word) / SL(act)の両方
GloVe埋め込み:300次元
2層のLSTM:300次元
パラメータは-0.1から0.1の一様分布で初期化
AdaGrad:(学習率:0.01,バッチサイズ:128)
20エポック学習
RL
学習率:0.001
5000エピソード学習
二つの指標により評価.
task specificなスコア
効用・合意案の公平性・発話の長さ・合意率
human-likeness
1〜5の5段階評価.高ければ高いほど良い.スコアはAMTのworkerによりつけられた.
教師あり学習をつけると人間らしさが向上.ただし,actベースの方がスコアが良い.
強化学習をつけると,wordベースのときは人間らしさが低下する.一方で,actベースのときは報酬を最適化しながらも,人間らしさを維持している.
]]>図引用: Decoupling Strategy and Generation in Negotiation Dialogues
ここでは,個人的な備忘録として,SVMの考え方を少しずつ厳選して扱います.
今,図のような分布の「●と😀の二値分類」をしたいときに,パーセプトロン系のネットワークでモデルを構築した場合,その識別面(境界線)は次の図の点線になるかもしれない.
パーセプトロンのようなネットワークでは,経験損失(empirical loss)の最小化によって学習が行われる.なので,境界線がスレスレであっても,訓練データに対して損失を最小化するように学習して引かれた正しい線である場合がある.しかしながら,境界線がスレスレだと,テストデータで分類した時に,「●と😀」が間違えて分類されてしまう可能性があり,あまり嬉しくない.
SVMは,この問題に対処することで汎化性能をあげようとしているモデルである.SVMの学習は,汎化損失(generalization loss)の最小化により行われる.着想としては,テストデータは訓練データと同じ確率分布に従っているものと仮定して,境界面(separator;識別面)と観測データの距離がなるべく大きくなるような境界面を選ぶことで,汎化損失の最小化を目指すものである.
サポートベクトル(support vector)
境界面(separator;識別面)にもっとも近い点のことをサポートベクトルと呼ぶ.サポートと呼ばれる所以は,サポートベクトルが境界面を「支えている」ことから来ているらしい.
通常,サポートベクトルの数は本来のサンプルサイズよりも小さくなるので,SVMはパラメトリックなモデルよりも更新の計算量が少なく済む.(サポートベクトル以外のデータが変化しても,境界は変化しないため.)また,重要なベクトルだけを見ればよいので,汎化性能も向上すると考えられる.
マージン(margin)
図中で点線によって囲われた範囲をマージンと呼ぶ.マージンは境界面と境界面からもっとも近い点(観測データ)との距離の2倍となる.
SVMにおいて,「境界面と観測データの距離がなるべく大きくなるように学習する」ということは,「マージンをなるべく大きくするように学習する」ということを意味している.
以降,簡単にではあるが数式で説明を行うため,あまり書かれていない基本的な部分の計算手法について説明する.
識別面は,入力データが $\boldsymbol{x}$ と表されるとき,以下のように表される.
ここで,$\boldsymbol{w}$ は重みベクトルであり,$b$ はバイアスベクトルである.また,各サポートベクトルが作る直線(上図の点線に相当する)は,$\boldsymbol{w^Tx} + b = 1$ か $\boldsymbol{w^Tx} + b = -1$ で定義される.
さて,$\boldsymbol{w^Tx} + b = 1$ となるような,$\boldsymbol{x}$ のうちの一つを,$\boldsymbol{x}_1$ とする.また,$\boldsymbol{w^Tx} + b = -1$ となるような,$\boldsymbol{x}$ のうちの一つを,$\boldsymbol{x}_2$ とする.
このとき,境界面と,サポートベクトル $\boldsymbol{x}_1$ の距離 $r_1$は,垂線の公式の拡張版を用いて,次のように表される.
ここではイメージを掴むのが目的なので,垂線の公式については触れません.
したがって,マージン: $m$は次のように表される.
ただし,$\boldsymbol{w^Tx}_1 + b = 1$ と $\boldsymbol{w^Tx}_2 + b = -1$ から,$\boldsymbol{w^T}(\boldsymbol{x}_1 - \boldsymbol{x}_2) = 2$ となることを用いた.
SVMの学習方法は2種類あり,直感的な解き方としては,マージンを最大化する問題を解くことであり,そこから派生した解法も存在する.そのため,以下で紹介する2手法は双対問題と言われる.双対問題とは,ある最適化問題の制約条件を用いて,より最適化しやすい問題に置き換えて解くような問題のことをいい,どちらかの解が両方の解になる性質を持つ.
さて,マージンは,$\frac{2}{||\bm{w}||}$ で定義されることを先ほど示した.マージンを最大化することは,重みベクトルのノルムを最小化することに他ならない.したがって,式でマージン最大化を表すと,$\argmax_{\bm{w}} \frac{2}{||\bm{w}||}$ となる.これでは,最適化をする際に計算の都合上扱いにくいので,大半の説明では以下が等価であるとみなしている.
最適化のための制約条件について考える.入力データ $\{ \bm{x}_1, \bm{x}_2, \bm{x}_3, \dots, \bm{x}_n \}$ があったときに,それに対応する(正解)ラベルデータが,$\{ y_1, y_2, y_3, \dots, y_n \}$ であるとする.ただし,$y_i$ は,$1$か$-1$をとる.このとき,以下の制約条件が成り立つ.
したがって,マージン最大化は上記の制約条件をもとに,条件を満たす重みベクトルを探す問題に帰着される.
一方で,別の解き方も存在して,ラグランジュの未定乗数法を使う解き方がある.ラグランジュアンを活用することで,上記のマージン最大化問題は次のように書き換えられる.
このとき,最適化条件を考慮すると,以下が得られる.
上記の最適化条件を,元のラグランジュアン $\mathcal{L}$ に代入して計算すると,以下が得られる.
また,双対問題時のKKT条件を考慮することにより,追加の制約条件: $\alpha_i \ge 0$ を得られる.以上から,マージン最大化の問題は,ラグランジュ関数を活用することで,$\alpha$ を最大化する問題に落としこむことができる.式で表すと下記のようになる.
これは,二次計画問題であるため,ソルバーを使えば最適解を得ることができる.最適なベクトル $\alpha$ を見つけることができれば,最適化条件である,$\bm{w} = \sum_{i=1}^{n} \alpha_i y_i \bm{x}_i$ を用いて,重みベクトル $\bm{w}$ も芋づる式に求めることができる.また,バイアス $b$ についても,上述のマージン最大化の制約条件から算出することができる.これによって,識別関数を求めることができるので,ラグランジュの未定乗数法による方法で,SVMの学習ができることがわかった.
ラグランジュの未定乗数法を用いる手法の方が種々の利点がある.
入力データが線形分離できない場合,非線形変換を施して高次元空間に写像してしまえば,分類可能となるときがある.例として,ある特徴空間 $F(\bm{x})$ が,次のように定義されるとする.
このとき,$F(\bm{x})$ でのラグランジュアン: 式(1)は次のように書き換えられる.
式(2)の値を求めるには,高次元空間 $F(\bm{x})$ での内積 $F(\bm{x}_i) \cdot F(\bm{x}_j)$の値を計算する必要がある.今回の例の場合,高次元空間における内積を計算してみると,以下のようになる.
高次元空間に変換してから内積を計算する手間がお分かりになったと思う.
そんなところに朗報で,実は上記の内積は変換前における,$(\bm{x_i}\cdot\bm{x_j})^2$ の演算結果に一致する.この$(\bm{x_i}\cdot\bm{x_j})^2$ を,カーネル関数(kernel function)と呼ぶ.カーネル関数さえわかれば,元の空間でのベクトル演算だけで済むので,逐一各入力ベクトルの値を高次元空間に写像する処理は必要なく,少ない手間で線形分離できないデータに対応することができる.このことをカーネルトリックともいう.
カーネル関数はいくつか種類があり,多項式カーネルとガウスカーネルが有名である.
多項式カーネルは次のように定義される.
ガウスカーネル(RBFカーネルと表記されていることが多い)は次のように定義される.
ただし,$\beta$は正の定数.なお,実際にSVMでデータ分類をする際には,ガウスカーネルを使う場合が多いようである.
参考:SVMのカーネルについて
理想的な環境であればデータにノイズが入ることはない.しかし,現実にはそうはいかない.今まで説明してきたSVMの考え方を元に,ノイズの入ったデータを分類させると,ノイズにサポートベクトルが左右されてしまうため,分類がうまくいかなくなってしまう.そこで,ソフトマージンという考え方が一般に知られている.反対に,これまで説明してきたSVMのマージンの決め方は,ハードマージンと呼ばれる.
今回は量が多くなってしまったので理論だけに留めておきますが,Pythonのライブラリである,scikit-learnにはSVMが実装されています.これを使えばひとまずは実データで分類ができるでしょう.
]]>
torchtextは,PyTorchで自然言語処理(NLP)系のデータを比較的簡単に読み込むことができるライブラリとして有名です.しかし,とっつきやすい性質を持つ分,細かいところで苦戦する場合があります.その一例として,交差検証をやりにくいという点が挙げられます.
正確には,torchtextで処理したデータを用いて交差検証をした例がネット上に少ないことに加え,torchtextのドキュメントにそれに関する記述がないことも災いしていると思われます.
通常なら,torchtextで交差検証をするのは諦めて,skorchなどの他のライブラリを使うと思いますが,ここではあえて「torchtext」と「sklearn」の KFold
を使うことで交差検証を適用する方法を紹介したいと思います.
映画レビュー文データセットである,IMDBデータセットを用いたネガティブ・ポジティブの2値分類タスクを解くモデルを,k分割交差検証にかけてみます.
ベースにするモデルは,GRUとSelf-Attentionで構成されたモデルです.この実装は,GitHubにて公開してあります.
それでは,torchtextで読み込んだデータを交差検証にかけられるようにしていきましょう.
コンストラクタ内では,通常のtorchtextの用法と同じく,datasets.IMDB.splits()
でIMDBデータセットを呼び出すようにします.
返り値は,self.train_data, self.test_data として保持しておきます.
import torch
from torchtext import data, datasets
import random
from sklearn.model_selection import KFold
import numpy as np
class load_data(object):
def __init__(self, SEED=1234):
torch.manual_seed(SEED)
torch.cuda.manual_seed(SEED)
torch.backends.cudnn.deterministic = True
TEXT = data.Field(tokenize='spacy')
LABEL = data.LabelField(dtype=torch.float)
self.train_data, self.test_data = datasets.IMDB.splits(TEXT, LABEL)
self.SEED = SEED
学習データを読み込む際は,get_fold_data()
を使うようにします.
scikit-learnの model_selection.KFold
クラスを使うことで,データセットを交差分割用に分割します.scikit-learnを普段から使っている人なら,おなじみかもしれません.
KFold
のメソッドである,split
は,引数にNumPy配列を渡す必要があるので,torchtextから生成されたデータセットでは型エラーとなってしまいます.そこで,データをNumPy配列に変換して渡してあげると型エラーにならずに動作してくれます.
しかしながら,無理やりNumPy配列に変換したことによる弊害も生じます.というのも,そのまま,torchtext.data.Iterator
にデータを渡すと,再び型エラーになってしまいます.学習をラクして回すためにイテレータは欲しいところです.
そこで,torchtext.data.Dataset
にNumPy配列に変換されてしまった学習データを渡して,イテレータを生成できる状態に戻してあげます.
以上が,学習データの読み込み部分になります.
def get_fold_data(self, num_folds=10):
TEXT = data.Field(tokenize='spacy')
LABEL = data.LabelField(dtype=torch.float)
fields = [('text', TEXT), ('label', LABEL)]
kf = KFold(n_splits=num_folds, random_state=self.SEED)
train_data_arr = np.array(self.train_data.examples)
for train_index, val_index in kf.split(train_data_arr):
yield(
TEXT,
LABEL,
data.Dataset(train_data_arr[train_index], fields=fields),
data.Dataset(train_data_arr[val_index], fields=fields),
)
テストデータの読み込みは,NumPy配列に変換する必要もないので,メソッドが呼び出されたら,そのままデータを渡してあげるだけで大丈夫です.
def get_test_data(self):
return self.test_data
呼び出し側は基本的には,交差検証無しのベースモデルと同じです.
追加されている点としては,data.Iterator
でイテレータを生成する作業が追加されていることです.また,各foldでの結果を保存するために,リスト: _history
を用意してあります.
細かい点は,GitHubにて実装を公開しているので,そちらを参照いただければと思います.
def main():
data_generator = load_data()
_history = []
device = None
model = None
criterion = None
for TEXT, LABEL, train_data, val_data in data_generator.get_fold_data():
TEXT.build_vocab(train_data, max_size=25000, vectors="glove.6B.300d")
LABEL.build_vocab(train_data)
model = Model(len(TEXT.vocab), args['embedding_dim'], args['hidden_dim'],
args['output_dim'], args['num_layers'], args['dropout'])
optimizer = optim.Adam(model.parameters())
criterion = nn.BCEWithLogitsLoss()
if args['gpu'] is True and args['gpu_number'] is not None:
torch.cuda.set_device(args['gpu_number'])
device = torch.device('cuda')
model = model.to(device)
criterion = criterion.to(device)
else:
device = torch.device('cpu')
model = model.to(device)
criterion = criterion.to(device)
train_iterator = data.Iterator(train_data, batch_size=args['batch_size'], sort_key=lambda x: len(x.text), device=device)
val_iterator = data.Iterator(val_data, batch_size=args['batch_size'], sort_key=lambda x: len(x.text), device=device)
for epoch in range(args['epochs']):
train_loss, train_acc = train_run(model, train_iterator, optimizer, criterion)
print(f'| Epoch: {epoch+1:02} | Train Loss: {train_loss:.3f} | Train Acc: {train_acc*100:.2f}%')
val_loss, val_acc = eval_run(model, val_iterator, criterion)
print(f'Val. Loss: {val_loss:.3f} | Val. Acc: {val_acc*100:.2f}% |')
_history.append([val_loss, val_acc])
_history = np.asarray(_history)
loss = np.mean(_history[:, 0])
acc = np.mean(_history[:, 1])
print(f'LOSS: {loss}, ACC: {acc}')
やや駆け足の解説となりましたが,一回NumPy配列に変換してあげることで交差検証が可能になるので,どうしてもtorchtextでデータセットを読み込みたい人には使えるテクニックだと思います.
実際のところtorchtextのレポジトリを見ると,交差検証に関するissueが出ているので,この機能を設けて欲しい人はそれなりにいるみたいですね.(ですが,今の所はこの投稿のような形で無理やり対処するしかないでしょう…)
ソースコードは,GitHubにて公開しています.
]]>