マッチ箱の脳(AI) -- パーセプトロン
はじめに
前回の「マッチ箱の脳(AI)」のネタで、今回は「パーセプトロン」を組んでみることにしました。
パーセプトロンとは
機械学習のアルゴリズムの1つで、ラベル付けされた多次元データを、ラベルごとに分類する境界を求めるアルゴリズムです。
以降は簡単のため、2次元データに対して2値のラベル(-1または+1)が付けられるケースに限定します。
例えば:
label | x | y |
---|---|---|
+1 | -2 | 1 |
+1 | -1 | 4 |
+1 | 1 | 3 |
+1 | 3 | 3 |
-1 | -3 | -1 |
-1 | 0 | -1 |
-1 | 2 | 1 |
-1 | 3 | 2 |
というデータに対しては、y = 0.5x + 1 が+1のデータと-1のデータを分類する境界となります。このような識別境界を求める手法の1つが、パーセプトロンです。
パーセプトロンのアルゴリズム
- \(ax + by + c = 0\) という直線を考える。\(a, b, c\)はランダムに初期化
- \(a, b, c\)が更新されなくなるまで、以下を行う:
- 各訓練データ\((x_i, y_i)\)について、
- \(k = ax_i + by_i + c \)を求める
- \(k\)が\(+\)だが訓練データのラベルが\(-\)である場合、\(a = a-x_i, b = b-y_i, c -= c-1\)と更新
- \(k\)が\(-\)だが訓練データのラベルが\(+\)である場合、\(a = a+x_i, b = b+y_i, c = c+1\)と更新
- 各訓練データ\((x_i, y_i)\)について、
上記が停止した時、\(ax + by + c = 0\)が識別境界となります。非常にシンプルです。なぜこの更新方法で良いのかについては、ちゃんとした理論があるようですが、正確に理解しているか怪しいので飛ばします- -;)
2次元データが「線形識別可能」、つまり直線により識別できるならば、上記のアルゴリズムは必ず停止し、答えが求まります。
例
上述した例に対してパーセプトロンを適用した結果です。5回の更新で識別境界を求めることが出来ました。ちなみにその境界は、 y = 0.581*x + 0.878.最終的な解に至る過程(cycle 0 -- cycle 4)も記しています(色で区別してるだけなので見難いかも)
補足
サンプルのJavaScriptソースコード(Node.js). https://gist.github.com/danglingfarpointer/2fa56b9359ec2e260f6b
マッチ箱の脳(AI) -- 遺伝的アルゴリズム
はじめに
「マッチ箱の脳(AI)―使える人工知能のお話」という人工知能系のアルゴリズムを分かりやすく解説したKindle本を買いました。その本に触発され、最近仕事で触っているNode.jsのお勉強も兼ねて、「遺伝的アルゴリズム」を題材にプログラムを組んでみることにしました。
- 作者: 森川幸人
- 出版社/メーカー: 森川幸人
- 発売日: 2014/01/05
- メディア: Kindle版
- この商品を含むブログ (7件) を見る
遺伝的アルゴリズム
ざっくり説明すると、以下の手続きです。
- 解こうとしている問題の解の候補を「遺伝子」として表現した上で、
- 優秀な遺伝子(適応度の高い遺伝子; 優れた解候補)が生き残るようにしつつ、
- 遺伝子間の交叉や突然変異を通して、世代(遺伝子の集まり)を反復的に進化させていき、
- 一定の世代数だけ反復した後、もっとも優れた遺伝子を解とする
まさに遺伝子の自然淘汰や突然変異、遺伝子間の交叉をモチーフにしたアプローチです。
適用先
「マッチ箱の脳(AI)」と同様に、遺伝的アルゴリズムをナップサック問題に適用します。ナップサック問題とは、以下のような問題です。
前提:
- 値段と重さが決まっているn個の品物がある
- 所定の重さまでしか品物を入れられないナップサックがある
問:
- ナップサックに入る範囲で、最も値段が高くなる品物の組み合わせは?
例えば、以下の表のような6個の品物をできるだけ値段が高くなるようにナップサックに入れたい。ただし、ナップサックには100KGしかはいらない。どのような品物の組み合わせがベストか?
id | 値段 | 重さ |
---|---|---|
1 | 4500円 | 40KG |
2 | 6000円 | 56KG |
3 | 4000円 | 34KG |
4 | 3000円 | 28KG |
5 | 3000円 | 36KG |
6 | 6900円 | 72KG |
この答えは、1と2の組み合わせです。10,500円で96KG < 100KGとなります。
ナップサック問題はNP困難であることが知られています。つまり、品物数nが大きい場合、厳密な解を求めようとすると、現実的な時間では終わりません。
しかし、(厳密な解ではなく)最適に近い解であれば、実際は効率的に求められるようです。そうなってくると、あえて遺伝的アルゴリズムの題材にするのはどうかと思いますが、ここはあくまで本の設定に従います。
遺伝子よる解候補の表現
まずはナップサック問題の解候補をどのように「遺伝子」として表現するか。もちろん遺伝子といっても要は単なるオブジェクト表現です。
n個の品物がある場合、解候補は、それぞれの品物の取捨を表すn個の真偽値で表現できます。例えば上述した例であれば、6個の品物から3番目と5番目を選択した解候補は、以下のような遺伝子で表現できます。
[false, false, true, false, true, false]
適応度の定義
「適応度」とは、遺伝子が解候補としてどの程度優れているかを表す値です。大きい値ほど優れているとします。
ナップサック問題の設定を考えると、選択した品物の値段の合計を適応度と定義するのが適当です。ただし、重さの和がナップサックの容量を超えた場合、解とはなりえないので、適応度は0とします。
例えば上述した例で、3番目と5番目を選択した解候補の適応度は、ナップサックの容量を超えていないので(34KG + 36KG < 100KG), 7000(4000円 + 3000円)となります。また、2番目と3番目と4番目を選択した解候補については、ナップサックの容量を超えてしまったので(56KG + 34KG + 28KG < 118KG), 適応度は0となります。
遺伝子の交叉
新しい遺伝子を生成する1つの方法が、遺伝子間の「交叉」です。交叉により、2つの遺伝子の性質を組み合わせて、新たな遺伝子を2つ生成します。
交叉の方法はいくつかパターンがあるようですが、今回は「2点交叉」というものを用います。これは、2つの遺伝子を同じ2箇所で切断し、中央の部分を入れ替える、というものです。
例: 1番目と2番目の間と4番目と5番目の間を切断し、中央部分を入れ替える。
A: t | f t f | t f -----> t | f f f | t f | | B: f | f f f | t t -----> f | f t f | t t
遺伝子の突然変異
新しい遺伝子を生成するもう1つの方法が、遺伝子の「突然変異」です。ある遺伝子の性質にランダムに変異を与えることで、「ちょっとだけ違う」遺伝子を生成します。
今回は、以下の処理をしています。
- まずは遺伝子から真偽値を1つだけランダムに選択して逆転
- 次に遺伝子の全ての真偽値について、それぞれ3%の確率で逆転
優秀な遺伝子の保持
新しい世代を作る際に、古い世代の優秀な遺伝子をいくつかそのまま残すようにします。
優秀な遺伝子の選択の仕方には、これもまたいくつかのパターンがあるようです。今回は、1つの世代を10遺伝子と固定しています。そこで、そのうち最も優れた2つの遺伝子を残すようにします。
プログラム
以上のような感じで作ったプログラムを、以下を置いています: https://gist.github.com/danglingfarpointer/4333496926a5e6dc8c7b
実行するには
Node.jsの実行環境とパッケージ・マネージャー(npm)が必要です。
それらを準備したら、適当なディレクトリを作り、プログラムをindex.jsというファイルで保存し、npm install underscore
(依存ライブラリのインストール)を実行して下さい。
MAC OS Xでの例:
mkdir knapsack cd knapsack pbpaste > index.js npm install underscore
次にナップサック問題の品物を定義するCSVファイルを準備します。1列ごとに、品物の値段と重さを書いていきます。上述の例だと、以下のようになります。
4500, 40 6000, 56 4000, 34 3000, 28 3000, 36 6900, 72
そして実行。CSVファイルのパスとナップサックの容量を与えます。
node index.js /path/to/csv/file 100
実行例
http://people.sc.fsu.edu/~jburkardt/datasets/knapsack_01/knapsack_01.html で配布されているベンチマークの中で、10個の品物から成るデータセット(P01)を試してみました。ナップサックの容量が165, 最適解(値段の合計)は309です。
パラメタが良くないせいか実行ごとに結構ばらつくのですが、ある試行(100世代反復)の結果をグラフ化しました。
縦軸は品物の値段の合計を表します。赤が最適解(309), 青が世代で一番優れた解候補の適応度、緑が世代の適応度の平均値です。80世代過ぎで最適解(309)に到達。適応度の平均値は50から170あたりを不規則にいったりきたりしています。
最後に
遺伝的アルゴリズムによってナップサック問題を解くプログラムをNode.jsで作りました。パラメタ設定や適応度・交叉・突然変異などの方式がナイーブなので、ばらつきの抑止も含め、よくしたいのであればいろいろ考える必要があります。
「マッチ箱の脳(AI)」の中の他の素材も、Node.jsで書いてみたいと思います。
神奈川新聞花火大会(8/5)
オフィスの中から撮影。窓越しだが絶好のポイントだった。
gitコミットログのユーザーやタイムスタンプを修正
git commit --amend \ --date="Sat Jun 10 13:00:32 2014 +0900" \ --author="author <author@example.com>"
で、コミットしたユーザー(author)とその日時(author date)を修正できる。デフォルトのgit log
で表示されるものが、これらである。
一方で、authorとauthor dateとは別に、committerとcommit dateがある。これらは、コミットをrebaseしたユーザーとその日時をトラックするために使われる。committerとcommit dateも修正するためには、以下のようにする:
GIT_COMMITTER_NAME="committer" \ GIT_COMMITTER_EMAIL="committer@example.com" \ GIT_COMMITTER_DATE="Sat Jun 10 13:05:32 2014 +0900" \ git commit --amend \ --date="Sat Jun 10 13:00:32 2014 +0900" \ --author="author <author@example.com>"
committerとcommit dateを確認するには、git log
にオプションを与える:
git log --pretty=fuller
OS Xのbashでランダムな文字列を生成
A-Za-z0-9
から成る32文字の文字列を生成:
LC_CTYPE=C tr -dc A-Za-z0-9 < /dev/urandom | head -c 32