{"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"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":21154,"databundleVersionId":1243559,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# Petals — LB PUSH <= 180 min (P100)\n# - 2x EfficientNetV2-S seeds\n# - Train on TRAIN+VAL, keep small internal holdout (id-hash) ONLY for val monitoring\n# - Same strong schedule as your best run: 2 frozen + 20 finetune, last 4 w/o mix, no smoothing\n# - Strong TTA (8 views)\n# - Optional 1-epoch \"polish\" on full train+val at tiny LR (cheap, often helps LB)\n# ============================================================\n\nimport os, random\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.applications import efficientnet_v2\n\ntf.keras.backend.clear_session()\nAUTO = tf.data.AUTOTUNE\nNUM_CLASSES = 104\nTFREC_SIZE = 512\n\nIMG_SIZE = 380\nBATCH_SIZE = 64\n\n# ---- internal holdout for monitoring only ----\nVAL_PCT = 5              # 5% holdout; try 3..5\n\n# ---- schedule (close to your best) ----\nMIXCUT = True\nEPOCHS_P1 = 2\nEPOCHS_P2 = 20\nLAST_EPOCHS_NO_MIX = 4   # IMPORTANT: keep like your best\nLABEL_SMOOTH = 0.05\nBASE_LR = 1.2e-4\nWD = 2e-4\nEMA_DECAY = 0.999\n\n# ---- time control ----\n# Keep steps as in your log (199) so runtime stays predictable under 180 minutes.\nSTEPS_PER_EPOCH = 199\n\n# optional final polish\nDO_POLISH = True\nPOLISH_EPOCHS = 1\nPOLISH_LR = 5e-6\n\nTRAIN_SPECS = [\n    {\"seed\": 42,  \"img\": IMG_SIZE, \"batch\": BATCH_SIZE},\n    {\"seed\": 123, \"img\": IMG_SIZE, \"batch\": BATCH_SIZE},\n]\n\n# ---------- paths ----------\ndef build_paths(tfrec_size=TFREC_SIZE):\n    base = f\"/kaggle/input/tpu-getting-started/tfrecords-jpeg-{tfrec_size}x{tfrec_size}\"\n    if os.path.exists(base):\n        return (os.path.join(base, \"train\", \"*.tfrec\"),\n                os.path.join(base, \"val\",   \"*.tfrec\"),\n                os.path.join(base, \"test\",  \"*.tfrec\"))\n    base = \"/kaggle/input/tpu-getting-started/tfrecords-jpeg-224x224\"\n    return (os.path.join(base, \"train\", \"*.tfrec\"),\n            os.path.join(base, \"val\",   \"*.tfrec\"),\n            os.path.join(base, \"test\",  \"*.tfrec\"))\n\nTRAIN_GLOB, VAL_GLOB, TEST_GLOB = build_paths(TFREC_SIZE)\nTRAINVAL_GLOBS = [TRAIN_GLOB, VAL_GLOB]\n\n# ---------- parsing ----------\ndef _parse_with_id(ex, img_size):\n    ex = tf.io.parse_single_example(ex, {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64),\n        \"id\":    tf.io.FixedLenFeature([], tf.string),\n    })\n    img = tf.image.decode_jpeg(ex[\"image\"], channels=3)\n    img = tf.image.convert_image_dtype(img, tf.float32)  # [0,1]\n    img = tf.image.resize(img, (img_size, img_size))\n    y = tf.one_hot(tf.cast(ex[\"class\"], tf.int32), NUM_CLASSES)\n    return img, y, ex[\"id\"]\n\ndef _parse_test(ex, img_size):\n    ex = tf.io.parse_single_example(ex, {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"id\":    tf.io.FixedLenFeature([], tf.string),\n    })\n    img = tf.image.decode_jpeg(ex[\"image\"], channels=3)\n    img = tf.image.convert_image_dtype(img, tf.float32)\n    img = tf.image.resize(img, (img_size, img_size))\n    return img, ex[\"id\"]\n\n# ---------- holdout split ----------\ndef is_holdout_id(id_):\n    bucket = tf.strings.to_hash_bucket_fast(id_, 100)\n    return bucket < VAL_PCT\n\n# ---------- augment (keep your original-style strong but safe aug) ----------\ndef aug_train(img01):\n    img01 = tf.image.random_brightness(img01, 0.10)\n    img01 = tf.image.random_contrast(img01, 0.85, 1.15)\n    img01 = tf.image.random_saturation(img01, 0.85, 1.15)\n    img01 = tf.image.random_flip_left_right(img01)\n\n    h = tf.shape(img01)[0]; w = tf.shape(img01)[1]\n    max_shift = tf.cast(tf.round(0.07 * tf.cast(tf.minimum(h, w), tf.float32)), tf.int32)\n    max_shift = tf.maximum(max_shift, 0)\n\n    dx = tf.cond(max_shift > 0,\n                 lambda: tf.random.uniform([], -max_shift, max_shift + 1, dtype=tf.int32),\n                 lambda: tf.constant(0, tf.int32))\n    dy = tf.cond(max_shift > 0,\n                 lambda: tf.random.uniform([], -max_shift, max_shift + 1, dtype=tf.int32),\n                 lambda: tf.constant(0, tf.int32))\n    img01 = tf.roll(img01, shift=[dy, dx], axis=[0, 1])\n\n    scale = tf.random.uniform([], 0.90, 1.10)\n    nh = tf.cast(tf.round(scale * tf.cast(h, tf.float32)), tf.int32)\n    nw = tf.cast(tf.round(scale * tf.cast(w, tf.float32)), tf.int32)\n    nh = tf.maximum(nh, 1); nw = tf.maximum(nw, 1)\n    img01 = tf.image.resize(img01, (nh, nw))\n    img01 = tf.image.resize_with_pad(img01, h, w)\n\n    return tf.clip_by_value(img01, 0.0, 1.0)\n\n# ---------- Beta w/o tfp ----------\ndef sample_beta(shape, alpha):\n    g1 = tf.random.gamma(shape, alpha, dtype=tf.float32)\n    g2 = tf.random.gamma(shape, alpha, dtype=tf.float32)\n    return g1 / (g1 + g2)\n\n# ---------- MixUp/CutMix (per-sample) ----------\ndef mixup_batch(x, y, alpha=0.4):\n    b = tf.shape(x)[0]\n    idx = tf.random.shuffle(tf.range(b))\n    x2, y2 = tf.gather(x, idx), tf.gather(y, idx)\n\n    lam = sample_beta([b], alpha)\n    lam_x = tf.reshape(lam, [b, 1, 1, 1])\n    lam_y = tf.reshape(lam, [b, 1])\n\n    x = x * lam_x + x2 * (1.0 - lam_x)\n    y = y * lam_y + y2 * (1.0 - lam_y)\n    return x, y\n\ndef rand_bbox(img_h, img_w, lam):\n    cut_rat = tf.sqrt(1.0 - lam)\n    cut_w = tf.cast(tf.round(tf.cast(img_w, tf.float32) * cut_rat), tf.int32)\n    cut_h = tf.cast(tf.round(tf.cast(img_h, tf.float32) * cut_rat), tf.int32)\n\n    cx = tf.random.uniform([], 0, img_w, dtype=tf.int32)\n    cy = tf.random.uniform([], 0, img_h, dtype=tf.int32)\n\n    x1 = tf.clip_by_value(cx - cut_w // 2, 0, img_w)\n    y1 = tf.clip_by_value(cy - cut_h // 2, 0, img_h)\n    x2 = tf.clip_by_value(cx + cut_w // 2, 0, img_w)\n    y2 = tf.clip_by_value(cy + cut_h // 2, 0, img_h)\n    return x1, y1, x2, y2\n\ndef cutmix_batch(x, y, alpha=1.0):\n    b = tf.shape(x)[0]\n    h = tf.shape(x)[1]\n    w = tf.shape(x)[2]\n\n    idx = tf.random.shuffle(tf.range(b))\n    x2, y2 = tf.gather(x, idx), tf.gather(y, idx)\n\n    lam = sample_beta([b], alpha)\n\n    def _one(i):\n        xi, yi = x[i], y[i]\n        xj, yj = x2[i], y2[i]\n        li = tf.clip_by_value(lam[i], 1e-3, 1.0 - 1e-3)\n        x1, y1, x2b, y2b = rand_bbox(h, w, li)\n\n        patch = xj[y1:y2b, x1:x2b, :]\n        pad_left, pad_right = x1, w - x2b\n        pad_top, pad_bottom = y1, h - y2b\n        patch = tf.pad(patch, [[pad_top, pad_bottom],[pad_left, pad_right],[0,0]])\n\n        mask = tf.ones([y2b-y1, x2b-x1, 1], tf.float32)\n        mask = tf.pad(mask, [[pad_top, pad_bottom],[pad_left, pad_right],[0,0]])\n        mask = tf.clip_by_value(mask, 0.0, 1.0)\n\n        x_new = xi * (1.0 - mask) + patch * mask\n        area = tf.cast((x2b-x1)*(y2b-y1), tf.float32)\n        lam_adj = 1.0 - area / tf.cast(h*w, tf.float32)\n        y_new = yi * lam_adj + yj * (1.0 - lam_adj)\n        return x_new, y_new\n\n    xs, ys = tf.map_fn(_one, tf.range(b), fn_output_signature=(tf.float32, tf.float32))\n    return xs, ys\n\ndef mixcut_batch(x, y, p_cutmix=0.5, mixup_alpha=0.4, cutmix_alpha=1.0):\n    r = tf.random.uniform([])\n    return tf.cond(\n        r < p_cutmix,\n        lambda: cutmix_batch(x, y, alpha=cutmix_alpha),\n        lambda: mixup_batch(x, y, alpha=mixup_alpha)\n    )\n\n# ---------- preprocess ----------\ndef preprocess_effv2(img01):\n    return efficientnet_v2.preprocess_input(img01 * 255.0)\n\n# ---------- datasets ----------\ndef make_train_ds(globs, img_size, batch, seed=42, use_mixcut=True):\n    files = []\n    for g in globs:\n        files += tf.io.gfile.glob(g)\n\n    opts = tf.data.Options()\n    opts.experimental_deterministic = False\n\n    ds = tf.data.TFRecordDataset(files, num_parallel_reads=AUTO).with_options(opts)\n    ds = ds.shuffle(8192, seed=seed, reshuffle_each_iteration=True)\n    ds = ds.repeat()\n\n    ds = ds.map(lambda ex: _parse_with_id(ex, img_size), num_parallel_calls=AUTO)\n    ds = ds.filter(lambda img, y, id_: tf.logical_not(is_holdout_id(id_)))\n    ds = ds.map(lambda img, y, id_: (aug_train(img), y), num_parallel_calls=AUTO)\n\n    ds = ds.batch(batch, drop_remainder=True)\n    if use_mixcut:\n        ds = ds.map(lambda x, y: mixcut_batch(x, y, 0.5, 0.4, 1.0), num_parallel_calls=AUTO)\n    ds = ds.map(lambda x, y: (preprocess_effv2(x), y), num_parallel_calls=AUTO)\n    return ds.prefetch(AUTO)\n\ndef make_holdout_ds(globs, img_size, batch):\n    files = []\n    for g in globs:\n        files += tf.io.gfile.glob(g)\n\n    ds = tf.data.TFRecordDataset(files, num_parallel_reads=AUTO)\n    ds = ds.map(lambda ex: _parse_with_id(ex, img_size), num_parallel_calls=AUTO)\n    ds = ds.filter(lambda img, y, id_: is_holdout_id(id_))\n    ds = ds.map(lambda img, y, id_: (preprocess_effv2(img), y), num_parallel_calls=AUTO)\n    return ds.batch(batch).prefetch(AUTO)\n\ndef make_full_plain_ds(globs, img_size, batch, seed=42):\n    # for POLISH: use ALL data (train+val), no holdout, no aug\n    files = []\n    for g in globs:\n        files += tf.io.gfile.glob(g)\n\n    ds = tf.data.TFRecordDataset(files, num_parallel_reads=AUTO)\n    ds = ds.shuffle(8192, seed=seed, reshuffle_each_iteration=True)\n    ds = ds.repeat()\n    ds = ds.map(lambda ex: _parse_with_id(ex, img_size), num_parallel_calls=AUTO)\n    ds = ds.map(lambda img, y, id_: (preprocess_effv2(img), y), num_parallel_calls=AUTO)\n    ds = ds.batch(batch, drop_remainder=True).prefetch(AUTO)\n    return ds\n\ndef make_test_ds_raw(test_glob, img_size, batch):\n    files = tf.io.gfile.glob(test_glob)\n    ds = tf.data.TFRecordDataset(files, num_parallel_reads=AUTO)\n    ds = ds.map(lambda ex: _parse_test(ex, img_size), num_parallel_calls=AUTO)\n    return ds.batch(batch).prefetch(AUTO)\n\n# ---------- model ----------\ndef build_effv2s(img_size, dropout=0.5):\n    inp = layers.Input((img_size, img_size, 3))\n    base = efficientnet_v2.EfficientNetV2S(include_top=False, weights=\"imagenet\", input_tensor=inp)\n    x = layers.GlobalAveragePooling2D()(base.output)\n    x = layers.Dropout(dropout)(x)\n    out = layers.Dense(NUM_CLASSES, activation=\"softmax\", dtype=\"float32\")(x)\n    return models.Model(inp, out), base\n\ndef freeze_bn(model):\n    for layer in model.layers:\n        if isinstance(layer, tf.keras.layers.BatchNormalization):\n            layer.trainable = False\n\n# ---------- EMA ----------\nclass EMA(tf.keras.callbacks.Callback):\n    def __init__(self, decay=0.999, save_path=\"ema.weights.h5\"):\n        super().__init__()\n        self.decay = decay\n        self.save_path = save_path\n        self.shadow = None\n\n    def on_train_begin(self, logs=None):\n        vars_ = self.model.trainable_variables\n        self.shadow = [tf.identity(tf.cast(v, tf.float32)) for v in vars_]\n\n    def on_train_batch_end(self, batch, logs=None):\n        vars_ = self.model.trainable_variables\n        for i, v in enumerate(vars_):\n            self.shadow[i] = self.decay * self.shadow[i] + (1.0 - self.decay) * tf.cast(v, tf.float32)\n\n    def on_train_end(self, logs=None):\n        vars_ = self.model.trainable_variables\n        backup = [tf.identity(v) for v in vars_]\n        for v, s in zip(vars_, self.shadow):\n            v.assign(tf.cast(s, v.dtype))\n        self.model.save_weights(self.save_path)\n        for v, b in zip(vars_, backup):\n            v.assign(b)\n\n# ---------- LR schedule ----------\nclass WarmCos(tf.keras.optimizers.schedules.LearningRateSchedule):\n    def __init__(self, base_lr, total_steps, warm_steps, min_lr=1e-6):\n        super().__init__()\n        self.base_lr = tf.constant(base_lr, tf.float32)\n        self.total_steps = tf.constant(total_steps, tf.float32)\n        self.warm_steps = tf.constant(warm_steps, tf.float32)\n        self.min_lr = tf.constant(min_lr, tf.float32)\n\n    def __call__(self, step):\n        step = tf.cast(step, tf.float32)\n        warm = self.base_lr * (step / tf.maximum(self.warm_steps, 1.0))\n        t = tf.clip_by_value(\n            (step - self.warm_steps) / tf.maximum(self.total_steps - self.warm_steps, 1.0),\n            0.0, 1.0\n        )\n        cos = self.min_lr + 0.5 * (self.base_lr - self.min_lr) * (1.0 + tf.cos(np.pi * t))\n        return tf.where(step < self.warm_steps, warm, cos)\n\n# ---------- train one ----------\ndef train_one(seed, img_size, batch):\n    tf.keras.backend.clear_session()\n    random.seed(seed); np.random.seed(seed); tf.random.set_seed(seed)\n\n    tf.keras.mixed_precision.set_global_policy(\"mixed_float16\")\n    for g in tf.config.list_physical_devices(\"GPU\"):\n        try:\n            tf.config.experimental.set_memory_growth(g, True)\n        except:\n            pass\n\n    print(f\"\\n==== Train effv2s seed={seed} img={img_size} batch={batch} (train+val, holdout={VAL_PCT}%) ====\")\n\n    train_ds_mix   = make_train_ds(TRAINVAL_GLOBS, img_size, batch, seed=seed, use_mixcut=True)\n    train_ds_plain = make_train_ds(TRAINVAL_GLOBS, img_size, batch, seed=seed, use_mixcut=False)\n    holdout_ds     = make_holdout_ds(TRAINVAL_GLOBS, img_size, batch)\n\n    model, base = build_effv2s(img_size, dropout=0.5)\n\n    ckpt_path = f\"best_effv2s_{img_size}_seed{seed}.weights.h5\"\n    ema_path  = f\"ema_effv2s_{img_size}_seed{seed}.weights.h5\"\n    final_path = f\"final_effv2s_{img_size}_seed{seed}.weights.h5\"\n\n    ckpt = tf.keras.callbacks.ModelCheckpoint(\n        ckpt_path, monitor=\"val_accuracy\", mode=\"max\",\n        save_best_only=True, save_weights_only=True, verbose=1\n    )\n    es = tf.keras.callbacks.EarlyStopping(\n        monitor=\"val_accuracy\", mode=\"max\",\n        patience=6, restore_best_weights=False, verbose=1\n    )\n\n    # Phase 1: frozen\n    base.trainable = False\n    opt1 = tf.keras.optimizers.AdamW(learning_rate=3e-3, weight_decay=1e-4)\n    loss1 = tf.keras.losses.CategoricalCrossentropy(label_smoothing=LABEL_SMOOTH)\n    model.compile(optimizer=opt1, loss=loss1, metrics=[\"accuracy\"])\n    model.fit(\n        train_ds_mix,\n        steps_per_epoch=STEPS_PER_EPOCH,\n        validation_data=holdout_ds,\n        epochs=EPOCHS_P1,\n        callbacks=[ckpt],\n        verbose=2\n    )\n\n    # Phase 2: finetune\n    base.trainable = True\n    freeze_bn(base)\n\n    total_steps = STEPS_PER_EPOCH * EPOCHS_P2\n    warm_steps = STEPS_PER_EPOCH * 1\n    sched = WarmCos(base_lr=BASE_LR, total_steps=total_steps, warm_steps=warm_steps, min_lr=1e-6)\n    opt2 = tf.keras.optimizers.AdamW(learning_rate=sched, weight_decay=WD, global_clipnorm=1.0)\n    ema_cb = EMA(decay=EMA_DECAY, save_path=ema_path)\n\n    epochs_mix = max(0, EPOCHS_P2 - LAST_EPOCHS_NO_MIX)\n\n    # mostly with mix + smoothing\n    if epochs_mix > 0:\n        loss2 = tf.keras.losses.CategoricalCrossentropy(label_smoothing=LABEL_SMOOTH)\n        model.compile(optimizer=opt2, loss=loss2, metrics=[\"accuracy\"])\n        model.fit(\n            train_ds_mix,\n            steps_per_epoch=STEPS_PER_EPOCH,\n            validation_data=holdout_ds,\n            epochs=epochs_mix,\n            callbacks=[ckpt, es, ema_cb],\n            verbose=2\n        )\n\n    # last epochs without mix, NO smoothing (like your best run)\n    if LAST_EPOCHS_NO_MIX > 0:\n        loss3 = tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.0)\n        model.compile(optimizer=opt2, loss=loss3, metrics=[\"accuracy\"])\n        model.fit(\n            train_ds_plain,\n            steps_per_epoch=STEPS_PER_EPOCH,\n            validation_data=holdout_ds,\n            epochs=EPOCHS_P2,\n            initial_epoch=epochs_mix,\n            callbacks=[ckpt, es, ema_cb],\n            verbose=2\n        )\n\n    # choose best between ckpt and ema by holdout\n    model.load_weights(ckpt_path)\n    acc_ckpt = model.evaluate(holdout_ds, verbose=0)[1]\n    model.load_weights(ema_path)\n    acc_ema = model.evaluate(holdout_ds, verbose=0)[1]\n\n    best_path = ckpt_path if acc_ckpt >= acc_ema else ema_path\n    print(f\"[seed={seed}] pick={'CKPT' if best_path==ckpt_path else 'EMA'} | ckpt={acc_ckpt:.5f} ema={acc_ema:.5f}\")\n\n    # optional polish: train 1 epoch on ALL train+val with tiny LR, no mix, no aug\n    if DO_POLISH:\n        full_plain = make_full_plain_ds(TRAINVAL_GLOBS, img_size, batch, seed=seed)\n        model.load_weights(best_path)\n        opt3 = tf.keras.optimizers.AdamW(learning_rate=POLISH_LR, weight_decay=0.0, global_clipnorm=1.0)\n        model.compile(optimizer=opt3,\n                      loss=tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.0),\n                      metrics=[\"accuracy\"])\n        model.fit(full_plain, steps_per_epoch=STEPS_PER_EPOCH, epochs=POLISH_EPOCHS, verbose=2)\n        model.save_weights(final_path)\n        best_path = final_path\n        print(f\"[seed={seed}] polished -> {final_path}\")\n\n    return {\"seed\": seed, \"img\": img_size, \"batch\": batch, \"best_path\": best_path}\n\n# ---------- TTA (STRONG 8 views like your original) ----------\ndef _resize_to(x, sz):\n    return tf.image.resize(x, (sz, sz))\n\ndef tta_log_probs_raw(model, xb_raw01, img_size):\n    views = []\n\n    def add(v):\n        v = _resize_to(v, img_size)\n        views.append(preprocess_effv2(v))\n\n    add(xb_raw01)\n    add(tf.image.flip_left_right(xb_raw01))\n    add(tf.image.rot90(xb_raw01, 1))\n    add(tf.image.rot90(xb_raw01, 3))\n\n    c95 = tf.image.central_crop(xb_raw01, 0.95)\n    c90 = tf.image.central_crop(xb_raw01, 0.90)\n    add(c95); add(tf.image.flip_left_right(c95))\n    add(c90); add(tf.image.flip_left_right(c90))\n\n    acc = None\n    for v in views:\n        p = model.predict(v, verbose=0)\n        p = np.clip(p, 1e-7, 1.0)\n        lg = np.log(p)\n        acc = lg if acc is None else (acc + lg)\n    return acc / len(views)\n\n# ---------- train all ----------\ntrained = []\nfor s in TRAIN_SPECS:\n    trained.append(train_one(seed=s[\"seed\"], img_size=s[\"img\"], batch=s[\"batch\"]))\n\nprint(\"Ensemble members:\", [(m[\"seed\"], m[\"img\"]) for m in trained])\n\n# ---------- cache test ids ----------\ntest_ds_ids = make_test_ds_raw(TEST_GLOB, IMG_SIZE, batch=64)\nall_ids = []\nfor _, ib in test_ds_ids:\n    all_ids.extend([b.numpy().decode(\"utf-8\") for b in ib])\n\n# ---------- ensemble inference ----------\nensemble_logits = None\n\nfor m in trained:\n    img_size = m[\"img\"]\n    tf.keras.backend.clear_session()\n    tf.keras.mixed_precision.set_global_policy(\"mixed_float16\")\n\n    model, _ = build_effv2s(img_size, dropout=0.0)  # dropout off\n    model.load_weights(m[\"best_path\"])\n\n    test_ds = make_test_ds_raw(TEST_GLOB, img_size, batch=64)\n\n    logits_list = []\n    ids_check = []\n    for xb_raw, ib in test_ds:\n        lg = tta_log_probs_raw(model, xb_raw, img_size)\n        logits_list.append(lg)\n        ids_check.extend([b.numpy().decode(\"utf-8\") for b in ib])\n\n    logits = np.vstack(logits_list)\n    if ids_check != all_ids:\n        raise RuntimeError(\"Test IDs order mismatch.\")\n\n    ensemble_logits = logits if ensemble_logits is None else (ensemble_logits + logits)\n\nensemble_logits /= len(trained)\nlabels = np.argmax(ensemble_logits, axis=1).astype(int)\n\nsub = pd.DataFrame({\"id\": all_ids, \"label\": labels})\nsub.to_csv(\"submission.csv\", index=False)\nprint(sub.head(), \"\\nSaved to: submission.csv\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null}]}