論文読み会のネタ探しに困った時

研究室に所属していると、定期的に論文を紹介する機会があります。しかし、紹介するのに丁度いい(ある程度インパクトがあり、かつ説明しやすい)論文が見つからないこともあります。今回は私が論文を探す時に行っていることを紹介します。

国際会議の受賞論文を探す

国際会議で受賞した論文は、その分野で特に影響力があり、革新的な内容を含んでいることが多いため、紹介するには最適です。また、論文の構成や図の描き方など、自身が学ぶことも多いです。「会議名 best paper」などでググると出てくると思います。

国際会議のpaper listを見る

国際会議の公式ウェブサイトには、その年に発表されたすべての論文のリストが掲載されています。私はCtrl+Fで関連キーワードをひたすら検索し、興味深いタイトルがないか探しています。これも同様に、「会議名 paper list」でググると出てきます。

Youtubeの論文紹介動画を見る

Youtubeには、論文を紹介する動画がたくさんあります。「会議名 Youtube」や「Youtube 論文名」でググると出てくることがあります。この方法のメリットは理解しやすく、かつ準備が楽になることです(英語をそのまま和訳すればいいので)。

最新論文を紹介しているサイトを見る

例えばAI-SCHOLARなどはAI分野で有名ですが、こういったサイトを参考にするのも便利です。Youtubeの時と同様、理解しやすく、かつ準備が楽になります。

multiprocessingの備忘録 : 並列処理が動かなくなった時の対処法

機械学習モデルのパイプラインを作成していると、並列処理を行いたい場面が出てくるとおもいます。私も同様に、データの前処理→並列処理でモデル学習という流れでパイプラインを作成していました。しかし、前処理の内部実装を変えた途端に、突然並列処理が動かなくなりました。

結論

spawnを使う。

def main():
  preprocessing() # 前処理メソッド
  train_test() # 並列で学習するメソッド

if __name__=="__main__":
  multiprocessing.set_start_method('spawn') #ここを追加
  main()

環境

今まで正常に動いていたコード

今まで正常に動いていたコードは以下のようなものです。

import multiprocessing
import sklearn
import lightgbm as lgb

def preprocessing():
  # 前処理の内部実装

def train_fold(X_train, y_train, X_test, y_test):
  #lightgbmのハイパラを定義
  params=~
  X_train_for_lgb = lgb.Dataset(X_train, label=y_train)
  model=lgb.train(params, X_train_for_lgb)

  # テストデータで評価
  y_pred=model.predict(X_test)
  accuracy=sklearn.metrics.accuracy_score(y_test, y_pred)
  return accuracy


def train():
  # 並列で学習する内部実装
  #学習データXとラベルyを定義
  X,y=~~
  results=[]
  pool=multiprocessing.Pool(processes=4)
  kf=sklearn.model_selection.KFold(n_splits=4).split(X,y)
  for train_index, test_index in kf:
    results.append(pool.apply_async(train_fold, args=(X[train_index], y[train_index], X[test_index], y[test_index])))


def main():
  preprocessing() # 前処理メソッド
  train() # 並列で学習するメソッド

if __name__=="__main__":
  main()

前処理の内部実装を変えたら並列処理が動かなくなった

前処理メソッドの中で、sklearnのあるメソッドを使うようにしたら、突然並列処理が動かなくなりました。正確には、上記コードのtrain_fold()メソッドのX_train_for_lgbまでは実行されるのですが、lgb.train()以降が実行されなくなりました。

解決策と原因

冒頭にある通り、spawnを使うことで解決しました。

def main():
  preprocessing() # 前処理メソッド
  train_test() # 並列で学習するメソッド

if __name__=="__main__":
  multiprocessing.set_start_method('spawn') #ここを追加
  main()

原因は「メモリの使いすぎ」説が濃厚。 Ubuntuでは、デフォルトでforkを使っているようです。forkは、親プロセスのメモリを子プロセスにコピーするため、メモリを大量に消費します。一方、spawnは、親プロセスのメモリを子プロセスにコピーしないため、メモリを消費しないとのことです。 (しかし前処理と学習のコードはメソッドが分かれており、前処理が終了した段階でメモリが解放されるはずでは?という疑問は残ったままです。)

参考

Optunaのハイパラ探索時に、複数の指標を保存しておく方法

はじめに

Optunaでは、例えばAccuracyを最大化するようなハイパラ探索を行います。その場合、Accuracyをリアルタイムで表示したり、studyオブジェクトに保存したりすることができます。しかし、Accuracy以外にも、例えばPrecisionやRecallなどの指標を保存したい場合があります。そこで、今回はOptunaのハイパラ探索時に、複数の指標を保存しておく方法を紹介します。(注意点:複数指標を使ってハイパラ探索を行うという話ではありません。)

使用したバージョン

  • python: 3.8.11
  • optuna: 3.2.0

コード

通常の使い方

まずは、通常の使い方を紹介します。以下のコードは、ハイパラ探索の結果を見るために、全てのtrialが終わるまで待つコードです。

import optuna

def objective(trial):
    #学習データとテストデータを作成。make_data()は適当な関数.
    X_train, y_train, X_test, y_test = make_data()
    #あるtrialの学習とテストを行う関数
    accuracy = train_test(X_train, y_train, X_test, y_test)

    # Accuracyに基づいてハイパラ探索を行うように、accuracyを返す
    return accuracy

# 100通りのハイパラを探索する
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
# studyを保存
with open('study.pkl', 'wb') as f:
    pickle.dump(study, f)

複数の指標を保存する使い方

trial毎に特定の指標を保存するには、set_user_attr()メソッドを使います。以下のコードは、各trialが終わるたびに、そのtrialのAccuracyとPrecisionとRecallを保存するコードです。

def objective(trial):
    #学習データとテストデータを作成。make_data()は適当な関数.
    X_train, y_train, X_test, y_test = make_data()
    #あるtrialの学習とテストを行う関数
    # 今回は、AccuracyとPrecisionとRecallを返すようにする
    accuracy,precision,recall = train_test(X_train, y_train, X_test, y_test)

    # trial毎にAccuracyとPrecisionとRecallを保存する
    trial.set_user_attr("precision", precision)
    trial.set_user_attr("recall", recall)
  
    # Accuracyに基づいてハイパラ探索を行うように、accuracyを返す
    return accuracy

# 100通りのハイパラを探索する
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)

# studyを保存
with open('study.pkl', 'wb') as f:
    pickle.dump(study, f)

まとめ

Optunaのハイパラ探索時に、複数の指標を保存しておく方法を紹介しました。 上記のようにtrialからset_user_attr()メソッドを叩くことで、trialと保存したい指標を紐づけることができます。修正点やご意見などありましたら、コメントお願いします。

Optunaの各trial毎に決まった処理を実行させる方法

はじめに

最近、Optunaというハイパラ調整ライブラリを使っています。ハイパラ調整はある種職人的技量が求められるため、この部分で困っている人にはおすすめです。このOptunaですが、デフォルトの場合、ハイパラ探索の結果を見れるのは全計算が終わった後になります。全体の計算が5分程度なら良いのですが、何時間もかかる場合はモヤモヤしながら待つことになります。そこで、各trial毎(すなわち一つの探索毎)に決まった処理を実行させる方法を紹介します。

使用したバージョン

  • python: 3.8.11
  • optuna: 3.2.0

コード

通常の使い方

まずは、通常の使い方を紹介します。以下のコードは、ハイパラ探索の結果を見るために、全てのtrialが終わるまで待つコードです。

import optuna
# 100通りのハイパラを探索する
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
# ハイパラ探索の最良結果を表示
print(study.best_params)
print(study.best_value)
print(study.best_trial)

各trial毎に処理を実行させる使い方

各trial毎に処理を実行させるには、callbackを使います。以下のコードは、各trialが終わるたびに、そのtrialのハイパラ探索の結果を表示するコードです。

import optuna

class MyCallback():
    def __init__(self):
        super().__init__()
    def __call__(self, study: optuna.study.Study, trial: optuna.trial.FrozenTrial):
        # trial毎の処理を記述
        print(f'current_trial: {trial}')
        print(f'current_params: {trial.params}')
        print(f'current_value: {trial.value}')
        print('------------------')

# callbackを設定
my_callback=MyCallback()
# 100通りのハイパラを探索する
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100, callbacks=[my_callback])

まとめ

Optunaの各trial毎に決まった処理を実行させる方法を紹介しました。今回はprint出力を例にしましたが、例えば、各trial毎にハイパラ探索の結果をファイルに保存したり、tensorboadにリアルタイムで表示させたりすることもできます。 修正点やご意見などありましたら、コメントお願いします。