Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 136 additions & 43 deletions ML03_BaiTap1_An.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,32 @@
"execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-11T15:39:57.204710Z",
"iopub.status.busy": "2026-06-11T15:39:57.203713Z",
"iopub.status.idle": "2026-06-11T15:39:58.496997Z",
"shell.execute_reply": "2026-06-11T15:39:58.496997Z"
"iopub.execute_input": "2026-06-11T16:23:56.122681Z",
"iopub.status.busy": "2026-06-11T16:23:56.122681Z",
"iopub.status.idle": "2026-06-11T16:23:57.347330Z",
"shell.execute_reply": "2026-06-11T16:23:57.347330Z"
}
},
"outputs": [],
"source": [
"# Thư viện vẽ biểu đồ và xử lý mảng/bảng dữ liệu.\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import pandas as pd\n",
"import sklearn.datasets as datasets\n",
"\n",
"# PCA dùng cho giảm chiều không giám sát; LDA dùng cho giảm chiều có giám sát bằng nhãn lớp.\n",
"from sklearn.decomposition import PCA\n",
"from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tóm tắt:** Cell này chuẩn bị thư viện cần dùng cho trực quan hóa, xử lý dữ liệu bảng/mảng, PCA và LDA.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand All @@ -64,10 +73,10 @@
"execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-11T15:39:58.498261Z",
"iopub.status.busy": "2026-06-11T15:39:58.498261Z",
"iopub.status.idle": "2026-06-11T15:39:58.512952Z",
"shell.execute_reply": "2026-06-11T15:39:58.512952Z"
"iopub.execute_input": "2026-06-11T16:23:57.348335Z",
"iopub.status.busy": "2026-06-11T16:23:57.348335Z",
"iopub.status.idle": "2026-06-11T16:23:57.363450Z",
"shell.execute_reply": "2026-06-11T16:23:57.363450Z"
}
},
"outputs": [
Expand All @@ -83,26 +92,35 @@
}
],
"source": [
"# Load bộ digits từ sklearn: mỗi ảnh 8x8 được flatten thành vector 64 feature.\n",
"digits = datasets.load_digits()\n",
"X = digits.data\n",
"y = digits.target\n",
"images = digits.images\n",
"X = digits.data # Ma trận feature, shape = (số mẫu, 64 pixel).\n",
"y = digits.target # Nhãn digit tương ứng từ 0 đến 9.\n",
"images = digits.images # Dạng ảnh 8x8, dùng để trực quan hóa mẫu.\n",
"\n",
"# In thông tin tổng quan để hiểu kích thước, số lớp và miền giá trị pixel.\n",
"print(f\"Shape dữ liệu: {X.shape}\")\n",
"print(f\"Số lớp digits: {len(np.unique(y))} - {digits.target_names.tolist()}\")\n",
"print(f\"Kích thước ảnh gốc: {images.shape[1]}x{images.shape[2]}\")\n",
"print(f\"Miền giá trị pixel: min={X.min()}, max={X.max()}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tóm tắt:** Dữ liệu `load_digits` được tách thành ma trận feature `X`, nhãn `y` và ảnh 8x8 để dùng cho phân tích tiếp theo.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-11T15:39:58.540413Z",
"iopub.status.busy": "2026-06-11T15:39:58.540413Z",
"iopub.status.idle": "2026-06-11T15:39:58.770873Z",
"shell.execute_reply": "2026-06-11T15:39:58.770364Z"
"iopub.execute_input": "2026-06-11T16:23:57.383087Z",
"iopub.status.busy": "2026-06-11T16:23:57.382571Z",
"iopub.status.idle": "2026-06-11T16:23:57.595982Z",
"shell.execute_reply": "2026-06-11T16:23:57.595982Z"
}
},
"outputs": [
Expand All @@ -118,9 +136,11 @@
}
],
"source": [
"# Hiển thị một ảnh mẫu cho mỗi digit để quan sát hình dạng dữ liệu gốc.\n",
"fig, axes = plt.subplots(2, 5, figsize=(10, 4))\n",
"\n",
"for digit, ax in zip(range(10), axes.ravel()):\n",
" # Lấy sample đầu tiên thuộc từng lớp digit.\n",
" sample_index = np.where(y == digit)[0][0]\n",
" ax.imshow(images[sample_index], cmap='gray_r')\n",
" ax.set_title(f\"Digit {digit}\")\n",
Expand All @@ -129,6 +149,13 @@
"plt.tight_layout()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tóm tắt:** Các ảnh mẫu giúp xác nhận mỗi observation là một chữ số viết tay 8x8, tạo nền cho việc hiểu ý nghĩa pixel/feature.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand All @@ -141,10 +168,10 @@
"execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-11T15:39:58.771877Z",
"iopub.status.busy": "2026-06-11T15:39:58.771877Z",
"iopub.status.idle": "2026-06-11T15:39:58.782613Z",
"shell.execute_reply": "2026-06-11T15:39:58.782613Z"
"iopub.execute_input": "2026-06-11T16:23:57.596986Z",
"iopub.status.busy": "2026-06-11T16:23:57.596986Z",
"iopub.status.idle": "2026-06-11T16:23:57.606749Z",
"shell.execute_reply": "2026-06-11T16:23:57.606749Z"
}
},
"outputs": [
Expand Down Expand Up @@ -301,13 +328,18 @@
}
],
"source": [
"# Fit PCA với toàn bộ số chiều để phân tích lượng phương sai được giữ bởi từng component.\n",
"pca_full = PCA()\n",
"pca_full.fit(X)\n",
"\n",
"# explained_variance_ratio_: tỷ lệ phương sai mà từng principal component giải thích.\n",
"explained_variance = pca_full.explained_variance_ratio_\n",
"cumulative_variance = np.cumsum(explained_variance)\n",
"\n",
"# Xác định số component tối thiểu để giữ được ít nhất 90% phương sai.\n",
"n_components_90 = int(np.argmax(cumulative_variance >= 0.90) + 1)\n",
"\n",
"# Bảng tóm tắt 15 component đầu tiên để dễ đọc và so sánh.\n",
"pca_summary = pd.DataFrame({\n",
" 'PC': np.arange(1, 16),\n",
" 'explained_variance_ratio': explained_variance[:15],\n",
Expand All @@ -318,15 +350,22 @@
"pca_summary"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tóm tắt:** PCA đầy đủ cho biết số component cần giữ để bảo toàn phần lớn phương sai và tạo bảng variance của các component đầu tiên.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-11T15:39:58.783617Z",
"iopub.status.busy": "2026-06-11T15:39:58.783617Z",
"iopub.status.idle": "2026-06-11T15:39:58.871927Z",
"shell.execute_reply": "2026-06-11T15:39:58.871927Z"
"iopub.execute_input": "2026-06-11T16:23:57.607753Z",
"iopub.status.busy": "2026-06-11T16:23:57.607753Z",
"iopub.status.idle": "2026-06-11T16:23:57.692202Z",
"shell.execute_reply": "2026-06-11T16:23:57.692202Z"
}
},
"outputs": [
Expand All @@ -342,9 +381,11 @@
}
],
"source": [
"# Trực quan hóa loading của 4 principal components đầu tiên dưới dạng ảnh 8x8.\n",
"fig, axes = plt.subplots(1, 4, figsize=(12, 3))\n",
"\n",
"for i, ax in enumerate(axes):\n",
" # Mỗi component có 64 hệ số loading, reshape lại về lưới pixel 8x8.\n",
" loading = pca_full.components_[i].reshape(8, 8)\n",
" max_abs = np.abs(loading).max()\n",
" im = ax.imshow(loading, cmap='coolwarm', vmin=-max_abs, vmax=max_abs)\n",
Expand All @@ -356,6 +397,13 @@
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tóm tắt:** Loading map cho thấy mỗi principal component nhấn mạnh các vùng pixel khác nhau trên ảnh chữ số.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand All @@ -368,10 +416,10 @@
"execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-11T15:39:58.873932Z",
"iopub.status.busy": "2026-06-11T15:39:58.873932Z",
"iopub.status.idle": "2026-06-11T15:39:58.879153Z",
"shell.execute_reply": "2026-06-11T15:39:58.879153Z"
"iopub.execute_input": "2026-06-11T16:23:57.693206Z",
"iopub.status.busy": "2026-06-11T16:23:57.693206Z",
"iopub.status.idle": "2026-06-11T16:23:57.699401Z",
"shell.execute_reply": "2026-06-11T16:23:57.699401Z"
}
},
"outputs": [
Expand Down Expand Up @@ -497,10 +545,13 @@
}
],
"source": [
"# Tính feature importance từ độ lớn loading của các component giữ 90% phương sai.\n",
"# Component giải thích nhiều phương sai hơn được gán trọng số cao hơn.\n",
"weighted_loadings = np.abs(pca_full.components_[:n_components_90]) * explained_variance[:n_components_90, None]\n",
"feature_importance = weighted_loadings.sum(axis=0)\n",
"feature_importance = feature_importance / feature_importance.sum()\n",
"\n",
"# Lấy 10 pixel quan trọng nhất để chỉ ra vị trí hàng/cột trên ảnh 8x8.\n",
"top_feature_idx = np.argsort(feature_importance)[-10:][::-1]\n",
"top_features = pd.DataFrame({\n",
" 'pixel_index': top_feature_idx,\n",
Expand All @@ -512,15 +563,22 @@
"top_features"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tóm tắt:** Feature importance được suy ra từ loading có trọng số theo phương sai, sau đó chọn ra 10 pixel đóng góp mạnh nhất.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-11T15:39:58.880662Z",
"iopub.status.busy": "2026-06-11T15:39:58.879153Z",
"iopub.status.idle": "2026-06-11T15:39:58.942700Z",
"shell.execute_reply": "2026-06-11T15:39:58.942700Z"
"iopub.execute_input": "2026-06-11T16:23:57.700944Z",
"iopub.status.busy": "2026-06-11T16:23:57.700944Z",
"iopub.status.idle": "2026-06-11T16:23:57.761009Z",
"shell.execute_reply": "2026-06-11T16:23:57.761009Z"
}
},
"outputs": [
Expand All @@ -536,9 +594,11 @@
}
],
"source": [
"# Vẽ heatmap feature importance để nhìn nhanh vùng pixel có đóng góp cao.\n",
"plt.figure(figsize=(5, 4))\n",
"plt.imshow(feature_importance.reshape(8, 8), cmap='viridis')\n",
"\n",
"# Ghi index của 10 feature quan trọng nhất trực tiếp lên heatmap.\n",
"for idx in top_feature_idx:\n",
" row, col = divmod(int(idx), 8)\n",
" color = 'white' if feature_importance[idx] > feature_importance.max() * 0.45 else 'black'\n",
Expand All @@ -550,6 +610,13 @@
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tóm tắt:** Heatmap biến bảng importance thành bản đồ pixel, giúp nhìn nhanh vùng nét chữ có vai trò nổi bật.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand All @@ -569,10 +636,10 @@
"execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-11T15:39:58.943868Z",
"iopub.status.busy": "2026-06-11T15:39:58.943868Z",
"iopub.status.idle": "2026-06-11T15:39:58.947861Z",
"shell.execute_reply": "2026-06-11T15:39:58.947861Z"
"iopub.execute_input": "2026-06-11T16:23:57.762013Z",
"iopub.status.busy": "2026-06-11T16:23:57.762013Z",
"iopub.status.idle": "2026-06-11T16:23:57.766482Z",
"shell.execute_reply": "2026-06-11T16:23:57.766482Z"
}
},
"outputs": [
Expand All @@ -585,38 +652,55 @@
}
],
"source": [
"# Giảm dữ liệu xuống 2 chiều bằng PCA để vẽ scatter plot.\n",
"pca = PCA(n_components=2)\n",
"X_pca = pca.fit_transform(X)\n",
"\n",
"# In tổng phương sai mà 2 component đầu giữ lại.\n",
"print(f\"PC1 + PC2 giải thích {pca.explained_variance_ratio_.sum():.2%} phương sai của dữ liệu.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tóm tắt:** PCA 2D tạo embedding không giám sát để phục vụ biểu đồ phân cụm và so sánh với LDA.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-11T15:39:58.948865Z",
"iopub.status.busy": "2026-06-11T15:39:58.948865Z",
"iopub.status.idle": "2026-06-11T15:39:58.963009Z",
"shell.execute_reply": "2026-06-11T15:39:58.963009Z"
"iopub.execute_input": "2026-06-11T16:23:57.767486Z",
"iopub.status.busy": "2026-06-11T16:23:57.767486Z",
"iopub.status.idle": "2026-06-11T16:23:57.781030Z",
"shell.execute_reply": "2026-06-11T16:23:57.781030Z"
}
},
"outputs": [],
"source": [
"# Giảm dữ liệu xuống 2 chiều bằng LDA; LDA cần nhãn y để tối ưu khoảng cách giữa các lớp.\n",
"lda = LDA(n_components=2)\n",
"X_lda = lda.fit_transform(X, y)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tóm tắt:** LDA 2D tạo embedding có giám sát, tận dụng nhãn digit để ưu tiên tách các lớp.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-11T15:39:58.964013Z",
"iopub.status.busy": "2026-06-11T15:39:58.964013Z",
"iopub.status.idle": "2026-06-11T15:39:59.359822Z",
"shell.execute_reply": "2026-06-11T15:39:59.359822Z"
"iopub.execute_input": "2026-06-11T16:23:57.782034Z",
"iopub.status.busy": "2026-06-11T16:23:57.782034Z",
"iopub.status.idle": "2026-06-11T16:23:58.172222Z",
"shell.execute_reply": "2026-06-11T16:23:58.171720Z"
}
},
"outputs": [
Expand All @@ -632,6 +716,7 @@
}
],
"source": [
"# Vẽ PCA và LDA cạnh nhau để so sánh khả năng tách cụm các digit.\n",
"fig, ax = plt.subplots(1, 2, figsize=(22, 10))\n",
"\n",
"unique_y = np.unique(y)\n",
Expand All @@ -641,6 +726,7 @@
"\n",
"for j in range(2):\n",
" for digit in unique_y:\n",
" # Mỗi digit được vẽ bằng một nhóm điểm riêng để quan sát overlap giữa các lớp.\n",
" ax[j].scatter(data[j][y == digit, 0], data[j][y == digit, 1], label=str(digit), alpha=0.75, s=28)\n",
" ax[j].set_title(titles[j])\n",
" ax[j].set_xlabel(axis_labels[j][0])\n",
Expand All @@ -650,6 +736,13 @@
"plt.tight_layout()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tóm tắt:** Biểu đồ cạnh nhau là cơ sở trực quan để nhận xét PCA thường overlap nhiều hơn LDA trên bài toán phân lớp digits.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
Loading