1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

保守性を考慮した機械学習モデル

Last updated at Posted at 2020-06-17

前職

前職はソフトウェア開発(Java, C#)でした。
そこでは、効率良く・綺麗なコードを書く、という指針の元、コードレビューも細かく実施していました。

現職

データ分析〜機械学習を担当しています。
Pythonで記載しますが、製品版ではない場合はコードレビューもないですし、動けばよいという雰囲気になっています。。
確かに分析対象、モデルやパラメータが頻繁に変わるので、設計までしっかりして取り組むよりまずは結果かと考えます。

オブジェクトの導入

とはいえ、1つのJupyter notebookに、複数のモデル(例えば重回帰、SVR、Lasso、RandomForestRegressor)を定義して、それぞれ流すコードは嫌いですし、モデル毎にnotebookを分けるのも、ファイル増えて管理の手間なので嫌いです。
なので、動かすnotebookは1つにしていじらない(結果確認用)、modelはconfigurationから指定して実行、のような形で少しオブジェクト指向っぽくしています。
「機械学習&notebookのモデルはそうじゃない」という方もいると思いますが。。

全体像

  • Jupyter notebook上は、固有のモデル名を記載しない。以下で言えば、ClassCreatorで生成した結果、モデルが出来上がる。
  • このmodelが何であろうと、jupyter notebook側は知らない。定義された抽象メソッドを呼ぶだけ。
    clazz_path = parser.get(path="regression", key="class_path")
    model = ClassCreator.create(clazz_path)
  • configファイルにて、以下のように定義する
[regression]
class_path = utility.SVRModel.SVRModel
  • ClassCreator内で、リフレクションでインスタンス化
    こちらを参考にさせていただきました。
import sys

class ClassCreator():
    @staticmethod
    def create(class_path):
        try:
            print("class:", class_path)
            component_path = str(class_path).split('.')
            package_path   = component_path[:-1]
            package_name   = ".".join(package_path)
            class_name     = component_path[-1]
            __import__(str(package_name))
            cls = getattr(sys.modules[package_name], class_name)
            return cls()
        except Exception as e:
            print('=== エラー内容 ===')
            print('type:' + str(type(e)))
            print('args:' + str(e.args))
            print('e自身:' + str(e))
  • 抽象クラス
    中身は自由で良いですが、サンプルとしてはこんな感じです。
from abc import ABCMeta, abstractmethod


class AbstractModel(metaclass=ABCMeta):

    @abstractmethod
    def run_grid_search(self, x_train_scaled, y_train):
        pass

    @abstractmethod
    def create_and_pred(self, x_train_scaled, y_train, x_test_scaled):
        pass

    @abstractmethod
    def print_eval(self, x_train_scaled, x_test_scaled, y_train, y_test, df):
        pass

    @abstractmethod
    def get_score(self, x_test_scaled, y_test):
        pass

    @abstractmethod
    def get_rmse(self, y_test, pred):
        pass

    @abstractmethod
    def get_modeltype(self):
        pass

    @abstractmethod
    def get_best_params(self):
        pass


  • 具象クラス(例: SVR)
    SVRではGridSearchCVを呼び出す必要があるため、以下のように定義しています。
    呼び出す抽象メソッドはPoC向けにえいやなので、「それいらないでしょう」というのもありますが。
from sklearn.svm import SVR
from sklearn.model_selection import GridSearchCV
import numpy as np
from sklearn.metrics import mean_squared_error
from utility.AbstractModel import AbstractModel

class SVRModel(AbstractModel):

    def __init__(self):
        self.clr = SVR()
        self.regr = None
        self.grid_search = None

    def run_grid_search(self, x_train_scaled, y_train):
        param_grid = {'C': [0.005, 0.0075, 0.1, 0.25, 0.4, 0.5, 0.6, 0.75, 1],
                      'epsilon': [0.000001, 0.00005, 0.00001, 0.0001, 0.0005, 0.001]}
        self.grid_search = GridSearchCV(self.clr, param_grid, cv=5)
        self.grid_search.fit(x_train_scaled, y_train)
        print("best param: {}".format(self.grid_search.best_params_))
        print("best score: {}".format(self.grid_search.best_score_))
        return self.grid_search

    def create_and_pred(self, x_train_scaled, y_train, x_test_scaled):
        self.regr = SVR(C=self.grid_search.best_params_["C"], epsilon=self.grid_search.best_params_["epsilon"])
        self.regr.fit(x_train_scaled, y_train)
        return self.regr.predict(x_test_scaled)

    def print_eval(self, x_train_scaled, x_test_scaled, y_train, y_test, df):
        if self.regr is None:
            raise Exception("needs to run 'create_and_pred' method before call this method.")

        print("テストデータにフィット")
        print("学習データの精度(決定係数r^2 score, 相関) =", self.regr.score(x_train_scaled, y_train))
        print("テストデータの精度(決定係数r^2 score, 相関) =", self.regr.score(x_test_scaled, y_test))
        pred = self.regr.predict(X=x_test_scaled)
        print("RMSE:", np.sqrt(mean_squared_error(y_test, pred)))

    def get_score(self, x_test_scaled, y_test):
        return self.regr.score(x_test_scaled, y_test)

    def get_rmse(self, y_test, pred):
        return np.sqrt(mean_squared_error(y_test, pred))

    def get_modeltype(self):
        return type(self.clr)

    def get_best_params(self):
        return self.grid_search.best_params_

もちろん更に良い形はあると思います。
何にしても、システム開発と違い、トライアンドエラーで繰り返すコードのため、
後で忘れないようにするのが大切かと思います。

この形を流用して、今後はサンプルデータでモデル構築をしてみます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?