この記事はGoodpatch Advent Calendar 2020 19日目の記事の逆行投稿です。
以前投稿した 今日から始めるCreative Coding環境3選 - Qiita の記事で @thi-ng/umbrella というTypeScriptのCreative Coding環境を紹介しました。thi.ng はフレームワークというより単独でも組み合わせても使える膨大なパッケージの集合体(developブランチには140個以上)で、現在も活発に開発が進んでいます。そのthi.ngに最近 @thi.ng/fuzzy というファジィ論理を扱うライブラリがα版として追加されました。家電やロボット制御などに応用されることの多いファジィ論理をCreative Codingのライブラリに取り入れるのは面白いなと思ったので、今回はそちらを簡単に紹介します。
ファジィ論理 (Fuzzy Logic)とは
ファジィ論理はコンピューターによる推論に「あいまいさ」を導入します。
ルールベースのロジック(いわゆるif文ですね)は一般に真(true)か偽(false)を推論結果として返しますが、ファジイ論理は真理値が0~1の間の値を取り、真偽の2つの値では表せないようなあいまいな推論を扱えます。
また「暑い」や「涼しい」、「苦い」や「甘辛い」など、人間の認知や言葉で変数を表現できることも特徴です。
定量的な入力値を言語学的変数(Linguistic variables, L-vars)にラベル付けして推論規則を考えることができます。
fuzzyはこうした推論を型付きの計算で簡単に取り使えるようなAPIを提供します。
fuzzyの使い方
thi-ng はモノレポで構成されているのでパッケージ単位でインストールできます。fuzzy と一緒に可視化を行うヘルパーライブラリの fuzzy-viz もセットでインストールしましょう。
$ yarn add @thi.ng/fuzzy @thi.ng/fuzzy-viz
ファジィ論理の典型的に使用される温度の例:
// L-varsの定義
const temp = variable(
// 値(温度の取りうる値域)
[-20, 40],
{
凍えそう: invSigmoid(0, 2),
寒い: trapezoid(-1, 2, 16, 20),
暖かい: trapezoid(15, 20, 30, 34),
暑い: sigmoid(32, 2),
}
);
具体的な入力値(= 18)をL-varsに適用
evaluate(temp, 18);
// {
// 凍えそう: 2.220446049250313e-16,
// 寒い: 0.5,
// 暖かい: 0.6,
// 暑い: 6.914400106935423e-13
// }
具体的な入力値(= 28)をL-varsに適用
evaluate(temp, 28);
// {
// 凍えそう: 0,
// 寒い: 0,
// 暖かい: 1,
// 暑い: 0.0003353501304664781
// }
候補の中で妥当な変数に分類
classify(temp, 28, 0.33));
// 暖かい
定義したL-varsの可視化:
import { varToSvg } from "@thi.ng/fuzzy-viz";
fs.writeFileSync("dist/temperature.svg", varToSvg(temp, { samples: 200 }));
推論ルールの定義
入力変数と出力変数を組み合わせて知識ベースの推論ルールを定義することができます。このルールにより、人間の認知ベースの意思決定をシミュレートすることができます。
const inputs = {
食事: variable([0, 10], {
まずい: invRamp(1, 3),
美味しい: ramp(7, 9),
}),
サービス: variable([0, 10], {
悪い: gaussian(0, 1.5),
良い: gaussian(5, 1.5),
素晴らしい: gaussian(10, 1.5),
}),
};
const outputs = {
チップ: variable([0, 30], {
少なめ: triangle(0, 5, 10),
普通: triangle(10, 15, 20),
多め: triangle(20, 25, 30),
}),
};
type I = typeof inputs;
type O = typeof outputs;
// ルール定義:
// 食事 が まずい か サービス が 悪い ならば チップ は 少なめ と推論
// サービス が 良い ならば チップ は 普通 と推論
// 食事 が 美味しい か サービス が 素晴らしい ならば チップ は 多め と推論
const rules = [
or<I, O>({ 食事: "まずい", サービス: "悪い" }, { チップ: "少なめ" }),
or<I, O>({ サービス: "良い" }, { チップ: "普通" }),
or<I, O>({ 食事: "美味しい", サービス: "素晴らしい" }, { チップ: "多め" }),
];
defuzz(
inputs,
outputs,
rules,
// 入力値
{ 食事: 7.32, サービス: 7.83 }
);
// 推論結果 { チップ: 22.650000000000034 }
アプリケーションに応用するとしたら
制御系のシステムでなくても、一般的なアプリケーションの振る舞いとしてルールベースの分岐を入れている箇所はいろいろあると思います。ファジィ論理への置き換えでアイディエーションしてみると
- ユーザーからの入力や、画面内の状態から振る舞い(戦略)を変える
- インタラクション
- 画面の表示
- 画面内の表示コンポーネント数によるパフォーマンス調整
- NPC的なアバターの操作
- カーソル移動の加速度で傾き
- ポップアップウインドウのちょうど良い配置
- 近いエリア内のオブジェクト配置の密を避ける。近いと邪魔だから避けるような振る舞い