{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"}],"dockerImageVersionId":31041,"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":"2025-06-23T13:52:54.363058Z","iopub.execute_input":"2025-06-23T13:52:54.363211Z","iopub.status.idle":"2025-06-23T13:52:54.661514Z","shell.execute_reply.started":"2025-06-23T13:52:54.363196Z","shell.execute_reply":"2025-06-23T13:52:54.660750Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install py7zr","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T13:52:54.662918Z","iopub.execute_input":"2025-06-23T13:52:54.663293Z","iopub.status.idle":"2025-06-23T13:53:00.449586Z","shell.execute_reply.started":"2025-06-23T13:52:54.663274Z","shell.execute_reply":"2025-06-23T13:53:00.448827Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport py7zr\n\n# 设置输入和输出路径\nINPUT_PATH = \"/kaggle/input/cifar-10\"\nOUTPUT_PATH = \"/kaggle/working\"  # Kaggle工作目录，可写\n\n# 定义文件路径\nTRAIN_7Z = os.path.join(INPUT_PATH, \"train.7z\")\nTEST_7Z = os.path.join(INPUT_PATH, \"test.7z\")\nTRAIN_OUTPUT = os.path.join(OUTPUT_PATH, \"train\")\nTEST_OUTPUT = os.path.join(OUTPUT_PATH, \"test\")\n\n# 确保输出目录存在\nos.makedirs(TRAIN_OUTPUT, exist_ok=True)\nos.makedirs(TEST_OUTPUT, exist_ok=True)\n\nprint(\"开始解压训练数据...\")\nwith py7zr.SevenZipFile(TRAIN_7Z, mode='r') as z:\n    z.extractall(path=TRAIN_OUTPUT)\nprint(f\"训练数据已解压到: {TRAIN_OUTPUT}\")\n\nprint(\"\\n开始解压测试数据...\")\nwith py7zr.SevenZipFile(TEST_7Z, mode='r') as z:\n    z.extractall(path=TEST_OUTPUT)\nprint(f\"测试数据已解压到: {TEST_OUTPUT}\")\n\n# 检查解压后的文件\nprint(\"\\n解压完成! 文件列表:\")\nprint(\"训练目录:\", os.listdir(TRAIN_OUTPUT))\nprint(\"测试目录:\", os.listdir(TEST_OUTPUT))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T13:53:00.450490Z","iopub.execute_input":"2025-06-23T13:53:00.450700Z","iopub.status.idle":"2025-06-23T13:55:19.575595Z","shell.execute_reply.started":"2025-06-23T13:53:00.450677Z","shell.execute_reply":"2025-06-23T13:55:19.574857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 导入必要的库\nimport os\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, regularizers\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nfrom sklearn.preprocessing import LabelEncoder\nfrom tensorflow.keras.callbacks import LearningRateScheduler, EarlyStopping, ModelCheckpoint","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T13:55:32.995173Z","iopub.execute_input":"2025-06-23T13:55:32.995617Z","iopub.status.idle":"2025-06-23T13:55:33.181187Z","shell.execute_reply.started":"2025-06-23T13:55:32.995597Z","shell.execute_reply":"2025-06-23T13:55:33.180397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 设置路径（根据解压结果调整）\nTRAIN_IMG_PATH = \"/kaggle/working/train/train\"\nTEST_IMG_PATH = \"/kaggle/working/test/test\"\nTRAIN_LABELS_PATH = \"/kaggle/input/cifar-10/trainLabels.csv\"\nSAMPLE_SUBMISSION_PATH = \"/kaggle/input/cifar-10/sampleSubmission.csv\"\nMODEL_SAVE_PATH = \"/kaggle/working/cifar10_resnet18_model.h5\"\n\n# 超参数设置\nBATCH_SIZE = 128\nEPOCHS = 40\nVAL_SPLIT = 0.15\nLEARNING_RATE = 1e-3\nL2_REG = 1e-4\n\n# 数据标准化参数\nCIFAR10_MEAN = [0.4914, 0.4822, 0.4465]\nCIFAR10_STD = [0.2470, 0.2435, 0.2616]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T13:55:33.184402Z","iopub.execute_input":"2025-06-23T13:55:33.184624Z","iopub.status.idle":"2025-06-23T13:55:33.189113Z","shell.execute_reply.started":"2025-06-23T13:55:33.184606Z","shell.execute_reply":"2025-06-23T13:55:33.188458Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_data():\n    \"\"\"加载和预处理数据\"\"\"\n    # 加载标签数据\n    labels_df = pd.read_csv(TRAIN_LABELS_PATH)\n    print(\"标签数据预览:\")\n    print(labels_df.head())\n    \n    # 创建标签编码器\n    label_encoder = LabelEncoder()\n    labels_df['label_encoded'] = label_encoder.fit_transform(labels_df['label'])\n    class_names = label_encoder.classes_\n    print(\"\\n类别名称:\", class_names)\n    \n    # 数据加载函数\n    def load_images(image_folder, label_df=None):\n        images = []\n        labels = []\n        filenames = []\n        \n        # 按文件名排序确保与标签顺序一致\n        file_list = sorted([f for f in os.listdir(image_folder) if f.endswith('.png')], \n                          key=lambda x: int(x.split('.')[0]))\n        \n        print(f\"找到 {len(file_list)} 个图像文件\")\n        \n        for i, filename in enumerate(file_list):\n            if i % 5000 == 0:  # 每5000张打印一次进度\n                print(f\"处理图像 {i}/{len(file_list)}...\")\n                \n            img_path = os.path.join(image_folder, filename)\n            img = tf.keras.preprocessing.image.load_img(img_path, target_size=(32, 32))\n            img_array = tf.keras.preprocessing.image.img_to_array(img)\n            images.append(img_array)\n            \n            img_id = int(filename.split('.')[0])\n            filenames.append(img_id)\n            \n            if label_df is not None:\n                label_row = label_df[label_df['id'] == img_id]\n                if not label_row.empty:\n                    label = label_row['label_encoded'].values[0]\n                    labels.append(label)\n        \n        images = np.array(images, dtype='float32')\n        \n        # CIFAR-10专用数据标准化\n        images = (images / 255.0 - CIFAR10_MEAN) / CIFAR10_STD\n        \n        if label_df is not None and labels:\n            labels = np.array(labels)\n            return images, labels, filenames\n        else:\n            return images, filenames\n    \n    # 加载训练数据\n    print(\"\\n加载训练数据...\")\n    X_train, y_train, train_filenames = load_images(TRAIN_IMG_PATH, labels_df)\n    print(f\"训练数据形状: {X_train.shape}, 标签形状: {y_train.shape}\")\n    \n    # 加载测试数据\n    print(\"\\n加载测试数据...\")\n    X_test, test_filenames = load_images(TEST_IMG_PATH)\n    print(f\"测试数据形状: {X_test.shape}\")\n    \n    # 划分训练集和验证集\n    X_train, X_val, y_train, y_val = train_test_split(\n        X_train, y_train, test_size=VAL_SPLIT, random_state=42, stratify=y_train\n    )\n    print(f\"\\n训练集: {X_train.shape}, 验证集: {X_val.shape}\")\n    \n    return X_train, y_train, X_val, y_val, X_test, test_filenames, label_encoder\n\n# 调用数据加载函数\nX_train, y_train, X_val, y_val, X_test, test_filenames, label_encoder = load_data()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T13:55:33.189819Z","iopub.execute_input":"2025-06-23T13:55:33.190070Z","iopub.status.idle":"2025-06-23T13:57:06.185537Z","shell.execute_reply.started":"2025-06-23T13:55:33.190053Z","shell.execute_reply":"2025-06-23T13:57:06.184508Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_data_augmenter():\n    \"\"\"创建数据增强生成器\"\"\"\n    datagen = ImageDataGenerator(\n        rotation_range=15,\n        width_shift_range=0.1,\n        height_shift_range=0.1,\n        horizontal_flip=True,\n        zoom_range=0.1,\n        fill_mode='constant',\n        cval=0.0\n    )\n    datagen.fit(X_train)\n    return datagen\n\n# 创建数据增强器\ndatagen = create_data_augmenter()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T13:57:06.186490Z","iopub.execute_input":"2025-06-23T13:57:06.186808Z","iopub.status.idle":"2025-06-23T13:57:06.432453Z","shell.execute_reply.started":"2025-06-23T13:57:06.186784Z","shell.execute_reply":"2025-06-23T13:57:06.431901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def residual_block(x, filters, downsample=False, name=None):\n    \"\"\"残差块实现\"\"\"\n    strides = 2 if downsample else 1\n    identity = x\n    \n    # 主路径\n    x = layers.Conv2D(filters, kernel_size=3, strides=strides, \n                      padding='same', kernel_initializer='he_normal',\n                      kernel_regularizer=regularizers.l2(L2_REG))(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation('relu')(x)\n    \n    x = layers.Conv2D(filters, kernel_size=3, strides=1, \n                      padding='same', kernel_initializer='he_normal',\n                      kernel_regularizer=regularizers.l2(L2_REG))(x)\n    x = layers.BatchNormalization()(x)\n    \n    # 捷径连接（如果下采样或通道数变化）\n    if downsample:\n        identity = layers.Conv2D(filters, kernel_size=1, strides=2, \n                                 padding='valid', kernel_initializer='he_normal',\n                                 kernel_regularizer=regularizers.l2(L2_REG))(identity)\n        identity = layers.BatchNormalization()(identity)\n    \n    # 添加捷径连接\n    x = layers.Add()([x, identity])\n    x = layers.Activation('relu')(x)\n    return x\n\ndef build_resnet18(input_shape=(32, 32, 3), num_classes=10):\n    \"\"\"构建ResNet-18模型\"\"\"\n    inputs = layers.Input(shape=input_shape)\n    \n    # 初始卷积层\n    x = layers.Conv2D(64, kernel_size=3, strides=1, padding='same', \n                      kernel_initializer='he_normal', \n                      kernel_regularizer=regularizers.l2(L2_REG))(inputs)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation('relu')(x)\n    \n    # 残差块序列\n    # Stage 1\n    x = residual_block(x, 64, name='block1_1')\n    x = residual_block(x, 64, name='block1_2')\n    \n    # Stage 2 (下采样)\n    x = residual_block(x, 128, downsample=True, name='block2_1')\n    x = residual_block(x, 128, name='block2_2')\n    \n    # Stage 3 (下采样)\n    x = residual_block(x, 256, downsample=True, name='block3_1')\n    x = residual_block(x, 256, name='block3_2')\n    \n    # Stage 4 (下采样)\n    x = residual_block(x, 512, downsample=True, name='block4_1')\n    x = residual_block(x, 512, name='block4_2')\n    \n    # 全局平均池化和输出层\n    x = layers.GlobalAveragePooling2D()(x)\n    outputs = layers.Dense(num_classes, activation='softmax',\n                           kernel_regularizer=regularizers.l2(L2_REG))(x)\n    \n    model = models.Model(inputs=inputs, outputs=outputs, name='ResNet18')\n    return model\n\n# 构建ResNet-18模型\nmodel = build_resnet18(input_shape=(32, 32, 3), num_classes=10)\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T13:57:06.433184Z","iopub.execute_input":"2025-06-23T13:57:06.433441Z","iopub.status.idle":"2025-06-23T13:57:09.224454Z","shell.execute_reply.started":"2025-06-23T13:57:06.433418Z","shell.execute_reply":"2025-06-23T13:57:09.223918Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def lr_schedule(epoch):\n    \"\"\"学习率调度 - 在指定epoch减少学习率\"\"\"\n    lr = LEARNING_RATE\n    if epoch > 75:\n        lr *= 0.1e-3\n    elif epoch > 60:\n        lr *= 1e-3\n    elif epoch > 45:\n        lr *= 1e-2\n    elif epoch > 30:\n        lr *= 1e-1\n    print(f'Epoch {epoch+1}: 学习率 = {lr:.6f}')\n    return lr\n\n# 回调函数\ncallbacks = [\n    EarlyStopping(monitor='val_accuracy', patience=10, restore_best_weights=True),\n    LearningRateScheduler(lr_schedule),\n    ModelCheckpoint(MODEL_SAVE_PATH, monitor='val_accuracy', \n                   save_best_only=True, save_weights_only=False)\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T13:57:09.225195Z","iopub.execute_input":"2025-06-23T13:57:09.225479Z","iopub.status.idle":"2025-06-23T13:57:09.230168Z","shell.execute_reply.started":"2025-06-23T13:57:09.225458Z","shell.execute_reply":"2025-06-23T13:57:09.229401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 编译模型\nmodel.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE),\n              loss='sparse_categorical_crossentropy',\n              metrics=['accuracy'])\n\n# 训练模型\nprint(\"\\n开始训练ResNet-18模型...\")\nhistory = model.fit(\n    datagen.flow(X_train, y_train, batch_size=BATCH_SIZE),\n    steps_per_epoch=len(X_train) // BATCH_SIZE,\n    epochs=EPOCHS,\n    validation_data=(X_val, y_val),\n    callbacks=callbacks,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T13:57:09.231023Z","iopub.execute_input":"2025-06-23T13:57:09.231641Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 评估模型\nprint(\"\\n模型评估:\")\nval_loss, val_acc = model.evaluate(X_val, y_val, verbose=0)\nprint(f\"验证集准确率: {val_acc*100:.2f}%\")\nprint(f\"验证集损失: {val_loss:.4f}\")\n\n# 可视化训练过程\ndef plot_training_history(history):\n    # 第一个图表：准确率\n    plt.figure(figsize=(6, 4))\n    plt.plot(history.history['accuracy'], linestyle='--', color='m', label='train acc')\n    plt.plot(history.history['val_accuracy'], linestyle='-.', color='g', label='valid acc')\n    plt.title('training and validation accuracy')\n    plt.xlabel('epoch')\n    plt.ylabel('accuracy')\n    plt.legend()\n    plt.grid(True)\n    plt.show()\n\n    # 第二个图表：损失值 - 注意这里缩进与第一个图表相同\n    plt.figure(figsize=(6, 4))\n    plt.plot(history.history['loss'], linestyle='-', color='b', label='train loss')\n    plt.plot(history.history['val_loss'], linestyle='-', color='orange', label='valid loss')\n    plt.title('training and validation loss')\n    plt.xlabel('epoch')\n    plt.ylabel('loss')\n    plt.legend()\n    plt.grid(True)\n    plt.show()\n\n# 绘制训练历史（注意这行在函数定义之外，没有缩进）\nplot_training_history(history)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 预测测试集\nprint(\"\\n预测测试集...\")\ntest_probs = model.predict(X_test, batch_size=128, verbose=1)\ntest_preds = np.argmax(test_probs, axis=1)\ntest_labels = label_encoder.inverse_transform(test_preds)\n\n# 创建提交文件\ndef create_submission(test_filenames, test_labels):\n    submission_df = pd.DataFrame({\n        'id': test_filenames,\n        'label': test_labels\n    })\n    \n    # 确保id顺序与sampleSubmission一致\n    sample_sub = pd.read_csv(SAMPLE_SUBMISSION_PATH)\n    submission_df = submission_df.sort_values('id').reset_index(drop=True)\n    \n    # 保存提交文件\n    submission_df.to_csv('resnet18_submission.csv', index=False)\n    print(\"\\n提交文件已保存: resnet18_submission.csv\")\n    print(f\"提交文件预览:\\n{submission_df.head()}\")\n    return submission_df\n\n# 生成提交文件\nsubmission_df = create_submission(test_filenames, test_labels)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}