LoginSignup
18
21

More than 5 years have passed since last update.

機械学習とは何か(ついでにsin(θ)を人工知能で計算)

Last updated at Posted at 2018-08-03

機械学習とは何か(ついでにsin(θ)を人工知能で計算)

「機械学習」についての私の理解を示します。ついでにsin(θ)を「人工知能」を使って計算してみたいと思います。

機械学習とは何か

機械学習とは何か

私の理解

機械学習は、人工知能を実現する手段の一つです。

従来の人工知能(=シンボリックAI)は、説明可能性を重視し、演繹的(論理学的)アプローチを取っていました。しかし、そのアプローチは、限界にぶつかっていました。シンボリックAIでは、コンピュータにアルゴリズムやルールを教える必要がありますが、対象分野によっては、アルゴリズムやルールを見つけることが難しいためです。

シンボリックAI

それに対し、機械学習は、説明可能性を一定程度あきらめ、帰納的(統計学的)アプローチを取り入れました。すなわち、コンピュータにデータ(=サンプルデータ)を与え、データの統計学的処理(=訓練)を重ねさせ、経験的に妥当なモデルを作らせることにしたのです。

機械学習

人間は、データを用意し、学習効果を測定する基準を考え、モデルの枠組み(=仮説)を構築します。仮説が構築できたら、コンピュータに訓練を実行させ、モデルを完成させます。訓練が終わったら、基準に照らし、学習効果(=モデルがどれだけ表現力を身に付けたか)を測定します。測定結果を検証し、パラメータのチューニングを行います。モデルが十分な表現力を得るまで、リソースと相談しつつ、一連のサイクルを繰り返します。

仮説検証のサイクル

モデルが十分な表現力を得たと判断できたら、アプリケーションに組み込み、現実世界の問題を解くのに利用します。

モデルの利用

なお、機械学習には、線形回帰、ロジスティック回帰、サポートベクターマシン、ランダムフォレスト等の技術があります。それらのクラシカルな機械学習と比べ、より説明可能性をあきらめた(=帰納的アプローチの度合いを高めた)のが、ディープラーニングです。ディープラーニングは、機械学習の対象分野を広げることに成功しました。

機械学習は、帰納的アプローチをとる関係上、多くのリソースを必要とします。訓練では、データ量と訓練回数、モデルの複雑さに応じ、消費するリソースが増大します。仮説の検証を何度も繰り返せば、さらにリソースが増大します。すなわち、多くのマシンパワー(空間)を使うか、多くの時間を使うかとなります。

ディープラーニングでは、特に多くのリソースを消費します。クラシカルな機械学習で済むなら済ませるべきだし、そもそも、本当にアルゴリズムやルールを見つけることができないか、一度は考えてみるべきです。

反省

以上のように理解しましたが、以下の点を反省します。

参考文献、参考サイト

sin(θ)を人工知能に計算させる

さて、それでは、以下四つのアプローチで、sin(θ)を計算させてみたいと思います。

  • 幾何学的計算
  • 解析学的計算
  • クラシカルな機械学習による計算
  • ディープラーニングによる計算

動作確認は、以下環境で行なっています。

  • Python 3.6.4
  • tensorflow 1.9.0
  • Keras 2.2.0

また、以下の記事に従い、毎回%matplotlib inlineと書かなくて良いように設定済みであるとします。

幾何学的計算

従来の人工知能(=シンボリックAI)のアプローチで、sin(θ)を計算させてみましょう。そのためにはコンピュータにアルゴリズムを教える必要があります。アルゴリズムは、プログラミングすることにより、教えることができます。

sin(θ)は、幾何学的には「x軸との角度がθとなるような直線と単位円の交点のy座標」として定義されます。コンピュータにsin(θ)を計算させるためには、どうプログラミングすればよいでしょう。

ゆるゆるプログラミングというサイトでは、x軸とy軸に単位ベクトルを配置し、目的の角度に十分近づくまで、両側から「挟み撃ち」していくようなアルゴリズムが紹介されています。アルゴリズムの詳しい説明はサイトをご参照ください。サイトに記載のJavaプログラムをPythonに移植の上、手を加えたものを、以下に記載します。

from math import pi, radians, sqrt
from numpy import absolute, frompyfunc, sin
import matplotlib.pyplot as plt
from statistics import mean, stdev

def _sin1(θ):
    # 角度θを0.0~359.999...の範囲に直す
    while True:
        if θ < 0.0:
            θ += 2.0 * pi
            continue
        if 2.0 * pi <= θ:
            θ -= 2.0 * pi
            continue
        break

    # 180度を超える場合、x軸に関して対称とする
    if pi < θ:
        return -1 * _sin1(2.0 * pi - θ)

    # 90〜180度の場合、y軸に関して対称とする
    if 0.5 * pi < θ:
        return _sin1(pi - θ)

    # 結果の初期化
    ans_sin = 0.0

    # 角度に応じて分岐する
    if θ == 0:
        ans_sin =  0.0
    elif θ == 0.5 * pi:
        ans_sin =  1.0
    else:
        θ1, θ2 = 0.0, 0.5 * pi
        x1, y1 = 1.0, 0.0
        x2, y2 = 0.0, 1.0
        delta = 0.0000000000000001
        loopnumber = 0
#        l = []
#        s = []

        # 無限ループ
        while True:
            # ループ回数
            loopnumber += 1
#            l.append(loopnumber)

            # 角度、座標の中間値を計算
            θm, xm, ym = (θ1 + θ2) / 2.0, (x1 + x2) / 2.0, (y1 + y2) / 2.0

            # 原点(0,0)と(xm,ym)を結ぶ線の単位ベクトル(new_x, new_y)を計算
            d = sqrt(xm ** 2 + ym ** 2)
            new_x, new_y = xm / d, ym / d
#            s.append(new_y)

            # 角度の差がdelta未満で終了
            if absolute(θm - θ) < delta:
                ans_sin = new_y

                '''
                # 収束状況を表示
                print("ループ回数:" + str(loopnumber))
                plt.plot(l, s, marker='.')
                plt.grid()
                plt.xlabel('ループ回数')
                plt.ylabel('sin(θ)')
                plt.show()
                '''
                break

            # 角度を狭める
            if (θ > θm):
                x1, y1, θ1 = new_x, new_y, θm
            else:
                x2, y2, θ2 = new_x, new_y, θm

    return ans_sin

def sin1(θ):
    return frompyfunc(_sin1, 1, 1)(θ)


deg = 30.0
rad = radians(deg)
x = sin1(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

deg = 45.0
rad = radians(deg)
x = sin1(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

deg = 60.0
rad = radians(deg)
x = sin1(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

rads = [radians(i) for i in range(0, 359)]
h = sin1(rads)
y = sin(rads)
e = absolute(y - h)
print("誤差最大値  :" + str(max(e)))
print("誤差最小値  :" + str(min(e)))
print("誤差平均値  :" + str(mean(e)))
print("誤差標準偏差:" + str(stdev(e)))
独自関数によるsin(30.0):0.5000000000000004
組込関数によるsin(30.0):0.49999999999999994
独自関数によるsin(45.0):0.7071067811865475
組込関数によるsin(45.0):0.7071067811865475
独自関数によるsin(60.0):0.8660254037844387
組込関数によるsin(60.0):0.8660254037844386
誤差最大値  :5.551115123125783e-16
誤差最小値  :0.0
誤差平均値  :1.386164670430837e-16
誤差標準偏差:1.1688186600394947e-16

高精度な値が得られています。コンピュータが、sin(θ)に関して知能を獲得したと言えます。また、プログラムの内容が説明可能であるところに、好感がもてます。説明可能なツールは、安心して利用できるためです。

解析学的計算

さて、数学の世界には、テイラー展開と呼ばれる技法があります。テイラー展開を用いることにより、以下の等式が得られます。

$$
sin(\theta) = \theta - \frac{\theta^3}{3!} + \frac{\theta^5}{5!} - \frac{\theta^7}{7!} + \frac{\theta^9}{9!} + ...
$$

オイラーの等式:$e^{i\pi} + 1 = 0$にもつながる、美しい等式です。この等式の右辺を用いてコンピュータにsin(θ)を計算させることもできるわけですが、そもそも、この右辺はいったい何をしているのでしょうか。

実は、この右辺は、いくつもの曲線を合成することで、サインカーブを再現しようとしています。

from math import factorial, pi
from numpy import arange, sin
import matplotlib.pyplot as plt

θ  = arange(0, 2.0 * pi, 0.1)
θ1 =  θ
θ3 = -θ**3 / factorial(3)
θ5 =  θ**5 / factorial(5)
θ7 = -θ**7 / factorial(7)
θ9 =  θ**9 / factorial(9)
θall = θ1 + θ3 + θ5 + θ7 + θ9
sinθ = sin(θ)

plt.figure()
plt.plot(θ, θ1, label='θ1', linestyle='--', alpha=0.5)
plt.plot(θ, θ3, label='θ3', linestyle='--', alpha=0.5)
plt.plot(θ, θ5, label='θ5', linestyle='--', alpha=0.5)
plt.plot(θ, θ7, label='θ7', linestyle='--', alpha=0.5)
plt.plot(θ, θ9, label='θ9', linestyle='--', alpha=0.5)
plt.plot(θ, θall, label='θ1+...+θ9')
plt.plot(θ, sinθ, label='sinθ')
plt.legend()
plt.ylim([-1.1, 1.1])
plt.grid()
plt.xlabel('θ')
plt.ylabel('f(θ)')
plt.show()

output_5_0.png

たった5つの曲線の合成でも、θ=3.5あたりまでは、それらしく再現できていることがわかります。

では、これをふまえて、解析学的にsin(θ)を計算するアルゴリズムを、コンピュータに教えることにしましょう。

from math import factorial, pi, radians
from numpy import absolute, frompyfunc, sin
from statistics import mean, stdev

def _sin2(θ):
    # 角度θを0.0~359.999...の範囲に直す
    while True:
        if θ < 0.0:
            θ += 2.0 * pi
            continue
        if 2.0 * pi <= θ:
            θ -= 2.0 * pi
            continue
        break

    # 180度を超える場合、x軸に関して対称とする
    if pi < θ:
        return -1 * _sin2(2.0 * pi - θ)

    # 90〜180度の場合、y軸に関して対称とする
    if 0.5 * pi < θ:
        return _sin2(pi - θ)

    return sum([(-1)**(i-1) * θ**(2*i-1) / factorial(2*i-1) for i in range(1, 12)])

def sin2(θ):
    return frompyfunc(_sin2, 1, 1)(θ)


deg = 30.0
rad = radians(deg)
x = sin2(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

deg = 45.0
rad = radians(deg)
x = sin2(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

deg = 60.0
rad = radians(deg)
x = sin2(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

rads = [radians(i) for i in range(359)]
h = sin2(rads)
y = sin(rads)
e = absolute(y - h)
print("誤差最大値  :" + str(max(e)))
print("誤差最小値  :" + str(min(e)))
print("誤差平均値  :" + str(mean(e)))
print("誤差標準偏差:" + str(stdev(e)))
独自関数によるsin(30.0):0.49999999999999994
組込関数によるsin(30.0):0.49999999999999994
独自関数によるsin(45.0):0.7071067811865475
組込関数によるsin(45.0):0.7071067811865475
独自関数によるsin(60.0):0.8660254037844385
組込関数によるsin(60.0):0.8660254037844386
誤差最大値  :3.3306690738754696e-16
誤差最小値  :0.0
誤差平均値  :1.1695900013687744e-16
誤差標準偏差:9.066751491381523e-17

またしても、高精度な値が得られています。コンピュータが、sin(θ)に関してさらなる知能を獲得したと言えます。しかし、テイラー展開を知らない人間に対しては、説明のしようのないプログラムではあります。でも、それは仕方ないでしょう。以下等式が成り立つことは数学的事実なのです。それゆえ、ツールの妥当性は担保されており、安心して利用できると言えます。

$$
sin(\theta) = \theta - \frac{\theta^3}{3!} + \frac{\theta^5}{5!} - \frac{\theta^7}{7!} + \frac{\theta^9}{9!} + ...
$$

さて、sin(θ)の計算方法を二つ、コンピュータに教えたわけですが、両者の性能を比較してみましょう。

from math import radians

deg = 30.0
rad = radians(deg)
%timeit x = sin1(rad)
%timeit x = sin2(rad)
64.1 µs ± 502 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
9.16 µs ± 62.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

解析学的な計算方法の方が約7倍速いという結果となりました。数学者ブルック・テイラーは偉大ですね。

クラシカルな機械学習による計算

クラシカルな機械学習による手法(重回帰分析)で、コンピュータにsin(θ)を計算させてみましょう。まず「sin(θ)がθのn次の多項式で表現できる」という仮説を立てます。

$$sin(\theta) = b_1\theta^1 + b_2\theta^2 + b_3\theta^3 ...$$

次に、0から0.5πの間に無数に存在する実数の内、300個をサンプルデータとして与えます。そして、統計学的処理(=訓練)を実行させ、回帰係数(=重み)bを推定させます。最後に、そうしてできたモデルをつかって、sin(θ)を計算するという流れです。

from os import mkdir
from os.path import exists
from sys import exc_info
from math import pi, radians
from numpy import absolute, array, arange, frompyfunc, sin
from pandas import DataFrame
from sklearn.externals.joblib import dump, load
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
from statistics import mean, stdev

class MregSin:
    # モデル
    modeldir = './data'
    modelfile = './data/MregSin.pkl'

    # データに関するパラメータ
    number_of_train_data = 300
    start_of_train_data = 0.0 * pi
    stop_of_train_data = 0.5 * pi

    # モデルに関するパラメータ
    number_of_features = 11

    # 訓練に関するパラメータ
    #   特になし

    def __init__(self, recycle=True):
        if recycle and exists(MregSin.modelfile):
            try:
                print('訓練済みモデルを読み込みます。')
                self.__model = load(MregSin.modelfile)
            except:
                print('訓練済みモデルの読み込み中にエラーが発生しました。')
                print('Unexpected error:', exc_info()[0])
                raise
        else:
            print('訓練を行うので、お待ちください。')

            # データ
            feature_names = [str(i) for i in
                             range(1, MregSin.number_of_features + 1, 1)]
            x = arange(MregSin.start_of_train_data, MregSin.stop_of_train_data,
                         (MregSin.stop_of_train_data - MregSin.start_of_train_data)
                         / MregSin.number_of_train_data)
            data = [[i**j for j in
                     range(1, MregSin.number_of_features + 1, 1)] for i in x]
            target = sin(x)

            # モデル
            self.__model = LinearRegression()

            # 訓練
            self.__model.fit(data, target)

            # 訓練状況の可視化
            print('回帰係数')
            coefficient = DataFrame({'feature' : feature_names
                                     , 'coefficient' : self.__model.coef_})
            print(coefficient)
            print('切片')
            print(self.__model.intercept_)

            # 予測
            x2 = [x[i] for i in
                  range(0, len(x), round(0.02 * MregSin.number_of_train_data))]
            data2 = [[i**j for j in
                      range(1, MregSin.number_of_features + 1, 1)] for i in x2]
            target2 = [target[i] for i in
                       range(0, len(target), round(0.02 * MregSin.number_of_train_data))]
            predicted = self.__model.predict(data2)
            plt.figure()
            plt.plot(x2, target2, marker='.', label='組込関数')
            plt.plot(x2, predicted, marker='.', label='独自関数')
            plt.legend(loc='best', fontsize=10)
            plt.grid()
            plt.xlabel('θ')
            plt.ylabel('sin(θ)')
            plt.show()

            # モデルの保存
            if not exists(MregSin.modeldir):
                try:
                    mkdir(MregSin.modeldir)
                except:
                    print('モデル保存フォルダの作成中にエラーが発生しました。')
                    print('Unexpected error:', exc_info()[0])
                    raise
            try:
                dump(self.__model, MregSin.modelfile)
            except:
                print('モデルの保存中にエラーが発生しました。')
                print('Unexpected error:', exc_info()[0])
                raise

    def __sin(self, θ):
        # 角度θを0.0~359.999...の範囲に直す
        while True:
            if θ < 0.0:
                θ += 2.0 * pi
                continue
            if 2.0 * pi <= θ:
                θ -= 2.0 * pi
                continue
            break

        # 180度を超える場合、x軸に関して対称とする
        if pi < θ:
            return -1 * self.sin(2.0 * pi - θ)

        # 90〜180度の場合、y軸に関して対称とする
        if 0.5 * pi < θ:
            return self.sin(pi - θ)

        data = [[θ**i for i in range(1, MregSin.number_of_features + 1, 1)]]
        return self.__model.predict(data)[0]

    def sin(self, θ):
        return frompyfunc(self.__sin, 1, 1)(θ)


ds = MregSin(recycle=False)

deg = 30.0
rad = radians(deg)
x = ds.sin(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

deg = 45.0
rad = radians(deg)
x = ds.sin(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

ds = MregSin()

deg = 60.0
rad = radians(deg)
x = ds.sin(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

rads = [radians(i) for i in range(359)]
h = ds.sin(rads)
y = sin(rads)
e = absolute(y - h)
print("誤差最大値  :" + str(max(e)))
print("誤差最小値  :" + str(min(e)))
print("誤差平均値  :" + str(mean(e)))
print("誤差標準偏差:" + str(stdev(e)))
訓練を行うので、お待ちください。
回帰係数
   feature   coefficient
0        1  1.000000e+00
1        2 -2.602449e-10
2        3 -1.666667e-01
3        4 -1.611588e-08
4        5  8.333389e-03
5        6 -1.227027e-07
6        7 -1.982343e-04
7        8 -1.720502e-07
8        9  2.863353e-06
9       10 -4.069894e-08
10      11 -1.754363e-08
切片
-8.915090887740007e-14

output_11_1.png

独自関数によるsin(30.0):0.5000000000000134
組込関数によるsin(30.0):0.49999999999999994
独自関数によるsin(45.0):0.7071067811865207
組込関数によるsin(45.0):0.7071067811865475
訓練済みモデルを読み込みます。
独自関数によるsin(60.0):0.8660254037844507
組込関数によるsin(60.0):0.8660254037844386
誤差最大値  :1.6620038678638593e-13
誤差最小値  :2.55351295663786e-15
誤差平均値  :2.1351626923889052e-14
誤差標準偏差:1.5984259514679425e-14

かなり、うまくいきましたね。もちろん、仮説が的確すぎた(=特徴量として最適なものを選んだ)ので、このような結果になるのも当然ですが…いずれにせよ、コンピュータがもう一つ、sin(θ)に関して、知能を獲得したと言えます。

しかし、なぜこの特徴量で良いのか、なぜこの重みで良いのか、説明できるでしょうか。「『sin(θ)がθのn次の多項式で表現できる』という仮説を立て、実際のデータを元に妥当な重みを計算。完成したモデルを検証用のデータで検証してみたところ、仮説が99.9%正しいことを証明できた。」といった説明しかできない気がします。統計学的に理論武装しておかないと、このツールの妥当性を説明できないかもしれませんね。

なお、このプログラムは、私が書いた初めてのscikit-learnプログラムとなります。公式サイトのほかに、以下サイトを参考にさせて頂きました。

ディープラーニングによる計算

最後に、ディープラーニングによって、コンピュータにsin(θ)を計算させてみましょう。0から0.5πの間に無数に存在する実数の内、30万個をサンプルデータとして与え、統計学的処理(=訓練)を実行させます。そうしてできたモデルをつかって、sin(θ)を計算するという流れです。

from os import mkdir
from os.path import exists
from sys import exc_info
from math import pi, radians
from numpy import absolute, arange, array, frompyfunc, sin 
from keras.models import load_model, Sequential
from keras.layers import Dense, Dropout
from keras.callbacks import EarlyStopping
import matplotlib.pyplot as plt
from statistics import mean, stdev

class DeepSin:
    # モデル
    modeldir = './data'
    modelfile = './data/DeepSin.h5'

    # データに関するパラメータ
    number_of_train_data = 300000
    start_of_train_data = 0.0 * pi
    stop_of_train_data = 0.5 * pi
    number_of_test_data = 0.3 * number_of_train_data
    start_of_test_data = 0.0
    stop_of_test_data = 0.5 * pi

    # モデルに関するパラメータ
    number_of_layer = 10
    units = 30

    # 訓練に関するパラメータ
#    min_delta = 0
#    patience = 0
    batch_size = round(0.01 * number_of_train_data)
    epochs = 20

    def __init__(self, recycle=True):
        if recycle and exists(DeepSin.modelfile):
            try:
                print('訓練済みモデルを読み込みます。')
                self.__model = load_model(DeepSin.modelfile)
            except:
                print('訓練済みモデルの読み込み中にエラーが発生しました。')
                print('Unexpected error:', exc_info()[0])
                raise
        else:
            print('訓練を行うので、お待ちください。')
#            print('学習が頭打ちになった場合、途中で訓練を打ち切ります。')

            # データ
            trX = arange(DeepSin.start_of_train_data, DeepSin.stop_of_train_data,
                         (DeepSin.stop_of_train_data - DeepSin.start_of_train_data)
                         / DeepSin.number_of_train_data)
            trY = sin(trX)
            valX = arange(DeepSin.start_of_test_data, DeepSin.stop_of_test_data,
                         (DeepSin.stop_of_test_data - DeepSin.start_of_test_data)
                          / DeepSin.number_of_test_data)
            valY = sin(valX)

            # モデル
            self.__model = Sequential()
            self.__model.add(Dense(1, input_dim=1, activation='linear'))
            for i in (range(DeepSin.number_of_layer)):
                self.__model.add(Dense(DeepSin.units, activation='relu'))
            self.__model.add(Dense(1))
            self.__model.compile(loss='mean_squared_error', optimizer='Adam')

            # 訓練
#            early_stopping = EarlyStopping(monitor='val_loss'
#                                           , min_delta=DeepSin.min_delta
#                                           , patience=DeepSin.patience
#                                           , mode='auto'
#                                          )
            hist = self.__model.fit(trX, trY,
                                    batch_size=DeepSin.batch_size
                                    , epochs=DeepSin.epochs
#                                    , callbacks=[early_stopping]
                                    , validation_data=(valX, valY)
                                   )

            # 訓練状況の可視化
            loss = hist.history['loss']
            val_loss = hist.history['val_loss']
            plt.figure()
            plt.plot(range(1, len(loss) + 1),
                     loss, marker='.', label='訓練データ')
            plt.plot(range(1, len(val_loss) + 1),
                     val_loss, marker='.', label='テストデータ')
            plt.legend(loc='best', fontsize=10)
            plt.grid()
            plt.xlabel('エポック')
            plt.ylabel('損失')
            plt.show()

            # 予測
            trX2 = array([trX[i] for i in
                          range(0, len(trX), round(0.02 * DeepSin.number_of_train_data))])
            trY2 = array([trY[i] for i in
                          range(0, len(trY), round(0.02 * DeepSin.number_of_train_data))])
            predicted = self.__model.predict(trX2)
            plt.figure()
            plt.plot(trX2, trY2, marker='.', label='組込関数')
            plt.plot(trX2, predicted, marker='.', label='独自関数')
            plt.legend(loc='best', fontsize=10)
            plt.grid()
            plt.xlabel('θ')
            plt.ylabel('sin(θ)')
            plt.show()

            # モデルの保存
            if not exists(DeepSin.modeldir):
                try:
                    mkdir(DeepSin.modeldir)
                except:
                    print('モデル保存フォルダの作成中にエラーが発生しました。')
                    print('Unexpected error:', exc_info()[0])
                    raise
            try:
                self.__model.save(DeepSin.modelfile)
            except:
                print('モデルの保存中にエラーが発生しました。')
                print('Unexpected error:', exc_info()[0])
                raise

    def __sin(self, θ):
        # 角度θを0.0~359.999...の範囲に直す
        while True:
            if θ < 0.0:
                θ += 2.0 * pi
                continue
            if 2.0 * pi <= θ:
                θ -= 2.0 * pi
                continue
            break

        # 180度を超える場合、x軸に関して対称とする
        if pi < θ:
            return -1 * self.sin(2.0 * pi - θ)

        # 90〜180度の場合、y軸に関して対称とする
        if 0.5 * pi < θ:
            return self.sin(pi - θ)
        return self.__model.predict([θ])[0][0]

    def sin(self, θ):
        return frompyfunc(self.__sin, 1, 1)(θ)


ds = DeepSin(recycle=False)

deg = 30.0
rad = radians(deg)
x = ds.sin(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

deg = 45.0
rad = radians(deg)
x = ds.sin(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

ds = DeepSin()

deg = 60.0
rad = radians(deg)
x = ds.sin(rad)
y = sin(rad)
print("独自関数によるsin(" + str(deg) + "):" + str(x))
print("組込関数によるsin(" + str(deg) + "):" + str(y))

rads = [radians(i) for i in range(359)]
h = ds.sin(rads)
y = sin(rads)
e = absolute(y - h)
print("誤差最大値  :" + str(max(e)))
print("誤差最小値  :" + str(min(e)))
print("誤差平均値  :" + str(mean(e)))
print("誤差標準偏差:" + str(stdev(e)))
Using TensorFlow backend.


訓練を行うので、お待ちください。
Train on 300000 samples, validate on 90000 samples
Epoch 1/20
300000/300000 [==============================] - 2s 5us/step - loss: 0.0697 - val_loss: 0.0012
Epoch 2/20
300000/300000 [==============================] - 1s 3us/step - loss: 5.5763e-04 - val_loss: 2.6283e-04
Epoch 3/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.3936e-04 - val_loss: 5.4291e-05
Epoch 4/20
300000/300000 [==============================] - 1s 3us/step - loss: 2.5608e-05 - val_loss: 9.9707e-06
Epoch 5/20
300000/300000 [==============================] - 1s 3us/step - loss: 5.6591e-06 - val_loss: 4.3353e-06
Epoch 6/20
300000/300000 [==============================] - 1s 3us/step - loss: 2.4574e-06 - val_loss: 1.3504e-06
Epoch 7/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.4784e-06 - val_loss: 9.6404e-07
Epoch 8/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.1821e-06 - val_loss: 5.3071e-07
Epoch 9/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.1959e-06 - val_loss: 2.5098e-06
Epoch 10/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.2143e-06 - val_loss: 4.1867e-07
Epoch 11/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.6130e-06 - val_loss: 2.5240e-07
Epoch 12/20
300000/300000 [==============================] - 1s 3us/step - loss: 7.9917e-07 - val_loss: 5.5269e-07
Epoch 13/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.9296e-06 - val_loss: 4.3476e-07
Epoch 14/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.2812e-06 - val_loss: 2.6409e-07
Epoch 15/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.2712e-06 - val_loss: 1.9644e-07
Epoch 16/20
300000/300000 [==============================] - 1s 3us/step - loss: 2.0916e-06 - val_loss: 3.1340e-07
Epoch 17/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.1972e-06 - val_loss: 8.7616e-07
Epoch 18/20
300000/300000 [==============================] - 1s 3us/step - loss: 2.6864e-06 - val_loss: 1.4640e-05
Epoch 19/20
300000/300000 [==============================] - 1s 3us/step - loss: 4.0446e-06 - val_loss: 1.4378e-05
Epoch 20/20
300000/300000 [==============================] - 1s 3us/step - loss: 1.1424e-06 - val_loss: 1.0116e-07

output_13_2.png

output_13_3.png

独自関数によるsin(30.0):0.500035
組込関数によるsin(30.0):0.49999999999999994
独自関数によるsin(45.0):0.70708025
組込関数によるsin(45.0):0.7071067811865475
訓練済みモデルを読み込みます。
独自関数によるsin(60.0):0.86587673
組込関数によるsin(60.0):0.8660254037844386
誤差最大値  :0.00250852108001709
誤差最小値  :3.4490914585516563e-06
誤差平均値  :0.0001902273061400219
誤差標準偏差:0.0002800383029470891

さすがに、精度の点では、他のアプローチに及びませんでした。人間がコンパスと分度器と定規でsin(θ)を求める場合と比較して、どちらが精度が高いでしょうね。

なお、このプログラムは、私が書いた初めてのディープラーニングですが、完成させるまでに試行錯誤を重ねました。

  • データの桁数が増えたら精度も増えるように見えたので、与えるデータ量を増やしたら、メモリがスワップアウトして、固まってしまった。
  • 別の条件下では、データの桁数を増やしたところ、かえって測定結果が悪化するように見えた。
  • ユニット数や中間層の数を増やせば、表現力がアップするのかと思いきや、必ずしもそうではなかった。結果を出すためには、ちょうど良いバランスを見つける必要があった。

結局のところ、サインカーブの対称性に着目し、データの範囲を0から0.5πの間にしぼることにより(=表現対象を単純化することにより)、それなりのリソース(時間、空間)で、それなりの精度が得られるようになりました。私の知識不足もあるのでしょうが、ディープラーニングの難しさを感じました。ディープラーニングの開発のマネージャーをやられている方々は、どうやってマネジメントされているんでしょうね。

そして説明可能性という点では…「そもそも、深層ニューラルネットワークは、パラメータ調整により、任意の関数を任意の精度で近似することができる。利用可能なリソースも考慮のうえ、これくらいのネットワークなら近似できるだろうという仮説を立て、パラメータを設定。実際のデータを元に訓練を実施し、完成したモデルを検証用のデータで検証してみたところ、一定程度の精度が得られたのでこれで良しとした。」といった説明しかできないように思います。これで納得してもらえるでしょうか。なかなか難しいと思いますね。LIMESHAPGrad-CAMなどの「説明可能性を取り戻す」仕組みの実用化が待たれます。

まとめ

機械学習という手法の登場によって、コンピュータに知能をもたせる取り組み——広義のプログラミングと言って良いかもしれない——は、大きく幅が広がりました。
演繹から帰納。論理学から統計学。思弁から経験。そのパラダイム転換に戸惑いつつも、新しい「おもちゃ」の登場に喜ぶ子どものような気分もあります。

とはいえ、喜んでばかりもいられません。二木康晴/塩野誠(2017)によれば、人工知能の法的位置付けは「当分の間、単なる道具」で、行為の責任は、設計開発者 and / or 管理利用者が負うことになるだろうとのことです。

一人の開発者としても、ツールの妥当性を説明する言葉をもたなければいけないと、痛切に感じています。その言葉はたぶん、統計学の言葉なんだろうなと思いつつ、この駄文に終止符を打つことにします。ここまで読んでくださる方、いるのかな。

18
21
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
18
21