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


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

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

## 機械学習とは何か

![機械学習とは何か](https://github.com/tanuk1647/explain-machine-learning/raw/master/figures/explain-machine-learning-0000.jpg)

### 私の理解

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

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

![シンボリックAI](https://github.com/tanuk1647/explain-machine-learning/raw/master/figures/explain-machine-learning-0001.jpg)

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

![機械学習](https://github.com/tanuk1647/explain-machine-learning/raw/master/figures/explain-machine-learning-0002.jpg)

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

![仮説検証のサイクル](https://github.com/tanuk1647/explain-machine-learning/raw/master/figures/explain-machine-learning-0003.jpg)

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

![モデルの利用](https://github.com/tanuk1647/explain-machine-learning/raw/master/figures/explain-machine-learning-0004.jpg)

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

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

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

### 反省

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

- 統計学は、むしろ説明のための学問である。説明可能性をあきらめて統計学的アプローチを取り入れた、という表現には違和感を感じる。単に、統計学で利用している数学とアルゴリズムを、取り入れただけではないか。
- 果たして、ディープラーニングは説明可能性をあきらめてしまったのか。それは2018年夏現在の状況を示しているだけで、近い将来、説明可能性を獲得するのではないか。
    - [説明するＡＩ、説得される人間【上】 どうする判断の根拠](https://newswitch.jp/p/12363)
    - [機械学習プロジェクトにおける課題と、スパースモデリングに期待が高まる背景](https://codezine.jp/article/detail/10957?p=2)
        - [LIME](https://arxiv.org/abs/1602.04938)
        - [SHAP](http://papers.nips.cc/paper/7062-a-unified-approach-to-interpreting-model-predictions)
        - [Grad-CAM](https://arxiv.org/abs/1610.02391)

### 参考文献、参考サイト

- [François Chollet著（2017）、クイーブ訳（2018）『PythonとKerasによるディープラーニング』マイナビ出版](http://iss.ndl.go.jp/books/R100000002-I028982416-00)
- [Drew Conway（2010）『The Data Science Venn Diagram』](http://drewconway.com/zia/2013/3/26/the-data-science-venn-diagram)
- [丸山宏『演繹から機能へ　〜新しいシステム開発パラダイム〜』](https://www.slideshare.net/pfi/20180305ppl2018)
- [丸山宏「機械学習工学に向けて」『機械学習型システム開発へのパラダイム転換』国立研究開発法人科学技術振興機構　研究開発戦略センター](https://www.jst.go.jp/crds/pdf/2017/WR/CRDS-FY2017-WR-11.pdf)
- [Tom Mitchell（1998）による定義](http://holehouse.org/mlclass/01_02_Introduction_regression_analysis_and_gr.html)
    - コンピュータプログラムが、ある種のタスクTと評価尺度Pにおいて、経験Eから学習するとは、タスクTにおけるその性能をPによって評価した際に、経験Eによってそれが改善されている場合である
- [佐伯竜輔（2018）『AIブームを支える「機械学習」～AIの現実的な始め方とは？』DB Press](https://enterprisezine.jp/dbonline/detail/10955?p=2)
- [Arthur Samuel（1959）による定義](http://holehouse.org/mlclass/01_02_Introduction_regression_analysis_and_gr.html)
    - 機械学習とは「明示的にプログラムしなくても学習する能力をコンピュータに与える研究分野」である
- [Wikipedia『Symbolic artificial intelligence』](https://en.wikipedia.org/wiki/Symbolic_artificial_intelligence)


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

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

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

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

- Python 3.6.4
- tensorflow 1.9.0
- Keras 2.2.0

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

- [macOSにPython環境を構築する（pyenv＋Anaconda）](https://qiita.com/tanuk1647/items/3cf2f4a348220d90c612#%E6%89%8B%E9%A0%868-jupyter-notebook%E3%81%AE%E8%A8%AD%E5%AE%9A)

### 幾何学的計算

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

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

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


```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(θ)を計算させることもできるわけですが、そもそも、この右辺はいったい何をしているのでしょうか。

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


```python
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](https://qiita-image-store.s3.amazonaws.com/0/275656/b10e1801-95a6-1402-d6c2-5ddf3193bf37.png)


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

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


```python
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(θ)の計算方法を二つ、コンピュータに教えたわけですが、両者の性能を比較してみましょう。


```python
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倍速いという結果となりました。数学者[ブルック・テイラー](https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%AB%E3%83%83%E3%82%AF%E3%83%BB%E3%83%86%E3%82%A4%E3%83%A9%E3%83%BC)は偉大ですね。

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

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

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

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


```python
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](https://qiita-image-store.s3.amazonaws.com/0/275656/efc3261d-53cf-96b3-acb3-036212134ec6.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プログラムとなります。[公式サイト](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html)のほかに、以下サイトを参考にさせて頂きました。

- [誰でも出来る！！scikit-learn(sklearn)で重回帰分析しちゃう](https://qiita.com/yossyyossy/items/ac6961ca5a0e765f56be)
- [pandasの使い方（２）　DataFrameの生成](http://python-remrin.hatenadiary.jp/entry/2017/05/08/212120)
- [\[Python3\] scikit-learnによる機械学習4: 学習が完了したモデルを永続化する](http://qs.nndo.jp/2017/09/16/849/)

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

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


```python
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](https://qiita-image-store.s3.amazonaws.com/0/275656/65ab759a-baec-a4de-0282-e59e2f4456a9.png)



![output_13_3.png](https://qiita-image-store.s3.amazonaws.com/0/275656/6b8d6a20-c48a-abda-4c03-a0df752d1d59.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πの間にしぼることにより（＝表現対象を単純化することにより）、それなりのリソース（時間、空間）で、それなりの精度が得られるようになりました。私の知識不足もあるのでしょうが、ディープラーニングの難しさを感じました。ディープラーニングの開発のマネージャーをやられている方々は、どうやってマネジメントされているんでしょうね。

そして説明可能性という点では…「そもそも、深層ニューラルネットワークは、パラメータ調整により、任意の関数を任意の精度で近似することができる。利用可能なリソースも考慮のうえ、これくらいのネットワークなら近似できるだろうという仮説を立て、パラメータを設定。実際のデータを元に訓練を実施し、完成したモデルを検証用のデータで検証してみたところ、一定程度の精度が得られたのでこれで良しとした。」といった説明しかできないように思います。これで納得してもらえるでしょうか。なかなか難しいと思いますね。[LIME](https://arxiv.org/abs/1602.04938)、[SHAP](http://papers.nips.cc/paper/7062-a-unified-approach-to-interpreting-model-predictions)、[Grad-CAM](https://arxiv.org/abs/1610.02391)などの「説明可能性を取り戻す」仕組みの実用化が待たれます。

### まとめ

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

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

- [二木康晴／塩野誠著（2017）『	いちばんやさしい人工知能ビジネスの教本』インプレス](http://iss.ndl.go.jp/books/R100000002-I028161666-00)

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