def save_transformers(self, dst_dir='./.models', child_dir=None, transformers_name='transformers.pkl.cmp'): """前処理・特徴量エンジニアリング用モデル等を保存. これらは一括して,dictオブジェクトにまとめられ,joblib.dumpされた 単一ファイルを想定. Parameters ---------- dst_dir : str, optional 保存先の親ディレクトリ, by default './.models' child_dir : str, optional 保存先の子ディレクトリ.実験タスク等の中間名, by default None transformers_name : str, optional 前処理・特徴量エンジニアリング用モデルの保存名 , by default 'transformers.pkl.cmp' TODO ---- model.pyのsave_modelメソッドと被る部分が多いのでまとめるか検討 """ if not self.transformers: print('モデルが学習またはロードされていないので保存しない') return dst_dir = Path(dst_dir).resolve() if child_dir is None: # '{acitve branchのHEAD commit ID}.pkl.cmp'のように表示 repo_abspath = Path(__file__).resolve().parents[6] repo = Repo(repo_abspath) child_dir = repo.active_branch.commit.hexsha dst_path = dst_dir.joinpath(child_dir, transformers_name) os.makedirs(dst_path.parent, exist_ok=True) joblib.dump(self.transformers, dst_path, compress=True) print(dst_path, 'に前処理・特徴量エンジニアリング用モデル等を保存') # 子ディレクトリ以下のパスを記録(推論時に使用) self.config['transformers_path'] = \ Path(child_dir).joinpath(transformers_name) cm = ConfigManager() cm.save_config(self.config, self.config_path) print(f'モデル保存先を設定ファイル{self.config_path}に上書き')
class Model(object): def __init__(self, config_path, mode): self.config_path = config_path self.clf = None self.cm = ConfigManager() if mode not in ['train', 'pred']: raise ValueError('modeに"train", "pred"を指定してない.') self.mode = mode expected_keys = [] if mode == 'pred': expected_keys = ['model_path', 'hyper_params'] self.config = \ self.cm.load_config(config_path, expected_keys) def _validate_dataset(self, dataset): if not isinstance(dataset, dict): raise TypeError('入力データセットがdictでない.') if 'X' not in dataset: raise KeyError('データセットに key: "X" が含まれていない') if self.mode == 'train' and 'y' not in dataset: raise KeyError('データセットに key: "y" が含まれていない') if not isinstance(dataset['X'], np.ndarray): raise TypeError('Xのvalueがarrayでない') if self.mode == 'train' and not isinstance(dataset['y'], np.ndarray): raise TypeError('yのvalueがarrayでない') def init_model(self, hyper_parameters=None): # ハイパーパラメータが引数に渡されなかった場合は,configから読み込む if hyper_parameters is None: hyper_parameters = self.config['hyper_params'] # ハイパーパラメータ辞書の検証 try: if not isinstance(hyper_parameters, dict): raise TypeError(f'{hyper_parameters}がdictでない.') expected_keys = [ 'random_state', 'solver', 'class_weight', 'n_jobs' ] Utils.validate_dict(hyper_parameters, expected_keys) isinstance(hyper_parameters['random_state'], int) isinstance(hyper_parameters['solver'], str) isinstance(hyper_parameters['class_weight'], str) isinstance(hyper_parameters['n_jobs'], int) except (TypeError, KeyError): """ configに'hyper_params'キーとそのvaluesにexpected_keys が存在しない場合 """ traceback.print_exc() hyper_parameters = { 'random_state': 0, 'solver': 'lbfgs', 'class_weight': 'balanced', 'n_jobs': -1 } # モデルの初期化 self.clf = LogisticRegression( random_state=hyper_parameters['random_state'], solver=hyper_parameters['solver'], class_weight=hyper_parameters['class_weight'], n_jobs=hyper_parameters['n_jobs']) def train_with_cv(self, dataset, cv=4, return_train_score=True): # 使用オブジェクトの検証 self._validate_dataset(dataset) if self.clf is None: raise TypeError('モデルが初期化またはロードされていない.') """ configに特定のcv, return_train_score が指定されていたらオプション値を更新 """ if 'cv' in self.config['hyper_params']: cv = self.config['hyper_params']['cv'] if 'return_train_score' in self.config['hyper_params']: return_train_score = \ self.config['hyper_params']['return_train_score'] # 学習(公差検証) self.scores = cross_validate(self.clf, dataset['X'], dataset['y'], cv=cv, return_train_score=return_train_score, return_estimator=True) """ 今回は,簡単のためCV中最も良いvalidationスコアが出たものを採用する. このあたりはタスクによって手法を適宜変えれば良い (e.g. 平均をとる) """ best_idx = self.scores['test_score'].argmax() self.clf = self.scores['estimator'][best_idx] def save_model(self, dst_dir='./.models', child_dir=None, model_name='logistic_regression.pkl.cmp'): self.dst_dir = dst_dir if not self.clf: print('モデルが学習またはロードされていないので保存しない') return dst_dir = Path(dst_dir).resolve() if child_dir is None: # '{acitve branchのHEAD commit ID}.pkl.cmp'のように表示 repo_abspath = Path(__file__).resolve().parents[6] repo = Repo(repo_abspath) child_dir = repo.active_branch.commit.hexsha dst_path = dst_dir.joinpath(child_dir, model_name) if not dst_path.parent.exists(): os.makedirs(dst_path.parent) joblib.dump(self.clf, dst_path, compress=True) print(dst_path, 'にモデルを保存') # 子ディレクトリ以下のパスを記録(推論時に使用) self.config['model_path'] = \ Path(child_dir).joinpath(model_name) self.cm.save_config(self.config, self.config_path) print(f'モデル保存先を設定ファイル{self.config_path}を更新') def predict(self, dataset): """入力データセット内'X'に対する推論結果yをデータセットに付与して返す Parameters ---------- dataset : dict 前処理・特徴量エンジニアリング済みデータセット {'X': shape(サンプル数, 変数の数), 'y': shape(サンプル数, )} Returns ------- dict 推論結果'y'が更新されたデータセット """ prefix = '/opt/ml/model' filename = Path(self.config['model_path']).name model_path_for_pred = Path(prefix).joinpath(filename) self._validate_dataset(dataset) self.clf = joblib.load(model_path_for_pred) dataset['y'] = self.clf.predict(dataset['X']) return dataset