💻

pythonで機械学習する際のloggingについてのまとめ

2022/10/11に公開

素敵なまとめ記事は多いものの, 学生さんらが学ぶのに苦労しているようだったのでまとめ記事をまとめておく.
おそらく柔軟なためにわかりやすいベストプラクティスが定まっていなそうなのと, 用途の違いで書き方が異なるため一見すると共通項を学習しづらいことにあるのではと.

所感

  1. 用途 (だいたい 解析の記録エラーの記録 )を意識する
  2. インスタンス化して使うことを心がける (絶対ではないが意識としては)
  3. 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が呼び出せるので, インスタンス初期化・作成は別立てした方がコードがすっきりする.

utils.py
import logging

def init_logger():

    # ~ loggerの初期化・設定 ~
    
    return logger

main.py
from utils import init_logger

logger = init_logger()

if __name__ == '__main__':
    # ~ 色々処理 ~
    logger.info('epoch: XXX, loss_train: XXX, elapsed_time: XXX')


基本的な使い方

以下の流れ:

  1. loggerインスタンスの作成
  2. handlerインスタンスの作成
  3. handlerの設定
  4. loggerへのログの登録

utils.pyなど別ファイルでloggerインスタンス作成機を用意しておき, 実行ファイルのログをとりたいシチュエーションとする.
実行ファイルは同じ階層と仮定, 単にimportの問題なので実際は好きにおける.

utils.py
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
  1. BasicConfigの設定
    • root loggerを弄る
    • ここで指定されたloggingレベル以上が出力されるので, 学習過程などで良く出力する(?)INFOレベルに設定しておく
    • 同時にファイル出力の設定もできるのでここでしておく, FileHandlerで詳細に定義もできるが機械学習用途ならまずこれで足りる
    • ただしシステム開発用途の場合は最新ログを残して過去ログを削除などしたくなるので, RotatingFileHandlerなどを別立てした方が便利
  2. インスタンス作成
    • 動作中モジュール名には公式が__name__を用いることを推奨しているので基本それ
  3. ハンドラーの作成
    • StreamHandlerは標準出力にログを出力するハンドラー, 他にも色々ある
  4. ハンドラーの設定
    • levelやformatを設定し, 最後に作成したloggerインスタンスへと紐づける
my_module.py
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()

  1. インスタンス作成
    • 最初に呼んでおく
    • CLIの場合, __name____main__になるから注意
  2. logger.info()などの形で各loggingレベルの情報を入れていく
    • strで入れることがほとんどだが, tupleなどの一般的なデータ型なら受け入れてくれる
  3. init_loggerではbaseのレベルをINFOに設定しているため, それより上のWARNINGは出力されるが, DEBUGは出力されない

その他

保存したlogの管理

重要に思うがあまり言及されていない印象.
ref3.ではevernoteにコピーしていたが手間なので, ディレクトリ指定してファイル内も探索してくれるようなものが理想的.
現状ベストプラクティスが作れないでいる.

  • とりあえずならブラウザの詳細検索が手っ取り早い?
  • スプレッドシートに転記させる?
  • がっつりやるならsqlにも出力するようにしてDB化?

configによる設定

configを使うことで外部ファイルに設定情報を書いて読み込むこともできる(この辺り)


参考

ref1. AIエンジニアが気を付けたい~
ref2. システム開発向け
ref3. 機械学習向け
ref4. 公式の要約記事
ref5. かなり詳細, 玄人向け
ref6. 適度に詳説しつつ要点をまとめている
ref7. ファイル出力についてまとまっている
ref8. 詳しい, ハンドラーなどの具体的な名称が載っている

Discussion