{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n'''\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session\n'''","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-01-30T06:21:12.204099Z","iopub.execute_input":"2023-01-30T06:21:12.205186Z","iopub.status.idle":"2023-01-30T06:21:12.236514Z","shell.execute_reply.started":"2023-01-30T06:21:12.204984Z","shell.execute_reply":"2023-01-30T06:21:12.235220Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 학습에 필요한 기본적인 module 가져오기","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# Import libraries\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nimport os\n\nimport glob\n\nimport gc\n\nfrom sklearn.model_selection import KFold, StratifiedKFold\n\n# pytorch lib\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport torch.optim as optim\n\n# Augmentation lib\nimport albumentations as A\n\n# openCV\nimport cv2\n\nfrom tqdm import tqdm\n\n# wandb.ai\nimport wandb  #for logging","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:21:12.238528Z","iopub.execute_input":"2023-01-30T06:21:12.238895Z","iopub.status.idle":"2023-01-30T06:21:15.738484Z","shell.execute_reply.started":"2023-01-30T06:21:12.238861Z","shell.execute_reply":"2023-01-30T06:21:15.737154Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 0. Configuration ⚙️","metadata":{}},{"cell_type":"code","source":"# Training Parameters\nclass CFG:\n    EPISODES = 0\n    NUM_FOLDS = 5\n    SEED = 42\n    MODE = 'train' #'train/test'\n    EPOCHS = 5\n    LR = 0.001\n    TRAIN_BATCH_SIZE = 8\n    TEST_BATCH_SIZE = 2*TRAIN_BATCH_SIZE\n    \n    NW = min(TRAIN_BATCH_SIZE,4) #num_workers\n    \n    G_FLAG = False\n    FEATURES = [\n                'x_position', 'y_position', 'speed', 'distance',\n                'direction', 'orientation', 'acceleration', 'sa'\n                ]\n    \n    FEATURES2 = [\n        'speed', 'distance','acceleration'\n    ]\n    SAMPLING_INTERVAL = 5 # sampling 하는 간격 ex) sampling_interval = 5 --> index = [0,5,10,15,...]\n    \n    # Path of csv files\n    BASE_PATH = '/kaggle/input/nfl-player-contact-detection/'\n    # Path of Image files\n    IMG_PATH = '/kaggle/input/nflcrop/'\n    # Path of checkpoints\n    SAVE_PATH = './checkpoints/'\n    \n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    wandb = False\n    \n##------------------------------------------------------    \nuse_cols = CFG.FEATURES\nfeatures = []\nfor i in range(2-CFG.G_FLAG):\n    for f in CFG.FEATURES:\n        features.append('_'.join([f,str(i+1)]))\nCFG.FEATURES = features+['distance']","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:21:15.740743Z","iopub.execute_input":"2023-01-30T06:21:15.741454Z","iopub.status.idle":"2023-01-30T06:21:15.822488Z","shell.execute_reply.started":"2023-01-30T06:21:15.741413Z","shell.execute_reply":"2023-01-30T06:21:15.821512Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"if CFG.wandb:\n    wandb.login()\n    wandb.init(config=CFG)","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:21:15.825713Z","iopub.execute_input":"2023-01-30T06:21:15.826405Z","iopub.status.idle":"2023-01-30T06:21:15.840116Z","shell.execute_reply.started":"2023-01-30T06:21:15.826368Z","shell.execute_reply":"2023-01-30T06:21:15.839412Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"# 1. Preprocessing","metadata":{}},{"cell_type":"code","source":"# Load csv\n# =====Train csv=====\ntrain_baseline_helmets = pd.read_csv(CFG.BASE_PATH + 'train_baseline_helmets.csv')\ntrain_player_tracking = pd.read_csv(CFG.BASE_PATH + 'train_player_tracking.csv')\ntrain_video_metadata = pd.read_csv(CFG.BASE_PATH + 'train_video_metadata.csv')\n\ntrain_labels = pd.read_csv(CFG.BASE_PATH + 'train_labels.csv')\n\n# =====Test csv=====\ntest_baseline_helmets = pd.read_csv(CFG.BASE_PATH + 'test_baseline_helmets.csv')\ntest_player_tracking = pd.read_csv(CFG.BASE_PATH + 'test_player_tracking.csv')\ntest_video_metadata = pd.read_csv(CFG.BASE_PATH + 'test_video_metadata.csv')\n\n# =====Sample submission=====\nsample_submission = pd.read_csv(CFG.BASE_PATH + 'sample_submission.csv')","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:21:15.841331Z","iopub.execute_input":"2023-01-30T06:21:15.842280Z","iopub.status.idle":"2023-01-30T06:21:40.509742Z","shell.execute_reply.started":"2023-01-30T06:21:15.842236Z","shell.execute_reply":"2023-01-30T06:21:40.508707Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def expand_contact_id(df):\n    \"\"\"\n    Splits out contact_id into seperate columns.\n    \"\"\"\n    df[\"game_play\"] = df[\"contact_id\"].str[:12]\n    df[\"step\"] = df[\"contact_id\"].str.split(\"_\").str[-3].astype(\"int\")\n    df[\"nfl_player_id_1\"] = df[\"contact_id\"].str.split(\"_\").str[-2]\n    df[\"nfl_player_id_2\"] = df[\"contact_id\"].str.split(\"_\").str[-1]\n    return df\n\ntest_labels = expand_contact_id(pd.read_csv(\"/kaggle/input/nfl-player-contact-detection/sample_submission.csv\"))","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:21:40.514670Z","iopub.execute_input":"2023-01-30T06:21:40.517058Z","iopub.status.idle":"2023-01-30T06:21:41.034351Z","shell.execute_reply.started":"2023-01-30T06:21:40.517011Z","shell.execute_reply":"2023-01-30T06:21:41.033211Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def create_features(df, tr_tracking, merge_col=\"step\", use_cols=[\"x_position\", \"y_position\"]):\n    output_cols = []\n    df_combo = (\n        df.astype({\"nfl_player_id_1\": \"str\"})\n        .merge(\n            tr_tracking.astype({\"nfl_player_id\": \"str\"})[\n                [\"game_play\", merge_col, \"nfl_player_id\",] + use_cols\n            ],\n            left_on=[\"game_play\", merge_col, \"nfl_player_id_1\"],\n            right_on=[\"game_play\", merge_col, \"nfl_player_id\"],\n            how=\"left\",\n        )\n        .rename(columns={c: c+\"_1\" for c in use_cols})\n        .drop(\"nfl_player_id\", axis=1)\n        .merge(\n            tr_tracking.astype({\"nfl_player_id\": \"str\"})[\n                [\"game_play\", merge_col, \"nfl_player_id\"] + use_cols\n            ],\n            left_on=[\"game_play\", merge_col, \"nfl_player_id_2\"],\n            right_on=[\"game_play\", merge_col, \"nfl_player_id\"],\n            how=\"left\",\n        )\n        .drop(\"nfl_player_id\", axis=1)\n        .rename(columns={c: c+\"_2\" for c in use_cols})\n        .sort_values([\"game_play\", merge_col, \"nfl_player_id_1\", \"nfl_player_id_2\"])\n        .reset_index(drop=True)\n    )\n    output_cols += [c+\"_1\" for c in use_cols]\n    output_cols += [c+\"_2\" for c in use_cols]\n    \n    if (\"x_position\" in use_cols) & (\"y_position\" in use_cols):\n        index = df_combo['x_position_2'].notnull()\n        \n        distance_arr = np.full(len(index), np.nan)\n        tmp_distance_arr = np.sqrt(\n            np.square(df_combo.loc[index, \"x_position_1\"] - df_combo.loc[index, \"x_position_2\"])\n            + np.square(df_combo.loc[index, \"y_position_1\"]- df_combo.loc[index, \"y_position_2\"])\n        )\n        \n        distance_arr[index] = tmp_distance_arr\n        df_combo['distance'] = distance_arr\n        output_cols += [\"distance\"]\n        \n    df_combo['G_flag'] = (df_combo['nfl_player_id_2']==\"G\")\n    output_cols += [\"G_flag\"]\n    return df_combo, output_cols","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:21:41.036341Z","iopub.execute_input":"2023-01-30T06:21:41.036949Z","iopub.status.idle":"2023-01-30T06:21:41.054097Z","shell.execute_reply.started":"2023-01-30T06:21:41.036909Z","shell.execute_reply":"2023-01-30T06:21:41.053188Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train, feature_cols = create_features(train_labels, train_player_tracking, use_cols=use_cols)\ntest, feature_cols = create_features(test_labels, test_player_tracking, use_cols=use_cols)\n\ndisplay(train.head())\ndisplay(test.head())","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:21:41.056038Z","iopub.execute_input":"2023-01-30T06:21:41.057124Z","iopub.status.idle":"2023-01-30T06:22:03.024725Z","shell.execute_reply.started":"2023-01-30T06:21:41.057088Z","shell.execute_reply":"2023-01-30T06:22:03.023866Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(feature_cols)","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:03.026172Z","iopub.execute_input":"2023-01-30T06:22:03.026609Z","iopub.status.idle":"2023-01-30T06:22:03.033591Z","shell.execute_reply.started":"2023-01-30T06:22:03.026573Z","shell.execute_reply":"2023-01-30T06:22:03.032427Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = train[train.G_flag == CFG.G_FLAG]\ntrain = train[train.distance<2.]\n\ntest = test[test.G_flag == CFG.G_FLAG]\ntest = test[test.distance<2.]","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:03.039252Z","iopub.execute_input":"2023-01-30T06:22:03.039586Z","iopub.status.idle":"2023-01-30T06:22:04.647938Z","shell.execute_reply.started":"2023-01-30T06:22:03.039552Z","shell.execute_reply":"2023-01-30T06:22:04.646956Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.preprocessing import MinMaxScaler\n\nscaler = MinMaxScaler()\nscaler.fit(train[CFG.FEATURES])","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:04.649511Z","iopub.execute_input":"2023-01-30T06:22:04.649899Z","iopub.status.idle":"2023-01-30T06:22:04.690698Z","shell.execute_reply.started":"2023-01-30T06:22:04.649862Z","shell.execute_reply":"2023-01-30T06:22:04.689796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = train.iloc[CFG.SAMPLING_INTERVAL*np.arange(len(train)//CFG.SAMPLING_INTERVAL)]\n","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:04.692058Z","iopub.execute_input":"2023-01-30T06:22:04.692509Z","iopub.status.idle":"2023-01-30T06:22:04.727827Z","shell.execute_reply.started":"2023-01-30T06:22:04.692472Z","shell.execute_reply":"2023-01-30T06:22:04.726908Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train[CFG.FEATURES] = scaler.transform(train[CFG.FEATURES])","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:04.729137Z","iopub.execute_input":"2023-01-30T06:22:04.729586Z","iopub.status.idle":"2023-01-30T06:22:04.745084Z","shell.execute_reply.started":"2023-01-30T06:22:04.729550Z","shell.execute_reply":"2023-01-30T06:22:04.744157Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:04.746681Z","iopub.execute_input":"2023-01-30T06:22:04.747146Z","iopub.status.idle":"2023-01-30T06:22:04.805958Z","shell.execute_reply.started":"2023-01-30T06:22:04.747105Z","shell.execute_reply":"2023-01-30T06:22:04.805071Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 2. Dataset & DataLoader","metadata":{}},{"cell_type":"code","source":"class Transforms:\n\n    train_transforms = A.Compose([\n        A.Resize(256, 256),\n        A.RandomCrop(224, 224),\n        A.HorizontalFlip(p=0.5),\n        A.ShiftScaleRotate(0.1,0.1,10,p=0.5),\n        A.RandomBrightnessContrast(brightness_limit=(-0.05, 0.05), contrast_limit=(-0.05, 0.05), p=0.5),\n    ])\n\n    valid_transforms = A.Compose([\n        A.Resize(256, 256),\n        A.RandomCrop(224, 224),\n    ])\n    \n    basic_transforms = A.Compose([\n        A.Resize(256, 256),\n        A.RandomCrop(224, 224),\n    ])","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:05.126867Z","iopub.execute_input":"2023-01-30T06:22:05.127225Z","iopub.status.idle":"2023-01-30T06:22:05.134993Z","shell.execute_reply.started":"2023-01-30T06:22:05.127189Z","shell.execute_reply":"2023-01-30T06:22:05.134100Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class NFLDataset(Dataset):\n    def __init__(self, df, features=[], mode='Train', transforms=None):\n        \n        self.df = df\n        self.features = features\n        self.mode = mode\n        \n        if transforms is not None:\n            self.transforms = transforms\n        else:\n            self.transforms =Transforms.basic_transforms\n            \n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        instance = self.df.iloc[idx]\n        \n        contact_id = instance.contact_id\n        \n        metadata = instance[self.features].values\n        \n        try:\n            img_endzone = plt.imread(os.path.join(CFG.IMG_PATH, self.mode, 'Endzone',contact_id+'.jpg'))\n        except:\n            img_endzone = np.zeros((256,256,3), dtype=np.uint8)\n    \n        try:\n            img_sideline = plt.imread(os.path.join(CFG.IMG_PATH, self.mode, 'Sideline',contact_id+'.jpg'))\n        except:\n            img_sideline = np.zeros((256,256,3), dtype=np.uint8)\n        \n        \n        img_endzone = self.transforms(image=img_endzone)['image'].transpose(2,0,1)/255.\n        img_sideline = self.transforms(image=img_sideline)['image'].transpose(2,0,1)/255.\n        \n        label = np.array([instance.contact])\n        \n#         return metadata, img_endzone, img_sideline, label\n        return {'metadata': metadata.astype(np.float32),\n                'img_endzone': img_endzone.astype(np.float32),\n                'img_sideline': img_sideline.astype(np.float32),\n                'label': label.astype(np.float32)} #items","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:05.136533Z","iopub.execute_input":"2023-01-30T06:22:05.137522Z","iopub.status.idle":"2023-01-30T06:22:05.149156Z","shell.execute_reply.started":"2023-01-30T06:22:05.137488Z","shell.execute_reply":"2023-01-30T06:22:05.148270Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dataset = NFLDataset(df=train, features=CFG.FEATURES, mode='Train', transforms=Transforms.train_transforms)\ntrain_loader = DataLoader(train_dataset, batch_size=CFG.TRAIN_BATCH_SIZE, shuffle=True, num_workers=2)","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:05.150521Z","iopub.execute_input":"2023-01-30T06:22:05.150882Z","iopub.status.idle":"2023-01-30T06:22:05.163850Z","shell.execute_reply.started":"2023-01-30T06:22:05.150844Z","shell.execute_reply":"2023-01-30T06:22:05.162772Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 3. Model","metadata":{}},{"cell_type":"code","source":"class Feature_Encoder(nn.Module):\n    def __init__(self):\n        super(Feature_Encoder, self).__init__()\n\n        self.fc1 = nn.Linear(len(CFG.FEATURES),128)\n        self.fc2 = nn.Linear(128,64)\n        self.fc3 = nn.Linear(64,32)\n\n    def forward(self, x):\n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = F.relu(self.fc3(x))\n        return x\n\nclass VGG16(nn.Module):\n    def __init__(self):\n\n        super(VGG16, self).__init__()\n\n        self.conv = nn.Sequential(\n        # input 3 224 224\n        nn.Conv2d(3, 64, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.Conv2d(64, 64, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.MaxPool2d(2, 2),\n        # 64 112 112\n        nn.Conv2d(64, 128, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.Conv2d(128, 128, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.MaxPool2d(2, 2),\n        # 128 56 56\n        nn.Conv2d(128, 256, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.Conv2d(256, 256, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.Conv2d(256, 256, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.MaxPool2d(2, 2),\n        # 256 28 28\n        nn.Conv2d(256, 512, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.Conv2d(512, 512, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.Conv2d(512, 512, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.MaxPool2d(2, 2),\n        # 512 14 14\n        nn.Conv2d(512, 512, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.Conv2d(512, 512, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.Conv2d(512, 512, 3, padding=1),nn.LeakyReLU(0.2),\n        nn.MaxPool2d(2, 2),\n        nn.AdaptiveAvgPool2d(7),\n        )\n\n        self.fcs = nn.Sequential(\n            nn.Linear(512 * 7 * 7, 4096),\n            nn.ReLU(),\n            nn.Dropout(),\n            nn.Linear(4096, 512),\n            nn.ReLU(),\n            nn.Dropout(),\n            nn.Linear(512, 64),\n        )\n\n    def forward(self, x):\n    \n        x = self.conv(x)\n        \n        x = x.view(-1, 512 * 7 * 7)\n\n        x = self.fcs(x)\n        return x\n\n\nclass Classification_head(nn.Module):\n    def __init__(self):\n        super(Classification_head, self).__init__()\n\n        self.fc1 = nn.Linear(64+64+32,256)\n        self.fc2 = nn.Linear(256,64)\n        self.fc3 = nn.Linear(64,1)\n\n    def forward(self, x):\n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = self.fc3(x)\n\n        return x\n\nclass Ensemble_Model(nn.Module):\n    def __init__(self):\n        super(Ensemble_Model, self).__init__()\n\n        self.Meta_Encoder = Feature_Encoder()\n\n        self.Endline_Backbone = VGG16()\n        self.Sideline_Backbone = VGG16()\n\n        self.classifier = Classification_head()\n\n\n    def forward(self, x_meta, x_end, x_side):\n        x_meta = self.Meta_Encoder(x_meta)\n\n        x_end = self.Endline_Backbone(x_end)\n\n        x_side = self.Sideline_Backbone(x_side)\n        \n        pred = self.classifier(torch.cat([x_meta, x_end, x_side], dim=1))\n\n        return pred","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:08.614260Z","iopub.execute_input":"2023-01-30T06:22:08.615064Z","iopub.status.idle":"2023-01-30T06:22:08.637654Z","shell.execute_reply.started":"2023-01-30T06:22:08.615007Z","shell.execute_reply":"2023-01-30T06:22:08.636605Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def build_model():\n    model = Ensemble_Model()\n    return model\n\ndef load_model(weight_path,verbose=0):\n    model = build_model()\n    model.load_state_dict(torch.load(weight_path))\n    model.eval()\n    if verbose:\n        print(\"Load model from %s\"%path)\n    return model\n\ndef save_model(fold, epoch, mode='last'):\n    os.makedirs(CFG.SAVE_PATH, exist_ok=True)\n    \n    torch.save({\n        'Epoch': epoch,\n        'Model':model.state_dict()\n    }, os.path.join(CFG.SAVE_PATH,f'{mode}_{fold}.pt'))","metadata":{"execution":{"iopub.status.busy":"2023-01-30T06:22:08.639079Z","iopub.execute_input":"2023-01-30T06:22:08.639681Z","iopub.status.idle":"2023-01-30T06:22:08.648670Z","shell.execute_reply.started":"2023-01-30T06:22:08.639645Z","shell.execute_reply":"2023-01-30T06:22:08.647793Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 4. Define Train/Test Functions","metadata":{}},{"cell_type":"markdown","source":"# 5. Training","metadata":{}}]}