よちよちpython

独習 python/Qpython/Pydroid3/termux

【Python機械学習】Numpyだけで回帰分析

Numpyだけで回帰分析を行う。

参考
NumPyで回帰分析(線形回帰)する - DeepAge

numpy.poly1d():1次元多項式オブジェクト



実行環境


Androidスマホ
termux
Python3.8
JupyterNotebook

はじめに


はじめに実行環境についてなど。
Pythonでの機械学習に必須とよくいわれるライブラリのNumpy、Pandas、matplotlibですが、Numpyは当然ながら、その他のライブラリもNumpyに依存して動いている。
科学計算ライブラリscipyや機械学習フレームワークのscikit-learn、グラフ描画のseabornなどもNumpyやscipyがないとinstallできませんし動かない。
通常、依存関係のライブラリは不備の場合には自動的にダウンロードされinstallされますが、Numpy等をスマホタブレットで動かすのはAndroidiOSもどうやら寸なり行きません。

PythonC言語Java言語と比較して処理速度が遅いとあちこちで書かれております。(遅いっても人間的感覚からすればめちゃくちゃ速い)。
で、Pythonが遅い理由は蛇だから、いや違う、「Pythonコンパイルせず動かすインタープリタ言語だから」だとか、「変数の型を宣言しないのでその判断や処理に時間が掛かっているんだ」とか、色んなことが書いてある。
それを克服するためにPythonのメモリ構造とは別の新たなやり方で処理する方法を採用したNumpyが作られた。そしてそれはC言語が利用されており高速処理を実現している、ということのようです。たぶん。

で、Numpy等をinstallする時にC関連ライブラリをinstallしなければならず、大抵そのへんが上手く行かない。または手間が掛かる。

我が事ながら、現時点でAndroidスマホではtermuxアプリでNumpyやPandasは動かせるが、scipyのinstallは成功していない。だからかsklearnがinstall出来ない。

「pydroid3を使わずGoogleColaboratoryも使わず、スマホでsklearnを動かし機械学習したい。」
という、PCやアプリを使えば済むものを、悪あがきと無駄な願望から「Numpyだけで機械学習できないのか?」と思ったのがこの投稿のきっかけ。



好奇心の種がどこかに引っ掛かれば...

目次


回帰分析とは?


回帰分析とは何かをろくに知らずに事を進める。

どうやら西欧の文脈では、統計学博物学、植物学や生物学から派生した学問のようで。ジャックと豆の樹じゃないけど、背丈や実のサイズを計測して遺伝的な関係を調べたり。
統計自体は天文観測して暦を作ったり、土地や生産物から税金の額を決めたり、何万何千年も前から人類はやってただろう。

回帰とは日本語では「元に戻る」という意味で、統計学用語では「regression(後退)」を意味する。19世紀のイギリスの統計学者、遺伝学者でゴルトンというダーウィンの従兄弟である男が、遺伝と身長の関係を調べてたら身長が平均に収束していくような結果が得られたことからその名がつけられた。

回帰とは、yという連続した値を取るとき、データにy = f(x)という定量的算出モデルをあてはめることを言う。
その際、

  • xを独立変数、または説明変数
  • yを従属変数、または目的変数

などと呼んでいる。
説明変数xが1つのとき(一次元のとき)を単回帰、xが2つ以上のとき(多次元のとき)を重回帰という。
またy=f(x)が直線の場合を線形回帰、それ以外を非線形回帰という。
目的変数yが離散のときを「分類」という。

目的変数」と呼んだり「連続値を取る際にモデルをあてはめる」というあたり、またはyの状況が連続か離散かで見極めるあたり、目的ありきで分析を進めなきゃいかんということですかね。そういう風にデータサイエンティストは皆言ってますし。グラフを見て確めるのもそういう意味ですかね。

使うライブラリ


計算用にNumpy。グラフ描画用にmatplotlib。
pipコマンドなどでinstallしておきます。



実験開始


まずは適当な変数xとyの配列をNumpyで作ります。

# インポート
import numpy as np
 # -4~4の範囲の一様乱数生成
x = np.random.rand(20)*8 - 4
x
array([-3.68246706,  2.19090875,  2.84593858, -3.2774098 ,  3.89731541,
       -1.67136724, -1.7866778 ,  3.40747041,  3.71838362,  2.6936431 ,
        0.31005243,  1.36159384, -2.63408156, -0.78339346,  3.11049379,
        3.26070723, -3.69116698,  1.46208728,  2.40316916, -2.86047097])

random.rand(個数)は、0から1までの一様なfloat型を指定個数生成する。
よってrandom.rand(個数)*8 - 4は、0*8-4 から 1*8-4 つまり-4から4の範囲の実数の配列を作る。



次に、y = sin(x)にxを代入してyの配列を作る。
Numpyを使うと、関数にxの配列をそのまま代入するだけでyの配列ができる。
yの値に揺らぎをもたせてイビツな配列にしたいので、関数にランダムな値を加える。

# sin関数にノイズを加えた
y = np.sin(x) + np.random.randn(20)*0.2 
y
array([ 0.48877994,  0.98018823, -0.21461067,  0.30090145, -0.67738274,
       -0.99405799, -1.04123267, -0.05643897, -0.38701752,  0.71441284,
        0.6638057 ,  1.03527399, -0.31614609, -0.95988806,  0.02602391,
       -0.03984401,  0.47086174,  1.24266925,  0.6243692 , -0.10122776])



グラフを作成

import matplotlib.pyplot as plt


# 作った配列x,yで散布図
plt.scatter(x, y)

# x ,y 軸にラベル名をつける
plt.xlabel("X")
plt.ylabel("Y")

# グラフにタイトルをつける
plt.title("Data")

# 網目のグリッド線でオシャレに
plt.grid()

# グラフの保存
plt.savefig("regression_1.png")

# グラフ表示
plt.show()

f:id:chayarokurokuro:20200119115907p:plain

xの配列のそれぞれの値に対してのyの値の点をグラフ上に置いていった図。xをサイン関数に入れたのでそれらしい形になっている。

xの配列を作る時にrandom.rand()を使って一様な分布になるようにしたが、もしx の値が偏っていたなら、このような曲線にならずにサイン関数上の一ヶ所に点がカタマった図になったはず。



ノイズを加えないy=sin(x)のグラフを付け加えてみる。

import matplotlib.pyplot as plt


# 作った配列x,yで散布図描画
plt.scatter(x, y)

# -----ピシャッとしたy=sin(x)の追加-----
x_pishatt = np.linspace(-4,4,100)
y_pishatt = np.sin(x_pishatt)
# 赤色の実線でピシャッと描画
plt.plot(x_pishatt, y_pishatt, color="red")

# ----- グラフの装飾 -----

# x ,y 軸にラベル名をつける
plt.xlabel("X")
plt.ylabel("Y")
# グラフにタイトルをつける
plt.title("Data")
# 網目のグリッド線でオシャレに
plt.grid()

# ------------------------
# グラフの保存
plt.savefig("regression_2.png")

# グラフ表示
plt.show()

f:id:chayarokurokuro:20200119115936p:plain

今から行う回帰分析によって、上の赤い線に近い方程式が導き出されたら分析者の勝ちです。
データを与えたらそれらの関係性が方程式で判るって凄すぎません?



蛇足で、上のコードのピシャッとした配列xを作る時に使ったnp.linspace(最小値, 最大値, 分割数)を説明しとこ。
最小値から最大値までの範囲を均等に指定個数で分割した配列を生成する。
分割数と同じ要素数を持つ配列ができる。

x_test_10 = np.linspace(-10, 10, 10)

print("配列\n", x_test_10)
print("要素数", x_test_10.size)
配列
 [-10.          -7.77777778  -5.55555556  -3.33333333  -1.11111111
   1.11111111   3.33333333   5.55555556   7.77777778  10.        ]
要素数 10

ピシャッとしたサイン関数のグラフを描きたいが為に、分割数=要素数100個で指定してxの配列を作りました。y=sin(x)に代入して出来たのy配列も要素数100個。

素数を減らしたらどんなグラフになるか示しておきます。

import matplotlib.pyplot as plt


# 作った配列x,yで散布図描画
plt.scatter(x, y)

# -----ピシャッとしてないy=sin(x)の追加-----
x_not_pishatt = np.linspace(-4,4,10)
y_not_pishatt = np.sin(x_not_pishatt)
# 赤色の実線でピシャッとしない描画
plt.plot(x_not_pishatt, y_not_pishatt, color="red")

# ----- グラフの装飾 -----

# x ,y 軸にラベル名をつける
plt.xlabel("X")
plt.ylabel("Y")
# グラフにタイトルをつける
plt.title("Data")
# 網目のグリッド線でオシャレに
plt.grid()

# ------------------------
# グラフの保存
plt.savefig("regression_3.png")

# グラフ表示
plt.show()

f:id:chayarokurokuro:20200119120003p:plain

配列の要素数が少ないんで滑らかにならず、カクカクしてます。三カク関数なだけに。カクカクカク



Numpyだけで回帰分析 山場


ここからが本番だが、いまいち理解していないので、ほぼ完コピのコードだけ。

回帰分析の背景にある考え方や数学的理論の解説と実装実践動画をリンクしておこ。
YouTube

YouTube

A = np.empty((6, 6))
A
array([[6.013e-321, 2.846e-321, 6.013e-321, 2.213e-321, 6.714e-321,
        2.213e-321],
       [6.714e-321, 1.912e-321, 6.013e-321, 1.912e-321, 6.013e-321,
        7.164e-322],
       [6.013e-321, 4.496e-322, 6.151e-321, 2.964e-322, 6.364e-321,
        2.964e-322],
       [6.714e-321, 2.964e-322, 6.714e-321, 0.000e+000, 6.359e-321,
        0.000e+000],
       [5.958e-321, 0.000e+000, 5.652e-321, 3.113e-322, 5.652e-321,
        7.213e-322],
       [5.652e-321, 1.912e-321, 5.400e-321, 1.912e-321, 5.400e-321,
        2.213e-321]])
for i in range(6):
    for j in range(6):
        A[i][j] = np.sum(x **(i +j))
        
A
array([[2.00000000e+01, 1.02747287e+01, 1.50598598e+02, 9.96386105e+01,
        1.56435763e+03, 1.10925092e+03],
       [1.02747287e+01, 1.50598598e+02, 9.96386105e+01, 1.56435763e+03,
        1.10925092e+03, 1.82514747e+04],
       [1.50598598e+02, 9.96386105e+01, 1.56435763e+03, 1.10925092e+03,
        1.82514747e+04, 1.37027183e+04],
       [9.96386105e+01, 1.56435763e+03, 1.10925092e+03, 1.82514747e+04,
        1.37027183e+04, 2.26789732e+05],
       [1.56435763e+03, 1.10925092e+03, 1.82514747e+04, 1.37027183e+04,
        2.26789732e+05, 1.83215485e+05],
       [1.10925092e+03, 1.82514747e+04, 1.37027183e+04, 2.26789732e+05,
        1.83215485e+05, 2.93371735e+06]])
b = np.empty(6)

for i in range(6):
    b[i] = np.sum(x ** i * y)

b
array([    1.75943979,     4.94550431,     6.49278429,   -67.81286579,
           7.52460362, -1419.00850781])
# linalg.inv()で逆行列を求める。
#dot関数で内積を求めてくれる。
omega = np.dot(np.linalg.inv(A), b.reshape(-1,1))
omega.shape
(6, 1)
  • np.linalg.inv()関数は行列の逆行列を求めてくれる関数

  • np.dot()は内積を求めてくれる関数
# ωを係数とした多項式を作る
f = np.poly1d(omega.flatten()[::-1])

XX = np.linspace(-4,4,100)

plt.xlabel('X')
plt.ylabel('y')
plt.title('trained data')
plt.grid()

plt.plot(XX, f(XX), color='green')
plt.plot(XX, np.sin(XX), color='blue')

# グラフの保存
plt.savefig("regression_4.png")
plt.show()

f:id:chayarokurokuro:20200119120623p:plain

fで与えられているのがNumpyと数式を使った回帰分析で求まった方程式。これが緑色の曲線。
青色の曲線がピシャッとしたサイン関数。
ほぼ重なっている。



多項式フィッティングを関数


Numpyの用意する関数np polyfit()を使うと、上でやったことが一発で済ませる。

omega_2 = np.polyfit(x, y, 5)

omega_2
array([ 0.00672026, -0.00132745, -0.18344917,  0.01574686,  1.12070744,
        0.03868981])
# 多項式
f_2 = np.poly1d(omega_2)
# ωを係数とした多項式を作る
f = f_2

XX = np.linspace(-4,4,100)

plt.xlabel('X')
plt.ylabel('y')
plt.title('trained data')
plt.grid()

# 導出グラフ
plt.plot(XX, f(XX), color='green')
# ピシャッとサイン派
plt.plot(XX, np.sin(XX), color='blue')

# グラフの保存
plt.savefig("regression_5.png")
plt.show()

f:id:chayarokurokuro:20200119120110p:plain



以上、写経を終わります。

無駄に長文書くと大きめの地震が起きるマイ直感ポンコツ機械学習のジンクスがある…