{"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":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-21T18:59:15.034409Z","iopub.execute_input":"2026-01-21T18:59:15.035161Z","iopub.status.idle":"2026-01-21T18:59:15.060170Z","shell.execute_reply.started":"2026-01-21T18:59:15.035122Z","shell.execute_reply":"2026-01-21T18:59:15.059432Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Спасибо Вам большое обзорный блокнот по этому заданию! Большую часть брал именно у Вас: https://www.kaggle.com/code/lkatran/start-w-o-pre-train","metadata":{}},{"cell_type":"markdown","source":"# Импорты","metadata":{}},{"cell_type":"code","source":"import os\nimport re\nimport random\nfrom time import time\n\nimport numpy as np\nimport pandas as pd\n\nimport tensorflow as tf\nimport tensorflow.keras as tfk\nfrom tensorflow.random import set_seed\n\nfrom tensorflow.keras import utils\nfrom tensorflow.keras import backend as K\nfrom tensorflow.keras.models import Sequential, Model\nfrom tensorflow.keras.regularizers import *\n\n\nfrom tensorflow.keras.layers import (\n    Input, Dense, Flatten, Lambda, Activation,\n    Conv2D, Conv3D, DepthwiseConv2D, SeparableConv2D, Conv3DTranspose,\n    MaxPooling2D, AveragePooling2D, GlobalAvgPool2D, UpSampling2D,\n    MaxPool2D, AvgPool2D, GlobalAveragePooling2D,\n    BatchNormalization, Dropout, SpatialDropout2D, GaussianDropout,\n    ReLU, LeakyReLU, PReLU,\n    Concatenate, Add\n)\n\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\nfrom tqdm.keras import TqdmCallback\n\nfrom tensorflow.keras.preprocessing import image\n\nimport matplotlib.pyplot as plt\n%matplotlib inline\n\nfrom kaggle_datasets import KaggleDatasets\n\ndef seed_everything(seed):\n    np.random.seed(seed)\n    set_seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    os.environ['TF_DETERMINISTIC_OPS'] = '1'\n\nSEED = 100500\nseed_everything(SEED)\n\nprint(\"Tensorflow version \" + tf.__version__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T18:59:15.061421Z","iopub.execute_input":"2026-01-21T18:59:15.061716Z","iopub.status.idle":"2026-01-21T18:59:15.071161Z","shell.execute_reply.started":"2026-01-21T18:59:15.061690Z","shell.execute_reply":"2026-01-21T18:59:15.070466Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# В примере было разобрано подключение TPU, но, к сожалению, на TPU очередь, поэтому делаем на двух крутилках T4","metadata":{}},{"cell_type":"code","source":"gpus = tf.config.experimental.list_physical_devices('GPU')\nif gpus:\n    try:\n        for gpu in gpus:\n            tf.config.experimental.set_memory_growth(gpu, True)\n        strategy = tf.distribute.MirroredStrategy()\n        print(f\"Running on {len(gpus)} GPU(s)\")\n    except RuntimeError as e:\n        print(e)\n        strategy = tf.distribute.get_strategy()\nelse:\n    strategy = tf.distribute.get_strategy()\n    print(\"Running on CPU\")\n\nprint(\"REPLICAS:\", strategy.num_replicas_in_sync)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T18:59:15.072052Z","iopub.execute_input":"2026-01-21T18:59:15.072303Z","iopub.status.idle":"2026-01-21T18:59:15.092736Z","shell.execute_reply.started":"2026-01-21T18:59:15.072275Z","shell.execute_reply":"2026-01-21T18:59:15.092095Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMAGE_SIZE = [331, 331]\nEPOCHS = 30\nBATCH_SIZE = 16 * strategy.num_replicas_in_sync\nNUM_CLASSES = 104\n\nLOCAL_DS_PATH = \"/kaggle/input/tpu-getting-started/tfrecords-jpeg-331x331\"\n\nTRAINING_FILENAMES = tf.io.gfile.glob(os.path.join(LOCAL_DS_PATH, \"train/*.tfrec\"))\nVALIDATION_FILENAMES = tf.io.gfile.glob(os.path.join(LOCAL_DS_PATH, \"val/*.tfrec\"))\nTEST_FILENAMES = tf.io.gfile.glob(os.path.join(LOCAL_DS_PATH, \"test/*.tfrec\"))\n\nprint(\"Train files:\", len(TRAINING_FILENAMES))\nprint(\"Val files:\", len(VALIDATION_FILENAMES))\nprint(\"Test files:\", len(TEST_FILENAMES))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T18:59:15.094012Z","iopub.execute_input":"2026-01-21T18:59:15.094258Z","iopub.status.idle":"2026-01-21T18:59:15.110371Z","shell.execute_reply.started":"2026-01-21T18:59:15.094239Z","shell.execute_reply":"2026-01-21T18:59:15.109859Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"AUTO = tf.data.experimental.AUTOTUNE\n\n# Декодируем в тензор и ресайзим\ndef decode_and_resize(image_data):\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.image.resize(image, IMAGE_SIZE, method='bilinear')\n    image = tf.cast(image, tf.float32) / 255.0\n    return image\n\n# Добавим немного аугментация для большего скора\ndef augment_image(image, label):\n    image = tf.image.random_flip_left_right(image)\n    image = tf.image.random_brightness(image, max_delta=0.15)\n    image = tf.image.random_saturation(image, lower=0.8, upper=1.2)\n    image = tf.image.random_contrast(image, lower=0.8, upper=1.2)\n    image = tf.image.random_hue(image, max_delta=0.05)\n    return image, label\n\n\n# Изображение и его метка\ndef read_labeled_record(example):\n    features = tf.io.parse_single_example(example, {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64),\n    })\n    image = decode_and_resize(features[\"image\"])\n    label = tf.cast(features[\"class\"], tf.int32)\n    return image, label\n\n# Изображение без метки для test, но с id\ndef read_unlabeled_record(example):\n    features = tf.io.parse_single_example(example, {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"id\": tf.io.FixedLenFeature([], tf.string),\n    })\n    image = decode_and_resize(features[\"image\"])\n    return image, features[\"id\"]\n\n\n# Читаем сразу несколько изображений их метки без учёта порядков\ndef build_dataset(filenames, labeled=True, ordered=False, augment=False):\n    options = tf.data.Options()\n    if not ordered:\n        options.experimental_deterministic = False\n\n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTO)\n    dataset = dataset.with_options(options)\n    \n    parse_fn = read_labeled_record if labeled else read_unlabeled_record\n    dataset = dataset.map(parse_fn, num_parallel_calls=AUTO)\n    \n    if augment:\n        dataset = dataset.map(augment_image, num_parallel_calls=AUTO)\n    \n    return dataset\n\n# Создаем трейн выборку с перемешиванием, повторением и аугментацией\ndef get_training_dataset():\n    dataset = build_dataset(TRAINING_FILENAMES, labeled=True, augment=True)\n    dataset = dataset.repeat()\n    dataset = dataset.shuffle(buffer_size=2048)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTO)\n    return dataset\n\n# Создаем валидационную выборку с кешированием\ndef get_validation_dataset():\n    dataset = build_dataset(VALIDATION_FILENAMES, labeled=True, ordered=False)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.cache()  \n    dataset = dataset.prefetch(AUTO)\n    return dataset\n\n# Создаем тестовую выборку с кешированием\ndef get_test_dataset(ordered=True):\n    dataset = build_dataset(TEST_FILENAMES, labeled=False, ordered=ordered)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTO)\n    return dataset\n\n# Подсчёт общего числа примеров\ndef count_samples(filenames):\n    counts = [int(re.search(r\"-([0-9]+)\\.\", f).group(1)) for f in filenames]\n    return np.sum(counts)\n\n# Расчёт количества примеров и шагов на эпоху\nNUM_TRAINING_IMAGES = count_samples(TRAINING_FILENAMES)\nNUM_VALIDATION_IMAGES = count_samples(VALIDATION_FILENAMES)\nNUM_TEST_IMAGES = count_samples(TEST_FILENAMES)\nSTEPS_PER_EPOCH = NUM_TRAINING_IMAGES // BATCH_SIZE\n\nprint(f\"Загружено данных: \"\n      f\"{NUM_TRAINING_IMAGES} обучающих, \"\n      f\"{NUM_VALIDATION_IMAGES} валидационных, \"\n      f\"{NUM_TEST_IMAGES} тестовых изображений.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T18:59:15.141608Z","iopub.execute_input":"2026-01-21T18:59:15.141863Z","iopub.status.idle":"2026-01-21T18:59:15.156281Z","shell.execute_reply.started":"2026-01-21T18:59:15.141843Z","shell.execute_reply":"2026-01-21T18:59:15.155552Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Строим классификационную модельку ResNet","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, Model\n\ndef resnet(input_shape, n_classes):\n    \n    # Блок из сверточного слоя, батч-нормализации и активации ReLU\n    def conv_bn_rl(x, f, k=1, s=1, p='same'):\n        x = layers.Conv2D(f, k, strides=s, padding=p)(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.ReLU()(x)\n        return x\n\n    # Identity блок ResNet (пропускает вход без изменения размерности)\n    def identity_block(tensor, f):\n        x = conv_bn_rl(tensor, f)\n        x = conv_bn_rl(x, f, 3)\n        x = layers.Conv2D(4*f, 1)(x)\n        x = layers.BatchNormalization()(x)\n        \n        # Добавляем shortcut connection\n        x = layers.Add()([x, tensor])\n        output = layers.ReLU()(x)\n        return output\n\n    # Convolutional блок ResNet (изменяет размерность входа)\n    def conv_block(tensor, f, s):\n        x = conv_bn_rl(tensor, f)\n        x = conv_bn_rl(x, f, 3, s)\n        x = layers.Conv2D(4*f, 1)(x)\n        x = layers.BatchNormalization()(x)\n        \n        # Shortcut путь (1x1 свертка для согласования размерностей)\n        shortcut = layers.Conv2D(4*f, 1, strides=s)(tensor)\n        shortcut = layers.BatchNormalization()(shortcut)\n        \n        # Добавляем shortcut connection\n        x = layers.Add()([x, shortcut])\n        output = layers.ReLU()(x)\n        return output\n\n    # Блок из нескольких residual блоков\n    def resnet_block(x, f, r, s=2):\n        x = conv_block(x, f, s)\n        for _ in range(r-1):\n            x = identity_block(x, f)\n        return x\n    \n    # Входной слой\n    input = layers.Input(input_shape)\n    \n    # Начальные слои (первичная обработка)\n    x = conv_bn_rl(input, 64, 7, 2)\n    x = layers.MaxPool2D(3, strides=2, padding='same')(x)\n    \n    # Основные блоки (ResNet-50 архитектура)\n    x = resnet_block(x, 64, 3, 1)   # Block 1: 3 слоя\n    x = resnet_block(x, 128, 4)     # Block 2: 4 слоя\n    x = resnet_block(x, 256, 6)     # Block 3: 6 слоя\n    x = resnet_block(x, 512, 3)     # Block 4: 3 слоя\n    \n    # Слой глобального усреднения для перехода от 2D к 1D\n    x = layers.GlobalAvgPool2D()(x)\n    \n    # Выходной полносвязный слой с softmax активацией\n    output = layers.Dense(n_classes, activation='softmax')(x)\n    \n    model = Model(input, output)\n    return model\n\ndef get_model(IMAGE_SIZE, NUM_CLASSES):\n    return resnet(input_shape=(*IMAGE_SIZE, 3), n_classes=NUM_CLASSES)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T18:59:15.158578Z","iopub.execute_input":"2026-01-21T18:59:15.159147Z","iopub.status.idle":"2026-01-21T18:59:15.171321Z","shell.execute_reply.started":"2026-01-21T18:59:15.159126Z","shell.execute_reply":"2026-01-21T18:59:15.170612Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Компилируем","metadata":{}},{"cell_type":"code","source":"# ========== СОЗДАНИЕ И КОМПИЛЯЦИЯ МОДЕЛИ С РАСПРЕДЕЛЕННОЙ СТРАТЕГИЕЙ ==========\n\nwith strategy.scope():\n    model = get_model(IMAGE_SIZE, NUM_CLASSES) \n    optimizer = tf.keras.optimizers.Nadam(learning_rate=1e-3)\n    \n    model.compile(\n        optimizer=optimizer,\n        # Функция потерь для целочисленных меток (из бейзлайна взял)\n        loss='sparse_categorical_crossentropy',\n        # Аналогично\n        metrics=['sparse_categorical_accuracy']\n    )\n\n# ========== ИНФОРМАЦИЯ О ПАРАМЕТРАХ ОБУЧЕНИЯ ==========\nprint(\"Начинаем обучение модели...\")\nprint(f\"  • Общее число эпох: {EPOCHS}\")\nprint(f\"  • Batch size: {BATCH_SIZE} (реплик: {strategy.num_replicas_in_sync})\")\nprint(f\"  • Шагов на эпоху: {STEPS_PER_EPOCH}\")\nprint(f\"  • Обучающих изображений: {NUM_TRAINING_IMAGES}\")\nprint(f\"  • Валидационных изображений: {NUM_VALIDATION_IMAGES}\")\nprint(\"-\" * 50)\n\n# ========== КОЛБЭКИ ДЛЯ УПРАВЛЕНИЯ ОБУЧЕНИЕМ ==========\n\n# Автоматическое сохранение лучших весов модели\ncheckpoint_cb = ModelCheckpoint(\n    'best_model.weights.h5',  \n    save_best_only=True,\n    save_weights_only=True,\n    monitor='val_loss',\n    mode='min'\n)\n\n# Уменьшение скорости обучения при застое\nreduce_lr_cb = ReduceLROnPlateau(\n    monitor='val_loss',\n    factor=0.5,\n    patience=3,\n    min_lr=1e-6\n)\n\n# Раняя остановка\nearly_stopping_cb = EarlyStopping(\n    monitor='val_loss',\n    patience=10,\n    restore_best_weights=True,\n    verbose=1\n)\n\ncallbacks_list = [\n    checkpoint_cb,\n    reduce_lr_cb,\n    early_stopping_cb,\n    TqdmCallback(verbose=1)\n]\n\n# ========== ПРОЦЕСС ОБУЧЕНИЯ ==========\n\nhistory = model.fit(\n    get_training_dataset(),\n    steps_per_epoch=STEPS_PER_EPOCH,\n    epochs=EPOCHS,\n    validation_data=get_validation_dataset(),\n    callbacks=callbacks_list,\n    verbose=1\n)\n\n\n# Или сохраняем только лучшую\nbest_checkpoint = ModelCheckpoint(\n    '/kaggle/working/best.weights.h5',\n    monitor='val_loss',\n    save_best_only=True,\n    save_weights_only=True,\n    mode='min',\n    verbose=1\n)\n\nprint(\"\\nОбучение завершено!\")\nprint(f\"  • Пройдено эпох: {len(history.history['loss'])}\")\nprint(f\"  • Лучшая val_loss: {min(history.history['val_loss']):.4f}\")\nprint(f\"  • Лучшая val_accuracy: {max(history.history['val_sparse_categorical_accuracy']):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T19:00:27.267315Z","iopub.execute_input":"2026-01-21T19:00:27.267939Z","iopub.status.idle":"2026-01-21T21:50:45.002041Z","shell.execute_reply.started":"2026-01-21T19:00:27.267911Z","shell.execute_reply":"2026-01-21T21:50:45.001222Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"save_model = model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T21:52:47.797106Z","iopub.execute_input":"2026-01-21T21:52:47.797695Z","iopub.status.idle":"2026-01-21T21:52:47.800782Z","shell.execute_reply.started":"2026-01-21T21:52:47.797646Z","shell.execute_reply":"2026-01-21T21:52:47.800081Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Визуализация процесса обучения","metadata":{}},{"cell_type":"code","source":"def plot_training_history(history):\n    fig, ax = plt.subplots(1, 2, figsize=(15, 5))\n    \n    # Loss\n    ax[0].plot(history.history['loss'], label='Train Loss')\n    ax[0].plot(history.history['val_loss'], label='Val Loss')\n    ax[0].set_title('Model Loss')\n    ax[0].set_xlabel('Epoch')\n    ax[0].set_ylabel('Loss')\n    ax[0].legend()\n    ax[0].grid(True)\n    \n    # Accuracy\n    if 'sparse_categorical_accuracy' in history.history:\n        acc_key = 'sparse_categorical_accuracy'\n    else:\n        acc_key = 'accuracy'\n    \n    ax[1].plot(history.history[acc_key], label='Train Accuracy')\n    ax[1].plot(history.history[f'val_{acc_key}'], label='Val Accuracy')\n    ax[1].set_title('Model Accuracy')\n    ax[1].set_xlabel('Epoch')\n    ax[1].set_ylabel('Accuracy')\n    ax[1].legend()\n    ax[1].grid(True)\n    \n    plt.tight_layout()\n    plt.show()\n\n# Построить графики\nplot_training_history(history)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Смотрим что на тесте и офрмируем сабмит","metadata":{}},{"cell_type":"code","source":"test_dataset = get_test_dataset(ordered=True)\n\ntest_ids = []\ntest_labels = []\n\n# Проходим по всем батчам\nfor images, ids in test_dataset:\n    predictions = model.predict(images, verbose=0)\n    labels = np.argmax(predictions, axis=1)\n    \n    test_ids.extend(ids.numpy())\n    test_labels.extend(labels)\n\n# ID из байтов в строки\ntest_ids = [id_.decode('utf-8') for id_ in test_ids]\n\nsubmission_df = pd.DataFrame({\n    'id': test_ids,\n    'label': test_labels\n})\n \nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"Файл submission.csv успешно создан!\")\nprint(\"Пример:\")\nprint(submission_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T21:53:06.958460Z","iopub.execute_input":"2026-01-21T21:53:06.958997Z","iopub.status.idle":"2026-01-21T21:55:21.017409Z","shell.execute_reply.started":"2026-01-21T21:53:06.958968Z","shell.execute_reply":"2026-01-21T21:55:21.016718Z"}},"outputs":[],"execution_count":null}]}