[英]Multiprocessing Logging - How to use loguru with joblib Parallel
我有一堆 Python 腳本來運行一些數據科學模型。 這需要相當長的時間,加快速度的唯一方法是使用多處理。 為此,我使用了joblib
庫,它運行良好。 然而,不幸的是,這會擾亂日志記錄,並且控制台 output 也會出現亂碼(然而,這是預期的),因為所有進程同時轉儲各自的輸出。
我是使用logging
庫的新手,並按照其他一些 SO 答案嘗試讓它工作。 我正在使用 8 個核心進行處理。 使用 SO 上的答案,我寫出日志文件,並預計每次迭代都會有 8 個新文件。 但是,它在第一次迭代中創建了 8 個文件,並且在每個循環中僅寫入/附加到這 8 個文件。 這有點不方便,所以我進一步探索並找到loguru
和logzero
。 雖然它們都涵蓋了使用multiprocessing
的示例,但它們都沒有展示如何將其與joblib
一起使用。 這是我到目前為止所擁有的:
運行模型.py
import math
import multiprocessing
import time
from datetime import datetime
from loguru import logger
import pandas as pd
import psutil
from joblib import Parallel, delayed
import helper
import log
import prep_data
import stock_subscriber_data
import train_model
def get_pred(cust_df, stock_id, logger):
logger.info('--------------------------------Stock loop {}-------------------------------'.format(stock_id))
cust_stockid_df = stock_subscriber_data.get_stockid_data(cust_df, stock_id)
weekly_timeseries, last_date, abn_df = prep_data.prep(cust_stockid_df, logger)
single_row_df = stock_subscriber_data.get_single_row(cust_df, stock_id)
stock_subscriber_data.write_data(abn_df, 't1')
test_y, prd = train_model.read_train_write(cust_df, stock_id, weekly_timeseries, last_date, logger)
return True
def main():
cust_df = stock_subscriber_data.get_data()
cust_df = helper.clean_data(cust_df)
stock_list = cust_df['intStockID'].unique()
max_proc = max(math.ceil(((psutil.virtual_memory().total >> 30) - 100) / 50), 1)
num_cores = min(multiprocessing.cpu_count(), max_proc)
logger.add("test_loguru.log", format="{time} {level}: ({file}:{module} - {line}) >> {message}", level="INFO", enqueue=True)
Parallel(n_jobs=num_cores)(delayed(get_pred)(cust_df, s, logger) for s in stock_list)
if __name__ == "__main__":
main()
訓練模型.py
import math
from datetime import datetime
from itertools import product
from math import sqrt
import pandas as pd
from keras import backend
from keras.layers import Dense
from keras.layers import LSTM
from keras.models import Sequential
from numpy import array
from numpy import mean
from pandas import DataFrame
from pandas import concat
from sklearn.metrics import mean_squared_error
import helper
import stock_subscriber_data
# bunch of functions here that don't need logging...
# walk-forward validation for univariate data
def walk_forward_validation(logger, data, n_test, cfg):
#... do stuff here ...
#... and here ...
logger.info('{0:.3f}'.format(error))
return error, model
# score a model, return None on failure
def repeat_evaluate(logger, data, config, n_test, n_repeats=10):
#... do stuff here ...
#... and here ...
logger.info('> Model{0} {1:.3f}'.format(key, result))
return key, result, best_model
def read_train_write(data_df, stock_id, series, last_date, logger):
#... do stuff here ...
#... and here ...
logger.info('done')
#... do stuff here ...
#... and here ...
# bunch of logger.info() statements here...
#
#
#
#
#... do stuff here ...
#... and here ...
return test_y, prd
當一次只有一個進程時,這很有效。 但是,我得到一個_pickle.PicklingError: Could not pickle the task to send it to the workers.
在多進程模式下運行時出錯。 我究竟做錯了什么? 我該如何解決這個問題? 我不介意切換到loguru
或logzero
以外的其他東西,只要我可以創建一個包含連貫日志的文件,甚至是n
文件,每個文件都包含joblib
每次迭代的日志。
我通過修改我的run_models.py
讓它工作。 現在,我每個循環都有一個日志文件。 這會創建大量日志文件,但它們都與每個循環相關,而不是混亂或任何東西。 我猜,一次一個步驟。 這是我所做的:
運行模型.py
import math
import multiprocessing
import time
from datetime import datetime
from loguru import logger
import pandas as pd
import psutil
from joblib import Parallel, delayed
import helper
import log
import prep_data
import stock_subscriber_data
import train_model
def get_pred(cust_df, stock_id):
log_file_name = "log_file_{}".format(stock_id)
logger.add(log_file_name, format="{time} {level}: ({file}:{module} - {line}) >> {message}", level="INFO", enqueue=True)
logger.info('--------------------------------Stock loop {}-------------------------------'.format(stock_id))
cust_stockid_df = stock_subscriber_data.get_stockid_data(cust_df, stock_id)
weekly_timeseries, last_date, abn_df = prep_data.prep(cust_stockid_df, logger)
single_row_df = stock_subscriber_data.get_single_row(cust_df, stock_id)
stock_subscriber_data.write_data(abn_df, 't1')
test_y, prd = train_model.read_train_write(cust_df, stock_id, weekly_timeseries, last_date, logger)
return True
def main():
cust_df = stock_subscriber_data.get_data()
cust_df = helper.clean_data(cust_df)
stock_list = cust_df['intStockID'].unique()
max_proc = max(math.ceil(((psutil.virtual_memory().total >> 30) - 100) / 50), 1)
num_cores = min(multiprocessing.cpu_count(), max_proc)
Parallel(n_jobs=num_cores)(delayed(get_pred)(cust_df, s) for s in stock_list)
if __name__ == "__main__":
main()
因此,將 loguru 與 joblib 結合使用的正確方法是將后端更改為多處理。
from loguru import logger
from joblib import Parallel, delayed
from tqdm.autonotebook import tqdm
logger.remove()
logger.add(sys.stdout, level = 'INFO', enqueue=True)
logger.info('test')
logger.debug('should not appear')
def do_thing(i):
logger.info('item %i' %i)
logger.debug('should not appaear')
return None
Parallel(n_jobs=4, backend='multiprocessing')(
delayed(do_thing)(i)
for i in tqdm(range(10))
)
Parallel(n_jobs=4)(
delayed(do_thing)(i)
for i in tqdm(range(10))
)
第一個並行調用有效。 第二個得到你之前提到的舊問題
核心概念:主進程和每個子進程都需要調用logger.add()
。 您可以使用相同的文件名將 pipe 所有日志保存到同一個文件中。
# Pseudo-code to get the idea
def main():
logfile = 'execution.log'
# Use enqueue to ensure works properly with multiprocessing
logger.add(logfile, enqueue=True)
...
# Add logfile to the params passed to get_pred
Parallel(n_jobs=num_cores)(delayed(get_pred)(cust_df, s, logfile) for s in stock_list)
# Add logfile as param to get_pred
def get_pred(cust_df, stock_id, logfile):
# Add the *same* logfile each time the child process is called!
logger.add(logfile, enqueue=True)
# Add identifiers to log messages to distinguish them
logger.info(f'{stock_id} - more info')
# ...
在@CodinginCircles answer中,在get_pred()
function 中,他們每次都使用唯一的日志文件名稱調用logger.add()
。 這會創建許多不同的日志文件。
相反,您可以每次使用相同的名稱調用logger.add()
,所有日志都將 go 到同一個日志文件。 設置enqueue=True
將有助於確保它與多處理一起正常工作。
要知道哪個日志對應於哪個事物(在我們的例子中是股票),只需將股票名稱添加到日志消息中,例如logger.info(f'{stock_id} - more info')
此外,我發現在logger.add()
中添加backtrace=True, diagnose=True
作為參數在使用 Loguru 時特別有用。
最后注意:您還可以將LOGFILE = 'execution.log'
定義為 function 定義之外的常量,這樣您就不需要將其作為參數傳遞給get_pred()
。 上面概述的方法可以讓您對日志文件名做更多的事情,就像給它一個獨特的時間簽名一樣。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.