Memo

メモ > 技術 > プログラミング言語: Python > 回帰による予測

■回帰による予測
以下の書籍の勉強メモ 最小二乗法を使って一次関数の式を求める やさしく学ぶ 機械学習を理解するための数学のきほん アヤノ&ミオと一緒に学ぶ 機械学習の理論と数学、実装まで https://www.amazon.co.jp/gp/product/B075GSMZDS この書籍のもとになったページが以下にある やる夫で学ぶ機械学習シリーズ - けんごのお屋敷 http://tkengo.github.io/blog/2016/06/06/yaruo-machine-learning0/ ■回帰の実践 click.csv
x,y 235,591 216,539 148,413 35,310 85,308 204,519 49,325 25,332 173,498 191,498 134,392 99,334 117,385 112,387 162,425 272,659 159,400 159,427 59,319 198,522
以下でグラフに配置して確認できる
import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt # 学習データを読み込む train = np.loadtxt('click.csv', delimiter=',', dtype='int', skiprows=1) train_x = train[:,0] train_y = train[:,1] # プロット plt.plot(train_x, train_y, 'o') plt.savefig('graph.png')
以下で一次関数の学習データをプロットできる
import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt # 学習データを読み込む train = np.loadtxt('click.csv', delimiter=',', dtype='int', skiprows=1) train_x = train[:,0] train_y = train[:,1] # 標準化を行う(データのスケールを揃えて学習が効果的に行われるようにする) mu = train_x.mean() sigma = train_x.std() def standardize(x): return (x - mu) / sigma train_z = standardize(train_x) # 更新用パラメータを初期化(初期値をランダムに設定する) theta0 = np.random.rand() theta1 = np.random.rand() # 予測関数(一次関数) def f(x): return theta0 + theta1 * x # 目的関数(二次関数。実際の値と予測値の二乗誤差の和の半分で、誤差関数とも呼ぶ) def E(x, y): return 0.5 * np.sum((y - f(x)) ** 2) # 学習率 ETA = 0.001 # 誤差の差分 diff = 1 # 更新回数 count = 0 # 誤差の差分が0.01以下になるまでパラメータ更新を繰り返す error = E(train_z, train_y) while diff > 0.01: # 更新結果を一時変数に保存 tmp_theta0 = theta0 - ETA * np.sum((f(train_z) - train_y)) tmp_theta1 = theta1 - ETA * np.sum((f(train_z) - train_y) * train_z) # パラメータを更新 theta0 = tmp_theta0 theta1 = tmp_theta1 # 前回の誤差との差分を計算 current_error = E(train_z, train_y) diff = error - current_error error = current_error # ログの出力 count += 1 log = '{}回目: theta0 = {:.3f}, theta1 = {:.3f}, 差分 = {:.4f}' print(log.format(count, theta0, theta1, diff)) # プロットして確認 x = np.linspace(-3, 3, 100) plt.plot(train_z, train_y, 'o') plt.plot(x, f(x)) plt.savefig('graph.png')
実行の際、以下のようなログが出力される
$ python3.7 regression1_linear.py 1回目: theta0 = 9.411, theta1 = 2.790, 差分 = 76035.5263 2回目: theta0 = 17.806, theta1 = 4.604, 差分 = 73024.5195 3回目: theta0 = 26.033, theta1 = 6.381, 差分 = 70132.7485 4回目: theta0 = 34.095, theta1 = 8.123, 差分 = 67355.4917 5回目: theta0 = 41.996, theta1 = 9.830, 差分 = 64688.2142 6回目: theta0 = 49.739, theta1 = 11.503, 差分 = 62126.5609 〜中略〜 389回目: theta0 = 428.985, theta1 = 93.443, 差分 = 0.0118 390回目: theta0 = 428.988, theta1 = 93.444, 差分 = 0.0113 391回目: theta0 = 428.991, theta1 = 93.444, 差分 = 0.0109 392回目: theta0 = 428.994, theta1 = 93.445, 差分 = 0.0105 393回目: theta0 = 428.997, theta1 = 93.446, 差分 = 0.0101 394回目: theta0 = 429.000, theta1 = 93.446, 差分 = 0.0097
「theta0 = 429.000」「theta1 = 93.446」と求められている これは予測関数「theta0 + theta1 * x」にある「theta0」と「theta1」の値を求めている つまり、予測関数は「429.000 + 93.446 * x」ということ プログラムの最後に以下を追加すると
print(f(standardize(100))) print(f(standardize(200))) print(f(standardize(300)))
実行時に以下も表示される つまり、個別に予測を表示できる
370.9672809730788 510.4700574222163 649.9728338713539
以下は詳細解説付きのコード(解説の都合上、一部プログラムも変更している)
import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt # データを読み込む(「click.csv」を「,」区切りの整数として読み込む。1行目は無視) train = np.loadtxt('click.csv', delimiter=',', dtype='int', skiprows=1) print(train) # [[235 591] # [216 539] # [148 413] # [ 35 310] # [ 85 308] # [204 519] # [ 49 325] # [ 25 332] # [173 498] # [191 498] # [134 392] # [ 99 334] # [117 385] # [112 387] # [162 425] # [272 659] # [159 400] # [159 427] # [ 59 319] # [198 522]] # 各0番目の要素(X座標)を抽出 train_x = train[:,0] print(train_x) # [235 216 148 35 85 204 49 25 173 191 134 99 117 112 162 272 159 159 59 198] # 各1番目の要素(Y座標)を抽出 train_y = train[:,1] print(train_y) # [591 539 413 310 308 519 325 332 498 498 392 334 385 387 425 659 400 427 319 522] # train_xの平均値を取得 mu = train_x.mean() print(mu) # 141.6 # train_xの標準偏差を取得 sigma = train_x.std() print(sigma) # 66.98537153737374 # 標準化(平均値との差を標準偏差で割って「平均が0、標準偏差が1」のデータにする)を行うための関数を定義 def standardize(x): return (x - mu) / sigma # 引数で渡されたxはnumpyの値なので、各値に対して計算が行われる # 標準化を行う(X軸のスケールを小さくする。標準化して扱うことで処理が早くなる) train_z = standardize(train_x) print(train_z) # [ 1.39433428 1.11069026 0.09554325 -1.59139223 -0.8449606 0.93154667 # -1.38239138 -1.74067856 0.46875906 0.73747445 -0.11345761 -0.63595975 # -0.36724436 -0.44188752 0.3045441 1.94669369 0.25975821 0.25975821 # -1.23310505 0.84197488] # 更新用パラメータを初期化(0〜1のランダムな数値を初期値に設定する) #theta0 = np.random.rand() #theta1 = np.random.rand() theta0 = 0.7248025635110643 # 解説の都合上、初期値を固定しておく theta1 = 0.36156516824075224 # 解説の都合上、初期値を固定しておく print(theta0) # 0.7248025635110643(0〜1のランダムな数値) print(theta1) # 0.36156516824075224(0〜1のランダムな数値) # 予測関数(一次関数) def f(x): return theta0 + theta1 * x # 目的関数(二次関数。実際の値と予測値の二乗誤差の和の半分で、誤差関数とも呼ぶ) def E(x, y): return 0.5 * np.sum((y - f(x)) ** 2) # 学習率 ETA = 0.001 # 誤差の差分 diff = 1 # 更新回数 count = 0 # ランダムな数値をもとに目的関数の誤差を確認 error = E(train_z, train_y) print(error) # 1931733.106949653(ランダムな数値を使用したので誤差が大きい) # 繰り返し1回目 if (diff > 0.01): # 目的関数の微分をもとに求めた式を使って取得した、更新結果を一時変数に保存 tmp_theta0 = theta0 - ETA * np.sum((f(train_z) - train_y)) tmp_theta1 = theta1 - ETA * np.sum((f(train_z) - train_y) * train_z) # パラメータを更新 theta0 = tmp_theta0 theta1 = tmp_theta1 print(theta0) # 9.293306512240843(正しい値に若干近づいた値) print(theta1) # 2.2239092232827655(正しい値に若干近づいた値) # 新しい値で目的関数の誤差を確認(誤差が若干減少した値) current_error = E(train_z, train_y) print(current_error) # 1855614.3975038927 # 前回の誤差との差分を計算 diff = error - current_error error = current_error print(diff) # 76118.70944576035 print(error) # 1855614.3975038927 # ログの出力 count += 1 log = '{}回目: theta0 = {:.3f}, theta1 = {:.3f}, 差分 = {:.4f}' print(log.format(count, theta0, theta1, diff)) # 1回目: theta0 = 9.293, theta1 = 2.224, 差分 = 76118.7094 # 繰り返し2回目 if (diff > 0.01): # 目的関数の微分をもとに求めた式を使って取得した、更新結果を一時変数に保存 tmp_theta0 = theta0 - ETA * np.sum((f(train_z) - train_y)) tmp_theta1 = theta1 - ETA * np.sum((f(train_z) - train_y) * train_z) # パラメータを更新 theta0 = tmp_theta0 theta1 = tmp_theta1 print(theta0) # 17.690440381996027(正しい値に若干近づいた値) print(theta1) # 4.0490063972239385(正しい値に若干近づいた値) # 新しい値で目的関数の誤差を確認(誤差が若干減少した値) current_error = E(train_z, train_y) print(current_error) # 1782509.9889521836 # 前回の誤差との差分を計算 diff = error - current_error error = current_error print(diff) # 73104.40855170903 print(error) # 1782509.9889521836 # ログの出力 count += 1 log = '{}回目: theta0 = {:.3f}, theta1 = {:.3f}, 差分 = {:.4f}' print(log.format(count, theta0, theta1, diff)) # 2回目: theta0 = 17.690, theta1 = 4.049, 差分 = 73104.4086 # 繰り返し3回目 if (diff > 0.01): # 目的関数の微分をもとに求めた式を使って取得した、更新結果を一時変数に保存 tmp_theta0 = theta0 - ETA * np.sum((f(train_z) - train_y)) tmp_theta1 = theta1 - ETA * np.sum((f(train_z) - train_y) * train_z) # パラメータを更新 theta0 = tmp_theta0 theta1 = tmp_theta1 print(theta0) # 25.919631574356107(正しい値に若干近づいた値) print(theta1) # 5.837601627686288(正しい値に若干近づいた値) # 新しい値で目的関数の誤差を確認(誤差が若干減少した値) current_error = E(train_z, train_y) print(current_error) # 1712300.5149791227 # 前回の誤差との差分を計算 diff = error - current_error error = current_error print(diff) # 70209.47397306096 print(error) # 1712300.5149791227 # ログの出力 count += 1 log = '{}回目: theta0 = {:.3f}, theta1 = {:.3f}, 差分 = {:.4f}' print(log.format(count, theta0, theta1, diff)) # 3回目: theta0 = 25.920, theta1 = 5.838, 差分 = 70209.4740 # 徐々に「前回の誤差との差分」が減っていることが分かる # さらに何度も実行されることにより、以下のように差分が減っていく # # 1回目: theta0 = 9.293, theta1 = 2.224, 差分 = 76118.7094 # 2回目: theta0 = 17.690, theta1 = 4.049, 差分 = 73104.4086 # 3回目: theta0 = 25.920, theta1 = 5.838, 差分 = 70209.4740 # 4回目: theta0 = 33.984, theta1 = 7.590, 差分 = 67429.1788 # 5回目: theta0 = 41.888, theta1 = 9.308, 差分 = 64758.9833 # 6回目: theta0 = 49.633, theta1 = 10.992, 差分 = 62194.5276 # 〜中略〜 # 389回目: theta0 = 428.984, theta1 = 93.443, 差分 = 0.0118 # 390回目: theta0 = 428.988, theta1 = 93.444, 差分 = 0.0114 # 391回目: theta0 = 428.991, theta1 = 93.444, 差分 = 0.0109 # 392回目: theta0 = 428.994, theta1 = 93.445, 差分 = 0.0105 # 393回目: theta0 = 428.997, theta1 = 93.446, 差分 = 0.0101 # 394回目: theta0 = 429.000, theta1 = 93.446, 差分 = 0.0097 # 394回目で、差分が0.01以下になった。これで「ほとんど誤差は発生しなくなった」とみなす # このときの値は theta0 = 429.000, theta1 = 93.446 となっている # つまり、求められた1次関数の式は # y = 429.000 + 93.446 * x # となる # この式をもとに予測することができる # # ただしxには標準化された値を渡す必要がある # 平均値 = 141.6, 標準偏差 = 66.98537153737374 # なので、xが100, 200, 300 のときの値は # # 100 = (100 - 141.6) / 66.98537153737374 = -0.62103111538 # y = 429.000 + 93.446 * (-0.62103111538) = 370.967126392 # よってx=100ならy=371 # # 200 = (200 - 141.6) / 66.98537153737374 = 0.87183214274 # y = 429.000 + 93.446 * 0.87183214274 = 510.46922641 # よってx=100ならy=510 # # 300 = (300 - 141.6) / 66.98537153737374 = 2.36469540087 # y = 429.000 + 93.446 * 2.36469540087 = 649.97132643 # よってx=100ならy=650 # # となり、求められた1次関数の式により予測ができていることを確認できる
以下メモ …だが、まだまだ理解を超えているので要勉強 予測データを関数として表すことができれば、与えられたデータをもとに予測を立てることができる シンプルな例として、今回の学習データは一次関数であると推測して実装する 一次関数の学習データは以下のように表すことができる \[y = ax + b \] この式を以下のように表すものとする \[f_{\theta}(x) = \theta_{0} + \theta_{1}x \] これが予測関数の元になっており、このシータ1とシータ2の値を求めることができれば正しい予測ができることになる そしてこれらの式において、 \[y = f_{\theta}(x) \] と一致するときが誤差のない状態となる これを変形すると \[y - f_{\theta}(x) = 0 \] と表すことができる 学習データが「n」個あるとして、学習データごとの誤差の和は以下のように表すことができる(この誤差の和が小さければ小さいほど正確な予測と言える) 左辺の E は、誤差を英語で表したときの「Error」の頭文字から取ったもの これが目的関数の元になっている なお、全体を2で割っているのは微分の計算を簡単にするため、2乗しているのは誤差が負の値になっても正確に算出するため \[E(\theta) = \frac{1}{2} \sum_{i=1}^{n} (y^{(i)} - f_{\theta}(x^{(i)}))^{2} \] このようなアプローチを最小二乗法と呼ぶ 標準化(学習データの平均を0、分散を1とする)は以下で行う 必須の処理ではないが、行っておくとパラメータの収束が早くなる 標準化については、このファイル内にある「数学の復習」の「標準偏差」も参照 \[z^{(i)} = \frac{x^{i} - \mu}{\sigma} \] 目的関数で E を小さくしていけば誤差が小さくなる ただし、適当に値を代入して求めるのは大変。このようなときは微分を使って求めていく この目的関数は \[f_{\theta}(x^{(i)}) \] を含んでいて、この値はシータ0とシータ1の2つのパラメーターを持っている つまりこの目的関数は、シータ0とシータ1の2つの変数を持つ2次関数になる よって普通の微分ではなく偏微分となり、更新式は以下のようになる \[\theta_{0} := \theta_{0} - \eta \frac{\partial E}{\partial \theta_{0}} \] \[\theta_{1} := \theta_{1} - \eta \frac{\partial E}{\partial \theta_{1}} \] これは最急降下法や勾配降下法と呼ばれるもの 分数の前に付いている記号は「イータ」で、学習率と呼ばれる正の定数 学習率の大小によって、最小値にたどり着くまでの更新回数が変わってくる 偏微分の計算を行うが、正攻法の微分は大変。よって合成関数の微分を使う。今回は以下のように定義する \[u = E(\theta) \] \[\nu = f_{\theta}(x) \] すると、以下のように段階的に微分できる \[\frac{\partial u}{\partial \theta_{0}} = \frac{\partial u}{\partial \nu} \cdot \frac{\partial \nu}{\partial \theta_{0}} \] これを計算すると、1つ目の値は \[\frac{\partial u}{\partial \nu} = \frac{\partial}{\partial \nu}(\frac{1}{2} \sum_{i=1}^{n} (y^{(i)} - \nu)^{2}) \] \[\frac{\partial u}{\partial \nu} = \frac{1}{2} \sum_{i=1}^{n} (\frac{\partial}{\partial \nu}(y^{(i)} - \nu)^{2}) \] \[\frac{\partial u}{\partial \nu} = \frac{1}{2} \sum_{i=1}^{n} (\frac{\partial}{\partial \nu}(y^{(i)2} - 2y^{(i)}\nu + \nu^{2})) \] \[\frac{\partial u}{\partial \nu} = \frac{1}{2} \sum_{i=1}^{n} (-2y^{(i)} + 2\nu) \] \[\frac{\partial u}{\partial \nu} = \sum_{i=1}^{n} (\nu - y^{(i)}) \] となる なお、3行目から4行目への変換時にカッコ内で行われている計算は ・「y^{(i)2}」はvで微分すると「0」になる ・「-2y^{(i)}v」はvで微分すると「-2y^(i)」になる ・「v^2」はvで微分すると「2v」になる ・それぞれを合わせて、「-2y^(i) + 2v」になる となる また2つ目の値は \[\frac{\partial \nu}{\partial \theta_{0}} = \frac{\partial}{\partial \theta_{0}}(\theta_{0} + \theta_{1}x) \] \[\frac{\partial \nu}{\partial \theta_{0}} = 1 \] になる これをかけ合わせると \[\frac{\partial u}{\partial \theta_{0}} = \frac{\partial u}{\partial \nu} \cdot \frac{\partial \nu}{\partial \theta_{0}} \] \[\frac{\partial u}{\partial \theta_{0}} = \sum_{i=1}^{n} (\nu - y^{(i)}) \cdot 1 \] \[\frac{\partial u}{\partial \theta_{0}} = \sum_{i=1}^{n} (f_{\theta}(x^{(i)}) - y^{(i)}) \] 引き続きシータ1についても微分してみる \[\frac{\partial u}{\partial \theta_{1}} = \frac{\partial u}{\partial \nu} \cdot \frac{\partial \nu}{\partial \theta_{1}} \] \[\frac{\partial u}{\partial \theta_{1}} = \frac{\partial \nu}{\partial \theta_{1}}(\theta_{0} + \theta_{1}x) \] \[\frac{\partial u}{\partial \theta_{1}} = x \] 微分する \[\frac{\partial u}{\partial \theta_{1}} = \frac{\partial u}{\partial \nu} \cdot \frac{\partial \nu}{\partial \theta_{1}} \] \[\frac{\partial u}{\partial \theta_{1}} = \sum_{i=1}^{n} (\nu - y^{(i)}) \cdot x^{(i)} \] \[\frac{\partial u}{\partial \theta_{1}} = \sum_{i=1}^{n} (f_{\theta}(x^{(i)}) - y^{(i)}) x^{(i)} \] 最終的に以下2つのパラメータの更新式を得ることができる これが更新式の元になっている \[\theta_{0} := \theta_{0} - \mu \sum_{i=1}^{n} (f_{\theta}(x^{(i)}) - y^{(i)}) \] \[\theta_{1} := \theta_{1} - \mu \sum_{i=1}^{n} (f_{\theta}(x^{(i)}) - y^{(i)})x^{(i)} \] 試行回数は一概には言えないが、今回は 10^-3 にしておく これらの内容をもとにプログラムを作成したものが先の内容となる 結果として \[\theta_{0} = 429.000 \] \[\theta_{1} = 93.446 \] という値を求められているが、つまりは求めたい1次関数は \[f_{\theta}(x) = \theta_{0} + \theta_{1}x \] \[f_{\theta}(x) = 429.000 + 93.446x \] \[y = 93.446x + 429.000 \] となる。具体的には xが100のときyは、93.446 * 100 + 429 = 9773.6 xが200のときyは、93.446 * 200 + 429 = 19118.2 xが300のときyは、93.446 * 300 + 429 = 28462.8 …だとおかしな値だが、もとのプログラムと同じく標準化の処理を加えると以下のようになる xが100のときyは370.9671010284257 xが200のときyは510.46967317686426 xが300のときyは649.9722453253028 ■回帰の実践(勉強中) 内容を単純化するため、学習データを「y = ax + b」ではなく「y = ax」にしてみる
import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt # 学習データを読み込む train = np.loadtxt('click.csv', delimiter=',', dtype='int', skiprows=1) train_x = train[:,0] train_y = train[:,1] # 標準化を行う(データのスケールを揃えて学習が効果的に行われるようにする) mu = train_x.mean() sigma = train_x.std() def standardize(x): return (x - mu) / sigma train_z = standardize(train_x) # 更新用パラメータを初期化(初期値をランダムに設定する) theta = np.random.rand() # 予測関数(一次関数) def f(x): return theta * x # 目的関数(二次関数。実際の値と予測値の二乗誤差の和の半分で、誤差関数とも呼ぶ) def E(x, y): return 0.5 * np.sum((y - f(x)) ** 2) # 学習率 ETA = 0.001 # 誤差の差分 diff = 1 # 更新回数 count = 0 # 誤差の差分が0.1以下になるまでパラメータ更新を繰り返す error = E(train_z, train_y) while diff > 0.1: # パラメータを更新 theta = theta - ETA * np.sum((f(train_z) - train_y) * train_z) # 前回の誤差との差分を計算 current_error = E(train_z, train_y) diff = error - current_error error = current_error # ログの出力 count += 1 log = '{}回目: theta = {:.3f}, 差分 = {:.4f}' print(log.format(count, theta, diff)) # プロットして確認 x = np.linspace(-3, 3, 100) plt.plot(train_z, train_y, 'o') plt.plot(x, f(x)) plt.savefig('graph.png')
かなり下の方にグラフが表示されるが、傾きは意図したものになっている 標準化された内容において原点 (0, 0) を通る必要があるので、傾きだけで合わせるならこのようなグラフになる …ということのはず ■多項式回帰(重回帰)の実践 ※まだ検証中。大きな勘違いがある可能性がある 以下で多項式回帰(重回帰)を検証中
import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt # 学習データを読み込む train = np.loadtxt('click.csv', delimiter=',', dtype='int', skiprows=1) train_x = train[:,0] train_y = train[:,1] # 標準化を行う(データのスケールを揃えて学習が効果的に行われるようにする) mu = train_x.mean() sigma = train_x.std() def standardize(x): return (x - mu) / sigma train_z = standardize(train_x) # 更新用パラメータを初期化(初期値をランダムに設定する) theta = np.random.rand(3) # 学習データの行列を作る def to_matrix(x): return np.vstack([np.ones(x.size), x, x ** 2]).T X = to_matrix(train_z) # 予測関数(列ベクトル) def f(x): return np.dot(x, theta) # 目的関数(二次関数。実際の値と予測値の二乗誤差の和の半分で、誤差関数とも呼ぶ) def E(x, y): return 0.5 * np.sum((y - f(x)) ** 2) # 学習率 ETA = 0.001 # 誤差の差分 diff = 1 # 更新回数 count = 0 # 誤差の差分が0.01以下になるまでパラメータ更新を繰り返す error = E(X, train_y) while diff > 0.01: # パラメータを更新 theta = theta - ETA * np.dot(f(X) - train_y, X) # 前回の誤差との差分を計算 current_error = E(X, train_y) diff = error - current_error error = current_error # ログの出力 count += 1 log = '{}回目: theta = {}, 差分 = {:.4f}' print(log.format(count, theta, diff)) # プロットして確認 x = np.linspace(-3, 3, 100) plt.plot(train_z, train_y, 'o') plt.plot(x, f(to_matrix(x))) plt.savefig('graph.png')
実行の際、以下のようなログが出力される
1回目: theta = [9.30220205 2.77489304 9.76441964], 差分 = 152581.1030 2回目: theta = [17.50386962 4.60251556 18.1524752 ], 差分 = 137373.5725 3回目: theta = [25.37374272 6.40522139 26.01357776], 差分 = 123765.8789 4回目: theta = [32.92899631 8.18277789 33.37728718], 差分 = 111587.5946 5回目: theta = [40.18587064 9.93499805 40.27153163], 差分 = 100686.5080 6回目: theta = [47.1597226 11.66173739 46.72269743], 差分 = 90926.6740 〜中略〜 791回目: theta = [405.71401902 95.10425227 23.29512797], 差分 = 0.0107 792回目: theta = [405.71683608 95.10405716 23.2935123 ], 差分 = 0.0105 793回目: theta = [405.71962911 95.10386371 23.29191041], 差分 = 0.0104 794回目: theta = [405.72239832 95.10367191 23.29032218], 差分 = 0.0102 795回目: theta = [405.72514391 95.10348175 23.2887475 ], 差分 = 0.0100 796回目: theta = [405.72786608 95.1032932 23.28718624], 差分 = 0.0098
thetaは「405.72786608」「95.1032932」「23.28718624」と求められている これは列ベクトルだが、求めたい関数は以下のように表すことができ、 \[{\theta}^{T}x = \theta_{0}x_{0} + \theta_{1}x_{1} + \theta_{2}x_{2} + … + \theta_{n}x_{n} \] 以下の等式が成立し、 \[f_{\theta}(x) = {\theta}^{T}x \] 結果的に予測関数は「405.72537805 + 95.10346544 * x + 23.28861321 * x^2」ということになる (現状かなり端折った内容になっているので、詳細は書籍を参照) プログラムの最後に以下を追加すると
print(f(to_matrix(standardize(100)))) print(f(to_matrix(standardize(200)))) print(f(to_matrix(standardize(300))))
実行時に以下も表示される つまり、個別に予測を表示できる
[355.64511094] [506.34110805] [760.84100797]
以下、メモ付きで検証中
import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt # 学習データを読み込む train = np.loadtxt('click.csv', delimiter=',', dtype='int', skiprows=1) print(train) # [[235 591] # [216 539] # [148 413] # [ 35 310] # [ 85 308] # [204 519] # [ 49 325] # [ 25 332] # [173 498] # [191 498] # [134 392] # [ 99 334] # [117 385] # [112 387] # [162 425] # [272 659] # [159 400] # [159 427] # [ 59 319] # [198 522]] train_x = train[:,0] print(train_x) # [235 216 148 35 85 204 49 25 173 191 134 99 117 112 162 272 159 159 59 198] train_y = train[:,1] print(train_y) # [591 539 413 310 308 519 325 332 498 498 392 334 385 387 425 659 400 427 319 522] # 標準化を行う(データのスケールを揃えて学習が効果的に行われるようにする) mu = train_x.mean() sigma = train_x.std() def standardize(x): return (x - mu) / sigma train_z = standardize(train_x) print(train_z) # [ 1.39433428 1.11069026 0.09554325 -1.59139223 -0.8449606 0.93154667 # -1.38239138 -1.74067856 0.46875906 0.73747445 -0.11345761 -0.63595975 # -0.36724436 -0.44188752 0.3045441 1.94669369 0.25975821 0.25975821 # -1.23310505 0.84197488] # 更新用パラメータを初期化(サイズが3の配列として、初期値をランダムに設定する) #theta = np.random.rand(3) theta = [0.52525576, 0.26226403, 0.01139251] print(theta) # [0.52525576 0.26226403 0.01139251] # 学習データの行列を作る # 多項式回帰に対応させるため、「(θ0) + (θ1)x + (θ2)x^2」の形式にする # つまり1行を1つの学習データとみなして「1 + (学習データ) + (学習データの2乗)」の形式にする # さらに、各学習データは縦方向で配列に連結する # 「T」で転置を行なう def to_matrix(x): return np.vstack([np.ones(x.size), x, x ** 2]).T X = to_matrix(train_z) print(X) # [[ 1. 1.39433428 1.94416809] # [ 1. 1.11069026 1.23363286] # [ 1. 0.09554325 0.00912851] # [ 1. -1.59139223 2.53252924] # [ 1. -0.8449606 0.71395842] # [ 1. 0.93154667 0.8677792 ] # [ 1. -1.38239138 1.91100592] # [ 1. -1.74067856 3.02996185] # [ 1. 0.46875906 0.21973506] # [ 1. 0.73747445 0.54386856] # [ 1. -0.11345761 0.01287263] # [ 1. -0.63595975 0.4044448 ] # [ 1. -0.36724436 0.13486842] # [ 1. -0.44188752 0.19526458] # [ 1. 0.3045441 0.09274711] # [ 1. 1.94669369 3.78961632] # [ 1. 0.25975821 0.06747433] # [ 1. 0.25975821 0.06747433] # [ 1. -1.23310505 1.52054807] # [ 1. 0.84197488 0.70892169]] # 予測関数(列ベクトル) # np.dot の挙動は https://qiita.com/ao_log/items/64768b67153e8fb6820b が参考になる # x には上で記載した X の値が格納されている。引数として渡されてくる。毎回固定の内容(上で記載した内容のまま) # theta にはパラメータの値が格納されている。具体的には「[0.52525576, 0.26226403, 0.01139251]」のような値が格納されている。徐々に最適化されて「[72.43856197 17.76450257 68.07431847]」のように変化し、最終的に「[405.72537805 95.10346544 23.28861321]」のような値になる def f(x): return np.dot(x, theta) # 目的関数(二次関数。実際の値と予測値の二乗誤差の和の半分で、誤差関数とも呼ぶ) def E(x, y): return 0.5 * np.sum((y - f(x)) ** 2) # 学習率 ETA = 0.001 # 誤差の差分 diff = 1 # 更新回数 count = 0 # 誤差の差分が0.01以下になるまでパラメータ更新を繰り返す error = E(X, train_y) print(error) # 1933525.9892911182 while diff > 0.01: # パラメータを更新 theta = theta - ETA * np.dot(f(X) - train_y, X) # 前回の誤差との差分を計算 current_error = E(X, train_y) diff = error - current_error error = current_error # ログの出力 count += 1 log = '{}回目: theta = {}, 差分 = {:.4f}' print(log.format(count, theta, diff)) # プロットして確認 x = np.linspace(-3, 3, 100) plt.plot(train_z, train_y, 'o') plt.plot(x, f(to_matrix(x))) plt.savefig('graph.png') # 検証 print(f(to_matrix(standardize(100)))) print(f(to_matrix(standardize(200)))) print(f(to_matrix(standardize(300))))
学習データは標準化と行列変換を経て X に格納される X の内容は以下のとおりで、これは以降変化しない [[ 1. 1.39433428 1.94416809] [ 1. 1.11069026 1.23363286] [ 1. 0.09554325 0.00912851] [ 1. -1.59139223 2.53252924] [ 1. -0.8449606 0.71395842] [ 1. 0.93154667 0.8677792 ] [ 1. -1.38239138 1.91100592] [ 1. -1.74067856 3.02996185] [ 1. 0.46875906 0.21973506] [ 1. 0.73747445 0.54386856] [ 1. -0.11345761 0.01287263] [ 1. -0.63595975 0.4044448 ] [ 1. -0.36724436 0.13486842] [ 1. -0.44188752 0.19526458] [ 1. 0.3045441 0.09274711] [ 1. 1.94669369 3.78961632] [ 1. 0.25975821 0.06747433] [ 1. 0.25975821 0.06747433] [ 1. -1.23310505 1.52054807] [ 1. 0.84197488 0.70892169]] 更新用パラメータはランダムに決定されて theta に格納される theta の内容は一例として以下のような値で、これはループによって最適化(更新)されていく [0.52525576, 0.26226403, 0.01139251] 10回最適化を行った段階では、以下のような値になる [72.43856197 17.76450257 68.07431847] 100回最適化を行った段階では、以下のような値になる [281.75465761 87.39699393 92.87228457] 最適化完了後(795回ループし、誤差が 0.01 以下になったとき)、以下のような値になる [405.72537805 95.10346544 23.28861321] これにより、求められた2次関数の式は y = 405.72537805 + 95.10346544 * x + 23.28861321 * x^2 となる xが100, 200, 300 のときの値は、「回帰の実践」のときと同様 100 = (100 - 141.6) / 66.98537153737374 = -0.62103111538 200 = (200 - 141.6) / 66.98537153737374 = 0.87183214274 300 = (300 - 141.6) / 66.98537153737374 = 2.36469540087 となるので、予測は以下のようになる y = 405.72537805 + 95.10346544 * (-0.62103111538) + 23.28861321 * (-0.62103111538 * -0.62103111538) = 355.645110936 y = 405.72537805 + 95.10346544 * (0.87183214274) + 23.28861321 * (0.87183214274 * 0.87183214274) = 506.34110805 y = 405.72537805 + 95.10346544 * (2.36469540087) + 23.28861321 * (2.36469540087 * 2.36469540087) = 760.841008005 よって x=100ならy=355 x=200ならy=506 x=300ならy=760 となる np.dotによる計算は、このファイル内の「Python > 行列の計算」にある「Aが2次元配列でBが1次元配列の場合」を参照 今回は、以下のような計算がされることになる
import numpy as np theta = [0.52525576, 0.26226403, 0.01139251] X = [[ 1, 1.39433428, 1.94416809], [ 1, 1.11069026, 1.23363286], [ 1, 0.09554325, 0.00912851], [ 1, -1.59139223, 2.53252924], [ 1, -0.8449606 , 0.71395842], [ 1, 0.93154667, 0.8677792 ], [ 1, -1.38239138, 1.91100592], [ 1, -1.74067856, 3.02996185], [ 1, 0.46875906, 0.21973506], [ 1, 0.73747445, 0.54386856], [ 1, -0.11345761, 0.01287263], [ 1, -0.63595975, 0.4044448 ], [ 1, -0.36724436, 0.13486842], [ 1, -0.44188752, 0.19526458], [ 1, 0.3045441 , 0.09274711], [ 1, 1.94669369, 3.78961632], [ 1, 0.25975821, 0.06747433], [ 1, 0.25975821, 0.06747433], [ 1, -1.23310505, 1.52054807], [ 1, 0.84197488, 0.70892169]] result = np.dot(X, theta) print(result)
result の内容は以下のとおり
[0.91308844 0.83060404 0.55041731 0.13674269 0.31178677 0.77945313 0.18447538 0.10325726 0.65069773 0.72486481 0.49564656 0.36307403 0.43047726 0.41158911 0.60618335 1.07897673 0.5941497 0.5941497 0.21917952 0.75415188]
先頭の 0.91308844 という値は、「theta」と「Xの1つめの要素」を使って以下のように求められている = (1 * 0.52525576) + (1.39433428 * 0.26226403) + (1.94416809 * 0.01139251) 次の 0.83060404 という値は、「theta」と「Xの2つめの要素」を使って以下のように求められている = (1 * 0.52525576) + (1.11069026 * 0.26226403) + (1.23363286 * 0.01139251) これを最後まで繰り返し、1次元配列の値を作っている ■メモ 以下なども参考になりそう 機械学習のパラメータチューニングを「これでもか!」というくらい丁寧に解説 - Qiita https://qiita.com/c60evaporator/items/ca7eb70e1508d2ba5359

Advertisement