{"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":"gpu","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":"import numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport os\nfrom sklearn.metrics import f1_score, classification_report\nimport warnings\nwarnings.filterwarnings('ignore')\n\nSEED = 42\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\nBASE_PATH = '/kaggle/input/tpu-getting-started'\n\nIMAGE_SIZE = 512 \nBATCH_SIZE = 16\nEPOCHS = 40\nLEARNING_RATE = 0.001\n\nfor size in [512, 384, 331, 224, 192]:\n    data_path = f'{BASE_PATH}/tfrecords-jpeg-{size}x{size}'\n    if os.path.exists(data_path):\n        DATA_PATH = data_path\n        IMAGE_SIZE = size\n        break  ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:18.436923Z","iopub.execute_input":"2026-01-22T15:22:18.437244Z","iopub.status.idle":"2026-01-22T15:22:27.609605Z","shell.execute_reply.started":"2026-01-22T15:22:18.437215Z","shell.execute_reply":"2026-01-22T15:22:27.608745Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def apply_augmentation(img):\n    img = tf.image.random_flip_left_right(img)\n    \n    k = tf.random.uniform(shape=[], minval=0, maxval=4, dtype=tf.int32)\n    img = tf.image.rot90(img, k=k)\n    \n    if tf.random.uniform([]) < 0.7:\n        crop_size = tf.random.uniform([], 0.85, 0.95)\n        new_size = tf.cast(tf.cast(tf.shape(img)[0], tf.float32) * crop_size, tf.int32)\n        img = tf.image.random_crop(img, size=[new_size, new_size, 3])\n        img = tf.image.resize(img, [IMAGE_SIZE, IMAGE_SIZE])\n    \n    img = tf.image.random_brightness(img, max_delta=0.15)\n    img = tf.image.random_contrast(img, lower=0.8, upper=1.2)\n    img = tf.image.random_saturation(img, lower=0.8, upper=1.2)\n    img = tf.image.random_hue(img, max_delta=0.05)\n    \n    return img\n\ndef augment_image(image, label, augment_prob=0.8): \n    image = tf.cond(\n        tf.random.uniform([]) < augment_prob,\n        lambda: apply_augmentation(image),\n        lambda: image\n    )\n    \n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.6111Z","iopub.execute_input":"2026-01-22T15:22:27.611807Z","iopub.status.idle":"2026-01-22T15:22:27.61978Z","shell.execute_reply.started":"2026-01-22T15:22:27.611778Z","shell.execute_reply":"2026-01-22T15:22:27.619154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def decode_image(image_data):\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.image.resize(image, [IMAGE_SIZE, IMAGE_SIZE], method='bicubic')\n    image = tf.cast(image, tf.float32)\n    \n    image = tf.keras.applications.efficientnet.preprocess_input(image)\n    return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.620775Z","iopub.execute_input":"2026-01-22T15:22:27.620989Z","iopub.status.idle":"2026-01-22T15:22:27.63879Z","shell.execute_reply.started":"2026-01-22T15:22:27.620968Z","shell.execute_reply":"2026-01-22T15:22:27.638182Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def read_labeled_tfrecord(example):\n    feature_description = {\n        'image': tf.io.FixedLenFeature([], tf.string),\n        'class': tf.io.FixedLenFeature([], tf.int64),\n    }\n    example = tf.io.parse_single_example(example, feature_description)\n    image = decode_image(example['image'])\n    label = tf.cast(example['class'], tf.int32)\n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.640615Z","iopub.execute_input":"2026-01-22T15:22:27.640896Z","iopub.status.idle":"2026-01-22T15:22:27.650894Z","shell.execute_reply.started":"2026-01-22T15:22:27.640874Z","shell.execute_reply":"2026-01-22T15:22:27.650348Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def read_unlabeled_tfrecord(example):\n    feature_description = {\n        'image': tf.io.FixedLenFeature([], tf.string),\n        'id': tf.io.FixedLenFeature([], tf.string),\n    }\n    example = tf.io.parse_single_example(example, feature_description)\n    image = decode_image(example['image'])\n    idnum = example['id']\n    return image, idnum","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.651957Z","iopub.execute_input":"2026-01-22T15:22:27.652211Z","iopub.status.idle":"2026-01-22T15:22:27.668181Z","shell.execute_reply.started":"2026-01-22T15:22:27.652188Z","shell.execute_reply":"2026-01-22T15:22:27.667461Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_train_dataset(filenames, augment=True):\n    dataset = tf.data.TFRecordDataset(filenames)\n    dataset = dataset.map(read_labeled_tfrecord, num_parallel_calls=tf.data.AUTOTUNE)\n    if augment:\n        dataset = dataset.map(lambda x, y: augment_image(x, y), \n                             num_parallel_calls=tf.data.AUTOTUNE)\n    dataset = dataset.shuffle(3000, reshuffle_each_iteration=True)\n    dataset = dataset.repeat()\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(tf.data.AUTOTUNE)\n    return dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.66986Z","iopub.execute_input":"2026-01-22T15:22:27.670256Z","iopub.status.idle":"2026-01-22T15:22:27.682371Z","shell.execute_reply.started":"2026-01-22T15:22:27.670233Z","shell.execute_reply":"2026-01-22T15:22:27.681691Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_val_dataset(filenames):\n    dataset = tf.data.TFRecordDataset(filenames)\n    dataset = dataset.map(read_labeled_tfrecord, num_parallel_calls=tf.data.AUTOTUNE)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(tf.data.AUTOTUNE)\n    return dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.683359Z","iopub.execute_input":"2026-01-22T15:22:27.683651Z","iopub.status.idle":"2026-01-22T15:22:27.695551Z","shell.execute_reply.started":"2026-01-22T15:22:27.683631Z","shell.execute_reply":"2026-01-22T15:22:27.694828Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_test_dataset(filenames):\n    dataset = tf.data.TFRecordDataset(filenames)\n    dataset = dataset.map(read_unlabeled_tfrecord, num_parallel_calls=tf.data.AUTOTUNE)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(tf.data.AUTOTUNE)\n    return dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.696392Z","iopub.execute_input":"2026-01-22T15:22:27.696676Z","iopub.status.idle":"2026-01-22T15:22:27.708256Z","shell.execute_reply.started":"2026-01-22T15:22:27.696654Z","shell.execute_reply":"2026-01-22T15:22:27.707504Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def count_tfrecord_items(filenames):\n    n = 0\n    for fname in filenames:\n        import re\n        match = re.search(r'-([0-9]*)\\.tfrec$', fname)\n        if match:\n            n += int(match.group(1))\n    return n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.709359Z","iopub.execute_input":"2026-01-22T15:22:27.709641Z","iopub.status.idle":"2026-01-22T15:22:27.721362Z","shell.execute_reply.started":"2026-01-22T15:22:27.70962Z","shell.execute_reply":"2026-01-22T15:22:27.720595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def smoothed_sparse_categorical_crossentropy(y_true, y_pred, smoothing=0.1):\n    y_true = tf.cast(y_true, tf.int32)\n    num_classes = tf.shape(y_pred)[-1]\n    \n    y_true_one_hot = tf.one_hot(y_true, depth=num_classes)\n    y_true_smoothed = y_true_one_hot * (1.0 - smoothing) + smoothing / tf.cast(num_classes, tf.float32)\n    \n    loss = tf.keras.losses.categorical_crossentropy(y_true_smoothed, y_pred, from_logits=False)\n    return tf.reduce_mean(loss)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.724022Z","iopub.execute_input":"2026-01-22T15:22:27.724383Z","iopub.status.idle":"2026-01-22T15:22:27.736835Z","shell.execute_reply.started":"2026-01-22T15:22:27.724355Z","shell.execute_reply":"2026-01-22T15:22:27.736112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_model():\n    try:\n        base_model = tf.keras.applications.EfficientNetB3(\n            include_top=False,\n            weights='imagenet',\n            input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3),\n            pooling='avg'\n        )\n        print(\"Используем EfficientNetB3\")\n    except:\n        base_model = tf.keras.applications.EfficientNetB2(\n            include_top=False,\n            weights='imagenet',\n            input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3),\n            pooling='avg'\n        )\n        print(\"Используем EfficientNetB2\")\n    \n    base_model.trainable = False\n    \n    model = tf.keras.Sequential([\n        base_model,\n        tf.keras.layers.Dropout(0.3),\n        tf.keras.layers.Dense(1024, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(1e-4)),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.4),\n        tf.keras.layers.Dense(512, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(1e-4)),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.3),\n        tf.keras.layers.Dense(104, activation='softmax')\n    ])\n    \n    return model, base_model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.73767Z","iopub.execute_input":"2026-01-22T15:22:27.737864Z","iopub.status.idle":"2026-01-22T15:22:27.74721Z","shell.execute_reply.started":"2026-01-22T15:22:27.737846Z","shell.execute_reply":"2026-01-22T15:22:27.746513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_with_tta(model, images, n_augments=11):\n    predictions = []\n    \n    pred = model.predict(images, verbose=0)\n    predictions.append(pred * 2)\n    \n    aug_images = tf.image.flip_left_right(images)\n    pred_aug = model.predict(aug_images, verbose=0)\n    predictions.append(pred_aug)\n    \n    aug_images = tf.image.flip_up_down(images)\n    pred_aug = model.predict(aug_images, verbose=0)\n    predictions.append(pred_aug)\n    \n    aug_images = tf.image.flip_up_down(tf.image.flip_left_right(images))\n    pred_aug = model.predict(aug_images, verbose=0)\n    predictions.append(pred_aug)\n    \n    for k in [1, 2, 3]:\n        aug_images = tf.image.rot90(images, k=k)\n        pred_aug = model.predict(aug_images, verbose=0)\n        predictions.append(pred_aug)\n    \n    for _ in range(n_augments - 8):\n        aug_images = images\n        \n        if np.random.random() < 0.7:\n            aug_images = tf.image.random_brightness(aug_images, max_delta=0.08)\n        if np.random.random() < 0.7:\n            aug_images = tf.image.random_contrast(aug_images, lower=0.92, upper=1.08)\n        \n        pred_aug = model.predict(aug_images, verbose=0)\n        predictions.append(pred_aug)\n    \n    avg_prediction = np.mean(predictions, axis=0)\n    return avg_prediction","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.748735Z","iopub.execute_input":"2026-01-22T15:22:27.74901Z","iopub.status.idle":"2026-01-22T15:22:27.761529Z","shell.execute_reply.started":"2026-01-22T15:22:27.748991Z","shell.execute_reply":"2026-01-22T15:22:27.760755Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_files = tf.io.gfile.glob(f'{DATA_PATH}/train/*.tfrec')\nval_files = tf.io.gfile.glob(f'{DATA_PATH}/val/*.tfrec')\ntest_files = tf.io.gfile.glob(f'{DATA_PATH}/test/*.tfrec')\n\ntrain_ds = create_train_dataset(train_files, augment=True)\nval_ds = create_val_dataset(val_files)\n\ntrain_items = count_tfrecord_items(train_files)\nval_items = count_tfrecord_items(val_files)\n\ntrain_steps = train_items // BATCH_SIZE\nval_steps = val_items // BATCH_SIZE","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:27.762663Z","iopub.execute_input":"2026-01-22T15:22:27.763156Z","iopub.status.idle":"2026-01-22T15:22:30.391373Z","shell.execute_reply.started":"2026-01-22T15:22:27.763132Z","shell.execute_reply":"2026-01-22T15:22:30.390749Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model, base_model = create_model()\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.AdamW(learning_rate=LEARNING_RATE, weight_decay=1e-4),\n    loss=lambda y_true, y_pred: smoothed_sparse_categorical_crossentropy(y_true, y_pred, smoothing=0.1),\n    metrics=['accuracy']\n)\n\ncallbacks = [\n    tf.keras.callbacks.EarlyStopping(\n        monitor='val_accuracy',\n        patience=15,\n        restore_best_weights=True,\n        min_delta=0.0005,\n        verbose=1,\n        mode='max'\n    ),\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor='val_accuracy',\n        factor=0.5,\n        patience=5,\n        min_lr=1e-7,\n        verbose=1,\n        mode='max'\n    ),\n    tf.keras.callbacks.ModelCheckpoint(\n        filepath='/kaggle/working/best_model.keras',\n        monitor='val_accuracy',\n        save_best_only=True,\n        save_weights_only=False,\n        verbose=1,\n        mode='max'\n    )\n]\n\nhistory1 = model.fit(\n    train_ds,\n    steps_per_epoch=train_steps,\n    validation_data=val_ds,\n    validation_steps=val_steps,\n    epochs=20,\n    callbacks=callbacks,\n    verbose=1\n)\n\nbase_model.trainable = True\n\nfor layer in base_model.layers[:-100]:\n    layer.trainable = False\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.AdamW(learning_rate=LEARNING_RATE / 10, weight_decay=1e-5),\n    loss=lambda y_true, y_pred: smoothed_sparse_categorical_crossentropy(y_true, y_pred, smoothing=0.05),\n    metrics=['accuracy']\n)\n\nhistory2 = model.fit(\n    train_ds,\n    steps_per_epoch=train_steps,\n    validation_data=val_ds,\n    validation_steps=val_steps,\n    epochs=15,\n    callbacks=callbacks,\n    verbose=1\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T15:22:30.39241Z","iopub.execute_input":"2026-01-22T15:22:30.392691Z","iopub.status.idle":"2026-01-22T16:49:29.693604Z","shell.execute_reply.started":"2026-01-22T15:22:30.392654Z","shell.execute_reply":"2026-01-22T16:49:29.692737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if os.path.exists('/kaggle/working/best_model.keras'):\n    model = tf.keras.models.load_model('/kaggle/working/best_model.keras', safe_mode=False)\n    print(\"Загружена лучшая модель\")\nelse:\n    print(\"Лучшая модель не найдена, используем последнюю\")\n\nval_labels = []\nval_preds = []\n\nfor i, (images, labels) in enumerate(val_ds):\n    if i >= val_steps:\n        break\n    \n    preds = model.predict(images, verbose=0)\n    val_preds.extend(np.argmax(preds, axis=1))\n    val_labels.extend(labels.numpy())\n\nf1 = f1_score(val_labels, val_preds, average='macro')\naccuracy = np.mean(np.array(val_labels) == np.array(val_preds))\n\nprint(f\"F1-score (macro): {f1:.4f}\")\nprint(f\"Accuracy: {accuracy:.4f}\")\n\nclass_f1 = f1_score(val_labels, val_preds, average=None)\nclass_accuracy = []\nfor class_idx in range(104):\n    mask = np.array(val_labels) == class_idx\n    if np.any(mask):\n        class_acc = np.mean(np.array(val_preds)[mask] == class_idx)\n        class_accuracy.append(class_acc)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-22T16:49:29.694896Z","iopub.execute_input":"2026-01-22T16:49:29.695246Z","iopub.status.idle":"2026-01-22T16:50:38.067528Z","shell.execute_reply.started":"2026-01-22T16:49:29.695219Z","shell.execute_reply":"2026-01-22T16:50:38.066657Z"}},"outputs":[],"execution_count":null}]}