Contents
code competitionでのKaggle API を使ったデータのアップロード
ここ1,2年、kaggleではcode competitionが増えています。code competitionとはkaggleのnotebook環境を使って予測ファイルを投稿することが求められるコンペで、自前の環境で訓練したモデルのパラメータデータをkaggle datasetにアップロードし、推論のみをnotebookで実施することが許されていることが多いです。この場合kaggle datasetへデータをアップロードすることが頻繁になります。
そこで、kaggleのAPIを使うことで、ウェブUIから手動でアップロードするより効率化することが可能です。
一方で実際にAPIを使ってみると、予めアップロードするデータのメタ情報を含むJSONファイルを用意しなければならない上に、データのあるディレクトリ、データの詳細コメント等をコマンドの引数として用意する必要があり、シェルスクリプトである程度自動化したとしても、意外にシェルスクリプトの編集が面倒です。おまけに編集忘れで意図しないファイルをアップロードしてしまうことが起きたりします。
この記事ではモデルの訓練をする際のパラメーター条件と同期を取りながら、自動でAPIコマンドを実施し、データアップロードに必要なJSONファイルの自動作成をするpythonスクリプトを紹介します。
フォルダ構成
前提として以下のようなフォルダ構成にしています。src/scripsに配置してあるpythonスクリプトを実行します。
src
|–logs:各種ログを格納するフォルダ
|–scripts:.pyスクリプトの格納フォルダ、ここでファイルを実行する
|–upload_model.py:データアップロード実行ファイル
|–model:訓練済みモデルの重みパラメータの格納フォルダ
|–model_path:アップロード対象の訓練済みモデルのパラメータのフォルダ
|–autoencoder_fold_ho_48.pth:アップロード対象のモデルのパラメータデータ
|–dataset-metadata.json:データメタ情報のJSON
データメタ情報のJSONファイルと通常のAPIコマンドによるアップロード法
普通にコマンドラインから、所定のフォルダにあるモデルパラメータデータをkaggle datasetにアップロードするためには、データに関するメタ上のJSONファイルが必要で、例えば以下のようなものです。見てわかるとおり、データの名称、メタ情報をアップロードするモデルパラメータの訓練条件を同期させて編集するのは大変です。
{"title": "データセット名",
"id": "kaggleアカウント/データセット名", "subtitle": "", "description": "", "isPrivate": true,
"licenses": [{"name": "unknown"}], "keywords": [], "collaborators": [],
"data": [{"description": "early stopping patiance 20--> 40; base EXT012",
"name": "autoencoder_fold_ho_48.pth",
"totalBytes": 4136646,
"columns": []}]}
そこで、以下のようなkaggle コマンドを実行しますが、model_path や-m
以降のコメントは都度の編集が面倒でミスしやすいです。
kaggle datasets version -p ../models/model_path -m "{モデル名称などのバージョンに関するコメント}"
モデル訓練パラメータのyamlファイル
scriptsフォルダ内には、モデルの訓練用パラメータのparameters.yamlが格納されており、例えば以下の内容になっているとします。ここの詳細説明はしませんが、モデルを訓練する上で必要なパラメータが予め設定されており、実験をするときにこのファイルを編集しながら、様々な条件で試行していきます。
EXT : '013'
TRAINING : True
USE_FINETUNE : True
FOLDS : 5
GROUP_GAP : 20
SEED : 66
INPUTPATH : ../../input
NUM_EPOCH : 300
BATCH_SIZE : 320
PATIANCE : 40
LR : 0.0005
MDL_PATH : ../models
MDL_NAME : autoencoder
VER : ho_base
THRESHOLD : 0
DATAVER : '02'
COMMENT: 'early stopping patiance 20--> 40; base EXT012'
次に、用意が面倒なJSONの自動作成と、parameters.yamlの内容と同期したkaggle APIコマンドの自動実行のpythonスクリプトです。
いくつか特徴について説明します。
- loggerを使用して、コンソールに標準出力させつつ、
src/logs
にログファイルを出力しています。 - yamlファイルにあるパラメータやコメントなどをアップロードするデータのメタ情報のJSONファイルで必要に応じて反映しています。
- yamlファイルにあるパラメータやコメントなどを反映して実行するkaggle APIコマンドを自動生成し、subprocessを使って実行します。
自動データアップロードスクリプト
import glob
import os
import json
import gc
import sys
import time
import yaml
import argparse
import logging
from tqdm import tqdm
import subprocess
import torch
print(torch.__version__)
def get_args():
# 引数の導入
parser = argparse.ArgumentParser(description='Cross Validation Train')
parser.add_argument('config_path', type=str, help='Setting parameter(.yaml)')
args = parser.parse_args()
return args
def upload_to_kaggle(
title: str,
k_id: str,
path: str,
comments: str,
update:bool,
logger=None,
extension = '.pth',
subtitle='',
description="",
isPrivate = True,
licenses = "unknown" ,
keywords = [],
collaborators = []
):
'''
>> upload_to_kaggle(title, k_id, path, comments, update)
Arguments
=========
title: the title of your dataset.
k_id: kaggle account id.
path: non-default string argument of the file path of the data to be uploaded.
comments:non-default string argument of the comment or the version about your upload.
logger: logger object if you use logging, default is None.
extension: the file extension of model weight files, default is ".pth"
subtitle: the subtitle of your dataset, default is empty string.
description: dataset description, default is empty string.
isPrivate: boolean to show wheather to make the data public, default is True.
licenses = the licenses description, default is "unkown"; must be one of /
['CC0-1.0', 'CC-BY-SA-4.0', 'GPL-2.0', 'ODbL-1.0', 'CC-BY-NC-SA-4.0', 'unknown', 'DbCL-1.0', 'CC-BY-SA-3.0', 'copyright-authors', 'other', 'reddit-api', 'world-bank'] .
keywords : the list of keywords about the dataset, default is empty list.
collaborators: the list of dataset collaborators, default is empty list.
'''
model_list = glob.glob(path+f'/*{extension}')
if len(model_list) == 0:
raise FileExistsError('File does not exist, check the file extention is correct \
or the file directory exist.')
if path[-1] == '/':
raise ValueError('Please remove the backslash in the end of the path')
#JSONファイルの作成
data_json = {
"title": title,
"id": f"{k_id}/{title}",
"subtitle": subtitle,
"description": description,
"isPrivate": isPrivate,
"licenses": [
{
"name": licenses
}
],
"keywords": [],
"collaborators": [],
"data": [
]
}
#JSONファイルのdata部分の更新
data_list = []
for mdl in model_list:
mdl_nm = mdl.replace(path+'/', '')
mdl_size = os.path.getsize(mdl)
data_dict = {
"description": comments,
"name": mdl_nm,
"totalBytes": mdl_size,
"columns": []
}
data_list.append(data_dict)
data_json['data'] = data_list
with open(path+'/dataset-metadata.json', 'w') as f:
json.dump(data_json, f)
#データセットを新規で作るときのkaggle APIコマンド
script0 = ['kaggle', 'datasets', 'create', '-p', f'{path}' , '-m' , f'\"{comments}\"']
#データセットを更新するときのkaggle APIコマンド
script1 = ['kaggle', 'datasets', 'version', '-p', f'{path}' , '-m' , f'\"{comments}\"']
if logger:
logger.info(data_json)
if update:
logger.info(script1)
logger.info(subprocess.check_output(script1))
else:
logger.info(script0)
logger.info(script1)
logger.info(subprocess.check_output(script0))
logger.info(subprocess.check_output(script1))
else:
print(data_json)
if update:
print(script1)
print(subprocess.check_output(script1))
else:
print(script0)
print(script1)
print(subprocess.check_output(script0))
print(subprocess.check_output(script1))
def main():
#コマンドラインの引数からパラメータのあるyamlファイルを読み込む
args = get_args()
with open(args.config_path, 'r') as f:
config = yaml.safe_load(f)
#yamlファイルにあるパラメータを変数に代入
EXT = config['EXT']
TRAINING = config['TRAINING']
USE_FINETUNE = config['USE_FINETUNE']
FOLDS = config['FOLDS']
GROUP_GAP = config['GROUP_GAP']
SEED = config['SEED']
INPUTPATH = config['INPUTPATH']
NUM_EPOCH = config['NUM_EPOCH']
BATCH_SIZE = config['BATCH_SIZE']
PATIANCE = config['PATIANCE']
LR =config['LR']
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(DEVICE)
MDL_PATH =config['MDL_PATH']
MDL_NAME =config['MDL_NAME']
VER = config['VER']
THRESHOLD = config['THRESHOLD']
COMMENT = config['COMMENT']
#logger関連の定義
format_str = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
logging.basicConfig(level = logging.INFO,format=format_str, filename=f'../logs/upload_log_{MDL_NAME}_{VER}_{EXT}.log')
logger = logging.getLogger('Log')
##https://ryoz001.com/1154.html
# コンソール画面用ハンドラー設定
# ハンドラーのログレベルを設定する (INFO以上を出力する)
consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.INFO)
# logger と コンソール用ハンドラーの関連付け
logger.addHandler(consoleHandler)
logger.info(config)
logger.info(sys.argv)
VER = (VER + '_' + EXT)
model_path = f'{MDL_PATH}/{MDL_NAME}_{VER}'
logger.info(model_path)
title = "データセット名"
k_id = "kaggleアカウント"
path = model_path
comments = VER
update = True
upload_to_kaggle(title, k_id, path, comments, update,logger=logger)
if __name__ == "__main__":
main()
そして以下を実行することで、自動でparameters.yamlに基づいて
python upload_model.py parameters.yaml
- 訓練されたモデルのパラメータデータのアップロード
- アップロードように必要なメタ情報JSONの出力
- ログと標準画面出力
結果として上記のことが実行されます。