{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":21154,"databundleVersionId":1243559}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"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","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:49:57.878260Z","iopub.execute_input":"2026-02-23T16:49:57.878556Z","iopub.status.idle":"2026-02-23T16:50:07.200541Z","shell.execute_reply.started":"2026-02-23T16:49:57.878531Z","shell.execute_reply":"2026-02-23T16:50:07.199919Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"AUTO = tf.data.AUTOTUNE\nIMG_SIZE = 512\nBATCH_SIZE = 16\nEPOCHS = 20\nNUM_CLASSES = 104\nSEED = 42\n\nGCS_PATH = \"/kaggle/input/tpu-getting-started/tfrecords-jpeg-512x512\"\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/tpu-getting-started/sample_submission.csv\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:07.201892Z","iopub.execute_input":"2026-02-23T16:50:07.202339Z","iopub.status.idle":"2026-02-23T16:50:07.206661Z","shell.execute_reply.started":"2026-02-23T16:50:07.202315Z","shell.execute_reply":"2026-02-23T16:50:07.206070Z"}},"outputs":[],"execution_count":null},{"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:07.207429Z","iopub.execute_input":"2026-02-23T16:50:07.207697Z","iopub.status.idle":"2026-02-23T16:50:07.562832Z","shell.execute_reply.started":"2026-02-23T16:50:07.207667Z","shell.execute_reply":"2026-02-23T16:50:07.562269Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tf.random.set_seed(SEED)\nnp.random.seed(SEED)\n\nprint(f\"TensorFlow {tf.__version__}, Keras {keras.__version__}\")\nprint(f\"IMG_SIZE={IMG_SIZE}, BATCH_SIZE={BATCH_SIZE}, EPOCHS={EPOCHS}, NUM_CLASSES={NUM_CLASSES}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:07.563680Z","iopub.execute_input":"2026-02-23T16:50:07.563917Z","iopub.status.idle":"2026-02-23T16:50:07.579641Z","shell.execute_reply.started":"2026-02-23T16:50:07.563896Z","shell.execute_reply":"2026-02-23T16:50:07.578906Z"}},"outputs":[],"execution_count":null},{"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:07.581487Z","iopub.execute_input":"2026-02-23T16:50:07.581715Z","iopub.status.idle":"2026-02-23T16:50:07.593125Z","shell.execute_reply.started":"2026-02-23T16:50:07.581694Z","shell.execute_reply":"2026-02-23T16:50:07.592352Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def decode_image(img_bytes):\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.0\n    return img\n\n\ndef parse_train(example):\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):\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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:07.593989Z","iopub.execute_input":"2026-02-23T16:50:07.594294Z","iopub.status.idle":"2026-02-23T16:50:07.607655Z","shell.execute_reply.started":"2026-02-23T16:50:07.594239Z","shell.execute_reply":"2026-02-23T16:50:07.607189Z"}},"outputs":[],"execution_count":null},{"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))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:07.608439Z","iopub.execute_input":"2026-02-23T16:50:07.608712Z","iopub.status.idle":"2026-02-23T16:50:07.648517Z","shell.execute_reply.started":"2026-02-23T16:50:07.608683Z","shell.execute_reply":"2026-02-23T16:50:07.647982Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ds_train_raw = tf.data.TFRecordDataset(train_files, num_parallel_reads=AUTO)\nds_train = (\n    ds_train_raw.map(parse_train, num_parallel_calls=AUTO)\n    .shuffle(buffer_size=1024, seed=SEED)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:07.649216Z","iopub.execute_input":"2026-02-23T16:50:07.649457Z","iopub.status.idle":"2026-02-23T16:50:09.214563Z","shell.execute_reply.started":"2026-02-23T16:50:07.649427Z","shell.execute_reply":"2026-02-23T16:50:09.213957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ds_val_raw = tf.data.TFRecordDataset(val_files, num_parallel_reads=AUTO)\nds_val = (\n    ds_val_raw.map(parse_train, num_parallel_calls=AUTO)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:09.215403Z","iopub.execute_input":"2026-02-23T16:50:09.215641Z","iopub.status.idle":"2026-02-23T16:50:09.245423Z","shell.execute_reply.started":"2026-02-23T16:50:09.215619Z","shell.execute_reply":"2026-02-23T16:50:09.244707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ds_test_raw = tf.data.TFRecordDataset(test_files, num_parallel_reads=AUTO)\nds_test = (\n    ds_test_raw.map(parse_test, num_parallel_calls=AUTO)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:09.246391Z","iopub.execute_input":"2026-02-23T16:50:09.246662Z","iopub.status.idle":"2026-02-23T16:50:09.294853Z","shell.execute_reply.started":"2026-02-23T16:50:09.246630Z","shell.execute_reply":"2026-02-23T16:50:09.294185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Файлов train:\", len(train_files), \"val:\", len(val_files), \"test:\", len(test_files))\nprint(\"Датасеты созданы: ds_train, ds_val, ds_test\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:09.295779Z","iopub.execute_input":"2026-02-23T16:50:09.296261Z","iopub.status.idle":"2026-02-23T16:50:09.300056Z","shell.execute_reply.started":"2026-02-23T16:50:09.296238Z","shell.execute_reply":"2026-02-23T16:50:09.299441Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MacroF1(keras.metrics.Metric):\n    def __init__(self, num_classes, name=\"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T18:24:45.312214Z","iopub.execute_input":"2026-02-23T18:24:45.312546Z","iopub.status.idle":"2026-02-23T18:24:45.319797Z","shell.execute_reply.started":"2026-02-23T18:24:45.312522Z","shell.execute_reply":"2026-02-23T18:24:45.319094Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"CNN, BatchNorm, MaxPool, Dense, Dropout","metadata":{}},{"cell_type":"code","source":"def build_model():\n    inp = keras.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\n\n    x = layers.Conv2D(32, 3, padding=\"same\", activation=\"relu\")(inp)\n    x = layers.BatchNormalization()(x)\n    x = layers.MaxPooling2D(2)(x)\n    x = layers.Dropout(0.1)(x)\n\n    x = layers.Conv2D(64, 3, padding=\"same\", activation=\"relu\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.MaxPooling2D(2)(x)\n    x = layers.Dropout(0.1)(x)\n\n    x = layers.Conv2D(128, 3, padding=\"same\", activation=\"relu\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.MaxPooling2D(2)(x)\n    x = layers.Dropout(0.2)(x)\n\n    x = layers.Conv2D(256, 3, padding=\"same\", activation=\"relu\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.MaxPooling2D(2)(x)\n    x = layers.Dropout(0.2)(x)\n\n    x = layers.Conv2D(512, 3, padding=\"same\", activation=\"relu\")(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.3)(x)\n\n    x = layers.Dense(512, activation=\"relu\")(x)\n    x = layers.Dropout(0.5)(x)\n    out = layers.Dense(NUM_CLASSES, activation=\"softmax\")(x)\n\n    return keras.Model(inputs=inp, outputs=out)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:09.314068Z","iopub.execute_input":"2026-02-23T16:50:09.314321Z","iopub.status.idle":"2026-02-23T16:50:09.330816Z","shell.execute_reply.started":"2026-02-23T16:50:09.314294Z","shell.execute_reply":"2026-02-23T16:50:09.330217Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with strategy.scope():\n    model = build_model()\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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:09.332532Z","iopub.execute_input":"2026-02-23T16:50:09.332772Z","iopub.status.idle":"2026-02-23T16:50:10.157570Z","shell.execute_reply.started":"2026-02-23T16:50:09.332754Z","shell.execute_reply":"2026-02-23T16:50:10.156803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:10.158501Z","iopub.execute_input":"2026-02-23T16:50:10.158760Z","iopub.status.idle":"2026-02-23T16:50:10.185631Z","shell.execute_reply.started":"2026-02-23T16:50:10.158726Z","shell.execute_reply":"2026-02-23T16:50:10.185155Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"callbacks = [\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.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-23T16:50:10.186344Z","iopub.execute_input":"2026-02-23T16:50:10.186570Z","iopub.status.idle":"2026-02-23T16:50:10.190802Z","shell.execute_reply.started":"2026-02-23T16:50:10.186550Z","shell.execute_reply":"2026-02-23T16:50:10.190177Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    ds_train,\n    validation_data=ds_val,\n    epochs=EPOCHS,\n    callbacks=callbacks,\n    verbose=1,\n)\n\nprint(\"готово\")\nprint(\"best модель сохранена в best_model.keras\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T16:50:10.191631Z","iopub.execute_input":"2026-02-23T16:50:10.191850Z","iopub.status.idle":"2026-02-23T17:51:39.053742Z","shell.execute_reply.started":"2026-02-23T16:50:10.191830Z","shell.execute_reply":"2026-02-23T17:51:39.053092Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def augment(image, label):\n    image = tf.image.random_flip_left_right(image)\n    image = tf.image.random_flip_up_down(image)\n    image = tf.image.random_brightness(image, max_delta=0.15)\n    image = tf.image.random_contrast(image, lower=0.8, upper=1.2)\n    image = tf.image.random_saturation(image, lower=0.8, upper=1.2)\n    image = tf.image.random_hue(image, max_delta=0.05)\n    image = tf.clip_by_value(image, 0.0, 1.0)\n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T19:46:55.167291Z","iopub.execute_input":"2026-02-23T19:46:55.167587Z","iopub.status.idle":"2026-02-23T19:46:55.172612Z","shell.execute_reply.started":"2026-02-23T19:46:55.167561Z","shell.execute_reply":"2026-02-23T19:46:55.171881Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ds_train_aug = (\n    ds_train_raw.map(parse_train, num_parallel_calls=AUTO)\n    .map(augment, num_parallel_calls=AUTO)\n    .shuffle(buffer_size=1024, seed=SEED)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T19:47:19.406285Z","iopub.execute_input":"2026-02-23T19:47:19.406991Z","iopub.status.idle":"2026-02-23T19:47:19.515887Z","shell.execute_reply.started":"2026-02-23T19:47:19.406962Z","shell.execute_reply":"2026-02-23T19:47:19.515362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EXTRA_EPOCHS = 10\n\ncallbacks_finetune = [\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.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-23T19:47:28.797203Z","iopub.execute_input":"2026-02-23T19:47:28.797803Z","iopub.status.idle":"2026-02-23T19:47:28.801612Z","shell.execute_reply.started":"2026-02-23T19:47:28.797775Z","shell.execute_reply":"2026-02-23T19:47:28.801046Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history_finetune = model.fit(\n    ds_train_aug,\n    validation_data=ds_val,\n    epochs=EXTRA_EPOCHS,\n    callbacks=callbacks_finetune,\n    verbose=1,\n)\n\nprint(\"готово\")\nprint(\"tuned best модель сохранена в best_model.keras\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T19:48:03.007545Z","iopub.execute_input":"2026-02-23T19:48:03.008279Z","iopub.status.idle":"2026-02-23T20:19:38.012976Z","shell.execute_reply.started":"2026-02-23T19:48:03.008250Z","shell.execute_reply":"2026-02-23T20:19:38.012376Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_model = keras.models.load_model(\"best_model.keras\", compile=False)\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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T20:22:17.142407Z","iopub.execute_input":"2026-02-23T20:22:17.143202Z","iopub.status.idle":"2026-02-23T20:23:48.066412Z","shell.execute_reply.started":"2026-02-23T20:22:17.143173Z","shell.execute_reply":"2026-02-23T20:23:48.065739Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = 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-23T20:24:27.323426Z","iopub.execute_input":"2026-02-23T20:24:27.324213Z","iopub.status.idle":"2026-02-23T20:24:27.343646Z","shell.execute_reply.started":"2026-02-23T20:24:27.324183Z","shell.execute_reply":"2026-02-23T20:24:27.342957Z"}},"outputs":[],"execution_count":null}]}