pythonで機械学習する際のloggingについてのまとめ
素敵なまとめ記事は多いものの, 学生さんらが学ぶのに苦労しているようだったのでまとめ記事をまとめておく.
おそらく柔軟なためにわかりやすいベストプラクティスが定まっていなそうなのと, 用途の違いで書き方が異なるため一見すると共通項を学習しづらいことにあるのではと.
所感
- 用途 (だいたい 解析の記録 か エラーの記録 )を意識する
- インスタンス化して使うことを心がける (絶対ではないが意識としては)
- loggerインスタンスの初期化・作成は
utils.py
などにまとめてインスタンスを返させるとすっきりする
1. loggingの用途
loggingの用途として大きく解析情報の記録とエラー記録がある.
まとめ記事はたくさんあるので, 用途を意識すると調べやすくなる.
2. loggerインスタンスを弄る, logging自体を使わない
loggerインスタンスは階層構造をとっている.
logging自体のfunctionで.info()
などがあるが, rootのloggerを直接弄ることに相当する.
機械学習の解析用途程度ならいいが, 基本的にはインスタンス化してから使う癖をつけとくと怖くない.
3. loggingの位置
import logging
logger = logging.getLogger(name=__name__)
とするとどこからでも同じloggerが呼び出せるので, インスタンス初期化・作成は別立てした方がコードがすっきりする.
import logging
def init_logger():
# ~ loggerの初期化・設定 ~
return logger
from utils import init_logger
logger = init_logger()
if __name__ == '__main__':
# ~ 色々処理 ~
logger.info('epoch: XXX, loss_train: XXX, elapsed_time: XXX')
基本的な使い方
以下の流れ:
- loggerインスタンスの作成
- handlerインスタンスの作成
- handlerの設定
- loggerへのログの登録
例
utils.py
など別ファイルでloggerインスタンス作成機を用意しておき, 実行ファイルのログをとりたいシチュエーションとする.
実行ファイルは同じ階層と仮定, 単にimportの問題なので実際は好きにおける.
import logging
import datetime
def init_logger(
module_name:str, outdir:str='', tag:str='',
level_console:str='warning', level_file:str='info'
):
"""
initialize logger
Parameters
----------
module_name: str
対象とするモジュール名, __name__を与えよとのこと
outdir: str
出力先dir
tag: str
出力ファイル名を弄る
level_console, level_file: str
loggingのレベルをstrで指定
"""
level_dic = {
'critical':logging.CRITICAL,
'error':logging.ERROR,
'warning':logging.WARNING,
'info':logging.INFO,
'debug':logging.DEBUG,
'notset':logging.NOTSET
}
if len(tag)==0:
tag = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
# コメント1
logging.basicConfig(
level=level_dic[level_file],
filename=f'{outdir}/log_{tag}.txt',
format='[%(asctime)s] [%(levelname)s] %(message)s',
datefmt='%Y%m%d-%H%M%S',
)
# コメント2
logger = logging.getLogger(module_name)
# コメント3
sh = logging.StreamHandler()
sh.setLevel(level_dic[level_console])
fmt = logging.Formatter(
"[%(asctime)s] [%(levelname)s] %(message)s",
"%Y%m%d-%H%M%S"
)
sh.setFormatter(fmt)
logger.addHandler(sh)
# コメント4
return logger
- BasicConfigの設定
- root loggerを弄る
- ここで指定されたloggingレベル以上が出力されるので, 学習過程などで良く出力する(?)
INFO
レベルに設定しておく - 同時にファイル出力の設定もできるのでここでしておく,
FileHandler
で詳細に定義もできるが機械学習用途ならまずこれで足りる - ただしシステム開発用途の場合は最新ログを残して過去ログを削除などしたくなるので,
RotatingFileHandler
などを別立てした方が便利
- インスタンス作成
- 動作中モジュール名には公式が
__name__
を用いることを推奨しているので基本それ
- 動作中モジュール名には公式が
- ハンドラーの作成
-
StreamHandler
は標準出力にログを出力するハンドラー, 他にも色々ある
-
- ハンドラーの設定
- levelやformatを設定し, 最後に作成したloggerインスタンスへと紐づける
from utils import init_logger
# コメント1
logger = init_logger('my_module', 'XXX')
def hoge():
return 1, 10, 100
def main():
tmp = hoge()
logger.info(f'this is str {tmp}')
# コメント2
logger.info(tmp)
# コメント3
logger.warning('warning level is shown')
logger.debug('debug level is not shown')
if __name__ == '__main__':
main()
- インスタンス作成
- 最初に呼んでおく
- CLIの場合,
__name__
が__main__
になるから注意
-
logger.info()
などの形で各loggingレベルの情報を入れていく- strで入れることがほとんどだが, tupleなどの一般的なデータ型なら受け入れてくれる
-
init_logger
ではbaseのレベルをINFO
に設定しているため, それより上のWARNING
は出力されるが,DEBUG
は出力されない
その他
保存したlogの管理
重要に思うがあまり言及されていない印象.
ref3.ではevernoteにコピーしていたが手間なので, ディレクトリ指定してファイル内も探索してくれるようなものが理想的.
現状ベストプラクティスが作れないでいる.
- とりあえずならブラウザの詳細検索が手っ取り早い?
- スプレッドシートに転記させる?
- がっつりやるならsqlにも出力するようにしてDB化?
configによる設定
configを使うことで外部ファイルに設定情報を書いて読み込むこともできる(この辺り)
参考
ref1. AIエンジニアが気を付けたい~
ref2. システム開発向け
ref3. 機械学習向け
ref4. 公式の要約記事
ref5. かなり詳細, 玄人向け
ref6. 適度に詳説しつつ要点をまとめている
ref7. ファイル出力についてまとまっている
ref8. 詳しい, ハンドラーなどの具体的な名称が載っている
Discussion