ファジー理論で遊んでみる

今回はファジー理論。
使用用途としてはAIの行動など、家電などにも使われたり利用用途はいろいろらしい。
これを使うことでちょっとだけ賢いAIが作れるはず・・・・。
よく理解できてないけど自分で理解した範囲でまとめてみる。

##ファジー理論でなにができるの？
えっと、例えば簡単な例だと　満腹度、hp あったとして、こんなルールがあるとします。
・満腹度が少ない場合場合ご飯を食べる
・hpが少ない場合回復する
・どちらでもない場合、うろうろする
この場合簡単なif分岐でやると
if(満腹度 < 30) ごはんたべる
else if(hp < 30) 回復する
else うろうろ
みたいになるかと思うのですが、これだと、
最初に満腹度が20であればhpが1のときさっさと回復して欲しいのにご飯を食べる選択をしてしまいます。
これは悲しいです。まぁこれくらいならもう少しif分岐増やしてあげればいいかもですけど、
これを解決するのがファジー理論です。

この場合例えば、満腹度 30以下でご飯を食べたい比率をあげ、
hp 30以下でhpを回復する比率を上げていきます。下手っぴですけどグラフにするとこんな感じ
あとは比率から高い方を優先して行動にするみたいな感じです。
<img src="https://qiita-image-store.s3.amazonaws.com/0/46444/d7b81f65-c169-a365-c694-ea832ea50874.png"/ width="200px" /><img src="https://qiita-image-store.s3.amazonaws.com/0/46444/1bb53fa9-5066-ee82-01f0-ac848be40ae4.png" width="200px" />

例：
Hpが100 満腹度100 の時は　回復0 ご飯0なので　うろうろ
Hpが100 満腹度20 の時は 回復0 ご飯0.333　なのでご飯
Hpが5 満腹度20 の時は　回復0.83 ご飯0.333　なので回復とできます。
この場合条件が2つですが、条件が大量に増えた時にとくに効力を発揮するかと思います。


##今回作ってみたメンバーシップ関数？
もう専門用語ばかりで単語があっているのかわからないけど、
とりあえず今回は
・傾斜型のメンバーシップ関数
・傾斜が逆のメンバーシップ関数
・三角形のメンバーシップ関数
・台形のメンバーシップ関数
の関数を作った。
<img src="https://qiita-image-store.s3.amazonaws.com/0/46444/4a9816fb-34e5-ab09-9358-a16aaab93e29.png"width="500px"/>
赤い文字のところが引数で渡す値
カーブなど使いたい場合はUnityならAnimationCurveつかえば楽ちんだけど、
どこにいっても使えるように今回は関数で一般的な形っぽいのだけ。

##コード

```csharp
	// 傾斜型のメンバーシップ関数
	public float FuzzyGrade(float value, float x0, float x1)
	{
		float result = 0;
		if(value <= x0) result = 0;
		else if(value >= x1) result = 1;
		else result = (value / (x1 - x0)) - (x0 / (x1 - x0));
		return result;
	}

	// 傾斜が逆のメンバーシップ関数
	public float FuzzyReverseGrade(float value, float x0, float x1)
	{
		float result = 0;
		if(value <= x0) result = 1;
		else if(value >= x1) result = 0;
		else result = (x1 / (x1 - x0) - (value / (x1 - x0)));
		return result;
	}

	// 三角形のメンバーシップ関数
	public float FuzzyTriangle(float value, float x0, float x1, float x2)
	{
		float result = 0;
		if(value <= x0 || value >= x2) result = 0;
		else if(value == x1) result = 1;
		else if((value > x0) && (value < x1)) result = (value / (x1 - x0)) - (x0 / (x1 - x0));
		else result =  (x2 / (x2 - x1) - (value / (x2 - x1)));
		return result;
	}

	// 台形のメンバーシップ関数
	public float FuzzyTrapezoid(float value, float x0, float x1, float x2, float x3)
	{
		float result = 0;
		if(value <= x0 || value >= x3) result = 0;
		else if((value >= x1) && (value <= x2)) result = 1;
		else if((value > x0) && (value < x1)) result = (value / (x1 - x0)) - (x0 / (x1 - x0));
		else result = (x3 / (x3 - x2) - (value / (x3 - x2)));
		return result;
	}
```

##使い方
まぁこんな感じでやれば0から1の値でかえってきます。

```csharp

	void Start()
	{
		Debug.Log(FuzzyGrade(10, 10f, 50f));
		Debug.Log(FuzzyGrade(50, 10f, 50f));
		Debug.Log(FuzzyGrade(-10, 10f, 50f));
		Debug.Log(FuzzyGrade(50, 10f, 50f));
		Debug.Log(FuzzyGrade(30, 10f, 50f));

		Debug.Log(FuzzyReverseGrade(10, 10f, 50f));
		Debug.Log(FuzzyReverseGrade(55, 10f, 50f));
		Debug.Log(FuzzyReverseGrade(25f, 10f, 50f));
		Debug.Log(FuzzyReverseGrade(30f, 10f, 50f));
		Debug.Log(FuzzyReverseGrade(15f, 10f, 50f));

		Debug.Log(FuzzyTriangle(0, 0, 50, 100));
		Debug.Log(FuzzyTriangle(100, 0, 50, 100));
		Debug.Log(FuzzyTriangle(-10, 0, 50, 100));
		Debug.Log(FuzzyTriangle(140, 0, 50, 100));
		Debug.Log(FuzzyTriangle(50, 0, 50, 100));
		Debug.Log(FuzzyTriangle(30, 0, 50, 100));
		Debug.Log(FuzzyTriangle(80, 0, 50, 100));

		Debug.Log(FuzzyTrapezoid(0, 0, 40, 60, 100));
		Debug.Log(FuzzyTrapezoid(100, 0, 40, 60, 100));
		Debug.Log(FuzzyTrapezoid(-10, 0, 40, 60, 100));
		Debug.Log(FuzzyTrapezoid(110, 0, 40, 60, 100));
		Debug.Log(FuzzyTrapezoid(50, 0, 40, 60, 100));
		Debug.Log(FuzzyTrapezoid(30, 0, 40, 60, 100));
		Debug.Log(FuzzyTrapezoid(80, 0, 40, 60, 100));
	}
```

##なんか説明だけだとあれだから見える形でなにかつくってみます
プレイヤーの情報としては
・Hp 
・満腹度 
・近くに自分より強い敵がいるか
・近くに自分より弱い敵がいるか
にしてみようかな。

Hpが 2割~5割で回復したい
満腹度 1割〜5割でご飯食べたい
自分より強い敵が近くいる場合、自分とのレベル差による脅威度により逃げる(レベル差 3~20)ぐらいで
自分より弱い敵および倒せそうな敵が近くにいる場合、自分とのレベル差により追いかけて仕留める(レベル差 -10~2) ぐらいで
なお、ちょっとめんどくさくなりそうなので、今回は簡単に、近くにいる敵は常に1匹とします。
また、敵のレベル差に間がないように設定することで必ずどれかしらの行動をとるように設定しています

上記条件であればこれで完成。先ほどのメンバーシップ関数をstaticメソッドにしています。
長いif分岐などがないからテストもしやすい。

```csharp
public class Test : MonoBehaviour {

	public int hp;				// 現在HP
	public int maxHp;			// 最大Hp
	public int satietyLevel;	// 現在満腹度
	public int maxSatietyLevel;	// 最大満腹度
	public int playerLv;		// プレイヤーのレベル
	public int enemyLv;			// 近くにいる敵のレベル

	void Start()
	{
		float hpRate = (float)hp / (float)maxHp;
		float satietyRate = (float)satietyLevel / (float)maxSatietyLevel;
		float wantHeal = FuzzyLogic.FuzzyReverseGrade(hpRate, 0.2f, 0.5f);
		float wantSatiety = FuzzyLogic.FuzzyReverseGrade(satietyRate, 0.1f, 0.5f);
		float wantEscape = FuzzyLogic.FuzzyGrade(enemyLv - playerLv , 3f, 20f);
		float wantAttack = FuzzyLogic.FuzzyReverseGrade(enemyLv - playerLv, -10f, 2f);

		Debug.Log("回復したい" + wantHeal + " ご飯食べたい" + wantSatiety + " 逃げたい" + wantEscape + "仕留めたい" + wantAttack);

		float maxValue = Mathf.Max(wantHeal, wantSatiety, wantEscape, wantAttack);
		if(maxValue == wantHeal) Debug.Log("回復します");
		else if(maxValue == wantSatiety) Debug.Log("ご飯食べます");
		else if(maxValue == wantEscape) Debug.Log("逃げます");
		else if(maxValue == wantAttack) Debug.Log("仕留めます");
	}

}
```

ちょっとビジュアル的によくわからないから　もうちょっとわかりやすくしてみました。
こんな感じにするとわかりやすいかな。
![FuzzyTest.gif](https://qiita-image-store.s3.amazonaws.com/0/46444/8c7c54a2-9150-40cb-51c1-0617dfb87768.gif)


とりあえず今回はここまで。