PythonにはJavaと同じ「オブジェクト指向」という概念があり、データと処理をまとめた「クラス」を定義し、その「クラス」から「インスタンス」を複数作成することができる。
また、Javaと同様にPythonでも、クラスの継承を行ったり、抽象メソッドを定義することもできる。
今回は、スーパークラス(親クラス)に、スケーリング(正規化・標準化等)を行う抽象メソッドと回帰直線を算出する処理を定義し、そのサブクラス(子クラス)で、スケーリング(正規化・標準化等)を行う処理を定義してみたので、そのサンプルプログラムを共有する。
なお、クラスの継承や抽象メソッドについての詳細は、以下のサイトを参照のこと。
https://smile-jsp.hateblo.jp/entry/2022/05/25/000000
スケーリング(標準化・正規化)を行う部分を抽象メソッドに定義した、回帰直線を算出するクラスのソースコードは、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | import numpy as np from abc import ABCMeta, abstractmethod # metaclass=ABCMetaを指定することで抽象クラスになる class OrigRegressionLineBase(metaclass=ABCMeta): # クラス変数 eta = 0.001 # 学習率η repeat_num = 10000 # 最急降下法の繰り返し回数 # 初期化処理 def __init__(self): # 目的関数y=ax+bのa,bを求めるための初期値を宣言 # a,b(スケーリング後)を初期化 self.a_scl = 1 self.b_scl = 1 # a,b(スケーリング前)を初期化 self.a = 0 self.b = 0 # 与えられた入力データから、目的関数y=ax+bのa,b(スケーリング前)を算出する def fit(self, data_x, data_y): # 入力データをスケーリングする input_data_x_scl = self.scaling(data_x) input_data_y_scl = self.scaling(data_y) # 最急降下法でa,b(スケーリング後)の値を算出 for num in range(OrigRegressionLineBase.repeat_num): self.a_scl = self.a_scl - OrigRegressionLineBase.eta \ * self.__da_f(self.a_scl, self.b_scl, input_data_x_scl, input_data_y_scl) self.b_scl = self.b_scl - OrigRegressionLineBase.eta \ * self.__db_f(self.a_scl, self.b_scl, input_data_x_scl, input_data_y_scl) # 算出した直線(y = a_scl * x + b_scl)をスケーリング戻し後の、a,bの値を算出 self.a = self.rev_scaling_a(self.a_scl, self.b_scl, data_x, data_y) self.b = self.rev_scaling_b(self.a_scl, self.b_scl, data_x, data_y) # 与えられたx座標から、y座標の予測値を返却する def predict(self, data_x): return self.a * data_x + self.b # 入力データ(input_data)をスケーリングする抽象メソッド # 具体的な処理はサブクラスで定義 @abstractmethod def scaling(self, input_data): pass # 算出した直線(y = a_scl * x + b_scl)をスケーリング戻し後の傾きaを算出する # 具体的な処理はサブクラスで定義 @abstractmethod def rev_scaling_a(self, a_scl, b_scl, input_data_x, input_data_y): pass # 算出した直線(y = a_scl * x + b_scl)をスケーリング戻し後の切片bを算出する # 具体的な処理はサブクラスで定義 @abstractmethod def rev_scaling_b(self, a_scl, b_scl, input_data_x, input_data_y): pass # 最急降下法で利用するda_fを定義 # メソッド名の先頭に__を付与することで、プライベートメソッドとする def __da_f(self, a, b, input_data_x, input_data_y): ret = 0 input_data_cnt = len(input_data_x) for tmp in range(input_data_cnt): tmp_x = input_data_x[tmp][0] tmp_y = input_data_y[tmp][0] ret = ret + (( a * tmp_x + b - tmp_y ) * tmp_x) / input_data_cnt return ret # 最急降下法で利用するdb_fを定義 def __db_f(self, a, b, input_data_x, input_data_y): ret = 0 input_data_cnt = len(input_data_x) for tmp in range(input_data_cnt): tmp_x = input_data_x[tmp][0] tmp_y = input_data_y[tmp][0] ret = ret + ( a * tmp_x + b - tmp_y ) / input_data_cnt return ret |
なお、回帰直線を算出する処理については、以下の記事を参照のこと。
また、先ほどのOrigRegressionLineBaseクラスのサブクラスで、スケーリング部分で標準化を行うクラスのソースコードは、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import numpy as np class OrigRegressionLineStd(OrigRegressionLineBase): # 入力データ(input_data)をスケーリングする抽象メソッドの具体的な処理 def scaling(self, input_data): # 入力データの標準化を実施 ave_val = input_data.mean() std_val = input_data.std() if std_val == 0: return [] else: return (input_data - ave_val) / std_val # 算出した直線(y = a_scl * x + b_scl)をスケーリング戻し後の傾きaを算出する # 抽象メソッドの具体的な処理 def rev_scaling_a(self, a_scl, b_scl, input_data_x, input_data_y): # 入力データの値(スケーリング前)の標準偏差を算出 y_stdd = input_data_y.std() x_stdd = input_data_x.std() # 入力データの標準化戻し後の傾きaを算出する return a_scl * y_stdd / x_stdd # 算出した直線(y = a_scl * x + b_scl)をスケーリング戻し後の切片bを算出する # 抽象メソッドの具体的な処理 def rev_scaling_b(self, a_scl, b_scl, input_data_x, input_data_y): # 入力データの値(スケーリング前)の平均・標準偏差を算出 x_ave = input_data_x.mean() x_stdd = input_data_x.std() y_ave = input_data_y.mean() y_stdd = input_data_y.std() # 入力データの標準化戻し後の切片bを算出する return -a_scl * y_stdd * x_ave / x_stdd + b_scl * y_stdd + y_ave |
なお、データを標準化した上で、回帰直線を算出する処理については、以下の記事を参照のこと。
さらに、OrigRegressionLineStdクラスのfitメソッドを呼び出してグラフ化した結果は、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | %matplotlib inline import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split # 入力データの読み込み input_data = np.array([[33,352], [33,324], [34,338], [34,317], [35,341], [35,360], [34,339], [32,329], [28,283], [35,372], [33,342], [28,262], [32,328], [33,326], [35,354], [30,294], [29,275], [32,336], [34,354], [35,368]]) # 入力データのx座標、y座標の抜き出し input_data_x = input_data[:, 0] input_data_y = input_data[:, 1] # 入力データを訓練用とテスト用で分割 # test_sizeには、テストデータの割合を指定する # random_stateを指定することで、分割方法を固定できる train_data_x, test_data_x, train_data_y, test_data_y \ = train_test_split(input_data_x, input_data_y, test_size=0.2, random_state=0) # OrigRegressionLineStdクラスを利用するため、抜き出したtrain_dataの # x座標・y座標を、2次元1列の配列とする縦ベクトルに変更 train_data_x = train_data_x.reshape(-1, 1) train_data_y = train_data_y.reshape(-1, 1) # OrigRegressionLineStdクラスの回帰直線のa,bの値を算出 orls = OrigRegressionLineStd() orls.fit(train_data_x, train_data_y) print("*** a,bの値 ***") print("a = " + str(orls.a) + ", b = " + str(orls.b)) # train_dataの値を散布図で表示 train_data_x = train_data_x.reshape(1, -1) train_data_y = train_data_y.reshape(1, -1) plt.scatter(input_data_x, input_data_y) plt.title("train_data") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(26, 36) plt.ylim(0, 450) plt.grid() # 算出した直線(y = ax + b)を追加で表示 x = np.linspace(26, 36, 1000) y = orls.a * x + orls.b plt.plot(x, y, label='y = ax+b', color='darkviolet') plt.legend() plt.show() # test_dataの値を散布図で表示 test_data_x = test_data_x.reshape(1, -1) test_data_y = test_data_y.reshape(1, -1) plt.scatter(test_data_x, test_data_y) plt.title("test_data") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(26, 36) plt.ylim(0, 450) plt.grid() # 算出した直線(y = ax + b)を追加で表示 x = np.linspace(26, 36, 1000) y = orls.a * x + orls.b plt.plot(x, y, label='y = ax+b', color='darkviolet') plt.legend() plt.show() |
また、先ほどのOrigRegressionLineBaseクラスのサブクラスで、スケーリング部分で正規化を行うクラスのソースコードは、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | import numpy as np class OrigRegressionLineNorm(OrigRegressionLineBase): # 入力データ(input_data)をスケーリングする抽象メソッドの具体的な処理 def scaling(self, input_data): # 入力データの正規化を実施 input_data_cnt = len(input_data) if input_data_cnt <= 0: return [] max_val = input_data.max() min_val = input_data.min() if max_val == min_val: return [] else: return (input_data - min_val) / (max_val - min_val) # 算出した直線(y = a_scl * x + b_scl)をスケーリング戻し後の傾きaを算出する # 抽象メソッドの具体的な処理 def rev_scaling_a(self, a_scl, b_scl, input_data_x, input_data_y): # 入力データの値(スケーリング前)の最大値・最小値を算出 x_max = input_data_x.max() x_min = input_data_x.min() y_max = input_data_y.max() y_min = input_data_y.min() # 入力データの正規化戻し後の傾きaを算出する return a_scl * (y_max - y_min) / (x_max - x_min) # 算出した直線(y = a_scl * x + b_scl)をスケーリング戻し後の切片bを算出する # 抽象メソッドの具体的な処理 def rev_scaling_b(self, a_scl, b_scl, input_data_x, input_data_y): # 入力データの値(スケーリング前)の最大値・最小値を算出 x_max = input_data_x.max() x_min = input_data_x.min() y_max = input_data_y.max() y_min = input_data_y.min() # 入力データの正規化戻し後の切片bを算出する return -a_scl * x_min * (y_max - y_min) / (x_max - x_min) + b_scl * (y_max - y_min) + y_min |
なお、データを正規化した上で、回帰直線を算出する処理については、以下の記事を参照のこと。
さらに、OrigRegressionLineNormクラスのfitメソッドを呼び出してグラフ化した結果は、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | %matplotlib inline import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split # 入力データの読み込み input_data = np.array([[33,352], [33,324], [34,338], [34,317], [35,341], [35,360], [34,339], [32,329], [28,283], [35,372], [33,342], [28,262], [32,328], [33,326], [35,354], [30,294], [29,275], [32,336], [34,354], [35,368]]) # 入力データのx座標、y座標の抜き出し input_data_x = input_data[:, 0] input_data_y = input_data[:, 1] # 入力データを訓練用とテスト用で分割 # test_sizeには、テストデータの割合を指定する # random_stateを指定することで、分割方法を固定できる train_data_x, test_data_x, train_data_y, test_data_y \ = train_test_split(input_data_x, input_data_y, test_size=0.2, random_state=0) # OrigRegressionLineNormクラスを利用するため、抜き出したtrain_dataの # x座標・y座標を、2次元1列の配列とする縦ベクトルに変更 train_data_x = train_data_x.reshape(-1, 1) train_data_y = train_data_y.reshape(-1, 1) # OrigRegressionLineNormクラスの回帰直線のa,bの値を算出 orls = OrigRegressionLineNorm() orls.fit(train_data_x, train_data_y) print("*** a,bの値 ***") print("a = " + str(orls.a) + ", b = " + str(orls.b)) # train_dataの値を散布図で表示 train_data_x = train_data_x.reshape(1, -1) train_data_y = train_data_y.reshape(1, -1) plt.scatter(input_data_x, input_data_y) plt.title("train_data") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(26, 36) plt.ylim(0, 450) plt.grid() # 算出した直線(y = ax + b)を追加で表示 x = np.linspace(26, 36, 1000) y = orls.a * x + orls.b plt.plot(x, y, label='y = ax+b', color='darkviolet') plt.legend() plt.show() # test_dataの値を散布図で表示 test_data_x = test_data_x.reshape(1, -1) test_data_y = test_data_y.reshape(1, -1) plt.scatter(test_data_x, test_data_y) plt.title("test_data") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(26, 36) plt.ylim(0, 450) plt.grid() # 算出した直線(y = ax + b)を追加で表示 x = np.linspace(26, 36, 1000) y = orls.a * x + orls.b plt.plot(x, y, label='y = ax+b', color='darkviolet') plt.legend() plt.show() |
要点まとめ
- Javaと同様にPythonでも、クラスの継承を行ったり、抽象メソッドを定義することができる。