普通のバックエンドエンジニアでも機械学習に関わる機会は割とありそうだしちょっと調べとくか。。
と思って機械学習周りの本いろいろ読んでたんだけど、今の自分の前提知識だと「自然言語処理アプリケーション開発入門」がちょうどいい感じだった。
https://www.amazon.co.jp/dp/B07YP84B1R?btkr=1
読んだ段階での自分のスキル
- プログラム実装は毎日やってるが実際の仕事での機械学習、自然言語処理を使った機能実装は未経験
- 文系卒ということもあり機械学習分野で使われる微分、確率統計、線形代数などの数学知識が薄弱。一応独学で大学数学の入門書をざっと読んだ程度
どんな人向きか
これより先に読んだ「ゼロから作るdeep-learning」だとライブラリを使わずにディープラーニングを実装する。っていう本でちゃんと理論を理解するには良書なんだけどそれに比べて「自然言語処理アプリケーション開発入門」は大分実践的な印象。理論よりも手を動かして作るのが好きなエンジニアにはウケのいい内容だと思う。
数式を使ったアルゴリズムの説明は出てこず、sklearnやkerasといった機械学習ライブラリを使ってとにかくプログラムを作っていく。なので機械学習の詳細な実装を知りたければ他の本を読まないといけないけど、その代わり精度を上げるための手法だったり具体的なデータの集め方など実際の現場で使えそうな内容が多かった。
内容
最初から最後まで基本的には自然言語処理の手法を使って対話エージェントを作る内容。最初は理解しやすい手法から始まって少しずつ精度を上げる手法が紹介され最終的にニューラルネットワークを使った手法にして。。といった具合に徐々に難易度を上げていくような進め方をする。
以下復習がてら本書での対話エージェントの作りをざっくり書いてみる。
対話エージェントの仕組み
質問をクラスに分類する
まず、似たような質問を「クラス」としてまとめる。例えば
「自然言語処理をしたいんだけど、どうすればいい?」や「日本語の自然言語処理について知りたい」
といった質問は「自然言語処理の学習について聞く質問」として「クラス0」とする
「好きなプログラミング言語は?」や「どの言語を使うのがいいかな?」
は「おすすめのプログラミング言語を聞く質問」として「クラス1」とする
で、それぞれのクラスに対する回答を用意する。例えばクラス1の「好きなプログラミング言語は?」に対しては「Pythonがおすすめです」など。
「ただこの文章入力に対してはこの回答をします」といったルールを愚直に登録するやり方には無理があるので入力される文の傾向をシステムに学習させて「どのような質問文がきたらどの回答文を返せばよいか」のパターンを取得していく。
つまり「文を入力すると、その文が属するクラスを予測し、クラスIDを出力するシステム」を作れば対話エージェントが成立する。
以下文が所属するクラスを予測する具体的な手法
まず入力された文書を「ある決まった個数の数字のまとまり」に変換する必要がある。「ある決まった個数の数字のまとまり」を以下「ベクトル」と表記する。
ベクトルの作り方
文章をわかちがきして単語に分ける→Bag of Wordsという手法で単語の出現回数をカウントする
Bag of Wordsはまず単語を辞書に登録して登録された単語ごとの出現数をカウントする特徴抽出の手法
辞書の例
単語 | 番号 |
---|---|
私 | 0 |
あなた | 1 |
ラーメン | 2 |
好き | 3 |
こと | 4 |
が | 5 |
は | 6 |
の | 7 |
な | 8 |
です | 9 |
ベクトルへの変換例
# 私 / は / 私 / の / こと / が / 好き/ な / あなた / が / 好き / です [2, 1, 0, 2, 1, 2, 1, 1, 1, 1] # 私はラーメンが好きです [1, 0, 1, 1, 0, 1, 1, 0, 0, 1]
この本ではわかちがきの実装にはmecabという形態素解析のライブラリを使った実装が記載されていて、BoWはスクラッチでの実装とscikit-learn を使った実装が記載されている。
SVMを使った識別器
次に上記で得られたベクトルを使って識別器を作る。特徴ベクトルを入力し、クラスIDを出力することを「識別」と呼びそれを行うオブジェクトや手法を「識別器」という。
まずこの本ではSVM(support vector machine)という手法で識別器の実装を進めていくけどSVMについての詳細説明は省略されていてscikit-learn(ライブラリ)を使ったSVMの使い方が説明されている。
まあ、とにかくベクトル作ってscikit-learnのプログラムに与えると文章に対する機械学習でのクラスIDの予測ができる。
チューニング
TF-IDF
ここまでではBoWで特徴ベクトルを計算していたけど、BoWの代わりにTF-IDFを使うことで精度を向上させることができる。 TF-IDFは「重要な単語の寄与を大きく、重要でない単語の寄与を小さくする」ことでBoWよりも文の特徴をよく表現できる。
例えば「私はラーメンもお好み焼きも好きです」、「私はラーメンが好きだ」、「私はサッカーも野球も好きだよ」という文章があったとしてこの文章を特徴づけているのは「ラーメン」「お好み焼き」「サッカー」「野球」といった「私の嗜好」を表す単語だが、BoWでは「私」や「好き」といった他の文章との比較において大して意味のない単語と同等に扱われてしまう。その問題をTF-IDFでは解決できる。
チューニングその他
文章の表記揺れや文字コードのズレを前処理で吸収する方法だったり、わかちがき部分のその他の手法だったり、SVM以外の識別器(決定木アンサンブル、k近傍方)の紹介だったりと分類の精度を高める方法が紹介されるけどそれ全て書くのはきついので略。
ニューラルネットワーク
内容の後半辺りからニューラルネットワークの説明とそれを使った識別器の実装についての説明に入る。
「ニューラルネットワーク」っていうのは世間で「AI」、「人工知能」という言葉を聞いた時によくイメージされる脳細胞を模して設計された計算モデルのこと。その内容を数学的なモデルに落とし込んだものを「パーセプトロン」という。
n個の入力とそれぞれに対応した重み、固定値の入力バイアスからなる。
こんなやつ(wikipedia)↓
1つのパーセプトロンは数値を1つ出力するので複数並べると全体として1つのベクトルを出力する。このまとまりを「レイヤー」という。
このレイヤーを増やして高い精度を出せるようにした手法が「ディープラーニング」と呼ばれる。
で、このニューラルネットワークを識別器として使うことができる
追記
本書ではニューラルネットワークとパーセプトロンについて上記のように書いてあったけど、他の本や記事ではノードの活性化関数にステップ関数を使ったのがパーセプトロンで、シグモイド関数のような滑らかな活性化関数を使ったのがニューラルネットワークと呼ばれてたりする
ニューラルネットワークを使った識別器
単純パーセプトロンを使ったクラス分類
以下単純パーセプトロンを使って2クラスに分類する具体的なイメージ(自分なりの解釈でベクトルや重みの値はサンプルコードから転用した適当な値)
まずTF-IDFを使って入力文書をベクトル化して入力し、その入力に対して重みとバイアスをかけてを実行すると0から1の間の数字が出力される。例えば「好きなプログラミング言語は?」、「自然言語処理をしたいんだけど、どうすればいい?」みたいな質問文を用意してベクトル化して[0.2, 0.3, -0.1]、[-0.2, -0.1, 0.9]という結果が出たとする。これをパーセプトロンの入力値とする。
で、重みを[-1.64, -0.98, 1.31]、バイアスを「-0.0.5」だとすると入力ベクトルに対して 0.2 * -1.64、 0.3 * -0.98‥‥といった風にそれぞれの値に重みをかけた後にバイアスの-0.05を足した値が出力される。
で、その出力が例えば「0.3093841557917043」と「0.8256347143825868」だとする。そして数字が0に近ければクラス0、数字が1に近ければクラス1といった風にクラス分けして識別器として使うことができる。
層を深くする場合、他クラス分類の場合
上記の例だと2クラスのどちらかにしか分類できないので実用できない。他クラスの場合は出力値をクラスIDの数だけ出力して最も1に近いクラスに分類する方法が紹介されている。例えば出力値が[0.33898331, 0.65419141, 0.498738291]だとすると2つめが最も1に近いので2つ目のクラスに分類する。
また、ニューラルネットワークの層を深くして流行りのディープラーニング化すると精度の高いシステムができるけどただ層を深くするだけではダメで実現のためには様々な問題を乗り越えなければいけないんだけど、この記事は全体の流れをざっくり整理したい。。と思って書いたんでその辺りの地道なチューニングの話は略。