プログラミング

【Python】ライブラリを使わずに簡単な機械学習を実装する

PythonのSklearnやTensorflow, PyTorchなどのライブラリを使えば、数学の知識がなくてもある程度の機械学習のプログラムを組むことができます。今回は、そんな機械学習の中身を数学を使い、Pythonで実装していきたいと思います。なお、ライブラリを一切使用しないので、Python以外の言語でも簡単に実装できます。是非やってみてください!

今回実装するのは、機械学習の中でも最も基本的な単回帰分析です。それでは実際に数学を使って計算、実装を行っていきましょう!

単回帰分析とは

例えば、以下のような表があったとします。ある国ある職業における、それぞれ別の人(9人)の実務経験と年収を集めてきたものと考えてください。

実務経験(年)年収(万円)
1400
2435
3600
4560
5660
6940
7980
81000
91100

この表を見ると、経験年数が長いほど年収が高い傾向にあることが分かります。では、このデータから経験年数が10年や20年、5.5年などの人の年収を予測するにはどうすれば良いのでしょうか? こういったときに使うツールの一つが回帰分析です。その中でも、一つの情報のみ(この場合は経験年数)から予測をする場合、単回帰分析といいます。

Pythonでライブラリを用いずに単回帰分析を行う

どのような直線を引くべきか

回帰分析では近似直線、あるいは近似曲線を導出することによって、未知のデータの予測を行います。具体的には、全てのデータの近くを通るような曲線を引けば良いです。これを数学的に言い換えると、「各データからの距離の二乗和が最小になるような」曲線で(なお、今回は単回帰分析なので、直線を引くことになります)、下の図のようなイメージになります。

引用: 高校数学の美しい物語 https://mathtrain.jp/leastsquares

「各データからの距離の二乗和が最小になるような」直線の式を導出する

「各データからの距離の二乗和が最小になるような」直線を数式で表すと、以下のようになります。

$$min(\sum_{i} {y_i - (ax_i + b)}^2) $$

xは経験年数で、yはその時のデータにある年収です。ここで、求めるのはax+bのaとbです。この式を今回の場合に合わせて具体的に示すと、以下のようになります。

$$(400 - (a + b))^2 + (435 - (2a + b))^2 + (600 - (3a + b))^2 + \\ (560 - (4a + b))^2 + (660 - (5a + b))^2 + (940 - (6a + b))^2 + \\ (980 - (7a + b))^2 + (1000 - (8a + b))^2 + (1100 - (9a + b))^2$$

この値が最も小さくなるようなaとbを計算によって求めます。

aとbを求める

ある関数の最大最小を求める代表的なやり方として、「二次関数の平方完成」「微分」がありますが、今回は計算量が少ない後者の手法で求めてみたいと思います。

では、どの文字を使って微分していけば良いでしょうか? 今回求めるのはaとbで、a、b共に変数であるため、aとbについてそれぞれ微分していきましょう。なお、複数の変数があるときに、その中の特定の変数のみに注目して微分することを偏微分と言います。そのため、今からやるのはaとbにそれぞれついての偏微分ということになります。

まずは先ほどの式を展開すると、以下のようになります。なお、nはデータの個数です(今回の場合は9)。

$$ (\sum_{i} x_i^2 )a^2 -2(\sum_{i} x_iy_i)a + 2(\sum_{i} x_i)ab + nb^2 -2(\sum_{i} y_i)b + \sum_{I}y_i^2$$

まず、aについて偏微分すると、以下のようになります。偏微分では、注目する変数以外は全て定数として扱って微分することに注意してください。

$$ (\sum_{i} xi^2)a + b(\sum_{i} x_i) = (\sum_{i} x_iy_i) $$

上は偏微分した結果=0を移項して両辺を2で割った形です。bについても同様にやると、

$$ (\sum_{i} x_i)a + nb = \sum_{i} y_i$$

この2つの式を連立させると、

$$ \left( \begin{array}{ll} \sum x_i^2 & \sum x_i \\ \sum x_i & n \end{array} \right) \left (\begin{array}{l} a \\ b \end{array} \right) = \left( \begin{array}{l} \sum x_iy_i \\ \sum y_i \end{array} \right) $$

これを変形すると、

$$ \left (\begin{array}{l} a \\ b \end{array} \right) = \left( \begin{array}{ll} \sum x_i^2 & \sum x_i \\ \sum x_i & n \end{array} \right)^{-1} \left( \begin{array}{l} \sum x_iy_i \\ \sum y_i \end{array} \right)$$

逆行列を計算すると以下のようになります。

$$ \left (\begin{array}{l} a \\ b \end{array} \right) = \frac{1}{n\sum x_i^2 - (\sum x_i)^2} \left( \begin{array}{cc} n & -\sum x_i \\ -\sum x_i & \sum x_i^2 \end{array} \right) \left( \begin{array}{l} \sum x_iy_i \\ \sum y_i \end{array} \right) $$

行列を計算すると、以下のようになります。

$$ \left (\begin{array}{l} a \\ b \end{array} \right) =\frac{1}{n\sum x_i^2 - (\sum x_i)^2} \left( \begin{array}{c} n\sum x_iy_i -\sum x_i \sum y_i \\ -\sum x_i \sum x_iy_i + \sum x_i^2 \sum y_i\end{array} \right) $$

これで、a, bが求まりますね!では、早速これをPythonで実装していきましょう! ちなみに、numpyというライブラリを使えば、逆行列が出てきた段階でコードに落とし込むことができます。

実装

以下の様に書いてみました!regressorとpredictでa,bのコードが被っているので、self.aとself.bをinit関数で初期化しても良いですね!

frac_determinantは、

$$\frac{1}{n\sum x_i^2 - (\sum x_i)^2} $$

upperは、

$$ n\sum x_iy_i -\sum x_i \sum y_i $$

lowerは、

$$ -\sum x_i \sum x_iy_i + \sum x_i^2 \sum y_i $$

をそれぞれ示しています。

結果を見ると、傾きが93であるため、おおよそ1年ごとに年収が93万円上がるという予測をしていることが分かります。

class SimpleLinearRegressionModel(object):
    def __init__(self, x_data, y_data):
        self.n = len(x_data)
        self.x_sum = sum(x_data)
        self.x_sum_squared = sum([x ** 2 for x in x_data])
        self.y_sum = sum(y_data)
        self.xy_sum = sum([x_data[i] * y_data[i] for i in range(self.n)])

    def regressor(self):
        frac_determinant = 1/(self.n * self.x_sum_squared - self.x_sum ** 2)
        upper = self.n * self.xy_sum - self.x_sum * self.y_sum
        lower = -self.x_sum * self.xy_sum + self.x_sum_squared * self.y_sum

        return frac_determinant * upper, frac_determinant * lower

    def predict(self, x_pred):
        a = self.regressor()[0]
        b = self.regressor()[1]
        return a * x_pred + b

    def show_formula(self):
        a = self.regressor()[0]
        b = self.regressor()[1]
        return f"y = {a}x + {b}"

if __name__ == '__main__':
    x_ = [n for n in range(1, 10)]
    y_ = [400, 435, 600, 560, 660, 940, 980, 1000, 1100]
    simple_linear_regression = SimpleLinearRegressionModel(x_, y_)
    print(simple_linear_regression.show_formula())
    print(simple_linear_regression.predict(10))
    print(simple_linear_regression.predict(20))
    print(simple_linear_regression.predict(5.5))


# y = 93.91666666666667x + 272.08333333333337
# 1211.25
# 2150.416666666667
# 788.6250000000001


いかかでしょうか。今回実装した単回帰分析は、機械学習の中でも基本的な手法となります。上の方法だけでなく、様々なやり方で実装してみてください!

  • この記事を書いた人
おととらべる

おととらべる

プログラミングで小銭を稼いだり、音楽を作ったりしている理系大学生です。 一人旅で次に行きたい国は、 エジプト、アルバニア、アルゼンチン、ボツワナ、ナミビア、オマーンです。

-プログラミング
-, ,

© 2021 おととらべる Powered by AFFINGER5