PyLearnMLCR 03 easy : ニューラルネットワーク(簡単版)

ニューラルネットワークを簡単に組むためのコードです。もうちょっと詳しくなりたい場合は、PyLearnMLCR 03を参照のこと。

A. クラス分類問題

ダミーデータの生成

ニューラルネットワークを組むには、教師データが必要になります。本来は実験などでデータを集める必要がありますが、現状、ありませんので、ダミーのデータを生成します。以下がそのコードになりますが、ちょっと難しいのでコピペでokです。

In [1]:
import numpy as np
np.random.seed(1) # 擬似乱数シード: 毎回同じ乱数を出す

# Class1: 成績上位群
mu = [7, 7]
sigma = [[0.5, 0.05], [0.05, 0.5]]
Dat01 = np.random.multivariate_normal(mu, sigma, 100)
Label01=[]
for i in range(0, len(Dat01)):
    Label01.append("Good")

# Class 2: 成績中位群
mu = [5.5, 4]
sigma = [[0.8, -0.5], [-0.5, 0.8]]
Dat02 = np.random.multivariate_normal(mu, sigma, 100)
Label02=[]
for i in range(0, len(Dat02)):
    Label02.append("Middle")

# Class 3: 成績下位群
mu = [2, 5]
sigma = [[0.3, 0.0], [0.0, 3]]
Dat03 = np.random.multivariate_normal(mu, sigma, 100)
Label03=[]
for i in range(0, len(Dat01)):
    Label03.append("Bad")

# データ結合
X = np.concatenate([Dat01, Dat02, Dat03], 0)
Label01.extend(Label02)
Label01.extend(Label03)
Y = Label01

今回、構築するニューラルネットワークの例として、勉強方法から成績を分類する予測モデルを立ててみます。上のコードを実行すると、2つの変数、XとYが生成されます。この意味は、

  • X[i, 0]: i人目の勉強時間(範囲: 0〜1)
  • X[i, 1]: i人目の理解傾向(範囲: 0〜1)
  • Y[i]: i人目の成績(Goog, Middle, Badのどれか)

となっています。例えば、10人目の勉強時間、理解傾向、成績は以下のようになります。

In [2]:
i=10
print("勉強時間: ", X[i, 0])
print("理解傾向: ", X[i, 1])
print("成績: ", Y[i])
勉強時間:  7.034179433559699
理解傾向:  8.120159697966963
成績:  Good

勉強時間が7、理解傾向が8だと、成績が良かったみたいです。 次に、勉強時間と理解傾向が、いくつからいくつまでの範囲なのか、みてみます。

In [3]:
a_min = np.round(np.min(X[:, 0]), 3)
a_max = np.round(np.max(X[:, 0]), 3)
print("勉強時間の範囲: ", a_min, " 〜 ", a_max)

a_min = np.round(np.min(X[:, 1]), 3)
a_max = np.round(np.max(X[:, 1]), 3)
print("理解傾向の範囲: ", a_min, " 〜 ", a_max)
勉強時間の範囲:  0.622  〜  8.502
理解傾向の範囲:  0.82  〜  8.561

これで、集めたデータの中で、勉強時間と理解傾向の範囲がわかりました。このコードを解読できない人は、numpyのmin, max, round関数を要復習です。

また、len関数を利用すると、データ数がわかります。

In [4]:
print("データ数: ", len(Y))
データ数:  300

300人分のデータを集めていることがわかります。続いて、成績Good, Middle, Badが何人いるかを数えてみます。

In [5]:
Count_good = 0
for i in range(len(Y)):
    if Y[i] == "Good":
        Count_good = Count_good + 1
print("Goodの数: ", Count_good)
Goodの数:  100

成績がGoodの人は、100人いることがわかりました。何故これでカウントができるのか、自分で考えてみましょう。また、理解できたら、Bad、Middleをカウントする数も、自分で考えてみてください。

3層型ニューラルネットワークの構築(分類型)

まず、ニューラルネットワークには、入力層、中間層、出力層があります。今回で言えば、

  • 入力層: 勉強時間と理解傾向(2次元)
  • 中間層: 自分で決定する好きな次元(n次元)
  • 出力層: 成績(3次元、Bad, Good, Middle、それぞれの確信度)

となります。2次元のベクトルを、n次元のベクトルに変換し、最後に3次元のベクトルに変換するという構造を有しています。入力層の2次元は勉強時間と理解傾向で、出力層の3次元が成績を表します。中間層のn次元は、自分で決める必要があります。プログラミングにおいては、

  • 中間層の次元数: hidden_layer_sizes=(n, ) / 推奨値: 2〜6程度

で指定します。nに好きな自然数(1, 2, 3, ...)を入れる必要があります。続いて、学習回数を指定します。学習回数とは、ニューラルネットワークに何回勉強させるかを指定するパラメータです。プログラミングにおいては、

  • 学習回数: max_iter / 推奨値: 1000, 5000, 10000回など

で指定します。また、学習係数も必要です。学習係数とは、1回の学習で、思考をどれだけ変化させるかを規定するパラメータです。プログラミングにおいては、

  • 学習係数: learning_rate_init / 推奨値: 0.1, 0.01など

で指定します。なお、ニューラルネットワークは、確率的に学習していくという構造を有しています。つまり、学習させるたびに結果が変わってしまいます。結果が毎回違うと困りますから、結果を固定させることが望ましいです。このため、

  • 乱数シード: random_state=0

を指定します。ここに何か数字を入れると、その数字を採用した場合、同じ結果が出るという特性があります。よくわからない場合はここはとにかく0にして、飛ばしてokです。

ここまで理解できたら、実際にニューラルネットワークを組んでみます。

In [6]:
from sklearn.neural_network import MLPClassifier

# ニューラルネットワークのモデル定義
NN = MLPClassifier(
    hidden_layer_sizes=(3, ), #中間層の次元数
    max_iter=1000, # 学習回数
    learning_rate_init=0.01, # 学習係数
    random_state=0, # 乱数シード
    verbose=False # 学習過程の可視化 True: 可視化する、False: 可視化しない
)

これでモデルの定義が終わりました。この次に、学習を開始させます。「学習」ですから「どんな入力のとき、どんな出力が正解なのか」というデータを与えないと、ニューラルネットワークは学習を始めることができません。ここでは、XからYを予測することが問題になるので、XとYを与えて学習を開始させる必要があります。これが、以下のコードです。定義して代入したNNに対して、fit関数を使用し、XとYを与えればokです。

In [7]:
NN.fit(X, Y)
# 勉強中の方は、上のコードの、verboseをTrueにしてください。
Out[7]:
MLPClassifier(hidden_layer_sizes=(3,), learning_rate_init=0.01, max_iter=1000,
              random_state=0)

これで学習が終わりました。学習がうまくいっているのか、可視化してみます。NNに対して、loss_curve_を利用すると、学習過程の数値を取り出すことができます。これをplot関数で見てみる、というコードになります。

In [8]:
import matplotlib.pyplot as plt
loss = NN.loss_curve_
plt.plot(loss)
plt.ylabel("Loss")
plt.xlabel("Iteration")
plt.grid()

縦軸のLossとは教師データの誤差(入力Xに対する、出力Yの誤差)を表します。横軸のIterationは学習回数を表します。ですので、学習が進むたびに、Lossが下がっていることがわかります。したがって、学習はうまくいっていそうです。ところで、学習回数は1000にしたのに、図を見ると、600で止まっています。これは早期終了と言って、Lossが改善されなくなったら、自動的に学習が打ち切られる機能が入っているためです。

続いて、学習させたモデルを使用して、予測を行ってみます。予測を行うとは、ここで言えば、「勉強時間と理解傾向を入力すると、成績を予測してくれる」という意味になります。このためには、predict関数を利用します。

In [10]:
x1 = 10 # 勉強時間
x2 = 5 # 理解傾向
res = NN.predict([[x1, x2]])
print(res)
['Good']

勉強時間が10、理解傾向が5のときは、成績が良いと予測されることがわかりました。

続いて、教師データの正答率を算出してみます。まずは、教師データすべての予測結果を、Y_predというリストに格納させてみましょう。

In [11]:
Y_pred = [] # 予測結果を格納するリスト
for i in range(len(Y)):
    x1 = X[i, 0] # i人目の勉強時間
    x2 = X[i, 1] # i人目の理解傾向
    res = NN.predict([[x1, x2]]) # 予測
    Y_pred.append(res) # 予測結果のappend

これで、教師データすべての予測結果を格納できました。現在のデータ構造は、

  • Y[i]: i人目の成績(本当の正解)
  • Y_pred[i]: i人目の成績(ニューラルの予測値)

ですから、これが等しいか、チェックすればいいわけです。

In [12]:
c = 0
for i in range(len(Y)):
    if Y[i] == Y_pred[i]:
        c = c + 1 # 正答数をカウント
print("正答数: ", c)
正答数:  298

298例当たっていることがわかりました。正答率を求めるには、全データ数で割ればいいので、以下のコードを書けばokです。

In [13]:
acc = c / len(Y)
print("正答率: ", np.round(acc, 3))
正答率:  0.993

99.3%の正答率があることがわかりました。学習回数、学習係数、中間層などをいろいろ変化させて、性能がどう変化するのか、調べてみましょう。

B. 回帰問題

先ほど、「A. クラス分類問題」では、「成績が上・中・下どれなのか」を予測しました。これをクラス分類問題といいます。一方で、「成績が80点、63点、15点」のように実数で求めたい場合もあります。このタイプの問題を、回帰問題と言います。先ほどはクラス分類問題を解くニューラルネットワークでしたが、ここでは、回帰問題を解くニューラルネットワークを構成します。

ダミーデータの生成

まずは、先ほどと同じようにダミーデータを作ります。ここはコピペでokです。

どの活性化関数を使用したNNも、比較的似た出力を行なっています。いずれも、「勉強をたくさん行い、理解傾向も高いと成績も良い」、「勉強だけして理解傾向が低いと、成績は中くらい」、「勉強をしないと理解傾向にあまり依存せず成績が低い」と推定する空間を得られていることが確認できます。一番左、線形のモデルは先ほど説明した通り識別境界が単純な直線になっていることがわかります。真ん中、シグモイド関数を使用したモデルは識別境界が曲がっています。この差は、SVMにおけるカーネル関数(線形カーネル、RBFカーネル)の差と一緒ですね。最後に、一番右のReLu関数を使用したモデルは、直線を細かく区切って識別境界を生成していることがわかります。

今回は、入力層2次元(勉強時間/理解傾向)、隠れ層10次元、隠れ層10次元、出力層3次元(Bad/Middle/Good)の4層階層型NNの構築を行いました。入力の次元が2次元ですので、可視化できましたが、実際には入力次元はもっと広いことが多く、問題も複雑です。そのため、今回のような単純な問題とは異なり、設定するハイパーパラメータによって、人工知能の個性がばんばん出てきます。ぜひ、良いモデルを目指せるようになりましょう。

深く理解したい人は、前述したハイパーパラメータを色々変えて見て、特徴量空間上の違いを考察して見てください。例えば、学習回数を減らすと、どうなるでしょうか?

In [14]:
import random

random.seed(1)
Val01, Val02, Val03 = [], [], []
for i in range(0, len(Dat01)):
    Val01.append(random.randint(70, 100-1))
    Val02.append(random.randint(40, 70-1))
    Val03.append(random.randint(10, 40-1))

Val01.extend(Val02)
Val01.extend(Val03)
Yreg = Val01
print(Yreg)
[74, 95, 78, 94, 90, 76, 70, 82, 94, 92, 93, 88, 80, 70, 70, 91, 93, 77, 85, 81, 77, 79, 83, 87, 73, 93, 73, 98, 86, 86, 91, 79, 85, 82, 71, 93, 83, 81, 92, 93, 84, 73, 86, 81, 70, 79, 89, 82, 75, 70, 87, 87, 86, 88, 99, 87, 70, 97, 93, 74, 87, 71, 81, 76, 85, 83, 87, 95, 84, 95, 75, 75, 95, 97, 78, 91, 97, 70, 78, 73, 75, 72, 78, 91, 92, 92, 85, 79, 83, 78, 98, 76, 96, 70, 71, 84, 91, 96, 95, 84, 58, 64, 43, 54, 52, 43, 68, 53, 64, 54, 65, 43, 40, 60, 68, 46, 40, 64, 57, 47, 64, 69, 66, 69, 45, 67, 63, 63, 69, 66, 46, 58, 67, 58, 55, 65, 61, 57, 64, 51, 61, 64, 66, 55, 55, 62, 58, 60, 56, 64, 69, 47, 51, 51, 48, 59, 52, 66, 56, 56, 46, 55, 58, 56, 66, 51, 57, 59, 59, 47, 57, 67, 57, 66, 41, 42, 40, 64, 47, 65, 51, 45, 56, 48, 49, 50, 43, 52, 65, 43, 63, 59, 40, 52, 63, 62, 53, 47, 62, 47, 37, 12, 25, 25, 35, 25, 36, 29, 10, 18, 17, 38, 10, 27, 22, 23, 26, 24, 17, 31, 24, 10, 39, 30, 30, 19, 20, 32, 23, 39, 19, 38, 26, 37, 17, 22, 15, 38, 31, 12, 26, 15, 22, 33, 11, 37, 28, 15, 17, 16, 37, 22, 37, 24, 31, 33, 35, 38, 35, 34, 23, 37, 27, 23, 21, 10, 29, 20, 10, 30, 28, 12, 35, 39, 36, 12, 24, 34, 18, 29, 19, 15, 15, 30, 24, 25, 10, 20, 16, 18, 26, 23, 17, 14, 15, 26, 27, 30, 26, 26]

以上が成績の点数です。先ほどと違い、Goodなどではなく、数字になりました。

3層型ニューラルネットワークの構築(回帰型)

続いて、ニューラルネットワークの構築に行きます。使用する関数が異なりますので、注意が必要です。

  • MLPClassifier / クラス分類問題
  • MLPRegressor / 回帰問題

Classifierは分類、Regressionは回帰という意味があります。これが名称になっているわけです。具体的な構築コードは以下の通りです。パラメータの意味はまったく同じです。

In [15]:
from sklearn.neural_network import MLPRegressor

NNreg = MLPRegressor(
    hidden_layer_sizes=(3, ), #中間層の次元数
    max_iter=3000, # 学習回数
    learning_rate_init=0.01, # 学習係数
    random_state=0, # 乱数シード
    verbose=False # 学習過程の可視化 True: 可視化する、False: 可視化しない
)

学習コードも、まったく同じです。

In [16]:
NNreg.fit(X, Yreg)
Out[16]:
MLPRegressor(hidden_layer_sizes=(3,), learning_rate_init=0.01, max_iter=3000,
             random_state=0)

これで学習が終わりました。Lossを見てみましょう。

In [17]:
import matplotlib.pyplot as plt
loss = NNreg.loss_curve_
plt.plot(loss)
plt.ylabel("Loss")
plt.xlabel("Iteration")
plt.grid()

Lossが下がっていますので、うまく学習できているようです。では、予測してみます。

In [18]:
x1 = 9 # 勉強時間
x2 = 5 # 理解傾向
res = NNreg.predict([[x1, x2]])
print(res)
[94.06667531]

勉強時間9、理解傾向5だと、94点を取れるそうです。クラス分類問題とは異なり、実数値を予測できることが確認できました。これが回帰問題を解くニューラルネットワークです。精度評価は、PyLearnST 07: 線形回帰モデルとまったく同じ方法でokです。

まずは、すべての教師データの予測値を得ます。

In [19]:
Yreg_pred = [] # 予測結果を格納するリスト
for i in range(len(Y)):
    x1 = X[i, 0] # i人目の勉強時間
    x2 = X[i, 1] # i人目の理解傾向
    res = NNreg.predict([[x1, x2]]) # 予測
    Yreg_pred.append(res) # 予測結果のappend

そして、平均二乗誤差を求めます(よくわからない場合は、ST07を復習)。

In [20]:
from sklearn.metrics import mean_squared_error
MSE = mean_squared_error(Yreg, Yreg_pred)
print('MSE = ', MSE)
MSE =  134.74711786219535

これで回帰問題を解くニューラルネットワークの精度評価ができました。学習回数を変化させたりして、MSEがどう変化するか、調べてみましょう。

警告が出る場合

時折、

ConvergenceWarning: Stochastic Optimizer: Maximum iterations (xxx) reached and the optimization hasn't converged yet.

という警告が出る場合があります。これは、学習回数がxxx回だと、Lossが収束していないので、まだ増やしたほうがいいよ、という警告です。これが出た場合は、学習回数を増やしてみると、精度が上がることがあります。