LoginSignup
0
0

More than 1 year has passed since last update.

コンピュータとオセロ対戦42 ~ネットワーク~

Last updated at Posted at 2022-02-11

前回

今回の目標

前回作成した層を使って、ネットワーククラスを作成します。

ここから本編

修正点

Sigmoid.java

シグモイド関数の計算が間違っていました。
今までは以下のように計算していましたが、

$$y = \frac{1}{1+e^x}$$

正しくは

$$y = \frac{1}{1+e^{-x}}$$

です。
プログラムも修正しました。
というか「\$\$」で囲むことで $\LaTeX$ 使えるんですね知りませんでした。

Layer.java

空のコンストラクタを追加しました。
また、ネットワーククラスで用いるためのコンストラクタを新しく用意しました。こちらではノードのインスタンス化を行っていません。

    /**
     * Constructor for this class.
     */
    public Layer(){
        ;
    }

    /**
     * Constructor to call from the Network class.
     * @param nodes_num number of nodes for this node.
     * @param type type of activation function for this node.
     */
    public Layer(int nodes_num, AF type){
        this.nodes_num = nodes_num;
        this.nodes = new Node[this.nodes_num];
        this.AF = type;
    }

ネットワーククラスで用いるため、順方向計算を仮に定義しました。
実際は使いません。

    /**
     * Doing forward propagation.
     * In this class, nothing to do.
     * @param in input matrix.
     * @return Matrix instance of output.
     */
    public Matrix forward(Matrix in){
        return in;
    }

cloneを追加。

    @Override
    public Layer clone(){
        Layer rtn = new Layer(this.nodes_num, this.AF);
        return rtn;
    }

forwardメソッド以外は、他の層クラスにも追加しました。

Input.java

toStringを少し変更しました。
これはInputクラスだけに限らず、Output、Denseにも同じような変更をしました。

    @Override
    public String toString(){
        String str = "Input\t";
        str += String.format("nodes num: %d", this.nodes_num);

        return str;
    }

Node.java

今回の内容とは関係ありませんが、toStringを若干変更しました。

    @Override
    public String toString(){
        String rtn = String.format("input: %d\n", this.in - 1);
        rtn += String.format("activation function: %s", this.function.toString());

        return rtn;
    }

ディレクトリ構成

MyNet
├── layer    // 層のパッケージ(すでに作成)
├── matrix   // 行列のパッケージ(すでに作成)
├── network  // ネットワークのパッケージ(今回作成)
├── nodes    // ノードのパッケージ(すでに作成)
│   └── out_function  // 活性化関数のパッケージ(すでに作成)
└── optimzer // 最適化関数のパッケージ(今は空)

Network.java

前回作成した層をコンストラクタの引数にとり、ネットワークを作成するクラスです。
インスタンス化としてはchainerやkerasに近い書き方になります。

package network;

import nodes.*;
import nodes.out_function.*;
import layer.*;
import matrix.*;

コンストラクタ

層をインスタンス化するときに一番面倒と感じたのは、ノード数と入力数をいちいち指定しなければいけないところです。
入力層以外でかつ全結合層なら、前の層のノード数が決まれば必然的に現在の層の入力数も決まります。なので、ネットワーククラスのコンストラクタに渡すのは、

  1. 入力層への入力数(説明変数の数)
  2. ノード数と活性化関数のみ指定した層のインスタンス配列

の二つだけにしたいと考えました。
ノードのインスタンス化の際は入力数の情報が必要となるので、層オブジェクトをネットワークのコンストラクタに渡す際にはまだインスタンス化したくない(というより、前の層の状態が不明である限り不可能)。
そういった理由で、上述部分で追加した層のコンストラクタを使用し、ノードがインスタンス化されていない状態の層オブジェクトを受け取ります。
そしてネットワーククラスのコンストラクタで、各層のノードをインスタンシエーションします。各層の入力数が分かっている状態ですので問題ありません。

また、コンストラクタを間違えた時用のエラー処理を施しました。
まだノードがインスタンス化されていないよね、ということを確認しています。
ネットワーククラスのインスタンシエーションはどうせ1プログラムで1回くらいしかしないだろうし、多少遅くなっても問題ないはずです。

public class Network {
    /** Layer array for this network. */
    public Layer[] layers;

    /**
     * Constructor for this class.
     * @param input_num number of input.
     * @param layers Each layers.
     */
    public Network(int input_num, Layer ... layers){
        for (int i = 0; i < layers.length; i++){
            if (layers[i].nodes[0] != null){
                System.out.println("Wrong constractor.");
                System.exit(-1);
            }
        }

        this.layers = new Layer[layers.length];
        for (int i = 0; i < this.layers.length; i++){
            this.layers[i] = layers[i].clone();
        }

        for (int i = 0; i < this.layers.length; i++){
            AF type = this.layers[i].AF;
            for (int j = 0; j < this.layers[i].nodes.length; j++){
                this.layers[i].nodes[j] = new Node(input_num, type);
            }
            input_num = this.layers[i].nodes_num;
        }
    }

forward

順方向計算。各層での計算結果の行数と列数は移り変わるのでこういう形をとりました。

    /**
     * Doing forward propagation.
     * @param in input matrix.
     * @return Matrix instance of output.
     */
    public Matrix forward(Matrix in){
        Matrix rtn = in.clone();
        for (int i = 0; i < this.layers.length; i++){
            rtn = this.layers[i].forward(rtn);
            // System.out.println(rtn);
        }

        return rtn;
    }

toString

入力数と各層の情報。

    @Override
    public String toString(){
        String rtn = String.format("input: %d\n", this.layers[0].nodes[0].in);

        for (int i = 0; i < this.layers.length; i++){
            rtn += "--------------------------------\n";
            rtn += this.layers[i].toString();
            rtn += "\n";
        }
        rtn += "--------------------------------\n";

        return rtn;
    }

summary

toStringで事足りるんですが、kerasっぽいほうが分かりやすいかなと思い実装しました。
あまり意味はないかもしれません。

    /**
     * Call toString method.
     * @return Summary of this network.
     */
    public String summary(){
        return this.toString();
    }

test.java

ネットワークを実際に動かしてみます。
説明変数の数は10で、3層のネットワーク。
ノード数はそれぞれ2、4、4。
活性化関数はそれぞれReLu、Sigmoid、Tanh。
順方向計算を行い結果を出力します。
なおこのプログラム実行の際、途中経過もコンソール出力するようにしています。

import matrix.Matrix;
import nodes.out_function.*;
import layer.*;
import network.*;

public class test {
    public static void main(String[] str){
        Network net = new Network(
            10,
            new Input(2, AF.RELU),
            new Dense(4, AF.SIGMOID),
            new Output(4, AF.TANH)
        );
        System.out.println(net.summary());

        Matrix in = new Matrix(new double[5][10]);
        for (int i = 0; i < in.row; i++){
            for (int j = 0; j < in.col; j++){
                in.matrix[i][j] = (double)i*2 + (double)j/2;
            }
        }
        System.out.println(in);
        Matrix out = net.forward(in);
        System.out.println(out);
    }
}

今回の入力行列はこちら。

image.png

この行列の末尾にバイアス用の1をつけ、入力層の各ノードが持つ重み行列と掛け算を行います。
入力層はノード数2の入力数11(説明変数の数+バイアスの数)ですので、重み行列は11行2列となります。
入力層の活性化関数はReLuなのでそれがこのまま入力層の出力となります。
(※ 実際は各ノード内で11行1列の重み行列と掛け算したのち、それぞれの結果を横方向に結合したものが入力層の出力となります)

image.png

これを中間層で、再度似たような計算を行います。
中間層はノード数4で入力数3ですので重み行列は3行4列です。

image.png

活性化関数(シグモイド)をかけます。

image.png

出力層で似たような計算を行います。

image.png
image.png

プログラムの実行結果はこちら。
ネットワークの概要を出力後、入力行列を出力し、各層の計算結果出力、最後に全体の結果を出力しているので出力層の計算結果が二回出力されています。

input: 11
--------------------------------
Input   nodes num: 2
--------------------------------
Dense   nodes num: 4
--------------------------------
Output  nodes num: 4
--------------------------------

[[0.0000 0.5000 1.0000 1.5000 2.0000 2.5000 3.0000 3.5000 4.0000 4.5000 ]
 [2.0000 2.5000 3.0000 3.5000 4.0000 4.5000 5.0000 5.5000 6.0000 6.5000 ]
 [4.0000 4.5000 5.0000 5.5000 6.0000 6.5000 7.0000 7.5000 8.0000 8.5000 ]
 [6.0000 6.5000 7.0000 7.5000 8.0000 8.5000 9.0000 9.5000 10.0000 10.5000 ]
 [8.0000 8.5000 9.0000 9.5000 10.0000 10.5000 11.0000 11.5000 12.0000 12.5000 ]]

[[23.5000 23.5000 ]
 [43.5000 43.5000 ]
 [63.5000 63.5000 ]
 [83.5000 83.5000 ]
 [103.5000 103.5000 ]]

[[1.0000 1.0000 1.0000 1.0000 ]
 [1.0000 1.0000 1.0000 1.0000 ]
 [1.0000 1.0000 1.0000 1.0000 ]
 [1.0000 1.0000 1.0000 1.0000 ]
 [1.0000 1.0000 1.0000 1.0000 ]]

[[0.9999 0.9999 0.9999 0.9999 ]
 [0.9999 0.9999 0.9999 0.9999 ]
 [0.9999 0.9999 0.9999 0.9999 ]
 [0.9999 0.9999 0.9999 0.9999 ]
 [0.9999 0.9999 0.9999 0.9999 ]]

[[0.9999 0.9999 0.9999 0.9999 ]
 [0.9999 0.9999 0.9999 0.9999 ]
 [0.9999 0.9999 0.9999 0.9999 ]
 [0.9999 0.9999 0.9999 0.9999 ]
 [0.9999 0.9999 0.9999 0.9999 ]]

Excelの結果と見比べると、同じになっているのが分かると思います。

次回は

出力結果を正解と比較し誤差の計算を行うメソッドを追加しようと思います。
どのような形にするかはまだ考え中です。

次回

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0