{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import math, re, os\nimport numpy as np\nimport tensorflow as tf\n\nprint(\"Tensorflow version: \" + tf.__version__)\n\n# 自动硬件检测雷达\ntry:\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.TPUStrategy(tpu)\n    print(\"✅ 成功连上 TPU！\")\nexcept ValueError:\n    print(\"⚠️ 未检测到 TPU，正在呼叫 GPU 支援...\")\n    # 扫描并获取当前的 GPU 列表\n    gpus = tf.config.list_logical_devices('GPU')\n    \n    if len(gpus) > 1:\n        # 如果你选了 GPU T4 x2，这里会启动双显卡并行策略！\n        strategy = tf.distribute.MirroredStrategy()\n        print(f\"✅ 狂飙吧！成功连上 {len(gpus)} 张 GPU！\")\n    elif len(gpus) == 1:\n        # 如果你选了 GPU P100\n        strategy = tf.distribute.get_strategy()\n        print(\"✅ 成功连上 1 张超级 GPU！\")\n    else:\n        strategy = tf.distribute.get_strategy()\n        print(\"❌ 哎呀，只找到了 CPU。\")\n\nprint(\"REPLICAS (同步核心数): \", strategy.num_replicas_in_sync)\n\n# GPU 也是直接读取本地极速硬盘，拒绝网络延迟！\nGCS_DS_PATH = '/kaggle/input/tpu-getting-started'\nprint(\"最终使用的数据路径为: \", GCS_DS_PATH)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# 第 2 个代码块：终极全盘扫描雷达版 (减负版)\n# ==========================================\nimport os\nimport tensorflow as tf\n\nIMAGE_SIZE = [512, 512]\n# 【修改这里】：把 16 改成 8，这样双显卡总 Batch Size 变成 16\nBATCH_SIZE = 8 * strategy.num_replicas_in_sync\n\nprint(\"🔍 启动全盘雷达，寻找隐藏的数据文件夹...\")\ntarget_folder = f'tfrecords-jpeg-{IMAGE_SIZE[0]}x{IMAGE_SIZE[0]}'\nGCS_PATH = \"\"\n\nfor root, dirs, files in os.walk('/kaggle/input'):\n    if target_folder in dirs:\n        GCS_PATH = os.path.join(root, target_folder)\n        break\n\nif GCS_PATH == \"\":\n    print(\"❌ 警告：没找到数据！\")\nelse:\n    print(\"🎯 破案了！真实数据路径在这里:\", GCS_PATH)\n\nTRAINING_FILENAMES = tf.io.gfile.glob(GCS_PATH + '/train/*.tfrec')\nVALIDATION_FILENAMES = tf.io.gfile.glob(GCS_PATH + '/val/*.tfrec')\nTEST_FILENAMES = tf.io.gfile.glob(GCS_PATH + '/test/*.tfrec')\n\nprint(\"✅ 已找到 训练集 文件数量:\", len(TRAINING_FILENAMES))\nprint(\"✅ 已找到 验证集 文件数量:\", len(VALIDATION_FILENAMES))\nprint(\"🚀 全局 Batch Size 降级为:\", BATCH_SIZE)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# 第 3 个代码块：数据管道 (防爆内存 + 无限续杯终极版)\n# ==========================================\n\nCLASSES = 104\n\ndef decode_image(image_data):\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.cast(image, tf.float32) / 255.0\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\n    return image\n\ndef read_labeled_tfrecord(example):\n    LABELED_TFREC_FORMAT = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64),\n    }\n    example = tf.io.parse_single_example(example, LABELED_TFREC_FORMAT)\n    image = decode_image(example['image'])\n    label = tf.cast(example['class'], tf.int32)\n    return image, label\n\ndef read_unlabeled_tfrecord(example):\n    UNLABELED_TFREC_FORMAT = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"id\": tf.io.FixedLenFeature([], tf.string),\n    }\n    example = tf.io.parse_single_example(example, UNLABELED_TFREC_FORMAT)\n    image = decode_image(example['image'])\n    idnum = example['id']\n    return image, idnum\n\ndef load_dataset(filenames, labeled=True, ordered=False):\n    ignore_order = tf.data.Options()\n    if not ordered:\n        ignore_order.experimental_deterministic = False \n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=tf.data.experimental.AUTOTUNE)\n    dataset = dataset.with_options(ignore_order)\n    dataset = dataset.map(read_labeled_tfrecord if labeled else read_unlabeled_tfrecord, num_parallel_calls=tf.data.experimental.AUTOTUNE)\n    return dataset\n\ndef data_augment(image, label):\n    image = tf.image.random_flip_left_right(image)\n    image = tf.image.random_contrast(image, lower=0.8, upper=1.2)\n    image = tf.image.random_brightness(image, max_delta=0.1)\n    image = tf.image.random_saturation(image, lower=0.7, upper=1.3)\n    return image, label\n\ndef get_training_dataset():\n    dataset = load_dataset(TRAINING_FILENAMES, labeled=True)\n    dataset = dataset.map(data_augment, num_parallel_calls=tf.data.experimental.AUTOTUNE)\n    \n    # 🟢 【核心修复在这里！】加上 repeat()，让数据源源不断地供应给模型\n    dataset = dataset.repeat() \n    \n    # 保持防内存崩溃的设置\n    dataset = dataset.shuffle(512)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)\n    return dataset\n\ndef get_validation_dataset(ordered=False):\n    dataset = load_dataset(VALIDATION_FILENAMES, labeled=True, ordered=ordered)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)\n    return dataset\n\ndef get_test_dataset(ordered=False):\n    dataset = load_dataset(TEST_FILENAMES, labeled=False, ordered=ordered)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)\n    return dataset\n\nds_train = get_training_dataset()\nds_valid = get_validation_dataset()\nds_test = get_test_dataset(ordered=True)\n\nprint(\"✅ 第 3 个代码块（无限续杯防爆版）已加载完毕！\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# 花卉分类引擎 (Kaggle TPU Flower) - 终极系统级架构设计\n# 核心技术：两阶段微调、顶层局部解冻、BN层绝对锁死、Dropout正则化\n# ==============================================================================\nimport tensorflow as tf\n\nprint(\"▶️ [Phase 1] 核心网络初始化与浅层特征锁死...\")\nwith strategy.scope():\n    # 1. 挂载预训练模型 (作为坚固的特征提取基座)\n    pretrained_model = tf.keras.applications.DenseNet201(\n        weights='imagenet',\n        include_top=False,\n        input_shape=[*IMAGE_SIZE, 3]\n    )\n    pretrained_model.trainable = False  # 第一阶段：绝对锁定\n\n    # 2. 构建拓扑结构\n    model = tf.keras.Sequential([\n        pretrained_model,\n        tf.keras.layers.GlobalAveragePooling2D(),\n        # 增加 Dropout 层：提升模型泛化能力，冲破过拟合瓶颈\n        tf.keras.layers.Dropout(0.3),\n        tf.keras.layers.Dense(CLASSES, activation='softmax')\n    ])\n\n    # 3. 编译模型\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),\n        loss='sparse_categorical_crossentropy',\n        metrics=['sparse_categorical_accuracy']\n    )\n\nprint(\"▶️ [Phase 1] 启动分类映射层训练 (12 Epochs)...\")\nNUM_TRAINING_IMAGES = 12753\nSTEPS_PER_EPOCH = NUM_TRAINING_IMAGES // BATCH_SIZE\n\nlr_callback_stage1 = tf.keras.callbacks.ReduceLROnPlateau(\n    monitor='val_loss', patience=2, factor=0.5, min_lr=1e-6, verbose=1\n)\n\nhistory_stage1 = model.fit(\n    ds_train.repeat(),  # 👈 【核心暴力破解】：直接在这里加上 .repeat()！\n    validation_data=ds_valid,\n    epochs=12, \n    steps_per_epoch=STEPS_PER_EPOCH,\n    callbacks=[lr_callback_stage1]\n)\n\n# ------------------------------------------------------------------------------\nprint(\"\\n▶️ [Phase 2] 解除安全锁，启动深层网络局部微调 (Partial Fine-Tuning)...\")\nwith strategy.scope():\n    # 1. 整体解冻，准备精准切割\n    pretrained_model.trainable = True\n    \n    # 2. 核心魔法：只开放顶层 30% 的卷积层，同时锁死所有 BN 层\n    fine_tune_at = int(len(pretrained_model.layers) * 0.7)\n    \n    for i, layer in enumerate(pretrained_model.layers):\n        # 冻结底部的 70% 基础特征层\n        if i < fine_tune_at:\n            layer.trainable = False\n        # 无视层级，彻底切断所有 BN 层的方差计算（防止 nan 爆炸）\n        if isinstance(layer, tf.keras.layers.BatchNormalization):\n            layer.trainable = False  \n\n    # 3. 重新编译：微小学习率，进行极其细腻的参数重塑\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5),\n        loss='sparse_categorical_crossentropy',\n        metrics=['sparse_categorical_accuracy']\n    )\n\nprint(\"▶️ [Phase 2] 执行极限打磨并开启时光机记录巅峰 (追加 5 Epochs)...\")\n\n# 1. 建立“时光机”回调函数，只保存验证集最高的那一瞬间\ncheckpoint_callback = tf.keras.callbacks.ModelCheckpoint(\n    'best_flower_model.keras',        # 把巅峰记忆存进这个文件里\n    monitor='val_sparse_categorical_accuracy', # 极其冷酷地只盯住验证集准确率\n    save_best_only=True,              # 核心魔法：只存最好的一次！稍差的直接扔掉！\n    mode='max',\n    verbose=1\n)\n\n# 2. 挂载时光机开始训练\nhistory_stage2 = model.fit(\n    ds_train.repeat(),\n    validation_data=ds_valid,\n    epochs=5, \n    steps_per_epoch=STEPS_PER_EPOCH,\n    callbacks=[checkpoint_callback]  # 👈 挂载时光机！\n)\n\n# 3. 训练结束后，立刻读取巅峰记忆！\nmodel.load_weights('best_flower_model.keras')\nprint(\"✅ 系统全流程执行完毕，已成功将模型回档至验证集得分最高的巅峰状态！\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# 第 5 个代码块：专业版预测与生成提交 (TTA 提分版)\n# ==========================================\nimport numpy as np\nimport tensorflow as tf\n\nprint('🚀 启动 TTA (测试时增强) 预测引擎...')\n\n# 1. 定义 TTA 专用的数据增强函数 (不改变 ID，只改变图片)\ndef tta_augment(image, idnum):\n    image = tf.image.random_flip_left_right(image)\n    image = tf.image.random_contrast(image, lower=0.8, upper=1.2)\n    image = tf.image.random_brightness(image, max_delta=0.1)\n    return image, idnum\n\nTTA_STEPS = 5\nall_probabilities = []\n\n# 2. 执行 5 轮不同的预测\nfor step in range(TTA_STEPS):\n    print(f'⏳ 正在执行 TTA 预测 第 {step + 1}/{TTA_STEPS} 轮...')\n    \n    if step == 0:\n        # 第一轮：原汁原味，不加任何修改（保证有一票是绝对真实的）\n        tta_ds = ds_test\n    else:\n        # 后续轮次：先拆散批次 -> 加入随机扰动 -> 重新打包\n        tta_ds = ds_test.unbatch().map(\n            tta_augment, num_parallel_calls=tf.data.experimental.AUTOTUNE\n        ).batch(BATCH_SIZE)\n    \n    # 提取纯图片用于预测\n    test_images_ds = tta_ds.map(lambda image, idnum: image)\n    probs = model.predict(test_images_ds)\n    all_probabilities.append(probs)\n\nprint('🧠 正在融合所有专家的预测结果 (Ensembling)...')\n# 3. 将 5 轮的概率矩阵叠加并求平均\nfinal_probabilities = np.mean(all_probabilities, axis=0)\n\n# 找出平均后概率最大的类别\npredictions = np.argmax(final_probabilities, axis=-1)\n\nprint('📄 正在动态提取测试集 IDs...')\n# 把测试集的 ID 单独提取出来 (用原汁原味的 ds_test 保证顺序绝对正确)\ntest_ids_ds = ds_test.map(lambda image, idnum: idnum).unbatch()\n\ntest_ids = []\nfor ids in test_ids_ds:\n    test_ids.append(ids.numpy().decode('utf-8'))\n\nprint('💾 正在生成终极版 submission.csv 文件...')\n# 将 ID 和预测结果拼成表格并保存\nnp.savetxt(\n    'submission.csv',\n    np.rec.fromarrays([test_ids, predictions]),\n    fmt=['%s', '%d'],\n    delimiter=',',\n    header='id,label',\n    comments='',\n)\n\nprint(f\"✅ TTA 增强提分版文件已生成！成功处理了 {len(test_ids)} 条结果！快去交卷吧！\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}