{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix\nimport seaborn as sns","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau\nimport cv2\nfrom PIL import Image","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 忽略TensorFlow警告\nimport logging\nlogging.getLogger('tensorflow').setLevel(logging.ERROR)\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'\n\n# 設定隨機種子以確保結果可重現\nnp.random.seed(42)\ntf.random.set_seed(42)\n\n# 資料預處理配置\nIMG_SIZE = 128  # 圖片尺寸 (可調整為64, 128, 224等)\nBATCH_SIZE = 32\nEPOCHS = 50","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_and_preprocess_data(train_dir, test_dir=None):\n    \"\"\"\n    載入並預處理圖片資料\n    \"\"\"\n    print(\"開始載入訓練資料...\")\n    \n    # 創建資料生成器\n    train_datagen = ImageDataGenerator(\n        rescale=1./255,              # 正規化到0-1\n        rotation_range=20,           # 隨機旋轉\n        width_shift_range=0.2,       # 水平移動\n        height_shift_range=0.2,      # 垂直移動\n        horizontal_flip=True,        # 水平翻轉\n        validation_split=0.2         # 20%作為驗證集\n    )\n    \n    test_datagen = ImageDataGenerator(rescale=1./255)\n    \n    # 載入訓練資料\n    train_generator = train_datagen.flow_from_directory(\n        train_dir,\n        target_size=(IMG_SIZE, IMG_SIZE),\n        batch_size=BATCH_SIZE,\n        class_mode='binary',\n        subset='training'\n    )\n    \n    # 載入驗證資料\n    validation_generator = train_datagen.flow_from_directory(\n        train_dir,\n        target_size=(IMG_SIZE, IMG_SIZE),\n        batch_size=BATCH_SIZE,\n        class_mode='binary',\n        subset='validation'\n    )\n    \n    # 如果有測試資料\n    test_generator = None\n    if test_dir:\n        test_generator = test_datagen.flow_from_directory(\n            test_dir,\n            target_size=(IMG_SIZE, IMG_SIZE),\n            batch_size=BATCH_SIZE,\n            class_mode=None,\n            shuffle=False\n        )\n    \n    return train_generator, validation_generator, test_generator","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_cnn_model():\n    \"\"\"\n    創建簡單的CNN模型\n    \"\"\"\n    model = keras.Sequential([\n        # 第一層卷積\n        layers.Conv2D(32, (3, 3), activation='relu', input_shape=(IMG_SIZE, IMG_SIZE, 3)),\n        layers.MaxPooling2D((2, 2)),\n        \n        # 第二層卷積\n        layers.Conv2D(64, (3, 3), activation='relu'),\n        layers.MaxPooling2D((2, 2)),\n        \n        # 第三層卷積\n        layers.Conv2D(128, (3, 3), activation='relu'),\n        layers.MaxPooling2D((2, 2)),\n        \n        # 第四層卷積\n        layers.Conv2D(128, (3, 3), activation='relu'),\n        layers.MaxPooling2D((2, 2)),\n        \n        # Flatten層\n        layers.Flatten(),\n        \n        # 全連接層\n        layers.Dense(512, activation='relu'),\n        layers.Dropout(0.5),  # 防止過擬合\n        \n        # 輸出層 (二分類)\n        layers.Dense(1, activation='sigmoid')\n    ])\n    \n    return model","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_training_history(history):\n    \"\"\"\n    繪製訓練歷史圖表\n    \"\"\"\n    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))\n    \n    # 準確率圖\n    ax1.plot(history.history['accuracy'], label='Training Accuracy')\n    ax1.plot(history.history['val_accuracy'], label='Validation Accuracy')\n    ax1.set_title('Model Accuracy')\n    ax1.set_xlabel('Epoch')\n    ax1.set_ylabel('Accuracy')\n    ax1.legend()\n    ax1.grid(True)\n    \n    # 損失圖\n    ax2.plot(history.history['loss'], label='Training Loss')\n    ax2.plot(history.history['val_loss'], label='Validation Loss')\n    ax2.set_title('Model Loss')\n    ax2.set_xlabel('Epoch')\n    ax2.set_ylabel('Loss')\n    ax2.legend()\n    ax2.grid(True)\n    \n    plt.tight_layout()\n    plt.show()\n\ndef prepare_data_from_kaggle():\n    \"\"\"\n    從Kaggle壓縮檔案準備訓練資料夾結構\n    \"\"\"\n    import shutil\n    import glob\n    import zipfile\n    \n    # Kaggle壓縮檔案路徑\n    zip_paths = [\n        '/kaggle/input/dogs-vs-cats/train.zip',\n        '/kaggle/input/dogs-vs-cats/test1.zip'\n    ]\n    \n    # 目標資料夾結構\n    new_train_dir = './train_data'\n    cats_dir = os.path.join(new_train_dir, 'cats')\n    dogs_dir = os.path.join(new_train_dir, 'dogs')\n    \n    # 創建資料夾\n    os.makedirs(cats_dir, exist_ok=True)\n    os.makedirs(dogs_dir, exist_ok=True)\n    \n    print(\"正在從壓縮檔案中解壓並整理資料...\")\n    \n    # 檢查是否已經整理過資料\n    existing_cats = len(glob.glob(os.path.join(cats_dir, '*.jpg')))\n    existing_dogs = len(glob.glob(os.path.join(dogs_dir, '*.jpg')))\n    \n    if existing_cats > 100 and existing_dogs > 100:\n        print(f\"✅ 找到已整理的資料: {existing_cats} 張貓圖片, {existing_dogs} 張狗圖片\")\n        return new_train_dir\n    \n    # 尋找並解壓train.zip\n    train_zip_path = None\n    for zip_path in zip_paths:\n        if os.path.exists(zip_path):\n            train_zip_path = zip_path\n            break\n    \n    if train_zip_path is None:\n        # 檢查其他可能的路徑\n        possible_paths = glob.glob('/kaggle/input/**/train.zip', recursive=True)\n        if possible_paths:\n            train_zip_path = possible_paths[0]\n    \n    if train_zip_path is None:\n        print(\"❌ 找不到train.zip檔案\")\n        print(\"請確認已將Dogs vs Cats資料集添加到Kaggle notebook\")\n        return None\n    \n    print(f\"📦 找到壓縮檔案: {train_zip_path}\")\n    \n    try:\n        cat_count = 0\n        dog_count = 0\n        \n        # 解壓並分類檔案\n        with zipfile.ZipFile(train_zip_path, 'r') as zip_ref:\n            file_list = zip_ref.namelist()\n            \n            # 過濾出圖片檔案\n            image_files = [f for f in file_list if f.lower().endswith(('.jpg', '.jpeg', '.png'))]\n            \n            print(f\"📊 找到 {len(image_files)} 個圖片檔案\")\n            \n            for file_path in image_files:\n                filename = os.path.basename(file_path)\n                \n                # 根據檔名判斷是貓還是狗\n                if filename.startswith('cat.'):\n                    # 解壓到cats資料夾\n                    with zip_ref.open(file_path) as source:\n                        target_path = os.path.join(cats_dir, filename)\n                        with open(target_path, 'wb') as target:\n                            shutil.copyfileobj(source, target)\n                    cat_count += 1\n                    \n                elif filename.startswith('dog.'):\n                    # 解壓到dogs資料夾\n                    with zip_ref.open(file_path) as source:\n                        target_path = os.path.join(dogs_dir, filename)\n                        with open(target_path, 'wb') as target:\n                            shutil.copyfileobj(source, target)\n                    dog_count += 1\n                \n                # 顯示進度\n                if (cat_count + dog_count) % 2000 == 0:\n                    print(f\"已處理 {cat_count + dog_count} 個檔案...\")\n        \n        print(f\"✅ 解壓完成！\")\n        print(f\"🐱 貓的圖片: {cat_count} 張\")\n        print(f\"🐶 狗的圖片: {dog_count} 張\")\n        \n        if cat_count > 0 and dog_count > 0:\n            return new_train_dir\n        else:\n            print(\"❌ 沒有找到有效的貓狗圖片\")\n            return None\n            \n    except Exception as e:\n        print(f\"❌ 解壓錯誤: {e}\")\n        return None\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndef main():\n    \"\"\"\n    主要訓練流程\n    \"\"\"\n    print(\"=== 貓狗辨識器 CNN 訓練 ===\")\n    \n    # 1. 準備資料\n    print(\"正在準備資料...\")\n    train_dir = prepare_data_from_kaggle()\n    \n    if train_dir is None:\n        print(\"❌ 找不到訓練資料！\")\n        print(\"\\n請確認以下其中一種情況:\")\n        print(\"1. 在Kaggle環境中，資料應該在 '/kaggle/input/dogs-vs-cats/train'\")\n        print(\"2. 在本地環境中，請手動設定train_dir變數\")\n        print(\"\\n如果你在本地環境，請:\")\n        print(\"- 下載 Dogs vs Cats 資料集\")\n        print(\"- 解壓並整理成以下結構:\")\n        print(\"  train_data/\")\n        print(\"    cats/\")\n        print(\"      cat.1.jpg, cat.2.jpg, ...\")\n        print(\"    dogs/\")\n        print(\"      dog.1.jpg, dog.2.jpg, ...\")\n        \n        # 提供手動設定路徑的選項\n        manual_path = input(\"\\n或者輸入你的資料路徑 (按Enter跳過): \").strip()\n        if manual_path and os.path.exists(manual_path):\n            train_dir = manual_path\n        else:\n            return None, None\n    \n    # 2. 載入資料\n    try:\n        train_gen, val_gen, test_gen = load_and_preprocess_data(train_dir)\n        print(f\"✅ 訓練樣本數: {train_gen.samples}\")\n        print(f\"✅ 驗證樣本數: {val_gen.samples}\")\n        print(f\"✅ 類別標籤: {train_gen.class_indices}\")\n    except Exception as e:\n        print(f\"❌ 資料載入錯誤: {e}\")\n        return None, None\n    \n    # 2. 創建模型\n    print(\"\\n創建CNN模型...\")\n    model = create_cnn_model()\n    \n    # 編譯模型\n    model.compile(\n        optimizer='adam',\n        loss='binary_crossentropy',\n        metrics=['accuracy']\n    )\n    \n    # 顯示模型架構\n    print(\"\\n模型架構:\")\n    model.summary()\n    \n    # 3. 設定回調函數\n    callbacks = [\n        EarlyStopping(\n            monitor='val_loss',\n            patience=5,\n            restore_best_weights=True\n        ),\n        ReduceLROnPlateau(\n            monitor='val_loss',\n            factor=0.2,\n            patience=3,\n            min_lr=0.0001\n        )\n    ]\n    \n    # 4. 訓練模型\n    print(\"\\n開始訓練...\")\n    history = model.fit(\n        train_gen,\n        epochs=EPOCHS,\n        validation_data=val_gen,\n        callbacks=callbacks,\n        verbose=1\n    )\n    \n    # 5. 繪製訓練歷史\n    print(\"\\n繪製訓練歷史...\")\n    plot_training_history(history)\n    \n    # 6. 評估模型\n    print(\"\\n評估模型性能...\")\n    val_loss, val_accuracy = model.evaluate(val_gen, verbose=0)\n    print(f\"驗證集準確率: {val_accuracy:.4f}\")\n    print(f\"驗證集損失: {val_loss:.4f}\")\n    \n    # 7. 保存模型\n    model.save('dogs_cats_cnn_model.h5')\n    print(\"\\n模型已保存為 'dogs_cats_cnn_model.h5'\")\n    \n    # 8. 預測範例\n    print(\"\\n進行預測範例...\")\n    predictions = model.predict(val_gen)\n    predicted_classes = (predictions > 0.5).astype(int)\n    \n    # 顯示部分預測結果\n    class_names = ['cat', 'dog']  # 0: cat, 1: dog\n    print(\"前10個預測結果:\")\n    for i in range(min(10, len(predictions))):\n        confidence = predictions[i][0]\n        predicted_class = class_names[predicted_classes[i][0]]\n        print(f\"樣本 {i+1}: {predicted_class} (信心度: {confidence:.4f})\")\n    \n    return model, history","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_single_image(model, image_path):\n    \"\"\"\n    預測單張圖片\n    \"\"\"\n    try:\n        # 載入並預處理圖片\n        img = Image.open(image_path)\n        img = img.resize((IMG_SIZE, IMG_SIZE))\n        img_array = np.array(img) / 255.0\n        img_array = np.expand_dims(img_array, axis=0)\n        \n        # 預測\n        prediction = model.predict(img_array)[0][0]\n        \n        if prediction > 0.5:\n            result = f\"狗 (信心度: {prediction:.4f})\"\n        else:\n            result = f\"貓 (信心度: {1-prediction:.4f})\"\n        \n        print(f\"預測結果: {result}\")\n        return prediction\n        \n    except Exception as e:\n        print(f\"預測錯誤: {e}\")\n        return None","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 執行訓練\nif __name__ == \"__main__\":\n    result = main()\n    \n    # 檢查是否成功返回模型和歷史\n    if result is not None and len(result) == 2:\n        model, history = result\n        print(\"\\n🎉 訓練完成！\")\n        \n        # 使用範例 (預測單張圖片)\n        # prediction = predict_single_image(model, \"path/to/your/test/image.jpg\")\n    else:\n        print(\"\\n❌ 訓練失敗，請檢查資料路徑和設定\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}