LoginSignup
2
1

More than 1 year has passed since last update.

Pythonの機械学習(keras)でマイヤーの三角形の判定をやってみました

Last updated at Posted at 2021-08-08

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って簡単に層の追加できるし、
活性化関数の種類がいっぱいあるし、
いろんなものの学習させて試したくなります

想像を刺激しますね

2
1
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
2
1