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

画像認識や音声認識で高い性能を発揮するニューラルネットワークはTensorflowやPytorchなどのライブラリを用いて作成することがほとんどです。

それゆえ、ニューラルネットワークのグラフ構造がどのようにコーディングされているのかについては意識せずに使用していました。

そこで、超基本的な部分だけではありますがTensorflowのニューラルネットワークのグラフ構造の構成要素を0から実装してみようと思います!numpyなどのライブラリすら使用しません!

Tensorflowで用いられているコンセプトはそのままに、非常に簡単で単純な例を用いて実装していきます!

UdemyのComplete Guide to TensorFlow for Deep Learning with Pythonを参考にしています。https://www.udemy.com/course/complete-guide-to-tensorflow-for-deep-learning-with-python/

Sponsored Links

ニューラルネットワークの構成要素

ニューラルネットワークは頂点(ノード)と辺(エッジ)からなるグラフとして表現されます。以下のような図を見たことがあるのではないでしょうか。

このグラフ中のノードが何を表現しているかというと、インプットに対して実行する演算(足し算,掛け算など)です。そしてエッジは演算を施す対象となる数値(インプット)を表します。

一般に参考書に出てくるニューラルネットワークのノードはインプットと重みWの掛け算、バイアス項の足し算、そしてその結果に対して行う活性化関数の処理を表現していることが多いと思います。

上記の処理をプログラムに落とし込む際に以下のクラスを作成します。

  • Operation
  • Placeholder
  • Variable

Operationは上記で説明した演算を定義するクラス。

Placeholderはインプットの具体的な数値が入ってくるまで待つために用意する「空の箱」のようなクラス。

Variableはニューラルネットワークの重み(上のW)を表現するクラス。(本来のニューラルネットワークはこの重みを更新していく過程が非常に重要なのですが、この記事では重みの更新は扱いません)

それでは早速上記のクラスを実装してみましょう!

class Operation():
    
    def __init__(self, input_nodes = []):

        self.input_nodes = input_nodes # The list of input nodes
        self.output_nodes = [] # List of nodes consuming this node's output
        
        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)

ここまでの3つのクラスでニューラルネットワークの部品ができました。

次にこれらの部品を使ってニューラルネットワークのグラフを組み上げていきます。その土台となるクラスである、Graphクラスを作成しましょう!

class Graph():
    
    def __init__(self):
        self.operations = []
        self.placeholders = []
        self.variables = []
        
    def set_as_default(self):
        
        global _default_graph
        _default_graph = self

set_as_defaultメソッドの内部でglobal _default_graphと宣言して変数である_default_graphをグローバル変数として使用できるようにします。

ここまででグラフを作成する準備はできました!

グラフを作成する

今回作成するのは以下のようなネットワークです!非常に簡単ではありますが、グラフ構造を理解する上では良い題材となると思います!

z = A * x + bの式のxがPlaceholder、AとbがVariableに該当します!Aとbに具体的な数値を入れて考えてみます。例えばA=2、b=5であったとするとz=2 * x + 5という式をこのグラフは保持シていることになります。そしてインプットをxに代入することで適宜zの値を返してくれるというわけです。

g = Graph()
g.set_as_default()
print(len(_default_graph.placeholders))
## 0

A = Variable(2)
b = Variable(5)
x = Placeholder()
print(len(_default_graph.placeholders))
## 1

y = multiply(A, x)

z = add(y, b)

コードの途中で_default_graph.placeholdersの要素の数を確認しています。最初にグラフオブジェクトを作成した直後は_default_graph.placeholdersの要素数は0ですが、x = Placeholder()でxをPlaceholderとして作成したあとではこの値が1に増えていることがわかります。

ここまででグラフで実行する処理をプログラムに落とし込むことができました!しかしまだxに数値を代入して計算を実行する部分ができていません。そこで登場するのがSessionというクラスです!

Sponsored Links

計算を実行する(Sessionクラスの実装)

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

まずtraverse_postorderという関数を定義しています。この関数の処理はグラフによって定義された演算を正しい順番で処理するためのものなのですが今回の内容にそれほど重要ではないので説明は省きます。

Sessionクラスのrunメソッドの引数である operationは上記で定義したグラフを渡します。

runメソッドのfeed_dictにはPlaceholderに渡す具体的な数値を辞書の形式で指定します。(具体例を見たほうがわかりやすいので下のコードを御覧ください)

Tensorflowでも同様にSessionオブジェクトを作成、feed_dictでPlaceholderに値を渡してrunで実行するという流れでグラフを用いた演算を行います!

これで準備は整いました!さっそく計算を実行してみましょう!

sess = Session()

result = sess.run(operation=z, feed_dict={x:10}) 
#zで定義したグラフの演算を実行する。Placeholderであるxには10を入れる

print(result)
## 25

zで定義されたz=2 * x + 5という式のx(Placeholder)に10を代入することで25という結果を得ることができました!

あまりにも簡単すぎる例ではありますが、グラフの中での演算の定義とPlaceholderを介したインプットの値の代入、演算の実行を実装することができました!

次の記事ではここまでで定義したグラフを用いて分類のタスクを行います!こちらもぜひご覧ください!

参考

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

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