- 投稿日:2021-08-09T23:07:38+09:00
MITの公開クラス(Introduction to Deep Learning)のlab1-1をやってみた
はじめに こんにちは。 私は最近MITがディープラーニング(以降 DL)の授業をYouTube上で公開しているのを知り、それを使って目下勉強中です。 下記リンクには授業内で使っているスライドと各授業のYouTube動画パスがあります。 6S.191 Introduction to Deep Learning もちろん英語ですが、読める方はぜひ挑戦してみてください。結構面白いです! Lab1-1 ディープラーニングを実際に実装しているソースコードが公開されているので、それの解説をしていきます(要は備忘録です) ソースコード ⇦ 今回はpart1です! 今回の内容はTensorFlowの基本的な機能の説明です 1. tesnorflowの機能の説明 2. パーセプトロンの実装 3. 勾配降下法の実装 以上のラインナップです。 ちなみにTensorFlowはなぜTensorFlowと呼ばれるかご存知でしょうか? Tensor(N次元のベクトルや行列)のFlow(nodeや計算)をするのでTensorFlowと言う名前らしいです!明日から友達に「TesnsorFlowの名前の由来知ってる?」でマウントを取れますね! コードの解説 TensorFlowで次元数の確認と特定の次元数を持つ変数を作成 # 0次元 sport = tf.constant('Tennis', tf.string) number = tf.constant(1.41421356237, tf.float64) print("`sport` is a {}-d Tensor".format(tf.rank(sport).numpy())) print("`number` is a {}-d Tensor".format(tf.rank(number).numpy())) output `sport` is a 0-d Tensor `number` is a 0-d Tensor 0次元のtensorを定義しました。 これはスカラーを表しています。スカラーはただの値なので次元を持っていません。 tf.rank(変数).numpy()メソッドを使うことによって、その変数の次元数を取得することができます。 numpy()で次元数のみの取得を実現しています。rank()だけだと不要な値まで返ってきます。 また、tf.constantでは公式ドキュメントより「Creates a constant tensor from a tensor-like object」とあるので、 tensor型の変数を作成しているというのがわかります。第二引数でどんな型のデータを持つかを決めます。 # 1次元 = ベクトル sports = tf.constant(["Tennis", "Basketball"], tf.string) numbers = tf.constant([3.141592, 1.414213, 2.71821], tf.float64) print("`sports` is a {}-d Tensor with shape: {}".format(tf.rank(sports).numpy(), tf.shape(sports))) print("`numbers` is a {}-d Tensor with shape: {}".format(tf.rank(numbers).numpy(), tf.shape(numbers))) output `sports` is a 1-d Tensor with shape: [2] `numbers` is a 1-d Tensor with shape: [3] 1次元のtensorを定義しました。 これはベクトルの形です。ベクトルは1次元として扱われます。tf.shape(変数)で要素数を返します。 ### Defining higher-order Tensors ### '''TODO: Define a 2-d Tensor''' matrix = tf.constant([['Baseball', 'Softball'], ['Soccer', 'Football'], ['Tennis', 'Badminton']], tf.string) assert isinstance(matrix, tf.Tensor), "matrix must be a tf Tensor object" assert tf.rank(matrix).numpy() == 2 # assertについて https://codezine.jp/article/detail/12179 print("`matrix` is a {}-d Tensor with shape: {}".format(tf.rank(matrix).numpy(), tf.shape(matrix))) output `matrix` is a 2-d Tensor with shape: [3 2] rank=次元数 shape=(行数、列数)を返す。 2次元のtensorを定義しました。 2次元のデータはマトリックス(行列)になります。形としてはリストの中にリストがある形となります。 assert関数を使うことで条件を満たしていない場合AssertionErrorを表示することができ何を満たせてないかがすぐわかるようになります。 assert 条件 例として上のコードを書き換えてみます。 ### Defining higher-order Tensors ### '''TODO: Define a 2-d Tensor''' # matrix = tf.constant([['Baseball', 'Softball'], ['Soccer', 'Football'], ['Tennis', 'Badminton']], tf.string) matrix = tf.constant([1, 2 ,3 ], tf.int64) assert isinstance(matrix, tf.Tensor), "matrix must be a tf Tensor object" assert tf.rank(matrix).numpy() == 2 # assertについて https://codezine.jp/article/detail/12179 print("`matrix` is a {}-d Tensor with shape: {}".format(tf.rank(matrix).numpy(), tf.shape(matrix))) output AssertionError Traceback (most recent call last) <ipython-input-42-2e33b2575781> in <module>() 6 7 assert isinstance(matrix, tf.Tensor), "matrix must be a tf Tensor object" ----> 8 assert tf.rank(matrix).numpy() == 2 9 # assertについて https://codezine.jp/article/detail/12179 10 AssertionError: このように次元数がクリアできてない(2次元以上ではない)のでエラーが出ています。 これは自分の書いた計算式とかがあっているや欲しいoutputが手に入っているのかエラーで教えてくれるのですごく便利なものだと思います! '''TODO: Define a 4-d Tensor.''' # Use tf.zeros to initialize a 4-d Tensor of zeros with size 10 x 256 x 256 x 3. # You can think of this as 10 images where each image is RGB 256 x 256. # (画像の数、画像のheight, 画像のwidth, 画像のチャネル数) images = tf.constant(tf.zeros([10, 256, 256, 3])) assert isinstance(images, tf.Tensor), "matrix must be a tf Tensor object" assert tf.rank(images).numpy() == 4, "matrix must be of rank 4" assert tf.shape(images).numpy().tolist() == [10, 256, 256, 3], "matrix is incorrect shape" 4次元のtensorを定義しました。 コメントアウトでも書いてますが4次元のリストは画像データを表す際に使われます。上のコードでは10枚のRGB表記の写真(256x256)となります。 実際にどのような構造になっているかみてみます。 images output <tf.Tensor: shape=(10, 256, 256, 3), dtype=float32, numpy= array([[[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.], ..., [0., 0., 0.], [0., 0., 0.], [0., 0., 0.]],... ちゃんと4次元のリストになっていますね。3個の要素が入ったリストが並んでいると思いますが、これはRGB(Red, Green, Blue)の値になります。それが256x256あると言うことになります。 このようにtf.zeros()を使うことで4次元のtensorを簡単に実現することができます。他にもtf.ones, tf.fill, tf.eyeなども同じようなことができます。 またリストやarrayと同じようにスライスを用いることで指定の要素にアクセスできます。 # use slices to access substensors row_vector = matrix[1] column_vector = matrix[:,1] scalar = matrix[0,1] print("`row_vector`: {}".format(row_vector.numpy())) print("`column_vector`: {}".format(column_vector.numpy())) print("`scalar`: {}".format(scalar.numpy())) output `row_vector`: [b'Soccer' b'Football'] `column_vector`: [b'Softball' b'Football' b'Badminton'] `scalar`: b'Softball' Tesnor型同士の計算 Tensor型同士の簡単な計算方法をみていきます。 # Create the nodes in the graph, and initialize values a = tf.constant(15) b = tf.constant(61) # Add them! c1 = tf.add(a,b) c2 = a + b # TensorFlow overrides the "+" operation so that it is able to act on Tensors print(c1) print(c2) output tf.Tensor(76, shape=(), dtype=int32) tf.Tensor(76, shape=(), dtype=int32) ### Defining Tensor computations ### # Construct a simple computation function def func(a,b): '''TODO: Define the operation for c, d, e, f (use tf.add, tf.subtract, tf.multiply, tf.divide).''' c = tf.add(a,b) d = tf.subtract(b,1) e = tf.multiply(c,d) f = tf.divide(e, a) return f # Consider example values for a,b a, b = 1.5, 2.5 # Execute the computation f_out = func(a,b) print(f_out) output tf.Tensor(4.0, shape=(), dtype=float32) このようにtensorflowのメソッドを使って計算を行うこともできますし、tesnorflowは+, -, *, /の計算オペレーションもカバーしているのでこれを使っても計算できます! パーセプトロンの実装 そして今からが面白いところですね!tensorflowライブラリを使ってディープニューラルネットワークの考えの基になっているパーセプトロンを実装してみましょう! パーセプトロンはすごくシンプルなものでy=ax+bの式に非常に似ています。ベクトルxをインプットとして受け取り、各xの要素に重みw1をかけて(複数output時は行列)、全てを足してバイアスw0を足すことで結果zを出します。そして最後に非線形関数(例:sigmoid)を適用して、出力zを出します。非線形関数を使う理由としてはそのままでいくと直線の出力しか出せなくて、分類を行う時直線でしか分類が行えなくなるので使います!イメージはこんな感じですね!より細かく分類することを可能にしています! パーセプトロンの式も書いておきます。 y=g(w_{0}+\sum_i^mx_{i}w_{i}) アウトプットが一つの単純パーセプトロンの式です。i=1からはじまります(書き方がわからなかったです)。 gは非線形関数を表しています。 今回は複数のアウトプットを求めますので下の式になります。 z_{i}=w_{0,i}+\sum_j^mx_{j}w_{j,i} やることはほとんど変わりません。変わったところはoutputを複数出すために重みとバイアスが増えて行列になっているところですね。 それではpythonで実装していきましょう! ここではクラス作成して使っています。tensorflowだけを使って実装することもできますが、クラス、関数を作ることで可読性や汎用性のあるコードにすることができますので、おすすめです! では、コード全体をみましょう! # Simple perceptron ### Defining a network Layer ### # n_output_nodes: number of output nodes # input_shape: shape of the input # x: input to the layer class OurDenseLayer(tf.keras.layers.Layer): def __init__(self, n_output_nodes): super(OurDenseLayer, self).__init__() # 親クラスのコンストラクタを呼び出し self.n_output_nodes = n_output_nodes def build(self, input_shape): d = int(input_shape[-1]) # Define and initialize parameters: a weight matrix W and bias b # parameter is random at first self.W = self.add_weight('weight', shape=[d, self.n_output_nodes]) # note the dimensionality self.b = self.add_weight('bias', shape=[1, self.n_output_nodes]) # note the dimensionality def call(self, x): '''TODO: define the operation for z (hint: use tf.matmul)''' z = tf.matmul(x, self.W) + self.b # TODO '''TODO: define the operation for out (hint: use tf.sigmoid)''' y = tf.sigmoid(z) # TODO return y # Since layer parameters are initialized randomly, we will set a random seed for reproducibility tf.random.set_seed(1) layer = OurDenseLayer(3) # 3 outputs layer.build((1,2)) # 1行、2列 → inputが2つある x_input = tf.constant([[1,2.]], shape=(1,2)) y = layer.call(x_input) # test the output print(y.numpy()) # should output 3 answers mdl.lab1.test_custom_dense_layer_output(y) output [[0.2697859 0.45750412 0.66536945]] [PASS] test_custom_dense_layer_output True となります。アウトプットを見てわかる通り今回は3つのアウトプットを出すようにしたので3つでてきてます。 では順番に解説していきます。クラスの解説は今回の目的とは外れるのでしません。 まずコンストラクタ__init__でアウトプットがいくつかを指定できるようにします。 build()メソッドでインプットの形を受け取ります。インプットはベクトルなので(1, n)となります。 それを使い必要な重みとバイアスを初期化しています。 最後にcall()メソッドで計算を行なっています。tf.matmulでベクトルxと重みとバイアスの行列を計算しています。 そして最後にシグモイド関数(tf.sigmoid)を適用してアウトプットyを出しています。 ちなみにcallメソッドはobj.call()かobj()で呼び出すことができます。 パーセプトロンのアルゴリズムは非常にシンプルでわかりやすかったと思います!今はインプット層とアウトプット層しかありませんでしたが、 これをもっと多層にしていけば最終的にディープニューラルネットワーク(Fully Connected Neural Network)になります! 続いてSequential modelを使っても実装してみます。 # Sequential model using Model API ### Defining a model using subclassing ### from tensorflow.keras import Model from tensorflow.keras.layers import Dense class SubclassModel(tf.keras.Model): # Define the model's layer def __init__(self, n_output_nodes): super(SubclassModel, self).__init__() '''TODO: Our model consists of a single Dense layer. Define this layer.''' self.dense_layer = Dense(n_output_nodes, activation='sigmoid') # アウトプット層のobj作成 # call function to define the Model's forward pass def call(self, inputs): return self.dense_layer(inputs).numpy() n_output_nodes = 3 model = SubclassModel(n_output_nodes) x_input = tf.constant([[1,2.]], shape=(1,2)) print(model.call(x_input)) output [[0.5607363 0.6566898 0.1249697]] Sequentialは次回以降も頻繁に使われますが、ネットワーク層を順番に定義していくことができ非常に読みやすく使いやすいです。 今回はアウトプット層のみですが、より多層にすることも可能です。 以上でパーセプトロンの実装例は終わりです!このアルゴリズムの理解は今後のニューラルネットワークの理解に非常に役に立ちますのでぜひ理解してください! 勾配降下法の実装 勾配降下法ニューラルネットワークの学習において非常に重要になってくる考えです。学習フェーズではLoss関数(例:relu)を使用して、予測されyと正解yのlossを出します。lossは小さければ誤差がない(正解に近い)のでそれを使って勾配降下法のアルゴリズムで重みを最適化していきます。要は重みに対するLossの微分をとって最適な重みを探そうと言うものです。 new W=W-n\frac{\partial L}{\partial W} 微分を求めることで傾きを知れるのでlossが小さくなる向きがわかります(つまりマイナス方向に移動したい)。nはlearning rateと呼ばれるもので実際にどのくらいの距離を移動するかを決定しています。そして移動した分をWから引いて新しいWを取得します。これを繰り返すことで図のようにlossが小さい方へと移動し精度の高い予測ができるようになります。簡単ですが以上が勾配降下法の説明です。これの派生で色々な手法や勾配降下方の問題点もありますので興味ある方は調べてみてください。 では、まずtensorflowを使って簡単な勾配(微分)を計算してみます。ここではわかりにくいですが確率的勾配降下法(SGD)が使われています。 GradientTapeを使ってしてみます。 # First, we will look at how we can compute gradients using GradientTape and access them for computation. ### Gradient computation with GradientTape ### # y = x^2 # Example: x = 3.0 x = tf.Variable(3.0) # Initiate the gradient tape with tf.GradientTape() as tape: # Define the function y = x * x # Access the gradient -- derivative of y with respect to x dy_dx = tape.gradient(y,x) assert dy_dx.numpy() == 6.0 print(dy_dx) output tf.Tensor(6.0, shape=(), dtype=float32) with tf.GradientTape()の中で関数を定義します。 tape.gradient(y,x)でxに対するyの勾配を求めます。 簡単に実装することができました!これは自分でも微分できるものなので正しく計算できているか、ぜひ確認してください。 次にlossが本当に小さくなっていくのかをみていきます。ここではxをランダムに決めて正解x=4に勾配降下法を使って近づけていきます。 パーセプトロンを使っての計算などはせずに単純に定義したxが正解xに近づけるかをみていきます。 # how actually we use gradient to minimize loss ### Function minimization with automatic differentiation and SGD ### # initialize a random value for our initial x x = tf.Variable([tf.random.normal([1])]) print('Initializing x={}'.format(x.numpy())) learning_rate = 1e-2 # learning rate for SGD history = [] # Define the target value x_f = 4 # We will run SGD for a number of iterations. At each iteration, we compute the loss, # compute the derivative of the loss with respect to x, and perform the SGD update. for i in range(500): with tf.GradientTape() as tape: '''TODO: define the loss as described above''' # loss = tf.keras.metrics.mean_squared_error(x_f, x) loss = (x_f - x)**2 # loss minimization using gradient tape grad = tape.gradient(loss, x) # compute the derivative of the loss with respect to x new_x = x - learning_rate*grad # sgd update x.assign(new_x) # update the value of x history.append(x.numpy()[0]) # plot the evolution of x as we optimize towards x_f plt.plot(history) plt.plot([0,500], [x_f,x_f]) plt.legend(('Predicted', 'True')) plt.xlabel('Iteration') plt.ylabel('x value') output Initializing x=[[-1.1771784]] 実際に正解に近づいてるのがわかりますね。やっていることは先程説明した通りで、勾配降下法で最適なxを見つけて、xをアップデートしてより正解xに近づくようにしこれを500回繰り返してます。グラフで見てわかるように正解にどんどん近づいていってます。 以上で勾配降下法の説明終わりです! まとめ 以上でtensorflowの説明を終わります。今回は学習や予測を通しての実装はしませんでしたが、今回の書いたコードをうまく組み合わせば、自分で予測を行うようプログラムできると思います。興味あればぜひやってみてください! パーセプトロンの理解は非常に重要ですので押さえておいてください。また、勾配降下法は学習において最重要なので、理屈を知っておくことは非常に重要です。 では、次回は音楽自動生成をRNNでしていきます! 間違いや理解しにくいところがあればコメントしていただけると幸いです。
- 投稿日:2021-08-09T23:07:38+09:00
パーセプトロンの実装とアルゴリズムの説明
はじめに こんにちは。 私は最近MITがディープラーニング(以降 DL)の授業をYouTube上で公開しているのを知り、それを使って目下勉強中です。 下記リンクには授業内で使っているスライドと各授業のYouTube動画パスがあります。 6S.191 Introduction to Deep Learning もちろん英語ですが、読める方はぜひ挑戦してみてください。結構面白いです! Lab1-1 ディープラーニングを実際に実装しているソースコードが公開されているので、それの解説をしていきます(要は備忘録です) ソースコード ⇦ 今回はpart1です! 今回の内容はTensorFlowの基本的な機能の説明です 1. tesnorflowの機能の説明 2. パーセプトロンの実装 3. 勾配降下法の実装 以上のラインナップです。 ちなみにTensorFlowはなぜTensorFlowと呼ばれるかご存知でしょうか? Tensor(N次元のベクトルや行列)のFlow(nodeや計算)をするのでTensorFlowと言う名前らしいです!明日から友達に「TesnsorFlowの名前の由来知ってる?」でマウントを取れますね! コードの解説 TensorFlowで次元数の確認と特定の次元数を持つ変数を作成 # 0次元 sport = tf.constant('Tennis', tf.string) number = tf.constant(1.41421356237, tf.float64) print("`sport` is a {}-d Tensor".format(tf.rank(sport).numpy())) print("`number` is a {}-d Tensor".format(tf.rank(number).numpy())) output `sport` is a 0-d Tensor `number` is a 0-d Tensor 0次元のtensorを定義しました。 これはスカラーを表しています。スカラーはただの値なので次元を持っていません。 tf.rank(変数).numpy()メソッドを使うことによって、その変数の次元数を取得することができます。 numpy()で次元数のみの取得を実現しています。rank()だけだと不要な値まで返ってきます。 また、tf.constantでは公式ドキュメントより「Creates a constant tensor from a tensor-like object」とあるので、 tensor型の変数を作成しているというのがわかります。第二引数でどんな型のデータを持つかを決めます。 # 1次元 = ベクトル sports = tf.constant(["Tennis", "Basketball"], tf.string) numbers = tf.constant([3.141592, 1.414213, 2.71821], tf.float64) print("`sports` is a {}-d Tensor with shape: {}".format(tf.rank(sports).numpy(), tf.shape(sports))) print("`numbers` is a {}-d Tensor with shape: {}".format(tf.rank(numbers).numpy(), tf.shape(numbers))) output `sports` is a 1-d Tensor with shape: [2] `numbers` is a 1-d Tensor with shape: [3] 1次元のtensorを定義しました。 これはベクトルの形です。ベクトルは1次元として扱われます。tf.shape(変数)で要素数を返します。 ### Defining higher-order Tensors ### '''TODO: Define a 2-d Tensor''' matrix = tf.constant([['Baseball', 'Softball'], ['Soccer', 'Football'], ['Tennis', 'Badminton']], tf.string) assert isinstance(matrix, tf.Tensor), "matrix must be a tf Tensor object" assert tf.rank(matrix).numpy() == 2 # assertについて https://codezine.jp/article/detail/12179 print("`matrix` is a {}-d Tensor with shape: {}".format(tf.rank(matrix).numpy(), tf.shape(matrix))) output `matrix` is a 2-d Tensor with shape: [3 2] rank=次元数 shape=(行数、列数)を返す。 2次元のtensorを定義しました。 2次元のデータはマトリックス(行列)になります。形としてはリストの中にリストがある形となります。 assert関数を使うことで条件を満たしていない場合AssertionErrorを表示することができ何を満たせてないかがすぐわかるようになります。 assert 条件 例として上のコードを書き換えてみます。 ### Defining higher-order Tensors ### '''TODO: Define a 2-d Tensor''' # matrix = tf.constant([['Baseball', 'Softball'], ['Soccer', 'Football'], ['Tennis', 'Badminton']], tf.string) matrix = tf.constant([1, 2 ,3 ], tf.int64) assert isinstance(matrix, tf.Tensor), "matrix must be a tf Tensor object" assert tf.rank(matrix).numpy() == 2 # assertについて https://codezine.jp/article/detail/12179 print("`matrix` is a {}-d Tensor with shape: {}".format(tf.rank(matrix).numpy(), tf.shape(matrix))) output AssertionError Traceback (most recent call last) <ipython-input-42-2e33b2575781> in <module>() 6 7 assert isinstance(matrix, tf.Tensor), "matrix must be a tf Tensor object" ----> 8 assert tf.rank(matrix).numpy() == 2 9 # assertについて https://codezine.jp/article/detail/12179 10 AssertionError: このように次元数がクリアできてない(2次元以上ではない)のでエラーが出ています。 これは自分の書いた計算式とかがあっているや欲しいoutputが手に入っているのかエラーで教えてくれるのですごく便利なものだと思います! '''TODO: Define a 4-d Tensor.''' # Use tf.zeros to initialize a 4-d Tensor of zeros with size 10 x 256 x 256 x 3. # You can think of this as 10 images where each image is RGB 256 x 256. # (画像の数、画像のheight, 画像のwidth, 画像のチャネル数) images = tf.constant(tf.zeros([10, 256, 256, 3])) assert isinstance(images, tf.Tensor), "matrix must be a tf Tensor object" assert tf.rank(images).numpy() == 4, "matrix must be of rank 4" assert tf.shape(images).numpy().tolist() == [10, 256, 256, 3], "matrix is incorrect shape" 4次元のtensorを定義しました。 コメントアウトでも書いてますが4次元のリストは画像データを表す際に使われます。上のコードでは10枚のRGB表記の写真(256x256)となります。 実際にどのような構造になっているかみてみます。 images output <tf.Tensor: shape=(10, 256, 256, 3), dtype=float32, numpy= array([[[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.], ..., [0., 0., 0.], [0., 0., 0.], [0., 0., 0.]],... ちゃんと4次元のリストになっていますね。3個の要素が入ったリストが並んでいると思いますが、これはRGB(Red, Green, Blue)の値になります。それが256x256あると言うことになります。 このようにtf.zeros()を使うことで4次元のtensorを簡単に実現することができます。他にもtf.ones, tf.fill, tf.eyeなども同じようなことができます。 またリストやarrayと同じようにスライスを用いることで指定の要素にアクセスできます。 # use slices to access substensors row_vector = matrix[1] column_vector = matrix[:,1] scalar = matrix[0,1] print("`row_vector`: {}".format(row_vector.numpy())) print("`column_vector`: {}".format(column_vector.numpy())) print("`scalar`: {}".format(scalar.numpy())) output `row_vector`: [b'Soccer' b'Football'] `column_vector`: [b'Softball' b'Football' b'Badminton'] `scalar`: b'Softball' Tesnor型同士の計算 Tensor型同士の簡単な計算方法をみていきます。 # Create the nodes in the graph, and initialize values a = tf.constant(15) b = tf.constant(61) # Add them! c1 = tf.add(a,b) c2 = a + b # TensorFlow overrides the "+" operation so that it is able to act on Tensors print(c1) print(c2) output tf.Tensor(76, shape=(), dtype=int32) tf.Tensor(76, shape=(), dtype=int32) ### Defining Tensor computations ### # Construct a simple computation function def func(a,b): '''TODO: Define the operation for c, d, e, f (use tf.add, tf.subtract, tf.multiply, tf.divide).''' c = tf.add(a,b) d = tf.subtract(b,1) e = tf.multiply(c,d) f = tf.divide(e, a) return f # Consider example values for a,b a, b = 1.5, 2.5 # Execute the computation f_out = func(a,b) print(f_out) output tf.Tensor(4.0, shape=(), dtype=float32) このようにtensorflowのメソッドを使って計算を行うこともできますし、tesnorflowは+, -, *, /の計算オペレーションもカバーしているのでこれを使っても計算できます! パーセプトロンの実装 そして今からが面白いところですね!tensorflowライブラリを使ってディープニューラルネットワークの考えの基になっているパーセプトロンを実装してみましょう! パーセプトロンはすごくシンプルなものでy=ax+bの式に非常に似ています。ベクトルxをインプットとして受け取り、各xの要素に重みw1をかけて(複数output時は行列)、全てを足してバイアスw0を足すことで結果zを出します。そして最後に非線形関数(例:sigmoid)を適用して、出力zを出します。非線形関数を使う理由としてはそのままでいくと直線の出力しか出せなくて、分類を行う時直線でしか分類が行えなくなるので使います!イメージはこんな感じですね!より細かく分類することを可能にしています! パーセプトロンの式も書いておきます。 y=g(w_{0}+\sum_i^mx_{i}w_{i}) アウトプットが一つの単純パーセプトロンの式です。i=1からはじまります(書き方がわからなかったです)。 gは非線形関数を表しています。 今回は複数のアウトプットを求めますので下の式になります。 z_{i}=w_{0,i}+\sum_j^mx_{j}w_{j,i} やることはほとんど変わりません。変わったところはoutputを複数出すために重みとバイアスが増えて行列になっているところですね。 それではpythonで実装していきましょう! ここではクラス作成して使っています。tensorflowだけを使って実装することもできますが、クラス、関数を作ることで可読性や汎用性のあるコードにすることができますので、おすすめです! では、コード全体をみましょう! # Simple perceptron ### Defining a network Layer ### # n_output_nodes: number of output nodes # input_shape: shape of the input # x: input to the layer class OurDenseLayer(tf.keras.layers.Layer): def __init__(self, n_output_nodes): super(OurDenseLayer, self).__init__() # 親クラスのコンストラクタを呼び出し self.n_output_nodes = n_output_nodes def build(self, input_shape): d = int(input_shape[-1]) # Define and initialize parameters: a weight matrix W and bias b # parameter is random at first self.W = self.add_weight('weight', shape=[d, self.n_output_nodes]) # note the dimensionality self.b = self.add_weight('bias', shape=[1, self.n_output_nodes]) # note the dimensionality def call(self, x): '''TODO: define the operation for z (hint: use tf.matmul)''' z = tf.matmul(x, self.W) + self.b # TODO '''TODO: define the operation for out (hint: use tf.sigmoid)''' y = tf.sigmoid(z) # TODO return y # Since layer parameters are initialized randomly, we will set a random seed for reproducibility tf.random.set_seed(1) layer = OurDenseLayer(3) # 3 outputs layer.build((1,2)) # 1行、2列 → inputが2つある x_input = tf.constant([[1,2.]], shape=(1,2)) y = layer.call(x_input) # test the output print(y.numpy()) # should output 3 answers mdl.lab1.test_custom_dense_layer_output(y) output [[0.2697859 0.45750412 0.66536945]] [PASS] test_custom_dense_layer_output True となります。アウトプットを見てわかる通り今回は3つのアウトプットを出すようにしたので3つでてきてます。 では順番に解説していきます。クラスの解説は今回の目的とは外れるのでしません。 まずコンストラクタ__init__でアウトプットがいくつかを指定できるようにします。 build()メソッドでインプットの形を受け取ります。インプットはベクトルなので(1, n)となります。 それを使い必要な重みとバイアスを初期化しています。 最後にcall()メソッドで計算を行なっています。tf.matmulでベクトルxと重みとバイアスの行列を計算しています。 そして最後にシグモイド関数(tf.sigmoid)を適用してアウトプットyを出しています。 ちなみにcallメソッドはobj.call()かobj()で呼び出すことができます。 パーセプトロンのアルゴリズムは非常にシンプルでわかりやすかったと思います!今はインプット層とアウトプット層しかありませんでしたが、 これをもっと多層にしていけば最終的にディープニューラルネットワーク(Fully Connected Neural Network)になります! 続いてSequential modelを使っても実装してみます。 # Sequential model using Model API ### Defining a model using subclassing ### from tensorflow.keras import Model from tensorflow.keras.layers import Dense class SubclassModel(tf.keras.Model): # Define the model's layer def __init__(self, n_output_nodes): super(SubclassModel, self).__init__() '''TODO: Our model consists of a single Dense layer. Define this layer.''' self.dense_layer = Dense(n_output_nodes, activation='sigmoid') # アウトプット層のobj作成 # call function to define the Model's forward pass def call(self, inputs): return self.dense_layer(inputs).numpy() n_output_nodes = 3 model = SubclassModel(n_output_nodes) x_input = tf.constant([[1,2.]], shape=(1,2)) print(model.call(x_input)) output [[0.5607363 0.6566898 0.1249697]] Sequentialは次回以降も頻繁に使われますが、ネットワーク層を順番に定義していくことができ非常に読みやすく使いやすいです。 今回はアウトプット層のみですが、より多層にすることも可能です。 以上でパーセプトロンの実装例は終わりです!このアルゴリズムの理解は今後のニューラルネットワークの理解に非常に役に立ちますのでぜひ理解してください! 勾配降下法の実装 勾配降下法ニューラルネットワークの学習において非常に重要になってくる考えです。学習フェーズではLoss関数(例:relu)を使用して、予測されyと正解yのlossを出します。lossは小さければ誤差がない(正解に近い)のでそれを使って勾配降下法のアルゴリズムで重みを最適化していきます。要は重みに対するLossの微分をとって最適な重みを探そうと言うものです。 new W=W-n\frac{\partial L}{\partial W} 微分を求めることで傾きを知れるのでlossが小さくなる向きがわかります(つまりマイナス方向に移動したい)。nはlearning rateと呼ばれるもので実際にどのくらいの距離を移動するかを決定しています。そして移動した分をWから引いて新しいWを取得します。これを繰り返すことで図のようにlossが小さい方へと移動し精度の高い予測ができるようになります。簡単ですが以上が勾配降下法の説明です。これの派生で色々な手法や勾配降下方の問題点もありますので興味ある方は調べてみてください。 では、まずtensorflowを使って簡単な勾配(微分)を計算してみます。ここではわかりにくいですが確率的勾配降下法(SGD)が使われています。 GradientTapeを使ってしてみます。 # First, we will look at how we can compute gradients using GradientTape and access them for computation. ### Gradient computation with GradientTape ### # y = x^2 # Example: x = 3.0 x = tf.Variable(3.0) # Initiate the gradient tape with tf.GradientTape() as tape: # Define the function y = x * x # Access the gradient -- derivative of y with respect to x dy_dx = tape.gradient(y,x) assert dy_dx.numpy() == 6.0 print(dy_dx) output tf.Tensor(6.0, shape=(), dtype=float32) with tf.GradientTape()の中で関数を定義します。 tape.gradient(y,x)でxに対するyの勾配を求めます。 簡単に実装することができました!これは自分でも微分できるものなので正しく計算できているか、ぜひ確認してください。 次にlossが本当に小さくなっていくのかをみていきます。ここではxをランダムに決めて正解x=4に勾配降下法を使って近づけていきます。 パーセプトロンを使っての計算などはせずに単純に定義したxが正解xに近づけるかをみていきます。 # how actually we use gradient to minimize loss ### Function minimization with automatic differentiation and SGD ### # initialize a random value for our initial x x = tf.Variable([tf.random.normal([1])]) print('Initializing x={}'.format(x.numpy())) learning_rate = 1e-2 # learning rate for SGD history = [] # Define the target value x_f = 4 # We will run SGD for a number of iterations. At each iteration, we compute the loss, # compute the derivative of the loss with respect to x, and perform the SGD update. for i in range(500): with tf.GradientTape() as tape: '''TODO: define the loss as described above''' # loss = tf.keras.metrics.mean_squared_error(x_f, x) loss = (x_f - x)**2 # loss minimization using gradient tape grad = tape.gradient(loss, x) # compute the derivative of the loss with respect to x new_x = x - learning_rate*grad # sgd update x.assign(new_x) # update the value of x history.append(x.numpy()[0]) # plot the evolution of x as we optimize towards x_f plt.plot(history) plt.plot([0,500], [x_f,x_f]) plt.legend(('Predicted', 'True')) plt.xlabel('Iteration') plt.ylabel('x value') output Initializing x=[[-1.1771784]] 実際に正解に近づいてるのがわかりますね。やっていることは先程説明した通りで、勾配降下法で最適なxを見つけて、xをアップデートしてより正解xに近づくようにしこれを500回繰り返してます。グラフで見てわかるように正解にどんどん近づいていってます。 以上で勾配降下法の説明終わりです! まとめ 以上でtensorflowの説明を終わります。今回は学習や予測を通しての実装はしませんでしたが、今回の書いたコードをうまく組み合わせば、自分で予測を行うようプログラムできると思います。興味あればぜひやってみてください! パーセプトロンの理解は非常に重要ですので押さえておいてください。また、勾配降下法は学習において最重要なので、理屈を知っておくことは非常に重要です。 では、次回は音楽自動生成をRNNでしていきます! 間違いや理解しにくいところがあればコメントしていただけると幸いです。
- 投稿日:2021-08-09T04:19:56+09:00
Pythonの機械学習(keras)でマイヤーの三角形の判定をやってみました
1 はじめに テストの本でマイヤーの三角形のテストを見てた時に これってkerasで学習させることってできるんじゃない? って思ってやってみました マイヤーの三角形 https://qiita.com/yuji38kwmt/items/26d9e3f63383b40d5554 keras https://keras.io/ja/ 1.1 この記事の目標 文字認識のサンプルは写経したけど、もう少し機械学習の基本的な使い方に触れてみたいという人に向けて書きました ”訓練データの整理”と ”訓練モデルのチューニング”と ”訓練モデルの保存と復元”と ”機械学習を進めるうえで詰まった課題と解決策の紹介”を通じて 機械学習のざっくりとした流れの経験を身近な三角形の判定を題材に 機械学習モデル構築の疑似体験が提供できたらいいなと思っています 2 学んだこと 2.1 訓練データの整理 2.1.1 2辺の和の判定 課題 z>x+y といった2辺の和の判定条件が安定しませんでした 原因 三角形の3辺の長さとその判定結果を訓練データとして モデルに与えてました そうすると z>x+y の判定条件が安定しませんでした どうやら、x+yなどの複合条件の判定は学習回数がすくないのか知らないけど、 訓練にならないようでした 対策 そこで、2辺の和を訓練データとして与えたところ、 z>x+y などの2辺の和の判定が安定するようになりました 最終的な訓練データ 引数 内容 1 辺1の長さ 2 辺2の長さ 3 辺3の長さ 4 辺1+辺2の長さ 5 辺2+辺3の長さ 6 辺3+辺1の長さ 7 三角形か否か 8 直角三角形か否か 9 2等辺三角形か否か 10 正三角形か否か 2.1.2 三角形の判定結果 課題 辺の長さが3,4,5だと”三角形である”になり、 3,5,4だと”三角形ではない”になり、 同じ形状の三角形の判定結果が違って悩んだ 原因 3,4,5と3,5,4が別の訓練データとして扱われるのために判定が割れたためであった 対策1 訓練データをモデルに読み込ませるときに辺の長さでソートした ※3,4,5も3,5,4もモデルには同じ訓練データとして渡すようになる 対策2 辺の長さでソートして予測データを入力した ※3,4,5も3,5,4もモデルには同じ予測データとして渡すようになる 結果 判定結果が一致するようになった 2.1.3 直角三角形と正三角形の判定結果 課題 直角三角形と正三角形の判定結果がずっとNGになり悩んだ 原因 直角三角形と正三角形の訓練データが少なかった ためでであった 対策 訓練データを読み込むときに訓練データを増殖させた 結果 一気に判定精度が上がった ※テンション爆上がりした 2.2 モデルの調整 2.2.1 活性化関数 課題 辺の長さが0やマイナスの時の判定結果が ”三角形である”になり悩んだ 原因 隠れ層の活性化関数にReLUを採用していた ※ReLUは0以下を切り捨てるので当たり前だ ReLU https://keras.io/ja/activations/ 対策 PReLUを採用した PReLU https://keras.io/ja/layers/advanced-activations/ 結果 辺の長さが0以下の場合の判定結果が"三角形ではない"と判定してくれるようになった 2.3 pythonの実装 課題 kerasの保存した重みを読み込みすると ~~~ 'str' object has no attribute 'decode' ~~~ のエラーが出て途方にくれた 解決方法 ※偉大な先人の知見に感謝です 3 環境構築 3.1 anaconda pythonのアナコンダのインストール説明が丁寧なので参照しながらお願いします https://www.python.jp/install/anaconda/index.html 3.2 h5pyのインポート pip3 show h5py sudo pip3 uninstall h5py sudo pip3 install h5py==2.10.0 3.3 tensorflowとkerasのインポート conda update -n base -c defaults conda conda update -all conda install tensorflow conda install keras これでインストール完了です 4 訓練データ準備 4.1 解説 三角形の訓練データを作成します detaフォルダにkeras_triangle.csvを作成します 4.1.1 訓練の流れ モデルに入力データを入れると判定結果を出力します 例) 入力データの辺1,辺2,辺3の長さがそれぞれ3,4,5だと、 直角三角形なので、 判定結果は 1,1,0,0 となります 入力データ 引数 内容 1 辺1の長さ 2 辺2の長さ 3 辺3の長さ 4 辺1+辺2の長さ 5 辺2+辺3の長さ 6 辺3+辺1の長さ 判定結果 引数 内容 1 三角形か否か 2 直角三角形か否か 3 2等辺三角形か否か 4 正三角形か否か 4.2 実行 python make_data.py 4.3 コード make_data.py import random import sys, csv, operator def judge(x,y,z): if x <= 0 or y <= 0 or z <= 0: return 0.0,0.0,0.0,0.0 if (x >= (y + z)) or (y >= (z + x)) or (z >= (x + y)): return 0.0,0.0,0.0,0.0 else: Righttriangle = 0.0 if (x*x == (y*y + z*z)) or (y*y == (z*z + x*x)) or (z*z == (x*x + y*y)): Righttriangle = 1.0 Isoscelestriangle = 0.0 if x == y or y == z or z == x: Isoscelestriangle = 1.0 Equilateraltriangle = 0.0 if x == y and y == z: Equilateraltriangle = 1.0 return 1.0,Righttriangle,Isoscelestriangle,Equilateraltriangle def normalize(in_v): out = in_v #out = out / 15.0 return out def write_data(writer,x,y,z): list_ = [x,y,z] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) list_ = [x,z,y] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) list_ = [y,x,z] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) list_ = [z,x,y] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) list_ = [y,z,x] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) list_ = [z,y,x] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) with open('data/keras_triangle_org.csv', 'w', newline="") as f: writer = csv.writer(f) #writer.writerow([11, 1, 2]) for i in range(50): x = random.randint(-3,10) y = random.randint(-3,10) z = random.randint(-3,10) r = judge(x,y,z) x = normalize(x) y = normalize(y) z = normalize(z) write_data(writer,x,y,z) for i in range(-1,11): for j in range(-1,11): for k in range(-1,11): x = i y = j z = k r = judge(x,y,z) x = normalize(x) y = normalize(y) z = normalize(z) list_ = [x,y,z] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) mycsv = csv.reader(open('data/keras_triangle_org.csv'),delimiter=',') result = sorted(mycsv, key=operator.itemgetter(3), reverse=True) print(result) with open("data/keras_triangle.csv", "w", newline="") as f: data = csv.writer(f, delimiter=',') for r in result: data.writerow(r) 5 訓練実施 5.1 モデル Layer (type) Output Shape Param # dense (Dense) (None, 6) 42 dense_1 (Dense) (None, 64) 448 dense_2 (Dense) (None, 64) 4160 p_re_lu (PReLU) (None, 64) 64 dense_3 (Dense) (None, 4) 260 入力層の要素 x,y,z,x+y,y+z,z+xの6個 出力層の要素 三角形,直角三角形,二等辺三角形,正三角形の4個 5.2 コード 5.2.1 実行 python keras_test.py 5.2.2 解説 5.2.2.1 判定結果がいまいちのとき model.fit(X_train, y_train, epochs=100, batch_size=32) epochsを大きくしてください 学習回数が増えます 5.2.2.2 判定結果がいまいちのとき2 model.add(Dense(units=6, activation='tanh')) model.add(Dense(units=64, activation='tanh')) model.add(Dense(units=64)) model.add(PReLU()) Denseを追加したり、活性化関数を変更してください 5.2.2.3 モデルを変更したときや、学習をやりなおしたいとき 'cnn_model.json' 'cnn_model.yaml' 'cnn_model_weights.hdf5' 上記3つのモデルと重みのファイルを削除してください 5.2.3 コード全体 keras_test.py # -*- coding: utf-8 -*- import numpy as np import os.path from tensorflow.keras.models import Sequential, model_from_json from tensorflow.keras.layers import Dense, Activation, PReLU X_train = [] y_train = [] model_filename_json = 'cnn_model.json' model_filename_yaml = 'cnn_model.yaml' weights_filename = 'cnn_model_weights.hdf5' import csv def normalize(in_v): out = in_v out = out / 10.0 return out def secification(x,y,z): in_data = [x,y,z] in_data.sort() in1 = normalize(in_data[0]) in2 = normalize(in_data[1]) in3 = normalize(in_data[2]) d1 = in1 + in2 d2 = in2 + in3 d3 = in3 + in1 return in1,in2,in3,d1,d2,d3 def print_judge(in_d): if in_d[0,0] > 0.9: if in_d[0,1] > 0.9: # 直角三角形 print('Righttriangle') return if in_d[0,2] > 0.9: if in_d[0,3] >0.9: # 正三角形 print('Equilateraltriangle') else: # 二等辺三角形 print('Isoscelestriangle') return # ただの三角形 print('triangle') else: # 三角形ではない print('****') with open('data/keras_triangle.csv') as f: reader = csv.reader(f) for row in reader: r0 = float(row[0]) r1 = float(row[1]) r2 = float(row[2]) if float(row[5]) != 0.0 or float(row[6]) != 0.0: # 二等辺三角形 正三角形 for i in range(100): X_train.append(secification(r0,r1,r2)) y_train.append([float(row[3]),float(row[4]),float(row[5]),float(row[6]) ]) elif float(row[4]) !=0.0: # 直角三角形 for i in range(1000): X_train.append(secification(r0,r1,r2)) y_train.append([float(row[3]),float(row[4]),float(row[5]),float(row[6]) ]) else: X_train.append(secification(r0,r1,r2)) y_train.append([float(row[3]),float(row[4]),float(row[5]),float(row[6]) ]) if True == os.path.isfile(model_filename_json) and True == os.path.isfile(weights_filename): print('load model') json_string = open(model_filename_json).read() model = model_from_json(json_string) print(type(model)) print('load weights') model.load_weights(weights_filename, by_name=False) model.compile(optimizer='sgd', loss='mean_squared_error') else: model = Sequential() model.add(Dense(units=6, activation='tanh')) model.add(Dense(units=64, activation='tanh')) model.add(Dense(units=64)) model.add(PReLU()) model.add(Dense(units=4, activation='sigmoid')) model.compile(optimizer='sgd', loss='mean_squared_error') model.fit(X_train, y_train, epochs=100, batch_size=32) loss_and_metrics = model.evaluate(X_train, y_train, batch_size=32) print('Test loss:', loss_and_metrics) # 学習結果を保存する print('save model') json_string = model.to_json() open(model_filename_json, 'w').write(json_string) yaml_string = model.to_yaml() open(model_filename_yaml, 'w').write(yaml_string) print('save weights') model.save_weights(weights_filename) print(type(model)) classes = model.predict(X_train, batch_size=32) print(classes) # 表示を小数点3桁にする np.set_printoptions(precision=3) # 学習結果を判定する print('------------------------') print("ok 1 0 0 0 e=tri") v_test = [secification(6.0,4.0,5.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ok 1 1 0 0 e=Right") v_test = [secification(3.0,4.0,5.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ok 1 1 0 0 e=Right") v_test = [secification(6.0,8.0,10.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ok 1 0 1 0 e=Iso") v_test =[secification(8.0,8.0,10.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ok 1 0 1 1 e=Equ") v_test =[secification(8.0,8.0,8.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ng") v_test = [secification(4.0,4.0,9.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ng") v_test = [secification(2.0,2.0,5.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ng") v_test = [secification(4.0,0.0,5.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ng") v_test = [secification(-1.0,4.0,5.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) model.summary() 6 判定結果 いろんなパターンを確認してみた 誤答がないので学習は成功していると思う 6.1 三角形の判定 ok 1 0 0 0 e=tri [(0.4, 0.5, 0.6, 0.9, 1.1, 1.0)] [[1.000e+00 2.769e-01 7.428e-04 2.564e-07]] triangle 各辺が4,5,6なので、ただの三角形として判定されている Good!! 6.2 小さい直角三角形の判定 ok 1 1 0 0 e=Right [(0.3, 0.4, 0.5, 0.7, 0.9, 0.8)] [[1.000e+00 9.831e-01 3.953e-05 1.107e-07]] Righttriangle 各辺が3,4,5なので、直角三角形として判定されている Good!! 6.3 大きい直角三角形の判定 ok 1 1 0 0 e=Right [(0.6, 0.8, 1.0, 1.4, 1.8, 1.6)] [[1.000e+00 9.913e-01 1.544e-07 1.128e-11]] Righttriangle 各辺が6,8,10なので、直角三角形として判定されている Good!! 6.4 二等辺三角形の判定 ok 1 0 1 0 e=Iso [(0.8, 0.8, 1.0, 1.6, 1.8, 1.8)] [[1.000e+00 2.271e-06 9.684e-01 2.313e-06]] Isoscelestriangle 各辺が8,8,10なので、二等辺三角形として判定されている Good!! 6.5 正三角形の判定 ok 1 0 1 1 e=Equ [(0.8, 0.8, 0.8, 1.6, 1.6, 1.6)] [[1.000e+00 1.083e-12 1.000e+00 9.782e-01]] Equilateraltriangle 各辺が8,8,8なので、正三角形として判定されている Good!! 6.6 三角形ではない判定1 ng [(0.4, 0.4, 0.9, 0.8, 1.3, 1.3)] [[6.076e-03 7.958e-21 1.566e-02 1.355e-10]] **** 各辺が4,4,9なので、三角形ではないと判定されている Good!! 6.7 三角形ではない判定2 ng [(0.2, 0.2, 0.5, 0.4, 0.7, 0.7)] [[1.515e-04 1.183e-17 3.476e-03 1.601e-07]] **** 各辺が2,2,5なので、三角形ではないと判定されている Good!! 6.8 三角形ではない判定3 ng [(0.0, 0.4, 0.5, 0.4, 0.9, 0.5)] [[6.118e-05 1.814e-08 2.491e-09 2.896e-09]] **** 各辺が0,4,5なので、三角形ではないと判定されている Good!! 6.9 三角形ではない判定4 ng [(-0.1, 0.4, 0.5, 0.30000000000000004, 0.9, 0.4)] [[2.748e-06 5.327e-10 5.782e-10 9.454e-09]] **** 各辺が-1,4,5なので、三角形ではないと判定されている Good!! 7 おわりに kerasって簡単に層の追加できるし、 活性化関数の種類がいっぱいあるし、 いろんなものの学習させて試したくなります 想像を刺激しますね
- 投稿日:2021-08-09T04:19:56+09:00
マイヤーの三角形の判定を機械学習でやってみました
1 はじめに テストの本でマイヤーの三角形のテストを見てた時に これってkerasで学習させることってできるんじゃない? って思ってやってみました マイヤーの三角形 https://qiita.com/yuji38kwmt/items/26d9e3f63383b40d5554 keras https://keras.io/ja/ 1.1 この記事の目標 文字認識のサンプルは写経したけど、もう少し機械学習の基本的な使い方に触れてみたいという人に向けて書きました ”訓練データの整理”と ”訓練モデルのチューニング”と ”訓練モデルの保存と復元”と ”機械学習を進めるうえで詰まった課題と解決策の紹介”を通じて 機械学習のざっくりとした流れの経験を身近な三角形の判定を題材に 機械学習モデル構築の疑似体験が提供できたらいいなと思っています 2 学んだこと 2.1 訓練データの整理 2.1.1 2辺の和の判定 課題 z>x+y といった2辺の和の判定条件が安定しませんでした 原因 三角形の3辺の長さとその判定結果を訓練データとして モデルに与えてました そうすると z>x+y の判定条件が安定しませんでした どうやら、x+yなどの複合条件の判定は学習回数がすくないのか知らないけど、 訓練にならないようでした 対策 そこで、2辺の和を訓練データとして与えたところ、 z>x+y などの2辺の和の判定が安定するようになりました 最終的な訓練データ 引数 内容 1 辺1の長さ 2 辺2の長さ 3 辺3の長さ 4 辺1+辺2の長さ 5 辺2+辺3の長さ 6 辺3+辺1の長さ 7 三角形か否か 8 直角三角形か否か 9 2等辺三角形か否か 10 正三角形か否か 2.1.2 三角形の判定結果 課題 辺の長さが3,4,5だと”三角形である”になり、 3,5,4だと”三角形ではない”になり、 同じ形状の三角形の判定結果が違って悩んだ 原因 3,4,5と3,5,4が別の訓練データとして扱われるのために判定が割れたためであった 対策1 訓練データをモデルに読み込ませるときに辺の長さでソートした ※3,4,5も3,5,4もモデルには同じ訓練データとして渡すようになる 対策2 辺の長さでソートして予測データを入力した ※3,4,5も3,5,4もモデルには同じ予測データとして渡すようになる 結果 判定結果が一致するようになった 2.1.3 直角三角形と正三角形の判定結果 課題 直角三角形と正三角形の判定結果がずっとNGになり悩んだ 原因 直角三角形と正三角形の訓練データが少なかった ためでであった 対策 訓練データを読み込むときに訓練データを増殖させた 結果 一気に判定精度が上がった ※テンション爆上がりした 2.2 モデルの調整 2.2.1 活性化関数 課題 辺の長さが0やマイナスの時の判定結果が ”三角形である”になり悩んだ 原因 隠れ層の活性化関数にReLUを採用していた ※ReLUは0以下を切り捨てるので当たり前だ ReLU https://keras.io/ja/activations/ 対策 PReLUを採用した PReLU https://keras.io/ja/layers/advanced-activations/ 結果 辺の長さが0以下の場合の判定結果が"三角形ではない"と判定してくれるようになった 2.3 pythonの実装 課題 kerasの保存した重みを読み込みすると ~~~ 'str' object has no attribute 'decode' ~~~ のエラーが出て途方にくれた 解決方法 ※偉大な先人の知見に感謝です 3 環境構築 3.1 anaconda pythonのアナコンダのインストール説明が丁寧なので参照しながらお願いします https://www.python.jp/install/anaconda/index.html 3.2 h5pyのインポート pip3 show h5py sudo pip3 uninstall h5py sudo pip3 install h5py==2.10.0 3.3 tensorflowとkerasのインポート conda update -n base -c defaults conda conda update -all conda install tensorflow conda install keras これでインストール完了です 4 訓練データ準備 4.1 解説 三角形の訓練データを作成します detaフォルダにkeras_triangle.csvを作成します 4.1.1 訓練の流れ モデルに入力データを入れると判定結果を出力します 例) 入力データの辺1,辺2,辺3の長さがそれぞれ3,4,5だと、 直角三角形なので、 判定結果は 1,1,0,0 となります 入力データ 引数 内容 1 辺1の長さ 2 辺2の長さ 3 辺3の長さ 4 辺1+辺2の長さ 5 辺2+辺3の長さ 6 辺3+辺1の長さ 判定結果 引数 内容 1 三角形か否か 2 直角三角形か否か 3 2等辺三角形か否か 4 正三角形か否か 4.2 実行 python make_data.py 4.3 コード make_data.py import random import sys, csv, operator def judge(x,y,z): if x <= 0 or y <= 0 or z <= 0: return 0.0,0.0,0.0,0.0 if (x >= (y + z)) or (y >= (z + x)) or (z >= (x + y)): return 0.0,0.0,0.0,0.0 else: Righttriangle = 0.0 if (x*x == (y*y + z*z)) or (y*y == (z*z + x*x)) or (z*z == (x*x + y*y)): Righttriangle = 1.0 Isoscelestriangle = 0.0 if x == y or y == z or z == x: Isoscelestriangle = 1.0 Equilateraltriangle = 0.0 if x == y and y == z: Equilateraltriangle = 1.0 return 1.0,Righttriangle,Isoscelestriangle,Equilateraltriangle def normalize(in_v): out = in_v #out = out / 15.0 return out def write_data(writer,x,y,z): list_ = [x,y,z] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) list_ = [x,z,y] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) list_ = [y,x,z] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) list_ = [z,x,y] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) list_ = [y,z,x] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) list_ = [z,y,x] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) with open('data/keras_triangle_org.csv', 'w', newline="") as f: writer = csv.writer(f) #writer.writerow([11, 1, 2]) for i in range(50): x = random.randint(-3,10) y = random.randint(-3,10) z = random.randint(-3,10) r = judge(x,y,z) x = normalize(x) y = normalize(y) z = normalize(z) write_data(writer,x,y,z) for i in range(-1,11): for j in range(-1,11): for k in range(-1,11): x = i y = j z = k r = judge(x,y,z) x = normalize(x) y = normalize(y) z = normalize(z) list_ = [x,y,z] ll = list(r) list_ = list_ + ll print(list_) writer.writerow(list_) mycsv = csv.reader(open('data/keras_triangle_org.csv'),delimiter=',') result = sorted(mycsv, key=operator.itemgetter(3), reverse=True) print(result) with open("data/keras_triangle.csv", "w", newline="") as f: data = csv.writer(f, delimiter=',') for r in result: data.writerow(r) 5 訓練実施 5.1 モデル Layer (type) Output Shape Param # dense (Dense) (None, 6) 42 dense_1 (Dense) (None, 64) 448 dense_2 (Dense) (None, 64) 4160 p_re_lu (PReLU) (None, 64) 64 dense_3 (Dense) (None, 4) 260 入力層の要素 x,y,z,x+y,y+z,z+xの6個 出力層の要素 三角形,直角三角形,二等辺三角形,正三角形の4個 5.2 コード 5.2.1 実行 python keras_test.py 5.2.2 解説 5.2.2.1 判定結果がいまいちのとき model.fit(X_train, y_train, epochs=100, batch_size=32) epochsを大きくしてください 学習回数が増えます 5.2.2.2 判定結果がいまいちのとき2 model.add(Dense(units=6, activation='tanh')) model.add(Dense(units=64, activation='tanh')) model.add(Dense(units=64)) model.add(PReLU()) Denseを追加したり、活性化関数を変更してください 5.2.2.3 モデルを変更したときや、学習をやりなおしたいとき 'cnn_model.json' 'cnn_model.yaml' 'cnn_model_weights.hdf5' 上記3つのモデルと重みのファイルを削除してください 5.2.3 コード全体 keras_test.py # -*- coding: utf-8 -*- import numpy as np import os.path from tensorflow.keras.models import Sequential, model_from_json from tensorflow.keras.layers import Dense, Activation, PReLU X_train = [] y_train = [] model_filename_json = 'cnn_model.json' model_filename_yaml = 'cnn_model.yaml' weights_filename = 'cnn_model_weights.hdf5' import csv def normalize(in_v): out = in_v out = out / 10.0 return out def secification(x,y,z): in_data = [x,y,z] in_data.sort() in1 = normalize(in_data[0]) in2 = normalize(in_data[1]) in3 = normalize(in_data[2]) d1 = in1 + in2 d2 = in2 + in3 d3 = in3 + in1 return in1,in2,in3,d1,d2,d3 def print_judge(in_d): if in_d[0,0] > 0.9: if in_d[0,1] > 0.9: # 直角三角形 print('Righttriangle') return if in_d[0,2] > 0.9: if in_d[0,3] >0.9: # 正三角形 print('Equilateraltriangle') else: # 二等辺三角形 print('Isoscelestriangle') return # ただの三角形 print('triangle') else: # 三角形ではない print('****') with open('data/keras_triangle.csv') as f: reader = csv.reader(f) for row in reader: r0 = float(row[0]) r1 = float(row[1]) r2 = float(row[2]) if float(row[5]) != 0.0 or float(row[6]) != 0.0: # 二等辺三角形 正三角形 for i in range(100): X_train.append(secification(r0,r1,r2)) y_train.append([float(row[3]),float(row[4]),float(row[5]),float(row[6]) ]) elif float(row[4]) !=0.0: # 直角三角形 for i in range(1000): X_train.append(secification(r0,r1,r2)) y_train.append([float(row[3]),float(row[4]),float(row[5]),float(row[6]) ]) else: X_train.append(secification(r0,r1,r2)) y_train.append([float(row[3]),float(row[4]),float(row[5]),float(row[6]) ]) if True == os.path.isfile(model_filename_json) and True == os.path.isfile(weights_filename): print('load model') json_string = open(model_filename_json).read() model = model_from_json(json_string) print(type(model)) print('load weights') model.load_weights(weights_filename, by_name=False) model.compile(optimizer='sgd', loss='mean_squared_error') else: model = Sequential() model.add(Dense(units=6, activation='tanh')) model.add(Dense(units=64, activation='tanh')) model.add(Dense(units=64)) model.add(PReLU()) model.add(Dense(units=4, activation='sigmoid')) model.compile(optimizer='sgd', loss='mean_squared_error') model.fit(X_train, y_train, epochs=100, batch_size=32) loss_and_metrics = model.evaluate(X_train, y_train, batch_size=32) print('Test loss:', loss_and_metrics) # 学習結果を保存する print('save model') json_string = model.to_json() open(model_filename_json, 'w').write(json_string) yaml_string = model.to_yaml() open(model_filename_yaml, 'w').write(yaml_string) print('save weights') model.save_weights(weights_filename) print(type(model)) classes = model.predict(X_train, batch_size=32) print(classes) # 表示を小数点3桁にする np.set_printoptions(precision=3) # 学習結果を判定する print('------------------------') print("ok 1 0 0 0 e=tri") v_test = [secification(6.0,4.0,5.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ok 1 1 0 0 e=Right") v_test = [secification(3.0,4.0,5.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ok 1 1 0 0 e=Right") v_test = [secification(6.0,8.0,10.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ok 1 0 1 0 e=Iso") v_test =[secification(8.0,8.0,10.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ok 1 0 1 1 e=Equ") v_test =[secification(8.0,8.0,8.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ng") v_test = [secification(4.0,4.0,9.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ng") v_test = [secification(2.0,2.0,5.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ng") v_test = [secification(4.0,0.0,5.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) print('------------------------') print("ng") v_test = [secification(-1.0,4.0,5.0)] print(v_test) classes = model.predict(v_test) print(classes) print_judge(classes) model.summary() 6 判定結果 いろんなパターンを確認してみた 誤答がないので学習は成功していると思う 6.1 三角形の判定 ok 1 0 0 0 e=tri [(0.4, 0.5, 0.6, 0.9, 1.1, 1.0)] [[1.000e+00 2.769e-01 7.428e-04 2.564e-07]] triangle 各辺が4,5,6なので、ただの三角形として判定されている Good!! 6.2 小さい直角三角形の判定 ok 1 1 0 0 e=Right [(0.3, 0.4, 0.5, 0.7, 0.9, 0.8)] [[1.000e+00 9.831e-01 3.953e-05 1.107e-07]] Righttriangle 各辺が3,4,5なので、直角三角形として判定されている Good!! 6.3 大きい直角三角形の判定 ok 1 1 0 0 e=Right [(0.6, 0.8, 1.0, 1.4, 1.8, 1.6)] [[1.000e+00 9.913e-01 1.544e-07 1.128e-11]] Righttriangle 各辺が6,8,10なので、直角三角形として判定されている Good!! 6.4 二等辺三角形の判定 ok 1 0 1 0 e=Iso [(0.8, 0.8, 1.0, 1.6, 1.8, 1.8)] [[1.000e+00 2.271e-06 9.684e-01 2.313e-06]] Isoscelestriangle 各辺が8,8,10なので、二等辺三角形として判定されている Good!! 6.5 正三角形の判定 ok 1 0 1 1 e=Equ [(0.8, 0.8, 0.8, 1.6, 1.6, 1.6)] [[1.000e+00 1.083e-12 1.000e+00 9.782e-01]] Equilateraltriangle 各辺が8,8,8なので、正三角形として判定されている Good!! 6.6 三角形ではない判定1 ng [(0.4, 0.4, 0.9, 0.8, 1.3, 1.3)] [[6.076e-03 7.958e-21 1.566e-02 1.355e-10]] **** 各辺が4,4,9なので、三角形ではないと判定されている Good!! 6.7 三角形ではない判定2 ng [(0.2, 0.2, 0.5, 0.4, 0.7, 0.7)] [[1.515e-04 1.183e-17 3.476e-03 1.601e-07]] **** 各辺が2,2,5なので、三角形ではないと判定されている Good!! 6.8 三角形ではない判定3 ng [(0.0, 0.4, 0.5, 0.4, 0.9, 0.5)] [[6.118e-05 1.814e-08 2.491e-09 2.896e-09]] **** 各辺が0,4,5なので、三角形ではないと判定されている Good!! 6.9 三角形ではない判定4 ng [(-0.1, 0.4, 0.5, 0.30000000000000004, 0.9, 0.4)] [[2.748e-06 5.327e-10 5.782e-10 9.454e-09]] **** 各辺が-1,4,5なので、三角形ではないと判定されている Good!! 7 おわりに kerasって簡単に層の追加できるし、 活性化関数の種類がいっぱいあるし、 いろんなものの学習させて試したくなります 想像を刺激しますね