{"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":[{"sourceId":21154,"databundleVersionId":1243559,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import tensorflow as tf\nprint(\"GPU Available: \", tf.config.list_physical_devices('GPU'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:47:24.731840Z","iopub.execute_input":"2026-01-24T12:47:24.732170Z","iopub.status.idle":"2026-01-24T12:47:24.737280Z","shell.execute_reply.started":"2026-01-24T12:47:24.732144Z","shell.execute_reply":"2026-01-24T12:47:24.736356Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\n\ninput_root = \"/kaggle/input\"\nfor root_dir, subdirs, files in os.walk(input_root):\n    for file_name in files:\n        print(os.path.join(root_dir, file_name))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:47:27.159601Z","iopub.execute_input":"2026-01-24T12:47:27.159926Z","iopub.status.idle":"2026-01-24T12:47:27.205302Z","shell.execute_reply.started":"2026-01-24T12:47:27.159899Z","shell.execute_reply":"2026-01-24T12:47:27.204205Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport matplotlib.pyplot as plt\n%matplotlib inline\n\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential, Model\nfrom tensorflow.keras import layers, utils\nfrom tensorflow.keras.layers import (\n    Input, Dense, Flatten, Dropout, BatchNormalization,\n    Conv2D, MaxPooling2D, AveragePooling2D,\n    GlobalAveragePooling2D, GlobalMaxPooling2D, SpatialDropout2D, GaussianDropout\n)\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\nfrom tensorflow.keras.regularizers import l2\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.random import set_seed\n\nfrom kaggle_datasets import KaggleDatasets\n\n# Фиксация случайных семян для воспроизводимости\ndef seed_everything(seed_value: int = 42) -> None:\n    np.random.seed(seed_value)\n    set_seed(seed_value)\n    os.environ['PYTHONHASHSEED'] = str(seed_value)\n    os.environ['TF_DETERMINISTIC_OPS'] = '1'\n\nSEED = 42\nseed_everything(SEED)\n\nprint(f\"TensorFlow version: {tf.__version__}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:47:31.223688Z","iopub.execute_input":"2026-01-24T12:47:31.224304Z","iopub.status.idle":"2026-01-24T12:47:31.340789Z","shell.execute_reply.started":"2026-01-24T12:47:31.224272Z","shell.execute_reply":"2026-01-24T12:47:31.339313Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport tensorflow as tf\n\n# Отключение XLA JIT-компиляции (используется при проблемах с GPU-совместимостью)\nos.environ[\"TF_XLA_FLAGS\"] = \"--tf_xla_auto_jit=0\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:47:35.007362Z","iopub.execute_input":"2026-01-24T12:47:35.008525Z","iopub.status.idle":"2026-01-24T12:47:35.014517Z","shell.execute_reply.started":"2026-01-24T12:47:35.008261Z","shell.execute_reply":"2026-01-24T12:47:35.013591Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nAUTOTUNE = tf.data.AUTOTUNE","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:47:38.199913Z","iopub.execute_input":"2026-01-24T12:47:38.200272Z","iopub.status.idle":"2026-01-24T12:47:38.204698Z","shell.execute_reply.started":"2026-01-24T12:47:38.200225Z","shell.execute_reply":"2026-01-24T12:47:38.203753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Автоматическое определение устройства: TPU → GPU/CPU\ntry:\n    tpu_resolver = tf.distribute.cluster_resolver.TPUClusterResolver()\n    print(f\"Running on TPU: {tpu_resolver.master()}\")\nexcept ValueError:\n    tpu_resolver = None\n\nif tpu_resolver is not None:\n    tf.config.experimental_connect_to_cluster(tpu_resolver)\n    tf.tpu.experimental.initialize_tpu_system(tpu_resolver)\n    distribution_strategy = tf.distribute.TPUStrategy(tpu_resolver)\nelse:\n    distribution_strategy = tf.distribute.get_strategy()\n\nprint(f\"Number of replicas in sync: {distribution_strategy.num_replicas_in_sync}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:47:39.841848Z","iopub.execute_input":"2026-01-24T12:47:39.842193Z","iopub.status.idle":"2026-01-24T12:47:39.848388Z","shell.execute_reply.started":"2026-01-24T12:47:39.842158Z","shell.execute_reply":"2026-01-24T12:47:39.847321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Определение пути к данным в зависимости от устройства\nif distribution_strategy.num_replicas_in_sync > 1 and \"TPU\" in str(distribution_strategy):\n    # Для TPU — используем GCS-путь через KaggleDatasets\n    gcs_dataset_path = KaggleDatasets().get_gcs_path(\"tpu-getting-started\")\nelse:\n    # Для GPU/CPU — локальный путь в Kaggle\n    gcs_dataset_path = \"/kaggle/input/tpu-getting-started\"\n\nprint(f\"Dataset path: {gcs_dataset_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:47:41.767397Z","iopub.execute_input":"2026-01-24T12:47:41.767807Z","iopub.status.idle":"2026-01-24T12:47:41.773455Z","shell.execute_reply.started":"2026-01-24T12:47:41.767776Z","shell.execute_reply":"2026-01-24T12:47:41.772525Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Гиперпараметры модели и обучения\nIMAGE_SIZE = (224, 224)  \nEPOCHS = 60      \nBATCH_SIZE = 32 * distribution_strategy.num_replicas_in_sync  \n# Размеры датасета (для tpu-getting-started)\nNUM_TRAINING_IMAGES = 12_753\nNUM_TEST_IMAGES = 7_382\nSTEPS_PER_EPOCH = NUM_TRAINING_IMAGES // BATCH_SIZE","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T13:09:00.540239Z","iopub.execute_input":"2026-01-24T13:09:00.540477Z","iopub.status.idle":"2026-01-24T13:09:00.554514Z","shell.execute_reply.started":"2026-01-24T13:09:00.540450Z","shell.execute_reply":"2026-01-24T13:09:00.552856Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Загрузка данных","metadata":{}},{"cell_type":"code","source":"def decode_image(image_bytes: tf.Tensor) -> tf.Tensor:\n    \"\"\"Декодирует JPEG-изображение, нормализует в [0, 1] и изменяет размер.\"\"\"\n    image = tf.image.decode_jpeg(image_bytes, channels=3)\n    image = tf.cast(image, tf.float32) / 255.0\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\n    return image\n\n\ndef read_labeled_tfrecord(example: tf.Tensor) -> tuple[tf.Tensor, tf.Tensor]:\n    \"\"\"Парсит TFRecord с меткой класса.\"\"\"\n    labeled_format = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64),\n    }\n    parsed = tf.io.parse_single_example(example, labeled_format)\n    image = decode_image(parsed[\"image\"])\n    label = tf.cast(parsed[\"class\"], tf.int32)\n    return image, label\n\n\ndef read_unlabeled_tfrecord(example: tf.Tensor) -> tuple[tf.Tensor, tf.Tensor]:\n    \"\"\"Парсит TFRecord без метки (для теста).\"\"\"\n    unlabeled_format = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"id\": tf.io.FixedLenFeature([], tf.string),\n    }\n    parsed = tf.io.parse_single_example(example, unlabeled_format)\n    image = decode_image(parsed[\"image\"])\n    return image, parsed[\"id\"]\n\n\ndef load_dataset(\n    file_paths: list[str],\n    labeled: bool = True,\n    ordered: bool = False\n) -> tf.data.Dataset:\n    \"\"\"Загружает TFRecord-датасет с опциональным перемешиванием.\"\"\"\n    options = tf.data.Options()\n    if not ordered:\n        options.experimental_deterministic = False\n    dataset = tf.data.TFRecordDataset(file_paths, num_parallel_reads=AUTOTUNE)\n    dataset = dataset.with_options(options)\n    parse_fn = read_labeled_tfrecord if labeled else read_unlabeled_tfrecord\n    dataset = dataset.map(parse_fn, num_parallel_calls=AUTOTUNE)\n    return dataset\n\n\ndef get_training_dataset() -> tf.data.Dataset:\n    \"\"\"Создаёт датасет для обучения с аугментацией и перемешиванием.\"\"\"\n    file_pattern = f\"{gcs_dataset_path}/tfrecords-jpeg-224x224/train/*.tfrec\"\n    file_paths = tf.io.gfile.glob(file_pattern)\n    dataset = load_dataset(file_paths, labeled=True, ordered=False)\n    dataset = dataset.repeat()\n    dataset = dataset.shuffle(buffer_size=2048)\n    dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset\n\n\ndef get_validation_dataset() -> tf.data.Dataset:\n    \"\"\"Создаёт датасет для валидации.\"\"\"\n    file_pattern = f\"{gcs_dataset_path}/tfrecords-jpeg-224x224/val/*.tfrec\"\n    file_paths = tf.io.gfile.glob(file_pattern)\n    dataset = load_dataset(file_paths, labeled=True, ordered=True)\n    dataset = dataset.batch(BATCH_SIZE, drop_remainder=False)\n    dataset = dataset.cache()\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset\n\n\ndef get_test_dataset(ordered: bool = False) -> tf.data.Dataset:\n    \"\"\"Создаёт датасет для теста.\"\"\"\n    file_pattern = f\"{gcs_dataset_path}/tfrecords-jpeg-224x224/test/*.tfrec\"\n    file_paths = tf.io.gfile.glob(file_pattern)\n    dataset = load_dataset(file_paths, labeled=False, ordered=ordered)\n    dataset = dataset.batch(BATCH_SIZE, drop_remainder=False)\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset\n\n\n# Создание датасетов\ntraining_dataset = get_training_dataset()\nvalidation_dataset = get_validation_dataset()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:47:48.950262Z","iopub.execute_input":"2026-01-24T12:47:48.951018Z","iopub.status.idle":"2026-01-24T12:47:49.107461Z","shell.execute_reply.started":"2026-01-24T12:47:48.950976Z","shell.execute_reply":"2026-01-24T12:47:49.106493Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Модель","metadata":{}},{"cell_type":"code","source":"def get_model(num_classes: int = 104) -> tf.keras.Model:\n    \"\"\"Создаёт CNN-модель с улучшенной регуляризацией и структурой.\"\"\"\n    inputs = Input(shape=(*IMAGE_SIZE, 3))\n    \n    # Первый сверточный блок\n    x = Conv2D(64, (3, 3), activation='relu', kernel_regularizer=l2(0.001))(inputs)\n    x = BatchNormalization()(x)\n    x = Conv2D(64, (3, 3), activation='relu', kernel_regularizer=l2(0.001))(x)\n    x = BatchNormalization()(x)\n    x = AveragePooling2D(pool_size=(2, 2))(x)\n    x = Dropout(0.25)(x)\n\n    # Второй сверточный блок\n    x = Conv2D(128, (3, 3), activation='relu', kernel_regularizer=l2(0.001))(x)\n    x = BatchNormalization()(x)\n    x = Conv2D(128, (3, 3), activation='relu', kernel_regularizer=l2(0.001))(x)\n    x = BatchNormalization()(x)\n    x = AveragePooling2D(pool_size=(2, 2))(x)\n    x = Dropout(0.3)(x)\n\n    # Третий сверточный блок\n    x = Conv2D(256, (3, 3), activation='relu', kernel_regularizer=l2(0.001))(x)\n    x = BatchNormalization()(x)\n    x = Conv2D(256, (3, 3), activation='relu', kernel_regularizer=l2(0.001))(x)\n    x = BatchNormalization()(x)\n    x = Conv2D(256, (3, 3), activation='relu', kernel_regularizer=l2(0.001))(x)\n    x = BatchNormalization()(x)\n    x = AveragePooling2D(pool_size=(2, 2))(x)\n    x = Dropout(0.35)(x)\n\n    # Четвёртый сверточный блок\n    x = Conv2D(512, (3, 3), activation='relu', kernel_regularizer=l2(0.001))(x)  # изменён с (5,5) → (3,3)\n    x = BatchNormalization()(x)\n    x = Conv2D(512, (3, 3), activation='relu', kernel_regularizer=l2(0.001))(x)\n    x = BatchNormalization()(x)\n    x = Conv2D(512, (3, 3), activation='relu', kernel_regularizer=l2(0.001))(x)\n    x = BatchNormalization()(x)\n    x = GlobalAveragePooling2D()(x)\n    x = Dropout(0.4)(x)\n\n    # Классификационная голова\n    x = Dense(512, activation='relu', kernel_regularizer=l2(0.001))(x)\n    x = BatchNormalization()(x)\n    x = Dropout(0.4)(x)\n    outputs = Dense(num_classes, activation='softmax')(x)\n\n    return Model(inputs=inputs, outputs=outputs)\n\n\n# Создание модели в контексте стратегии распределения\nwith distribution_strategy.scope():\n    model = get_model(num_classes=104)\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:47:52.418817Z","iopub.execute_input":"2026-01-24T12:47:52.419527Z","iopub.status.idle":"2026-01-24T12:47:52.804602Z","shell.execute_reply.started":"2026-01-24T12:47:52.419495Z","shell.execute_reply":"2026-01-24T12:47:52.803974Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Создаём стратегию для нескольких GPU\nstrategy = tf.distribute.MirroredStrategy()\nprint(f\"Количество GPU: {strategy.num_replicas_in_sync}\")\n\n# 2. Создаём и компилируем модель внутри strategy.scope()\nwith strategy.scope():\n    \n    model = get_model()\n\n    # Коллбэки\n    callbacks_list = [\n        EarlyStopping(\n            monitor=\"val_sparse_categorical_accuracy\",\n            patience=7,\n            restore_best_weights=True,\n            verbose=1\n        ),\n        ReduceLROnPlateau(\n            monitor=\"val_sparse_categorical_accuracy\",\n            factor=0.5,  \n            patience=4,\n            min_lr=1e-7,\n            verbose=1\n        )\n    ]\n\n    # Оптимизатор\n    optimizer = tf.keras.optimizers.Nadam(\n        learning_rate=3e-4,   \n        beta_1=0.9,\n        beta_2=0.999\n    )\n\n    # Компиляция\n    model.compile(\n        optimizer=optimizer,\n        loss=\"sparse_categorical_crossentropy\",\n        metrics=[\"sparse_categorical_accuracy\"]\n    )\n\n# 3. Обучение (вне scope — это нормально)\nhistory = model.fit(\n    training_dataset,\n    steps_per_epoch=STEPS_PER_EPOCH,\n    epochs=EPOCHS,\n    callbacks=callbacks_list,\n    validation_data=validation_dataset,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:48:49.582852Z","iopub.execute_input":"2026-01-24T12:48:49.583505Z","iopub.status.idle":"2026-01-24T12:51:49.571643Z","shell.execute_reply.started":"2026-01-24T12:48:49.583474Z","shell.execute_reply":"2026-01-24T12:51:49.570634Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Получение тестового датасета (упорядоченного)\ntest_dataset = get_test_dataset(ordered=True)\n\n# Извлечение только изображений для предсказания\ntest_images = test_dataset.map(lambda image, id_num: image)\n\n# Получение предсказаний модели\nprobabilities = model.predict(test_images)\npredicted_labels = np.argmax(probabilities, axis=-1)\nprint(\"Predictions shape:\", predicted_labels.shape)\nprint(\"Sample predictions:\", predicted_labels[:10])\n\n# Извлечение ID из тестового датасета\ntest_ids = test_dataset.map(lambda image, id_num: id_num).unbatch()\ntest_ids_batch = next(iter(test_ids.batch(NUM_TEST_IMAGES))).numpy().astype('U')\n\n# Сохранение submission.csv\nsubmission_path = \"submission.csv\"\nnp.savetxt(\n    submission_path,\n    np.rec.fromarrays([test_ids_batch, predicted_labels]),\n    fmt=['%s', '%d'],\n    delimiter=',',\n    header='id,label',\n    comments=''\n)\n\nprint(f\"Submission saved to {submission_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:52:30.326592Z","iopub.execute_input":"2026-01-24T12:52:30.327375Z","iopub.status.idle":"2026-01-24T12:52:56.287361Z","shell.execute_reply.started":"2026-01-24T12:52:30.327284Z","shell.execute_reply":"2026-01-24T12:52:56.286514Z"}},"outputs":[],"execution_count":null}]}