{"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":[{"sourceType":"competition","sourceId":3362,"databundleVersionId":31148}],"dockerImageVersionId":31400,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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)\nimport cv2\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\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:35:57.968838Z","iopub.execute_input":"2026-05-31T13:35:57.969330Z","iopub.status.idle":"2026-05-31T13:35:57.978280Z","shell.execute_reply.started":"2026-05-31T13:35:57.969287Z","shell.execute_reply":"2026-05-31T13:35:57.977431Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile\n\nwith zipfile.ZipFile('/kaggle/input/competitions/dogs-vs-cats/train.zip', 'r') as zip_ref:\n    zip_ref.extractall('/kaggle/working/train')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T12:39:49.380862Z","iopub.execute_input":"2026-05-31T12:39:49.381209Z","iopub.status.idle":"2026-05-31T12:39:56.898736Z","shell.execute_reply.started":"2026-05-31T12:39:49.381169Z","shell.execute_reply":"2026-05-31T12:39:56.897841Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\ntrain_dir = \"/kaggle/working/train/train\"\n\nprint(\"Số file:\", len(os.listdir(train_dir)))\n\nprint(os.listdir(train_dir)[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T12:39:56.899800Z","iopub.execute_input":"2026-05-31T12:39:56.900045Z","iopub.status.idle":"2026-05-31T12:39:56.932173Z","shell.execute_reply.started":"2026-05-31T12:39:56.900022Z","shell.execute_reply":"2026-05-31T12:39:56.931388Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tạo thư mục cats và dogs\nimport os\nimport shutil\n\nsource_dir = \"/kaggle/working/train/train\"\n\ncat_dir = \"/kaggle/working/data/cats\"\ndog_dir = \"/kaggle/working/data/dogs\"\n\nos.makedirs(cat_dir, exist_ok=True)\nos.makedirs(dog_dir, exist_ok=True)\n\nfor file in os.listdir(source_dir):\n\n    if file.startswith(\"cat\"):\n        shutil.copy(\n            os.path.join(source_dir, file),\n            os.path.join(cat_dir, file)\n        )\n\n    elif file.startswith(\"dog\"):\n        shutil.copy(\n            os.path.join(source_dir, file),\n            os.path.join(dog_dir, file)\n        )\n\nprint(\"Done\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T12:39:56.934099Z","iopub.execute_input":"2026-05-31T12:39:56.934391Z","iopub.status.idle":"2026-05-31T12:39:59.627642Z","shell.execute_reply.started":"2026-05-31T12:39:56.934365Z","shell.execute_reply":"2026-05-31T12:39:59.626837Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Kiểm tra kết quả\nprint(len(os.listdir(cat_dir)))\nprint(len(os.listdir(dog_dir)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T12:39:59.628605Z","iopub.execute_input":"2026-05-31T12:39:59.628897Z","iopub.status.idle":"2026-05-31T12:39:59.645116Z","shell.execute_reply.started":"2026-05-31T12:39:59.628862Z","shell.execute_reply":"2026-05-31T12:39:59.644365Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load dataset\nimport tensorflow as tf\n\nIMG_SIZE = (150, 150)\nBATCH_SIZE = 32\n\ntrain_ds = tf.keras.utils.image_dataset_from_directory(\n    \"/kaggle/working/data\",\n    validation_split=0.2,\n    subset=\"training\",\n    seed=42,\n    image_size=IMG_SIZE,\n    batch_size=BATCH_SIZE\n)\n\nval_ds = tf.keras.utils.image_dataset_from_directory(\n    \"/kaggle/working/data\",\n    validation_split=0.2,\n    subset=\"validation\",\n    seed=42,\n    image_size=IMG_SIZE,\n    batch_size=BATCH_SIZE\n)\n\nclass_names = train_ds.class_names\nprint(class_names)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T12:39:59.646062Z","iopub.execute_input":"2026-05-31T12:39:59.646420Z","iopub.status.idle":"2026-05-31T12:40:00.733852Z","shell.execute_reply.started":"2026-05-31T12:39:59.646395Z","shell.execute_reply":"2026-05-31T12:40:00.732961Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tăng tốc dataset\nAUTOTUNE = tf.data.AUTOTUNE\n\ntrain_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE)\nval_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T12:40:00.735013Z","iopub.execute_input":"2026-05-31T12:40:00.735367Z","iopub.status.idle":"2026-05-31T12:40:00.751105Z","shell.execute_reply.started":"2026-05-31T12:40:00.735330Z","shell.execute_reply":"2026-05-31T12:40:00.750052Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tạo CNN\nfrom tensorflow.keras import Sequential\nfrom tensorflow.keras.layers import (\n    Rescaling,\n    Conv2D,\n    MaxPooling2D,\n    Flatten,\n    Dense,\n    Dropout\n)\n\nmodel = Sequential([\n\n    Rescaling(1./255, input_shape=(150,150,3)),\n\n    Conv2D(32, (3,3), activation='relu'),\n    MaxPooling2D(),\n\n    Conv2D(64, (3,3), activation='relu'),\n    MaxPooling2D(),\n\n    Conv2D(128, (3,3), activation='relu'),\n    MaxPooling2D(),\n\n    Flatten(),\n\n    Dense(128, activation='relu'),\n\n    Dropout(0.5),\n\n    Dense(1, activation='sigmoid')\n])\n\nmodel.compile(\n    optimizer='adam',\n    loss='binary_crossentropy',\n    metrics=['accuracy']\n)\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T12:40:00.752336Z","iopub.execute_input":"2026-05-31T12:40:00.752765Z","iopub.status.idle":"2026-05-31T12:40:00.897317Z","shell.execute_reply.started":"2026-05-31T12:40:00.752737Z","shell.execute_reply":"2026-05-31T12:40:00.896400Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train model\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=5\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T12:40:00.898480Z","iopub.execute_input":"2026-05-31T12:40:00.899001Z","iopub.status.idle":"2026-05-31T13:15:53.916630Z","shell.execute_reply.started":"2026-05-31T12:40:00.898974Z","shell.execute_reply":"2026-05-31T13:15:53.915699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Train Accuracy:\", history.history['accuracy'][-1])\nprint(\"Validation Accuracy:\", history.history['val_accuracy'][-1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:15:53.919413Z","iopub.execute_input":"2026-05-31T13:15:53.919810Z","iopub.status.idle":"2026-05-31T13:15:53.925258Z","shell.execute_reply.started":"2026-05-31T13:15:53.919785Z","shell.execute_reply":"2026-05-31T13:15:53.924469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Vẽ biểu đồ\nimport matplotlib.pyplot as plt\n\nplt.figure(figsize=(12,5))\n\nplt.subplot(1,2,1)\nplt.plot(history.history['accuracy'])\nplt.plot(history.history['val_accuracy'])\nplt.title(\"Accuracy\")\nplt.legend([\"Train\",\"Validation\"])\n\nplt.subplot(1,2,2)\nplt.plot(history.history['loss'])\nplt.plot(history.history['val_loss'])\nplt.title(\"Loss\")\nplt.legend([\"Train\",\"Validation\"])\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:15:53.926243Z","iopub.execute_input":"2026-05-31T13:15:53.926558Z","iopub.status.idle":"2026-05-31T13:15:54.313575Z","shell.execute_reply.started":"2026-05-31T13:15:53.926534Z","shell.execute_reply":"2026-05-31T13:15:54.312798Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.save(\"cats_dogs_gradcam.keras\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:15:54.314563Z","iopub.execute_input":"2026-05-31T13:15:54.314870Z","iopub.status.idle":"2026-05-31T13:15:54.619037Z","shell.execute_reply.started":"2026-05-31T13:15:54.314840Z","shell.execute_reply":"2026-05-31T13:15:54.618278Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras.preprocessing import image\n\ndef predict_image(img_path):\n    \n    # Hiển thị ảnh\n    img_show = image.load_img(img_path)\n    \n    plt.figure(figsize=(5,5))\n    plt.imshow(img_show)\n    plt.axis(\"off\")\n    plt.show()\n    \n    # Resize cho model\n    img = image.load_img(\n        img_path,\n        target_size=(150,150)\n    )\n    \n    img_array = image.img_to_array(img)\n    img_array = np.expand_dims(img_array, axis=0)\n    \n    prediction = model.predict(img_array, verbose=0)\n    \n    pred = prediction[0][0]\n    \n    print(\"Raw prediction =\", pred)\n    \n    if pred > 0.5:\n        print(f\"Prediction: DOG ({pred*100:.2f}%)\")\n    else:\n        print(f\"Prediction: CAT ({(1-pred)*100:.2f}%)\")\n        \n    return pred","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:15:54.620156Z","iopub.execute_input":"2026-05-31T13:15:54.620514Z","iopub.status.idle":"2026-05-31T13:15:54.628141Z","shell.execute_reply.started":"2026-05-31T13:15:54.620480Z","shell.execute_reply":"2026-05-31T13:15:54.627473Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Test ảnh mèo\npredict_image(\"/kaggle/working/data/cats/cat.100.jpg\")\npredict_image(\"/kaggle/working/data/cats/cat.500.jpg\")\npredict_image(\"/kaggle/working/data/cats/cat.1000.jpg\")\npredict_image(\"/kaggle/working/data/cats/cat.2000.jpg\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:15:54.629225Z","iopub.execute_input":"2026-05-31T13:15:54.629592Z","iopub.status.idle":"2026-05-31T13:15:55.548377Z","shell.execute_reply.started":"2026-05-31T13:15:54.629560Z","shell.execute_reply":"2026-05-31T13:15:55.547681Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Test ảnh chó\npredict_image(\"/kaggle/working/data/dogs/dog.100.jpg\")\npredict_image(\"/kaggle/working/data/dogs/dog.500.jpg\")\npredict_image(\"/kaggle/working/data/dogs/dog.1000.jpg\")\npredict_image(\"/kaggle/working/data/dogs/dog.2000.jpg\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:15:55.549712Z","iopub.execute_input":"2026-05-31T13:15:55.549947Z","iopub.status.idle":"2026-05-31T13:15:56.270785Z","shell.execute_reply.started":"2026-05-31T13:15:55.549926Z","shell.execute_reply":"2026-05-31T13:15:56.270008Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tạo Grad-CAM\nimport tensorflow as tf\nimport numpy as np\n\ndef make_gradcam_heatmap(img_array, model, last_conv_layer_name):\n\n    last_conv_layer = model.get_layer(last_conv_layer_name)\n\n    grad_model = tf.keras.Model(\n        inputs=model.inputs,\n        outputs=[\n            last_conv_layer.output,\n            model.outputs[0]\n        ]\n    )\n\n    with tf.GradientTape() as tape:\n\n        conv_outputs, predictions = grad_model(img_array)\n\n        pred_index = tf.argmax(predictions[0])\n\n        class_channel = predictions[:, 0]\n\n    grads = tape.gradient(\n        class_channel,\n        conv_outputs\n    )\n\n    pooled_grads = tf.reduce_mean(\n        grads,\n        axis=(0, 1, 2)\n    )\n\n    conv_outputs = conv_outputs[0]\n\n    heatmap = conv_outputs @ pooled_grads[..., tf.newaxis]\n\n    heatmap = tf.squeeze(heatmap)\n\n    heatmap = tf.maximum(\n        heatmap,\n        0\n    ) / tf.math.reduce_max(heatmap)\n\n    return heatmap.numpy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:29:15.895538Z","iopub.execute_input":"2026-05-31T13:29:15.896554Z","iopub.status.idle":"2026-05-31T13:29:15.904458Z","shell.execute_reply.started":"2026-05-31T13:29:15.896508Z","shell.execute_reply":"2026-05-31T13:29:15.903619Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_gradcam(img_path):\n\n    img = image.load_img(\n        img_path,\n        target_size=(150,150)\n    )\n\n    img_array = image.img_to_array(img)\n\n    img_array = np.expand_dims(\n        img_array,\n        axis=0\n    )\n\n    heatmap = make_gradcam_heatmap(\n        img_array,\n        model,\n        \"conv2d_2\"\n    )\n\n    original = cv2.imread(img_path)\n\n    heatmap_resized = cv2.resize(\n        heatmap,\n        (original.shape[1], original.shape[0])\n    )\n\n    heatmap_uint8 = np.uint8(\n        255 * heatmap_resized\n    )\n\n    heatmap_color = cv2.applyColorMap(\n        heatmap_uint8,\n        cv2.COLORMAP_JET\n    )\n\n    overlay = cv2.addWeighted(\n        original,\n        0.6,\n        heatmap_color,\n        0.4,\n        0\n    )\n\n    plt.figure(figsize=(15,5))\n\n    plt.subplot(1,3,1)\n    plt.imshow(\n        cv2.cvtColor(\n            original,\n            cv2.COLOR_BGR2RGB\n        )\n    )\n    plt.title(\"Original\")\n    plt.axis(\"off\")\n\n    plt.subplot(1,3,2)\n    plt.imshow(heatmap, cmap=\"jet\")\n    plt.title(\"Heatmap\")\n    plt.axis(\"off\")\n\n    plt.subplot(1,3,3)\n    plt.imshow(\n        cv2.cvtColor(\n            overlay,\n            cv2.COLOR_BGR2RGB\n        )\n    )\n    plt.title(\"Grad-CAM\")\n    plt.axis(\"off\")\n\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:34:46.663172Z","iopub.execute_input":"2026-05-31T13:34:46.663966Z","iopub.status.idle":"2026-05-31T13:34:46.672429Z","shell.execute_reply.started":"2026-05-31T13:34:46.663923Z","shell.execute_reply":"2026-05-31T13:34:46.671418Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Chạy Grad-CAM ảnh dự đoán đúng\nshow_gradcam(\n    \"/kaggle/working/data/cats/cat.1000.jpg\"\n)\nshow_gradcam(\n    \"/kaggle/working/data/dogs/dog.2000.jpg\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:36:07.343931Z","iopub.execute_input":"2026-05-31T13:36:07.344728Z","iopub.status.idle":"2026-05-31T13:36:08.134008Z","shell.execute_reply.started":"2026-05-31T13:36:07.344689Z","shell.execute_reply":"2026-05-31T13:36:08.133125Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Chạy Grad-CAM cho ảnh dự đoán sai\nshow_gradcam(\n    \"/kaggle/working/data/cats/cat.100.jpg\"\n)\nshow_gradcam(\n    \"/kaggle/working/data/dogs/dog.1000.jpg\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-31T13:36:23.391919Z","iopub.execute_input":"2026-05-31T13:36:23.392919Z","iopub.status.idle":"2026-05-31T13:36:24.773245Z","shell.execute_reply.started":"2026-05-31T13:36:23.392850Z","shell.execute_reply":"2026-05-31T13:36:24.772502Z"}},"outputs":[],"execution_count":null}]}