JavaScriptでファジー理論

JavaScript Advent Calendar 2011 (オレ標準コース)の9日目の記事です。

「オレ標準コースは何を書いてもいいらしい」という噂を聞きつけてやってきました。@push_backです。

今回は簡単なファジー論理(cf. ファジィ論理 - Wikipedia)をJSでやってみよう!という試み。
元ネタは最後にある参考文献[1]を参照のこと。

一日で突貫工事したので、ところどころ微妙だったりします。許せ!


ファジー(あいまい)論理って?

AIの思考ロジックとかエアコンの制御なんかに使われている、ぼんやりした基準をもとに判断する方法。

一言で言うと"ある基準値との近さから0~1の真理値を求める"もの。

普通に真理値を求める(例えば、100 < N)とtrue/falseで0か1かの2値になる。
しかし、ファジー論理では、評価基準となるメンバーシップ関数の返す0~1のスコアとして真理値が求まる。
要するに、閾値でスパっと真偽を分けずに、スコアから柔軟に判断しよう、ということ。

……などど、いわれてもピンと来ないと思われるので、まずサンプルをご覧頂きたい。


サンプル

以下が動作サンプル。canvasにx座標で0~200までプロット。真理値は上が1.0、下が0.0。
コードはここに置いてあるので、いじってみたい人はどうぞ。

訂正:図中の台形クラスは"Fuzzy.Stage"ではなく"Fuzzy.Trapezoid"が正しいです。
Fuzzyクラスの真理値グラフ

テキトーに解説しておくと、図はy=f(x)なる関数のグラフです。
xが評価したい値、f(x)がxの値によって0~1の値を返すメンバーシップ関数。
実際のコードでは、真理値T=fuzzy.getT(x)となり、fuzzyが各ファジー論理クラス、getT(x)がf(x)に相当します。

各クラスの概要は以下。
  1. ある一定値以上は偽から真に変化
  2. またはその逆
  3. ある一点を中心に一定の割合で変化
  4. ある区間は真
  5. ある区間は真、その前後で偽から真、真から偽へ変化
  6. ある一点を頂点とする半楕円の軌跡に沿って変化(場所が余ったので適当)
こんな感じ。頂点になる部分だったり、変化の始まる部分、終わる部分が基準値だったりするけど、それは評価を返すメンバーシップ関数に依存しているのがなんとなくお分かりいただけただろうか。


使い道

具体的にどういう時使えるのかというと、あいまいな判断がしたい時。

例えばRPGで、敵キャラの戦闘用ロジックを作っているときに、以下のような条件をつける。
  • 自分のHPがX以下なら逃げる
  • プレイヤーのHPがY以下なら攻撃
そのときに、条件をただ
if(X > 敵キャラのHP)/* 逃げる */;
else if(Y > プレイヤーのHP)/* 攻撃 */;
/* 以下いろいろ続く…… */
のように処理すると、プレーヤーをあと一撃で倒せるのに逃げるチキンな敵になってしまい、非常に残念。
そこで、ファジー論理を用いて基準値との近さから判断すると、
  • ”HPが危険なので逃げる”の真理値0.6 = ”やや逃げるべき”
  • ”プレイヤーのHPが低いので攻撃”の真理値0.8 = ”かなり攻撃すべき”
のように評価して、0.6の確率で逃げるようにできる。
もしくは、単純に他の判断基準と比較して高い方(攻撃)を選ぶ、なんてことができる。
敵の行動がちょっとマシになったのでめでたしめでたし。

すごい大雑把に考えると、「基準値との近さから閾値を決めて、乱数で分岐するのをちょっとスマートにしたような何か。」というイメージ。

もう少し踏み込んだ考え方として、いくつかのメンバーシップ関数を組み合わせる。
こうすると複雑な条件を表現することが可能になってコードがスッキリするわ、使いまわせるわ、プロットすれば可視化もできるわ、たくさんのメリットが。


実装

さんざん、JavaScriptと関係ない話をしたので、そろそろソースを貼ってごまかしておきます。
一部省略しているので、実際のソースは上の方にあるサンプルのリンク先からどうぞ。

"傾き*x座標"である地点の真理値が求まる単純(y=ax)な理屈。
特筆すべき点はなし。論理演算はwikipediaの定義そっくりそのまま。

サブクラスはFuzzyを継承したうえで、真理値の計算関数getT()に計算式を埋め込んでます。
複数のFuzzyをANDで組み合わせたり、任意の図形の方程式を入れたり、使い方は無限大。
/*
    始点から終点の間で0~1と直線的に変化する真理値を表す
    start 始点のx座標
    end 終点のx座標
    [flip] 1~0と変化する場合trueにする
*/
function Fuzzy(start, end, flip){
    var startVal = +!!flip;
    var endVal = +!flip;
    var delta = (endVal - startVal) / (end - start);
    /*
        x地点での真理値を返す
        x 真理値を得たい地点のx座標
    */
    this.getT = function(x){
        if(start >= x)return startVal;
        else if(end <= x)return endVal;
        else return startVal + delta * (x - start);
    };
}
Fuzzy.prototype = {
    /*
        以下、論理演算
        x このオブジェクトのx地点
        fuzzy 演算するFuzzyオブジェクトまたは値
    */
    and : function(x, fuzzy){
        return Math.min(
            this.getT(x),
            isNaN(fuzzy)?fuzzy.getT(x):fuzzy);
    },
    or : function(x, fuzzy){
        return Math.max(
            this.getT(x),
            isNaN(fuzzy)?fuzzy.getT(x):fuzzy);
    },
    not : function(x){
        return 1 - this.getT(x);
    }
};

/*
    以下、サブクラス
*/
//三角(引数:始点、頂点、終点のx座標)
Fuzzy.Triangle = function(start, top, end){/* 略 */};
//パルス(引数:始点、終点のx座標)
Fuzzy.Palse = function(start, end){/* 略 */};
//台形(引数:上りの始点、上りの終点、下りの始点、下りの終点のx座標)
Fuzzy.Trapezoid = function(upStart, upEnd, downStart, downEnd){/* 略 */};
//半円(引数:始点、終点のx座標)
Fuzzy.Circle = function(start, end){/* 略 */};


まとめ

ここまでの話のポイントを3行で。
  • ファジー論理は比較対象の値とメンバーシップ関数から真理値を求める
  • 真理値は0~1の値を取るので、優先順位をつけづらい複雑な判断に向いている
  • 条件をコードから分離したり、組み合わせることでロジックがすっきりするかも

最後に一言。少しでも役に立つといいけれど、正直、記憶があいまいなので(これが書きたかっただけです、お詫び申し上げます)、興味のある人はこの記事のことを忘れて本を読んでみるといいです。


参考文献

  1. David M. Bourg, Glenn Seemann 著, 株式会社クイープ 訳: "ゲーム開発者のためのAI入門", 10章, オライリージャパン, 2005
補足:
O'Reilly Japan - ゲーム開発者のためのAI入門(出版元の紹介と目次)
ゲーム開発者のためのAI入門 - Google ブックス(試し読み可能)


蛇足(FAQ)

Q. どうしてこのネタなの?別にJavaScriptである必要性が見当たらないんですけど!
A. JavaScriptで書きたかったから書いただけ(断言)。

Q. 本当に?
A. 強いて言うなら「今ならグラフがcanvasで描けるなあ」と。昔は線を一本引くのにも結構な苦労があって(以下略)。

Q. "ファジー理論"と"ファジー論理"どっちがどっちなの?
A. ファジー理論の中に含まれるのがファジー論理だと信じています。余談ですが、細かいことを気にしないのが長生きの秘訣です。

Q. ファジー理論なんて無くても、乱数の閾値を判断基準から設定すればいいだけじゃないの?
A. そのコードをクラスに隠蔽してるのが、ファジー論理のメンバーシップ関数なんじゃないかなあ、と。ファジー論理なんて考えなくても同様の機能は作れるし、賢い人は無意識に作ってる気がしないでもない。

Q. この本が読みたいけど、お金がないのでどうにかしてください。
A. 図書館を利用しましょう。

Q. うまい棒は何味が好きですか?
A. コーンポタージュ味とチーズ味です。

コメント