{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":5441,"databundleVersionId":38425,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-23T18:09:19.381441Z","iopub.execute_input":"2025-10-23T18:09:19.381812Z","iopub.status.idle":"2025-10-23T18:09:19.396669Z","shell.execute_reply.started":"2025-10-23T18:09:19.381780Z","shell.execute_reply":"2025-10-23T18:09:19.395299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Импорт всех необходимых библиотек\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers, models, applications, optimizers\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport os\nfrom sklearn.model_selection import train_test_split\nimport zipfile\nimport pandas as pd\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport seaborn as sns\n\n# Проверяем доступность GPU\nprint(\"Доступные устройства:\", tf.config.list_physical_devices())\nprint(\"Используется GPU:\", \"Да\" if tf.test.is_gpu_available() else \"Нет\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T18:09:31.631477Z","iopub.execute_input":"2025-10-23T18:09:31.631794Z","iopub.status.idle":"2025-10-23T18:09:51.829829Z","shell.execute_reply.started":"2025-10-23T18:09:31.631771Z","shell.execute_reply":"2025-10-23T18:09:51.828632Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Загрузка и подготовка данных","metadata":{}},{"cell_type":"code","source":"# Разархивируем train.zip\nwith zipfile.ZipFile('/kaggle/input/dogs-vs-cats-redux-kernels-edition/train.zip', 'r') as zip_ref:\n    zip_ref.extractall('./')\n\n# Создаем директории для структурированного хранения данных\nbase_dir = 'cats_vs_dogs'\ntrain_dir = os.path.join(base_dir, 'train')\nvalidation_dir = os.path.join(base_dir, 'validation')\ntest_dir = os.path.join(base_dir, 'test')\n\nos.makedirs(base_dir, exist_ok=True)\nos.makedirs(train_dir, exist_ok=True)\nos.makedirs(validation_dir, exist_ok=True)\nos.makedirs(test_dir, exist_ok=True)\n\n# Создаем поддиректории для каждого класса\ntrain_cats_dir = os.path.join(train_dir, 'cats')\ntrain_dogs_dir = os.path.join(train_dir, 'dogs')\nvalidation_cats_dir = os.path.join(validation_dir, 'cats')\nvalidation_dogs_dir = os.path.join(validation_dir, 'dogs')\ntest_cats_dir = os.path.join(test_dir, 'cats')\ntest_dogs_dir = os.path.join(test_dir, 'dogs')\n\nfor dir_path in [train_cats_dir, train_dogs_dir, validation_cats_dir, \n                 validation_dogs_dir, test_cats_dir, test_dogs_dir]:\n    os.makedirs(dir_path, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T18:09:58.340821Z","iopub.execute_input":"2025-10-23T18:09:58.341751Z","iopub.status.idle":"2025-10-23T18:10:11.158289Z","shell.execute_reply.started":"2025-10-23T18:09:58.341716Z","shell.execute_reply":"2025-10-23T18:10:11.157382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Функция для разделения исходных данных на train/validation/test\ndef organize_dataset(source_dir, train_size=0.7, val_size=0.15, test_size=0.15):\n    \"\"\"\n    Организует данные в структурированные папки для обучения, валидации и тестирования.\n    Исходные данные должны быть в формате: 'cat.0.jpg', 'dog.0.jpg' и т.д.\n    \"\"\"\n    cat_images = [f for f in os.listdir(source_dir) if f.startswith('cat')]\n    dog_images = [f for f in os.listdir(source_dir) if f.startswith('dog')]\n    \n    # Разделяем данные для кошек\n    cat_train, cat_temp = train_test_split(cat_images, train_size=train_size, random_state=42)\n    cat_val, cat_test = train_test_split(cat_temp, train_size=val_size/(val_size+test_size), random_state=42)\n    \n    # Разделяем данные для собак\n    dog_train, dog_temp = train_test_split(dog_images, train_size=train_size, random_state=42)\n    dog_val, dog_test = train_test_split(dog_temp, train_size=val_size/(val_size+test_size), random_state=42)\n    \n    # Копируем файлы в соответствующие директории\n    for image in cat_train:\n        os.rename(os.path.join(source_dir, image), os.path.join(train_cats_dir, image))\n    for image in dog_train:\n        os.rename(os.path.join(source_dir, image), os.path.join(train_dogs_dir, image))\n    for image in cat_val:\n        os.rename(os.path.join(source_dir, image), os.path.join(validation_cats_dir, image))\n    for image in dog_val:\n        os.rename(os.path.join(source_dir, image), os.path.join(validation_dogs_dir, image))\n    for image in cat_test:\n        os.rename(os.path.join(source_dir, image), os.path.join(test_cats_dir, image))\n    for image in dog_test:\n        os.rename(os.path.join(source_dir, image), os.path.join(test_dogs_dir, image))\n\n# Вызываем функцию организации данных (выполнить один раз)\norganize_dataset('train')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T18:10:40.495191Z","iopub.execute_input":"2025-10-23T18:10:40.495649Z","iopub.status.idle":"2025-10-23T18:10:41.181371Z","shell.execute_reply.started":"2025-10-23T18:10:40.495621Z","shell.execute_reply":"2025-10-23T18:10:41.180158Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Параметры модели\nIMG_SIZE = (160, 160)\nBATCH_SIZE = 32\nLEARNING_RATE = 0.0001\n\n# Создаем генераторы данных с аугментацией\ntrain_datagen = ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=20,\n    width_shift_range=0.2,\n    height_shift_range=0.2,\n    horizontal_flip=True,\n    zoom_range=0.2,\n    shear_range=0.2\n)\n\nval_test_datagen = ImageDataGenerator(rescale=1./255)\n\n# Создаем генераторы\ntrain_generator = train_datagen.flow_from_directory(\n    train_dir,\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='binary'  # Это создаст метки с shape (batch_size,)\n)\n\nvalidation_generator = val_test_datagen.flow_from_directory(\n    validation_dir,\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='binary'\n)\n\ntest_generator = val_test_datagen.flow_from_directory(\n    test_dir,\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='binary',\n    shuffle=False\n)\n\nprint(\"Классы:\", train_generator.class_indices)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T18:12:05.197772Z","iopub.execute_input":"2025-10-23T18:12:05.198094Z","iopub.status.idle":"2025-10-23T18:12:06.078189Z","shell.execute_reply.started":"2025-10-23T18:12:05.198069Z","shell.execute_reply":"2025-10-23T18:12:06.077221Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_model_fixed(base_model_type='mobilenet_v2'):\n    \"\"\"\n    Создает модель с исправленной архитектурой для совместимости с binary_crossentropy\n    \"\"\"\n    \n    # Выбираем базовую модель\n    if base_model_type == 'mobilenet_v2':\n        base_model = applications.MobileNetV2(\n            input_shape=IMG_SIZE + (3,),\n            include_top=False,\n            weights='imagenet'\n        )\n    elif base_model_type == 'vgg16':\n        base_model = applications.VGG16(\n            input_shape=IMG_SIZE + (3,),\n            include_top=False,\n            weights='imagenet'\n        )\n    else:\n        raise ValueError(\"Поддерживаемые модели: 'mobilenet_v2' или 'vgg16'\")\n    \n    # Замораживаем веса базовой модели\n    base_model.trainable = False\n    \n    # Создаем полную модель с исправлением размерности выхода\n    inputs = keras.Input(shape=IMG_SIZE + (3,))\n    x = base_model(inputs, training=False)\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.2)(x)\n    x = layers.Dense(128, activation='relu')(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Dropout(0.5)(x)\n    \n    # Выходной слой - используем 1 нейрон с sigmoid для бинарной классификации\n    # Но теперь мы явно задаем правильную размерность\n    outputs = layers.Dense(1, activation='sigmoid', name='output_layer')(x)\n    \n    model = keras.Model(inputs, outputs)\n    \n    # Компилируем модель\n    model.compile(\n        optimizer=optimizers.Adam(learning_rate=LEARNING_RATE),\n        loss='binary_crossentropy',  # Автоматически работает с (batch_size,) и (batch_size, 1)\n        metrics=['accuracy']\n    )\n    \n    return model\n\n# Создаем исправленную модель\nmodel = create_model_fixed('mobilenet_v2')\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T18:12:24.027639Z","iopub.execute_input":"2025-10-23T18:12:24.027967Z","iopub.status.idle":"2025-10-23T18:12:26.532061Z","shell.execute_reply.started":"2025-10-23T18:12:24.027943Z","shell.execute_reply":"2025-10-23T18:12:26.530979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Callbacks для улучшения обучения\ncallbacks = [\n    keras.callbacks.EarlyStopping(\n        monitor='val_loss',\n        patience=5,\n        restore_best_weights=True,\n        verbose=1\n    ),\n    keras.callbacks.ModelCheckpoint(\n        'best_cats_vs_dogs_model.h5',\n        monitor='val_loss',\n        save_best_only=True,\n        verbose=1\n    ),\n    keras.callbacks.ReduceLROnPlateau(\n        monitor='val_loss',\n        factor=0.2,\n        patience=3,\n        min_lr=1e-7,\n        verbose=1\n    )\n]\n\n# Проверяем, что генераторы работают правильно\nprint(\"Проверка генераторов:\")\nfor images_batch, labels_batch in train_generator:\n    print(f\"Размер батча изображений: {images_batch.shape}\")\n    print(f\"Размер батча меток: {labels_batch.shape}\")\n    print(f\"Диапазон значений изображений: [{images_batch.min():.3f}, {images_batch.max():.3f}]\")\n    print(f\"Примеры меток: {labels_batch[:5]}\")\n    break\n\n# Первый этап: обучение только новых слоев\nprint(\"\\n=== ПЕРВЫЙ ЭТАП: Обучение только новых слоев ===\")\nhistory = model.fit(\n    train_generator,\n    steps_per_epoch=len(train_generator),\n    epochs=2,\n    validation_data=validation_generator,\n    validation_steps=len(validation_generator),\n    callbacks=callbacks,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T18:12:39.628705Z","iopub.execute_input":"2025-10-23T18:12:39.629035Z","iopub.status.idle":"2025-10-23T18:47:29.369001Z","shell.execute_reply.started":"2025-10-23T18:12:39.629014Z","shell.execute_reply":"2025-10-23T18:47:29.367682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def fine_tune_model_fixed(model, base_model_type='mobilenet_v2'):\n    \"\"\"\n    Размораживает часть слоев базовой модели для тонкой настройки\n    \"\"\"\n    \n    base_model = model.layers[1]  # Базовая модель - второй слой (после Input)\n    base_model.trainable = True\n    \n    # Размораживаем только верхние слои\n    if base_model_type == 'mobilenet_v2':\n        fine_tune_at = 100  # Размораживаем последние слои\n    else:\n        fine_tune_at = len(base_model.layers) // 2\n    \n    # Замораживаем первые слои\n    for layer in base_model.layers[:fine_tune_at]:\n        layer.trainable = False\n    \n    # Перекомпилируем с меньшим learning rate\n    model.compile(\n        optimizer=optimizers.Adam(learning_rate=LEARNING_RATE/10),\n        loss='binary_crossentropy',\n        metrics=['accuracy']\n    )\n    \n    print(f\"Разморожено слоев: {sum([l.trainable for l in base_model.layers])}\")\n    return model\n\n# Применяем fine-tuning\nprint(\"\\n=== ВТОРОЙ ЭТАП: Fine-tuning ===\")\nmodel = fine_tune_model_fixed(model, 'mobilenet_v2')\n\n# Продолжаем обучение с fine-tuning\nhistory_fine = model.fit(\n    train_generator,\n    steps_per_epoch=len(train_generator),\n    epochs=2,\n    validation_data=validation_generator,\n    validation_steps=len(validation_generator),\n    callbacks=callbacks,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T18:52:27.807611Z","iopub.execute_input":"2025-10-23T18:52:27.808578Z","iopub.status.idle":"2025-10-23T19:07:29.547622Z","shell.execute_reply.started":"2025-10-23T18:52:27.808543Z","shell.execute_reply":"2025-10-23T19:07:29.546444Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Визуализация и оценка\ndef plot_training_history(history, fine_history=None):\n    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n    \n    if fine_history is not None:\n        # Объединяем историю\n        acc = history.history['accuracy'] + fine_history.history['accuracy']\n        val_acc = history.history['val_accuracy'] + fine_history.history['val_accuracy']\n        loss = history.history['loss'] + fine_history.history['loss']\n        val_loss = history.history['val_loss'] + fine_history.history['val_loss']\n        epochs_range = range(1, len(acc) + 1)\n    else:\n        acc = history.history['accuracy']\n        val_acc = history.history['val_accuracy']\n        loss = history.history['loss']\n        val_loss = history.history['val_loss']\n        epochs_range = range(1, len(acc) + 1)\n    \n    # График точности\n    ax1.plot(epochs_range, acc, 'b-', label='Training Accuracy')\n    ax1.plot(epochs_range, val_acc, 'r-', label='Validation Accuracy')\n    ax1.set_title('Training and Validation Accuracy')\n    ax1.set_xlabel('Epochs')\n    ax1.set_ylabel('Accuracy')\n    ax1.legend()\n    ax1.grid(True)\n    \n    # График потерь\n    ax2.plot(epochs_range, loss, 'b-', label='Training Loss')\n    ax2.plot(epochs_range, val_loss, 'r-', label='Validation Loss')\n    ax2.set_title('Training and Validation Loss')\n    ax2.set_xlabel('Epochs')\n    ax2.set_ylabel('Loss')\n    ax2.legend()\n    ax2.grid(True)\n    \n    plt.tight_layout()\n    plt.show()\n\nplot_training_history(history, history_fine)\n\n# Оценка модели на тестовых данных\nprint(\"\\n=== ОЦЕНКА НА ТЕСТОВЫХ ДАННЫХ ===\")\ntest_loss, test_accuracy = model.evaluate(test_generator)\nprint(f\"Test Loss: {test_loss:.4f}\")\nprint(f\"Test Accuracy: {test_accuracy:.4f}\")\n\n# Вычисляем Log Loss\ntest_predictions = model.predict(test_generator)\ntest_true_labels = test_generator.classes\n\n# Исправляем размерности\ntest_predictions_flat = test_predictions.flatten()  # Преобразуем (n, 1) в (n,)\n\n# Вычисляем binary crossentropy (log loss) с правильными размерностями\nlog_loss = keras.losses.binary_crossentropy(\n    tf.convert_to_tensor(test_true_labels, dtype=tf.float32),\n    tf.convert_to_tensor(test_predictions_flat, dtype=tf.float32)\n)\nlog_loss = tf.reduce_mean(log_loss).numpy()\n\nprint(f\"Log Loss: {log_loss:.4f}\")\n\nif log_loss < 0.3:\n    print(\"УСПЕХ: Log Loss < 0.3 достигнут!\")\nelse:\n    print(f\"Log Loss = {log_loss:.4f} > 0.3\")\n    print(\"Необходимо улучшение!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T19:08:02.727135Z","iopub.execute_input":"2025-10-23T19:08:02.727944Z","iopub.status.idle":"2025-10-23T19:10:13.667343Z","shell.execute_reply.started":"2025-10-23T19:08:02.727910Z","shell.execute_reply":"2025-10-23T19:10:13.666111Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Предсказания и матрица ошибок\ntest_predictions = model.predict(test_generator)\npredicted_classes = (test_predictions > 0.5).astype(\"int32\").flatten()\ntrue_classes = test_generator.classes\n\n# Матрица ошибок\ncm = confusion_matrix(true_classes, predicted_classes)\nplt.figure(figsize=(8, 6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n            xticklabels=['Cats', 'Dogs'], \n            yticklabels=['Cats', 'Dogs'])\nplt.title('Confusion Matrix')\nplt.ylabel('True Label')\nplt.xlabel('Predicted Label')\nplt.show()\n\n# Отчет по классификации\nprint(\"\\nОтчет по классификации:\")\nprint(classification_report(true_classes, predicted_classes, \n                          target_names=['Cats', 'Dogs']))\n\n# Визуализируем примеры предсказаний\ndef plot_sample_predictions(num_samples=12):\n    fig, axes = plt.subplots(3, 4, figsize=(15, 12))\n    axes = axes.ravel()\n    \n    test_files = test_generator.filenames\n    \n    for i in range(num_samples):\n        # Берем случайные примеры\n        idx = np.random.randint(0, len(test_files))\n        \n        # Загружаем изображение\n        img_path = os.path.join(test_dir, test_files[idx])\n        img = plt.imread(img_path)\n        \n        # Предсказание\n        actual_label = 'cat' if true_classes[idx] == 0 else 'dog'\n        predicted_label = 'cat' if predicted_classes[idx] == 0 else 'dog'\n        confidence = test_predictions[idx][0] if predicted_label == 'dog' else 1 - test_predictions[idx][0]\n        \n        # Отображаем\n        axes[i].imshow(img)\n        color = 'green' if actual_label == predicted_label else 'red'\n        axes[i].set_title(f'True: {actual_label}\\nPred: {predicted_label}\\nConf: {confidence:.3f}', \n                         color=color, fontsize=10)\n        axes[i].axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\nplot_sample_predictions()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T19:10:38.782753Z","iopub.execute_input":"2025-10-23T19:10:38.783077Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Разархивируем test.zip \nwith zipfile.ZipFile('/kaggle/input/dogs-vs-cats-redux-kernels-edition/test.zip', 'r') as zip_ref:\n    zip_ref.extractall('./')\n\n# Проверяем содержимое test директории\nextracted_test_dir = 'test'  # Папка, созданная после распаковки test.zip\ntest_files = os.listdir(extracted_test_dir)\nprint(f\"Найдено {len(test_files)} тестовых изображений\")\nprint(\"Примеры файлов:\", test_files[:5])\n\n# Создаем генератор для тестовых данных конкурса\ncompetition_test_datagen = ImageDataGenerator(rescale=1./255)\n\ncompetition_test_generator = competition_test_datagen.flow_from_directory(\n    directory='./',  # Указываем корневую директорию\n    classes=[extracted_test_dir],  # Указываем поддиректорию с изображениями\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=None,  # Важно: нет меток классов\n    shuffle=False     # Важно: сохраняем порядок файлов для соответствия с sample_submission\n)\n\n# Проверяем генератор\nprint(f\"Всего тестовых изображений для конкурса: {competition_test_generator.samples}\")\n\n# Делаем предсказания на тестовых данных конкурса\nprint(\"\\n=== ПРЕДСКАЗАНИЯ ДЛЯ KAGGLE ===\")\ncompetition_predictions = model.predict(competition_test_generator, \n                                      verbose=1, \n                                      steps=len(competition_test_generator))\n\n# competition_predictions содержит вероятности класса 1 (собаки)\n# Для Kaggle нам нужно именно это - вероятность что изображение собаки\nkaggle_predictions = competition_predictions.flatten()\n\nprint(f\"Форма предсказаний: {kaggle_predictions.shape}\")\nprint(f\"Примеры предсказаний: {kaggle_predictions[:10]}\")\nprint(f\"Диапазон предсказаний: [{kaggle_predictions.min():.3f}, {kaggle_predictions.max():.3f}]\")\n\n# Получаем имена файлов в том же порядке, что и генератор\ntest_filenames = competition_test_generator.filenames\n\nprint(f\"\\nПримеры имен файлов: {test_filenames[:5]}\")\n\n# Извлекаем только числовые ID из имен файлов (убираем расширение .jpg)\n# Имена файлов выглядят как 'test/1234.jpg', нам нужно '1234'\ntest_ids = [os.path.splitext(os.path.basename(f))[0] for f in test_filenames]\n\nprint(f\"Примеры ID: {test_ids[:5]}\")\n\n# Создаем DataFrame для submission\nsubmission_df = pd.DataFrame({\n    'id': test_ids,\n    'label': kaggle_predictions\n})\n\nprint(\"\\nПервые 10 строк submission файла:\")\nprint(submission_df.head(10))\n\n# Проверяем статистику предсказаний\nprint(f\"\\nСтатистика предсказаний:\")\nprint(f\"Средняя вероятность: {submission_df['label'].mean():.4f}\")\nprint(f\"Медианная вероятность: {submission_df['label'].median():.4f}\")\nprint(f\"Стандартное отклонение: {submission_df['label'].std():.4f}\")\n\n# Визуализируем распределение предсказаний\nplt.figure(figsize=(10, 6))\nplt.hist(submission_df['label'], bins=50, alpha=0.7, edgecolor='black')\nplt.title('Распределение предсказанных вероятностей для тестового набора')\nplt.xlabel('Вероятность (собака)')\nplt.ylabel('Количество изображений')\nplt.grid(True, alpha=0.3)\nplt.show()\n\n# Сохраняем submission файл\nsubmission_filename = 'submission.csv'\nsubmission_df.to_csv(submission_filename, index=False)\n\nprint(f\"\\n✅ Submission файл сохранен как: {submission_filename}\")\nprint(f\"Размер файла: {os.path.getsize(submission_filename)} байт\")\n\n# Проверяем содержимое сохраненного файла\nsaved_submission = pd.read_csv(submission_filename)\nprint(f\"\\nПроверка сохраненного файла:\")\nprint(f\"Количество строк: {len(saved_submission)}\")\nprint(f\"Первые 5 строк:\")\nprint(saved_submission.head())\n\n# Дополнительная проверка: сравниваем с sample_submission\nsample_submission_path = '/kaggle/input/dogs-vs-cats-redux-kernels-edition/sample_submission.csv'\nif os.path.exists(sample_submission_path):\n    sample_submission = pd.read_csv(sample_submission_path)\n    print(f\"\\n=== СРАВНЕНИЕ С SAMPLE SUBMISSION ===\")\n    print(f\"Sample submission размер: {len(sample_submission)} строк\")\n    print(\"Sample submission первые 5 строк:\")\n    print(sample_submission.head())\n    \n    # Проверяем, что мои ID совпадают с sample (в том же порядке)\n    if len(saved_submission) == len(sample_submission):\n        print(\"Количество строк совпадает с sample submission\")\n    else:\n        print(\"Количество строк НЕ совпадает с sample submission\")\n        \n    # Проверяем диапазон предсказаний\n    print(f\"Наш диапазон предсказаний: [{saved_submission['label'].min():.3f}, {saved_submission['label'].max():.3f}]\")\n    print(f\"Sample диапазон: [{sample_submission['label'].min():.3f}, {sample_submission['label'].max():.3f}]\")\n\n# Создаем файл с метаинформацией о модели\nwith open('model_info.txt', 'w') as f:\n    f.write(f\"Модель: Cats vs Dogs Classifier\\n\")\n    f.write(f\"Базовая архитектура: MobileNetV2\\n\")\n    f.write(f\"Размер изображения: {IMG_SIZE}\\n\")\n    f.write(f\"Final Train Accuracy: {history.history['accuracy'][-1]:.4f}\\n\")\n    f.write(f\"Final Validation Accuracy: {history.history['val_accuracy'][-1]:.4f}\\n\")\n    f.write(f\"Test Accuracy: {test_accuracy:.4f}\\n\")\n    f.write(f\"Log Loss: {log_loss:.4f}\\n\")\n    f.write(f\"Количество тестовых изображений: {len(submission_df)}\\n\")\n    f.write(f\"Дата создания: {pd.Timestamp.now()}\\n\")\n\nprint(\"\\nВСЕ ГОТОВО ДЛЯ ОТПРАВКИ НА KAGGLE!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-23T19:27:36.713301Z","iopub.execute_input":"2025-10-23T19:27:36.713696Z","iopub.status.idle":"2025-10-23T19:30:10.002511Z","shell.execute_reply.started":"2025-10-23T19:27:36.713672Z","shell.execute_reply":"2025-10-23T19:30:10.001387Z"}},"outputs":[],"execution_count":null}]}