【これでわかる!】pythonでReLU関数を実装する方法と、パーセプトロンとの関係をなるべく分かりやすく解説してみた

この記事で解決できること
  • ReLU関数ってなに?
  • pythonでReLU関数を実装したい…
  • パーセプトロンにReLU関数を組み合わせるとどうなる?


こんにちは!しゅんです!

今回はReLU関数をpythonで実装する方法について解説していきます!

それではやっていきましょう!

普段は組合せ最適化の記事を書いてたりします。
ぜひ他の記事も読んでみてください!



このブログの簡単な紹介はこちらに書いてあります。
興味があったら見てみてください。

このブログでは経営工学を勉強している現役理系大学生が、経営工学に関することを色々話していきます!


ぼくが経営工学を勉強している中で感じたことや、興味深かったことを皆さんと共有出来たら良いなと思っています。


そもそも経営工学とは何なのでしょうか。Wikipediaによると

経営工学(けいえいこうがく、英: engineering management)は、人・材料・装置・情報・エネルギーを総合したシステムの設計・改善・確立に関する活動である。そのシステムから得られる結果を明示し、予測し、評価するために、工学的な分析・設計の原理・方法とともに、数学、物理および社会科学の専門知識と経験を利用する。

引用元 : 経営工学 – Wikipedia

長々と書いてありますが、要は経営、経済の課題を理系的な観点から解決する学問です。


ReLU関数ってなに?


ReLU関数

\(f(x) = \max(x,0)\)


上の関数がReLU関数です。ReLU関数は入力値\(x\)が0より大きいとそのまま\(x\)を返し、0以下だと0を返す巻子です。\(x\)に色んな値を代入してみると

\(f(0) = \max(0,0) = 0\)

\(f(1) = \max(1,0) = 1\)

\(f(5) = \max(5,0) = 5\)

\(f(-1) = \max(-1,0) = 0\)

\(f(-5) = \max(-5,0) = 0\)

という感じになります。またReLU関数をグラフで表すと下のようになります。




もっと深堀り!


ReLU関数を微分してみよう!

上のグラフを見れば分かるように、ReLU関数は\(x>0\)と\(x<0\)でグラフの傾きが異なります。


\(x>0\)のとき\(f(x)=x\)なので\(f'(x)=1\)となります。

\(x>0\)のとき\(f(x)=0\)なので\(f'(x)=0\)となります。

\(x=0\)のときは微分をすることができません。


ReLU関数をpythonで実装してみた


関数を実装してみた


それではReLU関数をpythonで実装してみましょう。

# numpyをインポート
import numpy as np

# ReLU関数を定義
def relu(x):
  return np.maximum(x, 0)


ReLU関数はnumpyを使えばたった数行のコードで定義できます。numpyはpythonで数値計算を効率的にしてくれるライブラリで、指数関数、対数関数、三角関数などを1行で実装できます。

今回は\(\max(x,0)\)を実装したいんですが、これは「np.maximum(x,0)」で実装できます。「x」に色々な数字を代入して結果を確認してみましょう。

for x in range(-10,11):
    print(f"{x}を代入したときの値:{relu(x)}")


-10から10まで「x」をfor文で回してReLU関数に代入したときの値を出力してみました。


グラフを描画してみた


それでは次にReLU関数のグラフを描画してみましょう。

# matplotlibをインポート
import matplotlib.pyplot as plt

# -10から10の範囲を100等分する
x = np.linspace(-10, 10, 100)

# xに対応するyを計算
y = relu(x)

# グラフを描画
plt.plot(x, y)
plt.title("ReLU function")
plt.xlabel("x")
plt.ylabel("f(x)")
plt.xlim(-10,10)
plt.xticks(np.arange(-10, 12, 2))
plt.grid(True)
plt.show()


上のコードではmatplotlibを使ってReLU関数のグラフを描画しています。

5行目では「np.linspace()」を使って-10から10の範囲を100等分したものを「x」としています。「x」を表示すると


こんな感じでちゃんと-10~10の範囲を100等分しています。

8行目では今設定した「x」をReLU関数に代入したときの値を「y」としています。「y」の計算には先ほど定義した「relu」関数を使っています。「y」を表示させると


こんな感じで「x」中の各要素をReLU関数に代入したときの値になっています。

もっと深堀り!


numpyの素晴らしい点は、「y」を計算するときfor文を使わずに1発で計算できることです。「x」には100個のデータが入っているので、普通に「y」を計算しようとしたらfor文で「x」中の要素1つ1つに対してReLU関数に代入する必要がありそうです。しかし「np.maximum()」を使うことで「x」中の要素全てをいっぺんに計算することができ、計算時間を大幅に短縮することができます。


パーセプトロンにReLU関数を組み合わせるとどうなる?


それでは次にパーセプトロンにReLU関数を組み合わせてみましょう。

\\\ パーセプトロンの詳しい説明はこちらから! ///



パーセプトロンについて簡単に説明すると、\(n\)個の入力\(x\)に対して重み\(w\)とバイアス\(b\)を用いて以下の重み付き和

\(a=w_1x_1+w_2x_2+…+w_nx_n+b\)

を計算します。そしてこの重み付き和が0より大きかったら1、0以下だったら0を返すのがパーセプトロンです。このことを重み付き和を\(a\)として

\(f(a) = \begin{cases} 1 & (a > 0) \\ 0 & (a \leq 0) \end{cases}\)

と表すことにします。この式は、関数\(f\)は重み付き和\(a = w_1x_1+…+w_nx_n+b\)が0より大きかったら1、0以下だったら0を返すような関数であることを表しています。

この関数\(f\)をステップ関数と言ったりします。

もっと深堀り!


ステップ関数をグラフで表すと下のようになります。

import numpy as np
import matplotlib.pyplot as plt

def step_function(x):
  return np.where(x > 0, 1, 0)  # x>0なら1、そうでないなら0を返す

a = np.linspace(-5, 5, 500)  # -5から5までを500等分した数値列
y = step_function(x)

plt.plot(a, y, label="Step Function")
plt.xlabel("a")
plt.ylabel("f(a)")
plt.title("Step Function")
plt.grid(True)
plt.legend()
plt.show()


丁度\(a = 0\)を境に0を返すか1を返すか分かれていますね。


このステップ関数をReLU関数に変更してみましょう。すなわち関数\(f\)をステップ関数からReLU関数に変更するわけです。

例えば\((x_1,x_2)=(1,2), (w_1,w_2)=(2,3),b=-5\)のとき重み付き和\(a\)は

\(a = w_1x_1+w_2x_2+b = 2\times1+3\times2-5 = 3\)

となりますが、もし関数\(f\)がステップ関数の場合

(ステップ関数の場合)\(f(a)=f(3)=1\)

となります。一方関数\(f\)がReLU関数の場合

(ReLU関数の場合)\(f(a)=f(3)=\max(3,0)=3\)

と出力値が異なります。

もっと深堀り!


この記事ではパーセプトロンにReLU関数を組み合わせることで出力値を変えましたが、他にも様々な関数が存在します。一般にこのような関数のことを活性化関数と言ったりします。


pythonでパーセプトロンとReLU関数を組み合わせてみた


コードの説明


それでは最後にpythonを使ってパーセプトロンとReLU関数を組み合わせましょう。

# numpyをインポート
import numpy as np

# ReLU関数を定義
def relu(x):
  return np.maximum(x, 0)

# パーセプトロンの定義
def perceptron(x,w,b):
    a = np.dot(w,x) + b
    return relu(a)

#入力例
x = np.array([1, 2])
w = np.array([2, 3])
b = -5
print(perceptron(x,w,b))


2行目でnumpyをインポートしています。

5~6行目でReLU関数を定義しています。ここは先ほど説明したので省略します。

9~11行目でパーセプトロンを定義しています。基本的には前回記事で説明したものと同じですが、「return relu(a)」の所だけ違い、ReLU関数「relu()」に重み付き和「a」を代入したものを返しています。

14~17行目で実際に数値を入力して結果を出力しています。このコードを実行すると

と出力されます。これは重みが\((w_1,w_2)=(2,3)\)、バイアスが\(b=-5\)のとき、\((x_1,x_2)=(1,2)\)をパーセプトロンに入力すると3が返ってくるということを表しています。


色々な値を代入してグラフを作成する


それではこのパーセプトロンに色々な値を入力して結果をグラフにしてみましょう。


\((w_1,w_2)=(1,2), b=-5\)


# ライブラリのインポート
import matplotlib.pyplot as plt
import numpy as np

# ReLU関数の定義
def relu(x):
  return np.maximum(0, x)

# パーセプトロンの定義 (ReLU関数を使用)
def perceptron_relu(x, w, b):
    a = np.dot(w, x) + b
    return relu(a)

# 重みとバイアスの設定
w = np.array([1, 2])
b = -3

# 入力値の生成
x1_range = np.arange(-5, 5.1, 0.1)
x2_range = np.arange(-5, 5.1, 0.1)
X1, X2 = np.meshgrid(x1_range, x2_range)

# 各入力値でのパーセプトロンの出力を計算 (ReLU)
Z = np.zeros_like(X1)
for i in range(X1.shape[0]):
    for j in range(X1.shape[1]):
        x = np.array([X1[i, j], X2[i, j]])
        Z[i, j] = perceptron_relu(x, w, b)

# グラフの描画
plt.figure(figsize=(8, 6))
contour = plt.contourf(X1, X2, Z, cmap="coolwarm", alpha=0.8, levels=np.linspace(0, Z.max(), 101))
plt.colorbar(contour, ticks=np.arange(0, 51, 5))
plt.xlabel("x1")
plt.ylabel("x2")
plt.title("Perceptron Outputs (ReLU)")
plt.show()


重みが\((w_1,w_2)=(1,2)\)、バイアスが\(b=-5\)のとき、入力値を\(-5 \leq x_1,x_2 \leq 5\)の範囲で動かしてパーセプトロンに入力したときの出力結果をグラフにしています。

グラフの色が出力値を表していて青色が0赤色になるにつれて値が大きくなっています。真ん中やや上の所で、それより左下は青一色ですが、それより右上側で色がどんどん変わっていく境界が見えます。


これがちょうど\(a=0\)となるラインです。数式で表すと

\(w_1x_1+w_2x_2+b=0 \to x_1+2x_2 = 5\)

という直線が上図の境界の方程式となります。

もっと深堀り!


活性化関数がステップ関数の場合と比べてみましょう。

ReLU関数

ステップ関数


この2つのグラフは似ていますね。ReLU関数のグラフの境界の所がステップ関数のグラフの緑色の点線と対応しています。

イメージとしてはステップ関数は緑色の点線を境に0か1かはっきり分けるのに対し、ReLU関数はその境界より下の範囲は全て0で、境界より上の範囲だと、上に行くほど値が大きくなるという感じです。


\((w_1,w_2)=(3,-5), b=10\)


# ライブラリのインポート
import matplotlib.pyplot as plt
import numpy as np

# ReLU関数の定義
def relu(x):
  return np.maximum(0, x)

# パーセプトロンの定義 (ReLU関数を使用)
def perceptron_relu(x, w, b):
    a = np.dot(w, x) + b
    return relu(a)

# 重みとバイアスの設定
w = np.array([3, -5])
b = 10

# 入力値の生成
x1_range = np.arange(-5, 5.1, 0.1)
x2_range = np.arange(-5, 5.1, 0.1)
X1, X2 = np.meshgrid(x1_range, x2_range)

# 各入力値でのパーセプトロンの出力を計算 (ReLU)
Z = np.zeros_like(X1)
for i in range(X1.shape[0]):
    for j in range(X1.shape[1]):
        x = np.array([X1[i, j], X2[i, j]])
        Z[i, j] = perceptron_relu(x, w, b)

# グラフの描画
plt.figure(figsize=(8, 6))
contour = plt.contourf(X1, X2, Z, cmap="coolwarm", alpha=0.8, levels=np.linspace(0, Z.max(), 101))
plt.colorbar(contour, ticks=np.arange(0, 51, 5))
plt.xlabel("x1")
plt.ylabel("x2")
plt.title("Perceptron Outputs (ReLU)")
plt.show()


重みが\((w_1,w_2)=(3,-5)\)、バイアスが\(b=10\)のとき、入力値を\(-5 \leq x_1,x_2 \leq 5\)の範囲で動かしてパーセプトロンに入力したときの出力結果をグラフにしています。

グラフの色が出力値を表していて青色が0赤色になるにつれて値が大きくなっています。真ん中やや上の所で、それより左上は青一色ですが、それより右下側で色がどんどん変わっていく境界が見えます。


これがちょうど\(a=0\)となるラインです。数式で表すと

\(w_1x_1+w_2x_2+b=0 \to 3x_1-5x_2 = -10\)

という直線が上図の境界の方程式となります。

もっと深堀り!


活性化関数がステップ関数の場合と比べてみましょう。

ReLU関数

ステップ関数


この2つのグラフは似ていますね。ReLU関数のグラフの境界の所がステップ関数のグラフの緑色の点線と対応しています。

イメージとしてはステップ関数は緑色の点線を境に0か1かはっきり分けるのに対し、ReLU関数はその境界より左上の範囲は全て0で、境界より右下の範囲だと、右下に行くほど値が大きくなるという感じです。


おわりに


いかがでしたか。

今回の記事ではReLU関数を解説しました。

今後もこのようなAI・機械学習に関する記事を書いていきます!

最後までこの記事を読んでくれてありがとうございました。


参考文献

created by Rinker
¥3,740 (2024/09/20 08:44:17時点 楽天市場調べ-詳細)
book:18172266

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA