{"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":"markdown","source":"Hello fellow Kagglers,\n\nThis notebook demonstrates a training approach using EfficientNetB0 for each real/imaginary frame for both Hanford and Livingston. This method trains 4 different CNN's whose embedding layer is concatenated and finally used to predict a target using a single neuron with sigmoid activation.\n\nThis is a work in progress and the validation AUC is hovering around 0.50, which is basically random guessing performance.\nThe strange thing is, the model seems to fit the training data well with ~0.99 training AUC, even with large dropout and tiny CNN backbones.\nIt is at this moment unclear why the model overfits so badly...\n\nI wanted to share this approach to give a baseline model architecture to be used in this competition.\n\nAny tips, remarks or questions are welcome.\n\nThe training dataset used is from: [G2Net EDA + 360x360 Dataset Creation\n](https://www.kaggle.com/code/markwijkhuizen/g2net-eda-360x360-dataset-creation)","metadata":{}},{"cell_type":"code","source":"# Package to read HDF5 files\n!pip install -q h5py","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:45.232319Z","iopub.execute_input":"2022-10-07T11:04:45.233049Z","iopub.status.idle":"2022-10-07T11:04:54.568946Z","shell.execute_reply.started":"2022-10-07T11:04:45.23301Z","shell.execute_reply":"2022-10-07T11:04:54.5678Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib as mpl\nimport tensorflow as tf\nimport tensorflow_addons as tfa\n\nfrom tqdm.notebook import tqdm\nfrom sklearn.model_selection import train_test_split\n\nimport h5py\nimport glob\nimport cv2\nimport gc\nimport os\nimport sys\n\ntqdm.pandas()","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:54.573055Z","iopub.execute_input":"2022-10-07T11:04:54.574122Z","iopub.status.idle":"2022-10-07T11:04:55.294335Z","shell.execute_reply.started":"2022-10-07T11:04:54.574079Z","shell.execute_reply":"2022-10-07T11:04:55.29336Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# MatplotLib Configuration","metadata":{}},{"cell_type":"code","source":"# MatplotLib Global Settings\nmpl.rcParams.update(mpl.rcParamsDefault)\nmpl.rcParams['xtick.labelsize'] = 16\nmpl.rcParams['ytick.labelsize'] = 16\nmpl.rcParams['axes.labelsize'] = 18\nmpl.rcParams['axes.titlesize'] = 24","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:55.295683Z","iopub.execute_input":"2022-10-07T11:04:55.296244Z","iopub.status.idle":"2022-10-07T11:04:55.304201Z","shell.execute_reply.started":"2022-10-07T11:04:55.296206Z","shell.execute_reply":"2022-10-07T11:04:55.302791Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Read Train DataFrame","metadata":{}},{"cell_type":"code","source":"# Read Train DataFrame\ntrain_labels = pd.read_csv('/kaggle/input/g2net-detecting-continuous-gravitational-waves/train_labels.csv')\n\ndisplay(train_labels.info())\n\ndisplay(train_labels.head())","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:55.30725Z","iopub.execute_input":"2022-10-07T11:04:55.308255Z","iopub.status.idle":"2022-10-07T11:04:55.358032Z","shell.execute_reply.started":"2022-10-07T11:04:55.30822Z","shell.execute_reply":"2022-10-07T11:04:55.356997Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Configuration","metadata":{}},{"cell_type":"code","source":"# Number of Samples in train dataset\nN_SAMPLES = len(train_labels)\n\n# Make 360x360 Patches\nTARGET_HEIGHT = 360\nTARGET_WIDTH = 360\nprint(f'TARGET_HEIGHT: {TARGET_HEIGHT}, TARGET_WIDTH: {TARGET_WIDTH}')\n\nBATCH_SIZE = 8\nEPOCHS = 7\n\nIS_INTERACTIVE = os.environ['KAGGLE_KERNEL_RUN_TYPE'] == 'Interactive'\n\nTRAIN_DIR = '/kaggle/input/g2net-detecting-continuous-gravitational-waves/train'\n\nINPUTS = ['x_h_r', 'x_h_i', 'x_l_r', 'x_l_i']","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:55.359633Z","iopub.execute_input":"2022-10-07T11:04:55.360019Z","iopub.status.idle":"2022-10-07T11:04:55.366995Z","shell.execute_reply.started":"2022-10-07T11:04:55.359982Z","shell.execute_reply":"2022-10-07T11:04:55.365613Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Dataset","metadata":{}},{"cell_type":"code","source":"# Get all sample file paths\nSAMPLE_IDXS = np.arange(len(glob.glob('/kaggle/input/g2net-360x360-dataset/train_samples/target/*')))\nprint(f'SAMPLE_IDXS shape: {SAMPLE_IDXS.shape}')","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:55.368842Z","iopub.execute_input":"2022-10-07T11:04:55.369229Z","iopub.status.idle":"2022-10-07T11:04:55.541087Z","shell.execute_reply.started":"2022-10-07T11:04:55.369192Z","shell.execute_reply":"2022-10-07T11:04:55.540139Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Training dataset chooses a random 360x360 patch from a recording\ndef get_train_dataset(idxs, bs):\n    while True:\n        X = {\n                'x_h_r': np.zeros(shape=[bs,TARGET_HEIGHT, TARGET_WIDTH], dtype=np.float32),\n                'x_h_i': np.zeros(shape=[bs,TARGET_HEIGHT, TARGET_WIDTH], dtype=np.float32),\n                'x_l_r': np.zeros(shape=[bs,TARGET_HEIGHT, TARGET_WIDTH], dtype=np.float32),\n                'x_l_i': np.zeros(shape=[bs,TARGET_HEIGHT, TARGET_WIDTH], dtype=np.float32),\n            }\n        y = np.zeros(shape=[bs], dtype=np.int8)\n        for i in range(bs):\n            # Choose random file path\n            index = np.random.choice(idxs, 1).squeeze()\n\n            # Load x and choose random frame\n            XX = np.load(f'/kaggle/input/g2net-360x360-dataset/train_samples/x/{index}.npy')\n            # Choose random patch\n            patch_index = np.random.choice(len(XX), 1).squeeze()\n            X['x_h_r'][i] = XX[patch_index, 0]\n            X['x_h_i'][i] = XX[patch_index, 1]\n            X['x_l_r'][i] = XX[patch_index, 2]\n            X['x_l_i'][i] = XX[patch_index, 3]\n\n            # Load target\n            y[i] = np.load(f'/kaggle/input/g2net-360x360-dataset/train_samples/target/{index}.npy')\n        \n        yield X, y","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:55.542658Z","iopub.execute_input":"2022-10-07T11:04:55.543317Z","iopub.status.idle":"2022-10-07T11:04:55.552914Z","shell.execute_reply.started":"2022-10-07T11:04:55.543279Z","shell.execute_reply":"2022-10-07T11:04:55.551828Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Train batch statistics\ndef train_dataset_test():\n    train_dataset = get_train_dataset(SAMPLE_IDXS, BATCH_SIZE)\n    X, y = next(train_dataset)\n    for k in INPUTS:\n        print(f'X {k} shape: {X[k].shape}, dtype: {X[k].dtype}', end=', ')\n        print(f'X {k} mean: {X[k].mean():.2E}, std: {X[k].std():.2f}, min: {X[k].min():.2f}, max: {X[k].max():.2f}')\n    print(f'y: {y}')\n    \ntrain_dataset_test()","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:55.55446Z","iopub.execute_input":"2022-10-07T11:04:55.554917Z","iopub.status.idle":"2022-10-07T11:04:58.730607Z","shell.execute_reply.started":"2022-10-07T11:04:55.554881Z","shell.execute_reply":"2022-10-07T11:04:58.729604Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Validation dataset reads each sample patch by patch\ndef get_val_dataset(idxs):\n    while True:\n        for index in idxs:\n            # Load target\n            y = np.expand_dims(\n                    np.load(f'/kaggle/input/g2net-360x360-dataset/train_samples/target/{index}.npy'),\n                    0,\n                )\n\n            # Load x and choose random frame\n            XX = np.load(f'/kaggle/input/g2net-360x360-dataset/train_samples/x/{index}.npy')\n            # Read all patches\n            for X_patch in XX:\n                X = {\n                    'x_h_r': np.expand_dims(X_patch[0], 0),\n                    'x_h_i': np.expand_dims(X_patch[1], 0),\n                    'x_l_r': np.expand_dims(X_patch[2], 0),\n                    'x_l_i': np.expand_dims(X_patch[3], 0),\n                }\n\n                yield X, y ","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:58.732137Z","iopub.execute_input":"2022-10-07T11:04:58.732796Z","iopub.status.idle":"2022-10-07T11:04:58.740496Z","shell.execute_reply.started":"2022-10-07T11:04:58.732753Z","shell.execute_reply":"2022-10-07T11:04:58.739499Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Validation dataset statistics\ndef val_dataset_test():\n    train_dataset = get_val_dataset(SAMPLE_IDXS)\n    X, y = next(train_dataset)\n    for k in INPUTS:\n        print(f'X {k} shape: {X[k].shape}, dtype: {X[k].dtype}', end=', ')\n        print(f'X {k} mean: {X[k].mean():.2E}, std: {X[k].std():.2f}, min: {X[k].min():.2f}, max: {X[k].max():.2f}')\n    print(f'y: {y}')\n    \nval_dataset_test()","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:58.744752Z","iopub.execute_input":"2022-10-07T11:04:58.745013Z","iopub.status.idle":"2022-10-07T11:04:59.062301Z","shell.execute_reply.started":"2022-10-07T11:04:58.744988Z","shell.execute_reply":"2022-10-07T11:04:59.061209Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train Validation Split","metadata":{}},{"cell_type":"code","source":"# Split samples in 90% train data and 10% validation data\n# Note each sample is either fully used for training or validation\nIDXS_train, IDXS_val = train_test_split(SAMPLE_IDXS, test_size=0.10, random_state=42)\nprint(f'IDXS_train shape: {IDXS_train.shape}, IDXS_val shape: {IDXS_val.shape}')","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:59.063619Z","iopub.execute_input":"2022-10-07T11:04:59.064375Z","iopub.status.idle":"2022-10-07T11:04:59.072041Z","shell.execute_reply.started":"2022-10-07T11:04:59.064333Z","shell.execute_reply":"2022-10-07T11:04:59.070908Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Compute Samples, since each recording consists of multiple patches\nN_TRAIN_SAMPLES = 0\nN_VAL_SAMPLES = 0\n\nfor idx in tqdm(IDXS_train):\n    N_TRAIN_SAMPLES += np.load(f'/kaggle/input/g2net-360x360-dataset/train_samples/x/{idx}.npy').shape[0]\n\nfor idx in tqdm(IDXS_val):\n    N_VAL_SAMPLES += np.load(f'/kaggle/input/g2net-360x360-dataset/train_samples/x/{idx}.npy').shape[0]\n\nprint(f'N_TRAIN_SAMPLES: {N_TRAIN_SAMPLES}, N_VAL_SAMPLES: {N_VAL_SAMPLES}')","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:04:59.073365Z","iopub.execute_input":"2022-10-07T11:04:59.07432Z","iopub.status.idle":"2022-10-07T11:06:59.310512Z","shell.execute_reply.started":"2022-10-07T11:04:59.074282Z","shell.execute_reply":"2022-10-07T11:06:59.309447Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Branch","metadata":{}},{"cell_type":"code","source":"\"\"\"\n    Four frames are used as input:\n        1) Hanford Real\n        2) Hanford imaginary\n        3) Livingston Real\n        4) Livingston Imaginary\n    Each frame is stacked 3 times to get a 3 channel input to the CNN\n\"\"\"\ndef get_branch(x_input, name):\n    x = tf.expand_dims(x_input, axis=-1)\n    x = tf.tile(x, [1,1,1,3])\n\n    cnn = tf.keras.applications.efficientnet.EfficientNetB0(\n            include_top=False,\n            input_shape=[TARGET_HEIGHT, TARGET_WIDTH, 3],\n            pooling='avg',\n            weights='imagenet',\n        )\n    \n    # Remove Rescaling and Normalizing\n    cnn.layers[1].scale = 1\n    cnn.layers[2].mean = 0.0\n    cnn.layers[2].variance = 1.0\n\n    # Rename to prevent naming conflicts\n    cnn._name  = f'{name}_{cnn._name }'\n\n    x = cnn(x)\n    x = tf.keras.layers.Dense(256, name=f'{name}_dense')(x)\n    \n    return x","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:06:59.312307Z","iopub.execute_input":"2022-10-07T11:06:59.313295Z","iopub.status.idle":"2022-10-07T11:06:59.32197Z","shell.execute_reply.started":"2022-10-07T11:06:59.313245Z","shell.execute_reply":"2022-10-07T11:06:59.320943Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"def get_model():\n    # 1) Hanford Real\n    h_r_input = tf.keras.layers.Input(shape=[TARGET_HEIGHT, TARGET_WIDTH], dtype=tf.float32, name='x_h_r')\n    # 2) Hanford imaginary\n    h_i_input = tf.keras.layers.Input(shape=[TARGET_HEIGHT, TARGET_WIDTH], dtype=tf.float32, name='x_h_i')\n    # 3) Livingston Real\n    l_r_input = tf.keras.layers.Input(shape=[TARGET_HEIGHT, TARGET_WIDTH], dtype=tf.float32, name='x_l_r')\n    # 4) Livingston Imaginary\n    l_i_input = tf.keras.layers.Input(shape=[TARGET_HEIGHT, TARGET_WIDTH], dtype=tf.float32, name='x_l_i')\n    \n    # Get embedding from CNN\n    h_r_embed = get_branch(h_r_input, 'h_r')\n    h_i_embed = get_branch(h_i_input, 'h_i')\n    l_r_embed = get_branch(l_r_input, 'l_r')\n    l_i_embed = get_branch(l_i_input, 'l_i')\n    \n    # Concatenate embeddings\n    x = tf.keras.layers.Concatenate()([h_r_embed, h_i_embed, l_r_embed, l_i_embed])\n    x = tf.keras.layers.Dropout(0.30)(x)\n    x = tf.keras.layers.Dense(128)(x)\n    x = tf.keras.layers.Dropout(0.20)(x)\n    # Target prediction in range [0,1] with sigmoid activation\n    output = tf.keras.layers.Dense(1, activation='sigmoid')(x)\n    \n    # Model\n    inputs = [h_r_input, h_i_input, l_r_input, l_i_input]\n    model = tf.keras.models.Model(inputs=inputs, outputs=output)\n    \n    # LOSS\n    loss = tf.keras.losses.BinaryCrossentropy(from_logits=False)\n\n    # OPTIMIZER\n    optimizer = tfa.optimizers.AdamW(learning_rate=5e-5, weight_decay=5e-7, epsilon=1e-5)\n\n    # METRICS\n    metrics = [\n        tf.keras.metrics.BinaryAccuracy(),\n        tf.keras.metrics.Precision(),\n        tf.keras.metrics.Recall(),\n        tf.keras.metrics.AUC(),\n    ]\n\n    model.compile(optimizer=optimizer, loss=loss, metrics=metrics)\n    \n    return model","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:06:59.323236Z","iopub.execute_input":"2022-10-07T11:06:59.323544Z","iopub.status.idle":"2022-10-07T11:06:59.339763Z","shell.execute_reply.started":"2022-10-07T11:06:59.323517Z","shell.execute_reply":"2022-10-07T11:06:59.338752Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tf.keras.backend.clear_session()\ngc.collect()\n\nmodel = get_model()","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:06:59.341439Z","iopub.execute_input":"2022-10-07T11:06:59.341894Z","iopub.status.idle":"2022-10-07T11:07:11.532444Z","shell.execute_reply.started":"2022-10-07T11:06:59.34186Z","shell.execute_reply":"2022-10-07T11:07:11.531446Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 4 small CNN's are used, keeping model size low\nprint(model.summary())","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:07:11.533956Z","iopub.execute_input":"2022-10-07T11:07:11.53431Z","iopub.status.idle":"2022-10-07T11:07:11.581376Z","shell.execute_reply.started":"2022-10-07T11:07:11.534275Z","shell.execute_reply":"2022-10-07T11:07:11.580415Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Model Architecture\ntf.keras.utils.plot_model(model, show_shapes=True, show_dtype=True, show_layer_names=True, expand_nested=False)","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:07:11.58288Z","iopub.execute_input":"2022-10-07T11:07:11.585638Z","iopub.status.idle":"2022-10-07T11:07:12.655496Z","shell.execute_reply.started":"2022-10-07T11:07:11.5856Z","shell.execute_reply":"2022-10-07T11:07:12.654349Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Weight Initialization Test","metadata":{}},{"cell_type":"code","source":"# Validation Baseline\n_ = model.evaluate(\n        get_val_dataset(IDXS_val),\n        steps=N_VAL_SAMPLES,\n        verbose=1,\n    )","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:07:12.65692Z","iopub.execute_input":"2022-10-07T11:07:12.657293Z","iopub.status.idle":"2022-10-07T11:07:59.286729Z","shell.execute_reply.started":"2022-10-07T11:07:12.657253Z","shell.execute_reply":"2022-10-07T11:07:59.285689Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Training Output Baseline\ntrain_preds = model.predict(\n        get_val_dataset(IDXS_train),\n        steps=N_TRAIN_SAMPLES,\n        verbose=1,\n    ).squeeze()","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:07:59.288518Z","iopub.execute_input":"2022-10-07T11:07:59.289116Z","iopub.status.idle":"2022-10-07T11:14:24.583058Z","shell.execute_reply.started":"2022-10-07T11:07:59.289073Z","shell.execute_reply":"2022-10-07T11:14:24.582026Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Make sure model correctly initialized\n# With a sigmoid activation large negative/positive prediction make gradient small in sigmoid activation and causes training to be hard\n# Correct initialization should be around 0.50, to have large gradients when training start, take a look at the sigmoid function to understand this\nplt.figure(figsize=(15,8))\nplt.title(f'Train Prediction Initialized Model')\npd.Series(train_preds).plot(kind='hist')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:14:24.585978Z","iopub.execute_input":"2022-10-07T11:14:24.586682Z","iopub.status.idle":"2022-10-07T11:14:24.905231Z","shell.execute_reply.started":"2022-10-07T11:14:24.586644Z","shell.execute_reply":"2022-10-07T11:14:24.904337Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training","metadata":{}},{"cell_type":"code","source":"# Train Model\nhistory = model.fit(\n        get_train_dataset(IDXS_train, BATCH_SIZE),\n        steps_per_epoch=N_TRAIN_SAMPLES // BATCH_SIZE,\n        validation_data=get_val_dataset(IDXS_val),\n        validation_steps=N_VAL_SAMPLES,\n        epochs = EPOCHS,\n        verbose = 1 if IS_INTERACTIVE else 2,\n        callbacks = [],\n    )\n\nmodel.save_weights('model.h5')","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:14:24.908996Z","iopub.execute_input":"2022-10-07T11:14:24.909276Z","iopub.status.idle":"2022-10-07T11:16:22.565681Z","shell.execute_reply.started":"2022-10-07T11:14:24.90925Z","shell.execute_reply":"2022-10-07T11:16:22.564637Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training History","metadata":{}},{"cell_type":"code","source":"def plot_history_metric(metric, f_best=np.argmax, ylim=None, yscale=None, yticks=None):\n    plt.figure(figsize=(20, 10))\n    \n    values = history.history[metric]\n    N_EPOCHS = len(values)\n    val = 'val' in ''.join(history.history.keys())\n    # Epoch Ticks\n    if N_EPOCHS <= 20:\n        x = np.arange(1, N_EPOCHS + 1)\n    else:\n        x = [1, 5] + [10 + 5 * idx for idx in range((N_EPOCHS - 10) // 5 + 1)]\n\n    x_ticks = np.arange(1, N_EPOCHS+1)\n\n    # Validation\n    if val:\n        val_values = history.history[f'val_{metric}']\n        val_argmin = f_best(val_values)\n        plt.plot(x_ticks, val_values, label=f'val')\n\n    # summarize history for accuracy\n    plt.plot(x_ticks, values, label=f'train')\n    argmin = f_best(values)\n    plt.scatter(argmin + 1, values[argmin], color='red', s=75, marker='o', label=f'train_best')\n    if val:\n        plt.scatter(val_argmin + 1, val_values[val_argmin], color='purple', s=75, marker='o', label=f'val_best')\n\n    plt.title(f'Model {metric}', fontsize=24, pad=10)\n    plt.ylabel(metric, fontsize=20, labelpad=10)\n\n    if ylim:\n        plt.ylim(ylim)\n\n    if yscale is not None:\n        plt.yscale(yscale)\n        \n    if yticks is not None:\n        plt.yticks(yticks, fontsize=16)\n\n    plt.xlabel('epoch', fontsize=20, labelpad=10)        \n    plt.tick_params(axis='x', labelsize=8)\n    plt.xticks(x, fontsize=16) # set tick step to 1 and let x axis start at 1\n    plt.yticks(fontsize=16)\n    \n    plt.legend(prop={'size': 10})\n    plt.grid()\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:16:22.569183Z","iopub.execute_input":"2022-10-07T11:16:22.570487Z","iopub.status.idle":"2022-10-07T11:16:22.586019Z","shell.execute_reply.started":"2022-10-07T11:16:22.570443Z","shell.execute_reply":"2022-10-07T11:16:22.585143Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Show Training Metric Histories\n# The model can fit the training data well, validation metrics are however random guessing performance...\nfor metric in model.metrics:\n    plot_history_metric(metric.name)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-10-07T11:16:22.587266Z","iopub.execute_input":"2022-10-07T11:16:22.587742Z","iopub.status.idle":"2022-10-07T11:16:24.365754Z","shell.execute_reply.started":"2022-10-07T11:16:22.587706Z","shell.execute_reply":"2022-10-07T11:16:24.364809Z"},"trusted":true},"execution_count":null,"outputs":[]}]}