{"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":[{"sourceType":"competition","sourceId":5441,"databundleVersionId":38425,"isSourceIdPinned":false}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, random\nimport numpy as np\nimport pandas as pd\n\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\n\nprint(\"TF:\", tf.__version__)\nprint(\"GPU:\", tf.config.list_physical_devices(\"GPU\"))\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-22T17:44:38.023647Z","iopub.execute_input":"2026-02-22T17:44:38.023939Z","iopub.status.idle":"2026-02-22T17:44:55.116184Z","shell.execute_reply.started":"2026-02-22T17:44:38.023908Z","shell.execute_reply":"2026-02-22T17:44:55.115401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport zipfile\n\n# Рабочая директория\nDATA_DIR = \"/kaggle/working/data\"\nos.makedirs(DATA_DIR, exist_ok=True)\n\nTRAIN_ZIP = \"/kaggle/input/dogs-vs-cats-redux-kernels-edition/train.zip\"\nTEST_ZIP  = \"/kaggle/input/dogs-vs-cats-redux-kernels-edition/test.zip\"\n\n# Проверка наличия архивов\nif not os.path.exists(TRAIN_ZIP):\n    raise FileNotFoundError(f\"Не найден файл: {TRAIN_ZIP}\")\n\nif not os.path.exists(TEST_ZIP):\n    raise FileNotFoundError(f\"Не найден файл: {TEST_ZIP}\")\n\n# Распаковка train\nif not os.path.exists(os.path.join(DATA_DIR, \"train\")):\n    print(\"Extracting train.zip...\")\n    with zipfile.ZipFile(TRAIN_ZIP, \"r\") as zip_ref:\n        zip_ref.extractall(DATA_DIR)\n\n# Распаковка test\nif not os.path.exists(os.path.join(DATA_DIR, \"test\")):\n    print(\"Extracting test.zip...\")\n    with zipfile.ZipFile(TEST_ZIP, \"r\") as zip_ref:\n        zip_ref.extractall(DATA_DIR)\n\nTRAIN_DIR = os.path.join(DATA_DIR, \"train\")\nTEST_DIR  = os.path.join(DATA_DIR, \"test\")\n\nprint(\"TRAIN_DIR:\", TRAIN_DIR)\nprint(\"TEST_DIR :\", TEST_DIR)\nprint(\"Train images:\", len(os.listdir(TRAIN_DIR)))\nprint(\"Test images :\", len(os.listdir(TEST_DIR)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T17:47:31.293577Z","iopub.execute_input":"2026-02-22T17:47:31.293893Z","iopub.status.idle":"2026-02-22T17:47:31.326244Z","shell.execute_reply.started":"2026-02-22T17:47:31.293867Z","shell.execute_reply":"2026-02-22T17:47:31.325543Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_files = sorted([f for f in os.listdir(TRAIN_DIR) if f.endswith(\".jpg\")])\n\ndef infer_label(fname: str) -> int:\n    # В redux train файлы вида cat.0.jpg / dog.0.jpg\n    if fname.startswith(\"cat.\"):\n        return 0\n    if fname.startswith(\"dog.\"):\n        return 1\n    # fallback\n    if \"cat\" in fname:\n        return 0\n    if \"dog\" in fname:\n        return 1\n    raise ValueError(f\"Не смог определить класс для файла: {fname}\")\n\ndf = pd.DataFrame({\n    \"filename\": train_files,\n    \"label\": [infer_label(f) for f in train_files],\n})\ndf[\"filepath\"] = df[\"filename\"].apply(lambda x: os.path.join(TRAIN_DIR, x))\n\nprint(df.head())\nprint(df[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T17:49:09.052812Z","iopub.execute_input":"2026-02-22T17:49:09.053368Z","iopub.status.idle":"2026-02-22T17:49:09.123181Z","shell.execute_reply.started":"2026-02-22T17:49:09.053339Z","shell.execute_reply":"2026-02-22T17:49:09.122496Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\ntrain_df, val_df = train_test_split(\n    df,\n    test_size=0.2,\n    random_state=SEED,\n    stratify=df[\"label\"]\n)\n\nprint(\"train:\", len(train_df), \"val:\", len(val_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T17:49:23.422535Z","iopub.execute_input":"2026-02-22T17:49:23.422830Z","iopub.status.idle":"2026-02-22T17:49:23.570060Z","shell.execute_reply.started":"2026-02-22T17:49:23.422805Z","shell.execute_reply":"2026-02-22T17:49:23.569295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_SIZE = 224\nBATCH_SIZE = 32\nAUTOTUNE = tf.data.AUTOTUNE\n\naugment = keras.Sequential([\n    layers.RandomFlip(\"horizontal\"),\n    layers.RandomRotation(0.08),\n    layers.RandomZoom(0.12),\n    layers.RandomTranslation(0.08, 0.08),\n], name=\"augment\")\n\ndef load_image(path, label):\n    img = tf.io.read_file(path)\n    img = tf.io.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, (IMG_SIZE, IMG_SIZE))\n    img = tf.cast(img, tf.float32) / 255.0\n    return img, tf.cast(label, tf.float32)\n\ndef make_ds(dataframe, training: bool):\n    paths = dataframe[\"filepath\"].values\n    labels = dataframe[\"label\"].values\n\n    ds = tf.data.Dataset.from_tensor_slices((paths, labels))\n    if training:\n        ds = ds.shuffle(len(dataframe), seed=SEED, reshuffle_each_iteration=True)\n\n    ds = ds.map(load_image, num_parallel_calls=AUTOTUNE)\n\n    if training:\n        ds = ds.map(lambda x, y: (augment(x, training=True), y), num_parallel_calls=AUTOTUNE)\n\n    ds = ds.batch(BATCH_SIZE)\n    ds = ds.prefetch(AUTOTUNE)\n    return ds\n\ntrain_ds = make_ds(train_df, training=True)\nval_ds   = make_ds(val_df, training=False)\n\nfor x, y in train_ds.take(1):\n    print(\"batch x:\", x.shape, \"batch y:\", y.shape, \"labels example:\", y[:10].numpy())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T17:49:33.925505Z","iopub.execute_input":"2026-02-22T17:49:33.926525Z","iopub.status.idle":"2026-02-22T17:49:36.602458Z","shell.execute_reply.started":"2026-02-22T17:49:33.926490Z","shell.execute_reply":"2026-02-22T17:49:36.601597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.applications import MobileNetV2\n\nbase = MobileNetV2(\n    include_top=False,\n    weights=\"imagenet\",\n    input_shape=(IMG_SIZE, IMG_SIZE, 3)\n)\nbase.trainable = False\n\ninputs = keras.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\nx = base(inputs, training=False)\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.Dropout(0.3)(x)\noutputs = layers.Dense(1, activation=\"sigmoid\")(x)\n\nmodel = keras.Model(inputs, outputs)\n\nmodel.compile(\n    optimizer=keras.optimizers.Adam(1e-4),\n    loss=\"binary_crossentropy\",\n    metrics=[keras.metrics.BinaryAccuracy(name=\"acc\")]\n)\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T17:50:49.200865Z","iopub.execute_input":"2026-02-22T17:50:49.201190Z","iopub.status.idle":"2026-02-22T17:50:50.410905Z","shell.execute_reply.started":"2026-02-22T17:50:49.201165Z","shell.execute_reply":"2026-02-22T17:50:50.410306Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"callbacks = [\n    keras.callbacks.EarlyStopping(monitor=\"val_loss\", patience=3, restore_best_weights=True),\n    keras.callbacks.ReduceLROnPlateau(monitor=\"val_loss\", factor=0.5, patience=2, min_lr=1e-7),\n]\n\nEPOCHS_STAGE1 = 12\n\nhistory1 = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS_STAGE1,\n    callbacks=callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T17:51:18.204174Z","iopub.execute_input":"2026-02-22T17:51:18.204539Z","iopub.status.idle":"2026-02-22T18:19:05.609932Z","shell.execute_reply.started":"2026-02-22T17:51:18.204511Z","shell.execute_reply":"2026-02-22T18:19:05.609088Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base.trainable = True\n\nfine_tune_at = len(base.layers) - 30\nfor layer in base.layers[:fine_tune_at]:\n    layer.trainable = False\n\nmodel.compile(\n    optimizer=keras.optimizers.Adam(1e-5),\n    loss=\"binary_crossentropy\",\n    metrics=[keras.metrics.BinaryAccuracy(name=\"acc\")]\n)\n\nEPOCHS_STAGE2 = 5\n\nhistory2 = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS_STAGE2,\n    callbacks=callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T18:20:17.811820Z","iopub.execute_input":"2026-02-22T18:20:17.812176Z","iopub.status.idle":"2026-02-22T18:32:14.965866Z","shell.execute_reply.started":"2026-02-22T18:20:17.812146Z","shell.execute_reply":"2026-02-22T18:32:14.965117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import log_loss\n\ny_true = val_df[\"label\"].values.astype(np.float32)\ny_pred = model.predict(val_ds, verbose=1).ravel()\ny_pred = np.clip(y_pred, 1e-7, 1 - 1e-7)\n\nprint(\"Validation LogLoss:\", log_loss(y_true, y_pred))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T18:33:42.835900Z","iopub.execute_input":"2026-02-22T18:33:42.836475Z","iopub.status.idle":"2026-02-22T18:33:55.952451Z","shell.execute_reply.started":"2026-02-22T18:33:42.836439Z","shell.execute_reply":"2026-02-22T18:33:55.951578Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_files = sorted([f for f in os.listdir(TEST_DIR) if f.endswith(\".jpg\")])\ntest_paths = [os.path.join(TEST_DIR, f) for f in test_files]\n\ndef load_test_image(path):\n    img = tf.io.read_file(path)\n    img = tf.io.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, (IMG_SIZE, IMG_SIZE))\n    img = tf.cast(img, tf.float32) / 255.0\n    return img\n\ntest_ds = tf.data.Dataset.from_tensor_slices(test_paths)\ntest_ds = test_ds.map(load_test_image, num_parallel_calls=AUTOTUNE)\ntest_ds = test_ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n\ntest_pred = model.predict(test_ds, verbose=1).ravel()\ntest_pred = np.clip(test_pred, 1e-7, 1 - 1e-7)\n\nids = [int(os.path.splitext(f)[0]) for f in test_files]  # \"1234.jpg\" -> 1234\n\nsubmission = pd.DataFrame({\"id\": ids, \"label\": test_pred})\nsubmission = submission.sort_values(\"id\")\nsubmission.to_csv(\"submission.csv\", index=False)\n\nsubmission.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T18:34:06.604203Z","iopub.execute_input":"2026-02-22T18:34:06.604587Z","iopub.status.idle":"2026-02-22T18:34:27.330846Z","shell.execute_reply.started":"2026-02-22T18:34:06.604558Z","shell.execute_reply":"2026-02-22T18:34:27.330209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"submission exists:\", os.path.exists(\"submission.csv\"))\nprint(\"size:\", os.path.getsize(\"submission.csv\"), \"bytes\")\npd.read_csv(\"submission.csv\").head(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T18:35:00.190599Z","iopub.execute_input":"2026-02-22T18:35:00.190936Z","iopub.status.idle":"2026-02-22T18:35:00.208689Z","shell.execute_reply.started":"2026-02-22T18:35:00.190907Z","shell.execute_reply":"2026-02-22T18:35:00.207941Z"}},"outputs":[],"execution_count":null}]}