matplotlibで領域を図示する、SymPyで関数の式を表示させる

## 概要
matplotlibを使って式の条件を満たす領域を図示する方法です。
「ゼロから作るDeep Learning」でパーセプトロンの挙動について学ぶ際、
入力と出力の関係を領域として図示してみたいと思ったので試しました。

また、SymPyを使って式の内容を表示させることもやってみました。

## 実行環境
- Windows10 Pro 64bit (Dockerのホストとして用いています）
- Dockerイメージ python:3.8
    - Debian GNU/Linux (version 10.10)
    - python 3.8.11
    - numpy 1.19.5
    - matplotlib 3.4.2
    - sympy 1.8

## 方法
### matplotlibによる領域の図示
まずはmatplotlibを使った領域の図示のやり方を説明します。

必要なライブラリをインポートします。

```python
import numpy as np
import matplotlib.pyplot as plt
```

今回は以下の関数の領域を図示しようと思います。

```python
def AND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    tmp = np.sum(w * x) + b

    if tmp <= 0:
        return 0
    else:
        return 1

def OR(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.7, 0.7])
    b = -0.4
    tmp = np.sum(w * x) + b

    if tmp <= 0:
        return 0
    else:
        return 1

def NAND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([-0.5, -0.5])
    b = 0.7
    tmp = np.sum(w * x) + b

    if tmp <= 0:
        return 0
    else:
        return 1

def XOR(x1, x2):
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    return AND(s1, s2)
```

領域の図示にはmatplotlibのcontourf関数を使います。
そのために、まず格子点を準備します。
（領域(等高線)の図示について詳しくは
[matplotlibでの描写 等高線プロットの基礎をわかりやすく説明してみた - Qiita](https://qiita.com/kazukiii/items/8f0e9559524c0bb0f5ba)
が分かりやすくまとまっています。）

```python
# xとyの最大・最小値を設定
xmin, xmax = 0, 1
ymin, ymax = 0, 1

# 領域を図示するための格子点を作成
x_arr = np.linspace(xmin, xmax, 101)
y_arr = np.linspace(ymin, ymax, 101)
xx, yy = np.meshgrid(x_arr, y_arr)
```

次に、格子点を直接代入できるようにするために、
最初に作成した関数をユニバーサル関数化します。

```python
# パーセプトロンの関数をユニバーサル関数化
v_AND = np.vectorize(lambda x1, x2 : AND(x1, x2))
v_OR = np.vectorize(lambda x1, x2 : OR(x1, x2))
v_NAND = np.vectorize(lambda x1, x2 : NAND(x1, x2))
v_XOR = np.vectorize(lambda x1, x2 : XOR(x1, x2))
```

これで準備は完了です。
`plt.contourf()`を使って領域を図示します。

```python
# 軸の目盛りの間隔を設定
tick_interval = 0.1

# ANDの領域を図示
plt.contourf(xx, yy, v_AND(xx, yy), cmap="Blues")

# 図の表示に関する設定
plt.grid()  # グリッド線を表示
plt.colorbar()  # カラーバーを表示
plt.xlim(xmin, xmax)  # x軸の表示範囲を設定
plt.ylim(ymin, ymax)  # y軸の表示範囲を設定
plt.xticks(np.arange(xmin, xmax + 0.01, tick_interval))  # x軸の目盛りを設定
plt.yticks(np.arange(ymin, ymax + 0.01, tick_interval))  # y軸の目盛りを設定
plt.xlabel("x1")  # 横軸の名前を表示
plt.ylabel("x2")  # 縦軸の名前を表示
plt.title("AND")  # 図のタイトルを表示

plt.show()
```
![AND_area.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614475/329c2abf-53c7-a8c9-e718-6a923ac0a856.png)
横軸がAND()の入力のうちx1を、縦軸がx2を表しており、
水色の領域がAND()の出力が0になる範囲を、紺色の領域が1になる範囲を表しています。

その他の関数についても同様に図示します。

```python
# ORの領域を図示
plt.contourf(xx, yy, v_OR(xx, yy), cmap="Blues")

# 図の表示に関する設定
plt.grid()  # グリッド線を表示
plt.colorbar()  # カラーバーを表示
plt.xlim(xmin, xmax)  # x軸の表示範囲を設定
plt.ylim(ymin, ymax)  # y軸の表示範囲を設定
plt.xticks(np.arange(xmin, xmax + 0.01, tick_interval))  # x軸の目盛りを設定
plt.yticks(np.arange(ymin, ymax + 0.01, tick_interval))  # y軸の目盛りを設定
plt.xlabel("x1")  # 横軸の名前を表示
plt.ylabel("x2")  # 縦軸の名前を表示
plt.title("OR")  # 図のタイトルを表示

plt.show()
```
![OR_area.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614475/a5025f1e-26c8-28d9-8916-969ebfa99cda.png)

```python
# NANDの領域を図示
plt.contourf(xx, yy, v_NAND(xx, yy), cmap="Blues")

# 図の表示に関する設定
plt.grid()  # グリッド線を表示
plt.colorbar()  # カラーバーを表示
plt.xlim(xmin, xmax)  # x軸の表示範囲を設定
plt.ylim(ymin, ymax)  # y軸の表示範囲を設定
plt.xticks(np.arange(xmin, xmax + 0.01, tick_interval))  # x軸の目盛りを設定
plt.yticks(np.arange(ymin, ymax + 0.01, tick_interval))  # y軸の目盛りを設定
plt.xlabel("x1")  # 横軸の名前を表示
plt.ylabel("x2")  # 縦軸の名前を表示
plt.title("NAND")  # 図のタイトルを表示

plt.show()
```
![NAND_area.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614475/1a65b91b-7dda-9ca5-2f46-4ac765ea76f2.png)

```python
# XORの領域を図示
plt.contourf(xx, yy, v_XOR(xx, yy), cmap="Blues")

# 図の表示に関する設定
plt.grid()  # グリッド線を表示
plt.colorbar()  # カラーバーを表示
plt.xlim(xmin, xmax)  # x軸の表示範囲を設定
plt.ylim(ymin, ymax)  # y軸の表示範囲を設定
plt.xticks(np.arange(xmin, xmax + 0.01, tick_interval))  # x軸の目盛りを設定
plt.yticks(np.arange(ymin, ymax + 0.01, tick_interval))  # y軸の目盛りを設定
plt.xlabel("x1")  # 横軸の名前を表示
plt.ylabel("x2")  # 縦軸の名前を表示
plt.title("XOR")  # 図のタイトルを表示

plt.show()
```
![XOR_area.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614475/3deba338-ed80-8b71-02ce-18f55a6e4701.png)

### SymPyによる関数の式の表示
SymPyというライブラリを使えば、関数の式を表示することもできます。

```python
import sympy as sy
```

まず、表示形式に関する設定を行います。

```python
sy.init_printing(use_latex=False, use_unicode=True)
```
デフォルトでは式がlatexで表示されますが、
これだと出力が「1」などの数字単体の場合に見にくかったので
`use_latex=False, use_unicode=True`にすることでUnicode文字として表示されるようにしました。

次に変数を設定します。
これを関数に代入することによって式を求めることが出来ます。

```python
x1 = sy.Symbol("x1")
x2 = sy.Symbol("x2")
```

また、SymPyでは条件分岐の部分はそのままではエラーになってしまうので、
代わりに`sy.Piecewise()`で条件を記述して関数を定義しなおします。
引数に`(返す値, 条件)`という形のタプルを渡してやることで、条件分岐を行うことができます。

```python
def AND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    tmp = np.sum(w * x) + b

    return sy.Piecewise((0, tmp <= 0), (1, tmp > 0))
```

この関数に先程定義した変数を代入してやることで式を表示することができます。

```python
AND(x1, x2)
```
（実行結果）

```
⎧0  for 0.5⋅x₁ + 0.5⋅x₂ - 0.7 ≤ 0
⎨                                
⎩1            otherwise
```

`sy.Piecewise()`を使った状態でも、普通の関数と同様に計算したり結果を図示することが出来ます。

```python
# ANDの領域を図示
plt.contourf(xx, yy, v_AND(xx, yy), cmap="Blues")

# 図の表示に関する設定
plt.grid()  # グリッド線を表示
plt.colorbar()  # カラーバーを表示
plt.xlim(xmin, xmax)  # x軸の表示範囲を設定
plt.ylim(ymin, ymax)  # y軸の表示範囲を設定
plt.xticks(np.arange(xmin, xmax + 0.01, tick_interval))  # x軸の目盛りを設定
plt.yticks(np.arange(ymin, ymax + 0.01, tick_interval))  # y軸の目盛りを設定
plt.xlabel("x1")  # 横軸の名前を表示
plt.ylabel("x2")  # 縦軸の名前を表示
plt.title("AND")  # 図のタイトルを表示

plt.show()
```
![AND_area.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614475/329c2abf-53c7-a8c9-e718-6a923ac0a856.png)

## 参考ページ
[matplotlibでの描写 等高線プロットの基礎をわかりやすく説明してみた - Qiita](https://qiita.com/kazukiii/items/8f0e9559524c0bb0f5ba)
[numpy.vectorizeの使い方 - Qiita](https://qiita.com/3x8tacorice/items/3cc5399e18a7e3f9db86)
[matplotlib ログスケール表示とグリッド表示 - Qiita](https://qiita.com/irs/items/cd1556c568887ff2bdd7)

[SymPyで代数演算してみる - Qiita](https://qiita.com/zawawahoge/items/1be137a8147902a5e6cb)
[SymPyで条件分岐をやってみる - Qiita](https://qiita.com/kimisyo/items/b75db1ebf1a62094caa3)
[Printing — SymPy 1.8 documentation](https://docs.sympy.org/latest/tutorial/printing.html)