Pythonで超基本的なニューラルネットワークを0から実装してみた -Part 2

関連記事

画像認識や音声認識で高い性能を発揮するニューラルネットワークはTensorflowやPytorchなどのライブラリを用いて作成することがほとんどです。 それゆえ、ニューラルネットワークのグラフ構造がどのようにコーディングさ[…]

IMG

前回の記事ではニューラルネットワークのグラフ構造を実装してみました。しかし、前回は演算を行うことのできるグラフを作成しただけで分類などの機械学習のタスクを行っていません。

そこで今回は前回作成したグラフを使って分類のタスクを行ってみます!

Sponsored Links

前回実装したニューラルネットワークのグラフ

class Operation():
    
    def __init__(self, input_nodes=[]):
        
        self.input_nodes = input_nodes
        self.output_nodes = []
        
        for node in input_nodes:
            node.output_nodes.append(self)
            
        _default_graph.operations.append(self)
            
    def compute(self):
        pass

class add(Operation):
    
    def __init__(self, x, y):
        super().__init__([x,y])
        
    def compute(self, x_var, y_var):
        self.inputs = [x_var, y_var]
        return x_var + y_var
    
    

class multiply(Operation):
     
    def __init__(self, a, b):
        
        super().__init__([a, b])
    
    def compute(self, a_var, b_var):
         
        self.inputs = [a_var, b_var]
        return a_var * b_var

class matmul(Operation):
    
    def __init__(self, x, y):
        super().__init__([x,y])
        
    def compute(self, x_var, y_var):
        self.inputs = [x_var, y_var]
        return x_var.dot(y_var)
    

class Placeholder():

    def __init__(self):
        
        self.output_nodes = []
        
        _default_graph.placeholders.append(self)

        
class Variable():
    
    def __init__(self, initial_value=None):
        
        self.value = initial_value
        self.output_nodes = []
        
        _default_graph.variables.append(self)

        
class Graph():
    
    def __init__(self):
        self.operations = []
        self.placeholders = []
        self.variables = []
        
    def set_as_default(self):
        
        global _default_graph
        _default_graph = self
        
        
def traverse_postorder(operation):
    
    nodes_postorder = []
    def recurse(node):
        if isinstance(node, Operation):
            for input_node in node.input_nodes:
                recurse(input_node)
        nodes_postorder.append(node)

    recurse(operation)
    return nodes_postorder


class Session():
    
    def run(self, operation, feed_dict={}):
        nodes_postorder = traverse_postorder(operation)
        for node in nodes_postorder:            
            if type(node)  == Placeholder:
                
                node.output = feed_dict[node]
                
            elif type(node) == Variable:
                
                node.output = node.value
                
            else:#operation
                node.inputs = [input_node.output for input_node in node.input_nodes]
                
                node.output = node.compute(*node.inputs)
                
                
            if type(node.output) == list:
                node.output = np.array(node.output)
                
        return operation.output

データの準備

まずは分類する対象となるデータを準備します!scikit-learnにはmake_blobsという複数のクラスターを作成してくれる関数が用意されているのでこれを利用します。ちなみにblobは(インクなどの)しみ、ぼんやりした(形の)ものという意味らしいです。参照:https://ejje.weblio.jp/content/blob

from sklearn.datasets import make_blobs

data = make_blobs(n_samples=50, n_features=2, centers = 2, cluster_std=0.2,random_state=0)

n_samples=50で50個の点を生成することを指定しています。n_features=2で各点を二次元で表現することを指定します(x座標とy座標で表現していると考えればわかりやすいと思います)。centersで中心の数、つまり何個のクラスターを作成するかを指定します。cluster_stdは各クラスターに所属するポイントの標準偏差(中心から平均してどれくらい離れているか)を指定します。random_stateは再現性のために適当な値を指定しています。

import matplotlib.pyplot as plt

features = data[0]
labels = data[1]

fig, ax = plt.subplots()
ax.scatter(features[labels==0][:,0], features[labels==0][:,1], c="blue",label="0")
ax.scatter(features[labels==1][:,0], features[labels==1][:,1], c="red", label="1")
ax.legend()
plt.show()

dataの返り値はtupleになっていて要素の1つ目が各ポイントの座標、要素の2つ目がどのクラスターに所属しているかのラベルとなっています。

作成したデータをプロットしたものが上の図です。各25個ずつの2つのクラスターが作成できています。

Sponsored Links

2つのクラスターを分離する境界線の指定

上で作成したデータをきれいに2つに分離する直線を引いてみます。

import numpy as np

x = np.linspace(0, 3, 10)
y= x + 1

fig, ax = plt.subplots()
ax.scatter(features[labels==0][:,0], features[labels==0][:,1], c="blue",label="0")
ax.scatter(features[labels==1][:,0], features[labels==1][:,1], c="red", label="1")
ax.plot(x, y)

ax.legend()
plt.show()

先程のプロットにy=x+1の直線を引いてみました。かなりきれいに2つのクラスターを分離できているのではないでしょうか。

この直線より上側の領域がラベル0のクラスター、逆にこの直線より下側がラベル1のクラスターというふうに定義するとします。

つまり、

y−x−1>0のときラベル0のクラスター

y−x−1<0のときラベル1のクラスター

として定義するとします。

この直線からの距離が離れているほどそれぞれのクラスターに所属している可能性が高いと仮定すると

y−x−1の値が大きいほどラベル0のクラスターである可能性がより高くなる

y−x−1の値が小さいほどラベル1のクラスターである可能性がより高くなる

と考えられます。

次にこの境界線を基準にどちらのクラスターに所属している確率が高いかを計算する活性化関数を導入します。

活性化関数の導入

実際の分類用ニューラルネットワークの最後のアウトプットの活性化関数にも用いられるシグモイド関数を導入します。シグモイド関数の定義は非常に簡単です。

def sigmoid(z):
    return 1/(1+np.exp(-z))

x = np.linspace(-10,10,100)
y = sigmoid(x)
plt.plot(x,y)

シグモイド関数はxが0より大きけいほどyは1に、xば0より小さいほどyは0に近づいていく曲線になります。この関数によってどんな値がxに来ても0〜1の値に変換できる、つまり確率に変換することができると考えられます。

このシグモイドを用いて前のセクションで定義した境界線からの離れ具合を確率に変換してみます。

from mpl_toolkits.mplot3d import Axes3D


x = np.linspace(0, 3, 20)
y = np.linspace(0, 5, 20)

X, Y = np.meshgrid(x, y)
Z = np.c_[np.ravel(X), np.ravel(Y)]
Probability = np.array([sigmoid(y-x-1) for x, y in Z])

Probability = Probability.reshape(X.shape)

fig = plt.figure()
ax = Axes3D(fig)
surf = ax.plot_surface(X, Y, Probability,alpha=0.6,cmap='coolwarm')
fig.colorbar(surf)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("Probability")

ax.scatter(features[labels==0][:,0], features[labels==0][:,1],zs=1, c="blue", label="0" )

ax.scatter(features[labels==1][:,0], features[labels==1][:,1],zs=0, c="red", label="1" )
ax.legend()
plt.show()

xy平面上の各点がクラスター0に分類される確率を縦軸として3Dプロットで描画しました!

こうみてみるとシグモイド関数が境界線(y−x−1=0直線)からの離れ具合で各クラスターに所属する確率を計算する様子がよくわかると思います。

Sponsored Links

ニューラルネットワークへのシグモイド関数の導入

前置きが長すぎましたが、やっとここでニューラルネットワークに活性化関数を導入してクラスターの予測を行います。

とは言っても前回作成したグラフの最後のアウトプットにシグモイド関数を加えるだけです。

今回の境界線はy−x−1=0を指定したのでA=(-1,1), b=-1を代入します。図にすると以下のようになります。

まずシグモイド関数をOperationクラスとして実装します。

class Sigmoid(Operation):
 
    
    def __init__(self, z):

        super().__init__([z])

    def compute(self, z_val):
        
        return 1/(1+np.exp(-z_val))

これですべてのパーツが揃いました。グラフ全体を作成してみましょう。

g = Graph()

g.set_as_default()

x = Placeholder()

A = Variable([-1,1])

b = Variable(-1)

z = add(matmul(A,x),b)

a = Sigmoid(z)

このグラフは点(x,y)が得られたときクラスター0に所属する確率を返してくれます。

ここで、新たな点(x,y)=(1,4)が得られときどちらのクラスターに分類されるかをこのニューラルネットワークを用いて予測してみましょう!

sess = Session()
prediction = sess.run(operation=a,feed_dict={x:[1,4]})
print(prediction)
# 0.8807970779778823

約88%の確率でクラスター0であるという結果が返ってきました!内部で実行されている計算を図示すると以下のようになります。

もう一点、(x,y)=(3,1)が得られたときの予測も見てみましょう!

sess = Session()
prediction = sess.run(operation=a,feed_dict={x:[3,1]})
print(prediction)
# 0.04742587317756678

(x,y)=(3,1)がクラスター0である確率は約5%であるという結果が帰ってきました!

上記で予測した2点をプロットしたものが以下です。かなり妥当な結果だと思われます。

x = np.linspace(0, 3, 10)
y= x + 1

fig, ax = plt.subplots()
ax.scatter(features[labels==0][:,0], features[labels==0][:,1], c="blue",label="0",alpha=0.1)
ax.scatter(features[labels==1][:,0], features[labels==1][:,1], c="red", label="1",alpha=0.1)
ax.plot(x, y)

ax.scatter([1],[4], c="green",label="New Point 1")
ax.text(1,4,"Probability: 0.880")
ax.scatter([3],[1], c="purple",label="New Point 2")
ax.text(3,1,"Probability: 0.047",ha='right')
ax.legend()
plt.show()

まとめ

ニューラルネットワークによる分類予測の一連の流れを実装してみました。本来であればこのあと境界線を変化させてよりよく2つのクラスターを分離する境界線に更新していく学習のステップが非常に重要になるのですが、それはまた別の機会に扱いたいと思います。

ここまでご覧いただいてありがとうございました。

 

参考

https://www.udemy.com/course/complete-guide-to-tensorflow-for-deep-learning-with-python/

https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_blobs.html

Sponsored Links
SKJブログはTwitterでも役に立つ情報を発信しています!