{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.12"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":21154,"databundleVersionId":1243559}],"dockerImageVersionId":31286,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":5536.456905,"end_time":"2026-02-23T21:57:03.266977","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2026-02-23T20:24:46.810072","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"0400ab2a","cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras import applications","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.status.busy":"2026-02-24T10:06:36.894023Z","iopub.execute_input":"2026-02-24T10:06:36.894292Z","iopub.status.idle":"2026-02-24T10:07:02.725814Z","shell.execute_reply.started":"2026-02-24T10:06:36.894267Z","shell.execute_reply":"2026-02-24T10:07:02.725208Z"},"papermill":{"duration":25.691487,"end_time":"2026-02-23T20:25:14.961606","exception":false,"start_time":"2026-02-23T20:24:49.270119","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"41103bb2","cell_type":"code","source":"AUTO = tf.data.AUTOTUNE\nIMG_SIZE = 331\nBATCH_SIZE = 16\nEPOCHS_STAGE1 = 6\nEPOCHS_STAGE2 = 12\nNUM_CLASSES = 104\nSEED = 42\n\nGCS_PATH = \"/kaggle/input/competitions/tpu-getting-started/tfrecords-jpeg-331x331\"\nTRAIN_GLOB = f\"{GCS_PATH}/train/*.tfrec\"\nVAL_GLOB = f\"{GCS_PATH}/val/*.tfrec\"\nTEST_GLOB = f\"{GCS_PATH}/test/*.tfrec\"\nSAMPLE_SUBMISSION_PATH = \"/kaggle/input/competitions/tpu-getting-started/sample_submission.csv\"","metadata":{"execution":{"iopub.status.busy":"2026-02-24T10:09:01.295522Z","iopub.execute_input":"2026-02-24T10:09:01.295851Z","iopub.status.idle":"2026-02-24T10:09:01.300320Z","shell.execute_reply.started":"2026-02-24T10:09:01.295823Z","shell.execute_reply":"2026-02-24T10:09:01.299548Z"},"papermill":{"duration":0.009886,"end_time":"2026-02-23T20:25:14.975110","exception":false,"start_time":"2026-02-23T20:25:14.965224","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"0d242dcf","cell_type":"code","source":"strategy = tf.distribute.get_strategy()\ngpus = tf.config.list_physical_devices(\"GPU\")\nif gpus:\n    print(\"Используется GPU:\", [gpu.name for gpu in gpus])\nelse:\n    print(\"GPU не найден, используется CPU\")\nprint(\"Реплик в стратегии:\", strategy.num_replicas_in_sync)","metadata":{"execution":{"iopub.status.busy":"2026-02-24T10:09:09.693470Z","iopub.execute_input":"2026-02-24T10:09:09.693810Z","iopub.status.idle":"2026-02-24T10:09:09.698616Z","shell.execute_reply.started":"2026-02-24T10:09:09.693784Z","shell.execute_reply":"2026-02-24T10:09:09.698049Z"},"papermill":{"duration":0.817494,"end_time":"2026-02-23T20:25:15.795765","exception":false,"start_time":"2026-02-23T20:25:14.978271","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"d344df8e","cell_type":"code","source":"tf.random.set_seed(SEED)\nnp.random.seed(SEED)\n\nprint(f\"TensorFlow {tf.__version__}, Keras {keras.__version__}\")\nprint(\n    f\"IMG_SIZE={IMG_SIZE}, BATCH_SIZE={BATCH_SIZE}, \"\n    f\"EPOCHS_STAGE1={EPOCHS_STAGE1}, EPOCHS_STAGE2={EPOCHS_STAGE2}, \"\n    f\"NUM_CLASSES={NUM_CLASSES}\"\n)","metadata":{"execution":{"iopub.status.busy":"2026-02-24T10:09:15.296450Z","iopub.execute_input":"2026-02-24T10:09:15.296748Z","iopub.status.idle":"2026-02-24T10:09:15.302617Z","shell.execute_reply.started":"2026-02-24T10:09:15.296723Z","shell.execute_reply":"2026-02-24T10:09:15.301594Z"},"papermill":{"duration":0.010003,"end_time":"2026-02-23T20:25:15.809208","exception":false,"start_time":"2026-02-23T20:25:15.799205","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"ec56ea4f","cell_type":"code","source":"FEATURES_TRAIN = {\n    \"id\": tf.io.FixedLenFeature([], tf.string),\n    \"class\": tf.io.FixedLenFeature([], tf.int64),\n    \"image\": tf.io.FixedLenFeature([], tf.string),\n}\nFEATURES_TEST = {\n    \"id\": tf.io.FixedLenFeature([], tf.string),\n    \"image\": tf.io.FixedLenFeature([], tf.string),\n}","metadata":{"execution":{"iopub.status.busy":"2026-02-24T10:09:29.260854Z","iopub.execute_input":"2026-02-24T10:09:29.261431Z","iopub.status.idle":"2026-02-24T10:09:29.265585Z","shell.execute_reply.started":"2026-02-24T10:09:29.261402Z","shell.execute_reply":"2026-02-24T10:09:29.264818Z"},"papermill":{"duration":0.009346,"end_time":"2026-02-23T20:25:15.821782","exception":false,"start_time":"2026-02-23T20:25:15.812436","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"910f5626","cell_type":"code","source":"def decode_image(img_bytes: tf.Tensor) -> tf.Tensor:\n    img = tf.io.decode_jpeg(img_bytes, channels=3)\n    img = tf.image.resize(img, [IMG_SIZE, IMG_SIZE], method=\"bilinear\")\n    img = tf.cast(img, tf.float32)  # без деления на 255: EfficientNet сама нормализует\n    return img\n\n\ndef parse_train(example: tf.Tensor):\n    parsed = tf.io.parse_single_example(example, FEATURES_TRAIN)\n    image = decode_image(parsed[\"image\"])\n    label = tf.cast(parsed[\"class\"], tf.int32)\n    return image, label\n\n\ndef parse_test(example: tf.Tensor):\n    parsed = tf.io.parse_single_example(example, FEATURES_TEST)\n    image = decode_image(parsed[\"image\"])\n    sample_id = parsed[\"id\"]\n    return image, sample_id","metadata":{"execution":{"iopub.status.busy":"2026-02-24T10:09:44.819524Z","iopub.execute_input":"2026-02-24T10:09:44.819817Z","iopub.status.idle":"2026-02-24T10:09:44.825930Z","shell.execute_reply.started":"2026-02-24T10:09:44.819792Z","shell.execute_reply":"2026-02-24T10:09:44.825193Z"},"papermill":{"duration":0.010215,"end_time":"2026-02-23T20:25:15.835234","exception":false,"start_time":"2026-02-23T20:25:15.825019","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"76557e6c","cell_type":"code","source":"train_files = sorted(tf.io.gfile.glob(TRAIN_GLOB))\nval_files = sorted(tf.io.gfile.glob(VAL_GLOB))\ntest_files = sorted(tf.io.gfile.glob(TEST_GLOB))\n\nds_train_raw = tf.data.TFRecordDataset(train_files, num_parallel_reads=AUTO)\nds_val_raw = tf.data.TFRecordDataset(val_files, num_parallel_reads=AUTO)\nds_test_raw = tf.data.TFRecordDataset(test_files, num_parallel_reads=AUTO)\n\nds_train = (\n    ds_train_raw.map(parse_train, num_parallel_calls=AUTO)\n    .shuffle(buffer_size=2048, seed=SEED)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)\n\nds_val = (\n    ds_val_raw.map(parse_train, num_parallel_calls=AUTO)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)\n\nds_test = (\n    ds_test_raw.map(parse_test, num_parallel_calls=AUTO)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)\n\nprint(\"Файлов train:\", len(train_files), \"val:\", len(val_files), \"test:\", len(test_files))\nprint(\"Датасеты созданы: ds_train, ds_val, ds_test\")","metadata":{"execution":{"iopub.status.busy":"2026-02-24T10:10:05.245760Z","iopub.execute_input":"2026-02-24T10:10:05.246380Z","iopub.status.idle":"2026-02-24T10:10:05.392537Z","shell.execute_reply.started":"2026-02-24T10:10:05.246348Z","shell.execute_reply":"2026-02-24T10:10:05.391828Z"},"papermill":{"duration":0.055156,"end_time":"2026-02-23T20:25:15.893598","exception":false,"start_time":"2026-02-23T20:25:15.838442","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"889eafd7","cell_type":"code","source":"class MacroF1(keras.metrics.Metric):\n    def __init__(self, num_classes: int, name: str = \"macro_f1\", **kwargs):\n        super().__init__(name=name, **kwargs)\n        self.num_classes = num_classes\n        self.confusion = self.add_weight(\n            shape=(num_classes, num_classes),\n            initializer=\"zeros\",\n            dtype=tf.float32,\n        )\n\n    def update_state(self, y_true, y_pred, sample_weight=None):\n        y_pred = tf.argmax(y_pred, axis=-1)\n        y_true = tf.reshape(tf.cast(y_true, tf.int32), [-1])\n        y_pred = tf.reshape(tf.cast(y_pred, tf.int32), [-1])\n        conf = tf.math.confusion_matrix(\n            y_true, y_pred, num_classes=self.num_classes, dtype=tf.float32\n        )\n        self.confusion.assign_add(conf)\n\n    def result(self):\n        tp = tf.linalg.diag_part(self.confusion)\n        fp = tf.reduce_sum(self.confusion, axis=0) - tp\n        fn = tf.reduce_sum(self.confusion, axis=1) - tp\n        f1_per_class = (2.0 * tp) / (2.0 * tp + fp + fn + 1e-7)\n        return tf.reduce_mean(f1_per_class)\n\n    def reset_state(self):\n        self.confusion.assign(tf.zeros((self.num_classes, self.num_classes)))\n\n    def get_config(self):\n        config = super().get_config()\n        config[\"num_classes\"] = self.num_classes\n        return config","metadata":{"execution":{"iopub.status.busy":"2026-02-24T10:10:16.049621Z","iopub.execute_input":"2026-02-24T10:10:16.050313Z","iopub.status.idle":"2026-02-24T10:10:16.057494Z","shell.execute_reply.started":"2026-02-24T10:10:16.050285Z","shell.execute_reply":"2026-02-24T10:10:16.056811Z"},"papermill":{"duration":0.012371,"end_time":"2026-02-23T20:25:18.148849","exception":false,"start_time":"2026-02-23T20:25:18.136478","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"d0246a94","cell_type":"code","source":"with strategy.scope():\n    data_augmentation = keras.Sequential(\n        [\n            layers.RandomFlip(\"horizontal\"),\n            layers.RandomRotation(0.1),\n            layers.RandomZoom(0.1),\n            layers.RandomContrast(0.2),\n        ],\n        name=\"data_augmentation\",\n    )\n\n    base_model = applications.EfficientNetB3(\n        include_top=False,\n        weights=\"imagenet\",\n        input_shape=(IMG_SIZE, IMG_SIZE, 3),\n        pooling=\"avg\",\n    )\n    base_model.trainable = False\n\n    preprocess_input = applications.efficientnet.preprocess_input\n\n    inputs = keras.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\n    x = data_augmentation(inputs)\n    x = preprocess_input(x)\n    x = base_model(x, training=False)\n    x = layers.Dense(512, activation=\"relu\")(x)\n    x = layers.Dropout(0.5)(x)\n    outputs = layers.Dense(NUM_CLASSES, activation=\"softmax\")(x)\n\n    model = keras.Model(inputs, outputs, name=\"flowers_efficientnetb3\")\n\n    model.compile(\n        optimizer=keras.optimizers.Adam(learning_rate=1e-3),\n        loss=keras.losses.SparseCategoricalCrossentropy(),\n        metrics=[\"accuracy\", MacroF1(num_classes=NUM_CLASSES)],\n    )","metadata":{"execution":{"iopub.status.busy":"2026-02-24T10:10:18.454871Z","iopub.execute_input":"2026-02-24T10:10:18.455627Z","iopub.status.idle":"2026-02-24T10:10:21.737684Z","shell.execute_reply.started":"2026-02-24T10:10:18.455598Z","shell.execute_reply":"2026-02-24T10:10:21.736845Z"},"papermill":{"duration":0.013992,"end_time":"2026-02-23T20:25:18.172785","exception":false,"start_time":"2026-02-23T20:25:18.158793","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"29e0ec91-4661-463c-ad90-97fce84c76b8","cell_type":"code","source":"model.summary()\nprint(\"моделька EfficientNetB3 создана\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-24T10:10:24.315923Z","iopub.execute_input":"2026-02-24T10:10:24.316246Z","iopub.status.idle":"2026-02-24T10:10:24.341795Z","shell.execute_reply.started":"2026-02-24T10:10:24.316217Z","shell.execute_reply":"2026-02-24T10:10:24.341198Z"}},"outputs":[],"execution_count":null},{"id":"2add5c6a","cell_type":"code","source":"callbacks_stage1 = [\n    keras.callbacks.ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.5,\n        patience=2,\n        min_lr=1e-6,\n        verbose=1,\n    ),\n    keras.callbacks.ModelCheckpoint(\n        \"best_model_stage1.keras\",\n        monitor=\"val_macro_f1\",\n        mode=\"max\",\n        save_best_only=True,\n        verbose=1,\n    ),\n]","metadata":{"execution":{"iopub.status.busy":"2026-02-24T10:10:30.346473Z","iopub.execute_input":"2026-02-24T10:10:30.347047Z","iopub.status.idle":"2026-02-24T10:10:30.351339Z","shell.execute_reply.started":"2026-02-24T10:10:30.347019Z","shell.execute_reply":"2026-02-24T10:10:30.350584Z"},"papermill":{"duration":0.010291,"end_time":"2026-02-23T20:25:19.084069","exception":false,"start_time":"2026-02-23T20:25:19.073778","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"b22be404","cell_type":"code","source":"history_stage1 = model.fit(\n    ds_train,\n    validation_data=ds_val,\n    epochs=EPOCHS_STAGE1,\n    callbacks=callbacks_stage1,\n    verbose=1,\n)\n\nprint(\"готово\")\nprint(\"best модель сохранена в best_model.keras\")","metadata":{"execution":{"iopub.status.busy":"2026-02-24T10:10:32.672171Z","iopub.execute_input":"2026-02-24T10:10:32.672973Z","iopub.status.idle":"2026-02-24T10:28:19.679070Z","shell.execute_reply.started":"2026-02-24T10:10:32.672942Z","shell.execute_reply":"2026-02-24T10:28:19.678286Z"},"papermill":{"duration":3548.172132,"end_time":"2026-02-23T21:24:27.260258","exception":false,"start_time":"2026-02-23T20:25:19.088126","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"5975d700-52de-4bcc-8eaf-a6631519900a","cell_type":"code","source":"with strategy.scope():\n    base_model.trainable = True\n\n    fine_tune_at = max(0, len(base_model.layers) - 20)\n    for layer in base_model.layers[:fine_tune_at]:\n        layer.trainable = False\n\n    model.compile(\n        optimizer=keras.optimizers.Adam(learning_rate=5e-4),\n        loss=keras.losses.SparseCategoricalCrossentropy(),\n        metrics=[\"accuracy\", MacroF1(num_classes=NUM_CLASSES)],\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-24T10:29:02.120948Z","iopub.execute_input":"2026-02-24T10:29:02.121703Z","iopub.status.idle":"2026-02-24T10:29:02.192027Z","shell.execute_reply.started":"2026-02-24T10:29:02.121673Z","shell.execute_reply":"2026-02-24T10:29:02.191004Z"}},"outputs":[],"execution_count":null},{"id":"9c8ce311-1bf6-42bf-a9d4-086a3be4b931","cell_type":"code","source":"callbacks_stage2 = [\n    keras.callbacks.ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.5,\n        patience=3,\n        min_lr=1e-6,\n        verbose=1,\n    ),\n    keras.callbacks.ModelCheckpoint(\n        \"best_model_finetuned.keras\",\n        monitor=\"val_macro_f1\",\n        mode=\"max\",\n        save_best_only=True,\n        verbose=1,\n    ),\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-24T10:29:10.352786Z","iopub.execute_input":"2026-02-24T10:29:10.353436Z","iopub.status.idle":"2026-02-24T10:29:10.357649Z","shell.execute_reply.started":"2026-02-24T10:29:10.353405Z","shell.execute_reply":"2026-02-24T10:29:10.356814Z"}},"outputs":[],"execution_count":null},{"id":"ea5a35b5-1f29-4deb-84d6-bf63d6c7c94c","cell_type":"code","source":"history_stage2 = model.fit(\n    ds_train,\n    validation_data=ds_val,\n    epochs=EPOCHS_STAGE2,\n    callbacks=callbacks_stage2,\n    verbose=1,\n)\n\nprint(\"готово\")\nprint(\"best модель сохранена в best_model.keras\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-24T10:29:13.288243Z","iopub.execute_input":"2026-02-24T10:29:13.288570Z","iopub.status.idle":"2026-02-24T11:07:46.090996Z","shell.execute_reply.started":"2026-02-24T10:29:13.288530Z","shell.execute_reply":"2026-02-24T11:07:46.090184Z"}},"outputs":[],"execution_count":null},{"id":"178f4208-58bc-471f-99fc-b3ab0dae18e2","cell_type":"code","source":"best_model = keras.models.load_model(\n    \"best_model_finetuned.keras\",\n    compile=False,\n)\n\nids_list = []\nlabels_list = []\n\nfor batch_images, batch_ids in ds_test:\n    preds = best_model.predict(batch_images, verbose=0)\n    batch_labels = tf.argmax(preds, axis=-1).numpy()\n    batch_ids_decoded = [x.numpy().decode(\"utf-8\") for x in batch_ids]\n    ids_list.extend(batch_ids_decoded)\n    labels_list.extend(batch_labels)\n\nsubmission = pd.DataFrame({\"id\": ids_list, \"label\": labels_list})\nsubmission_path = \"submission.csv\"\nsubmission.to_csv(submission_path, index=False)\n\nprint(\"Submission сохранён в\", submission_path)\nprint(submission.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-24T11:13:18.313430Z","iopub.execute_input":"2026-02-24T11:13:18.313790Z","iopub.status.idle":"2026-02-24T11:15:24.923798Z","shell.execute_reply.started":"2026-02-24T11:13:18.313760Z","shell.execute_reply":"2026-02-24T11:15:24.922962Z"}},"outputs":[],"execution_count":null}]}