Trái ngược hoàn toàn với thế giới quan của Hồi quy tuyến tính (OLS) vốn luôn cố gắng tìm ra một phương trình hàm số tổng quát đi qua toàn bộ dữ liệu, thuật toán K-Nearest Neighbors (KNN) đại diện cho một triết lý phân tích khác biệt: trường phái phi tham số (Non-parametric Approach). Bản chất của KNN là giải phóng mô hình khỏi mọi định kiến về mặt phân phối dữ liệu, từ chối việc áp đặt dữ liệu phải tuân theo bất kỳ một phương trình cứng nhắc nào.
Bứt phá khỏi phương trình toàn cục bằng triết lý "Học lười" (Lazy Learning) KNN tiếp cận bài toán dự báo theo nguyên lý học tập dựa trên thực nghiệm trực tiếp (Instance-based Learning) hay thường được gọi vui là "học lười" (Lazy Learning). Thuật toán này hoàn toàn không có giai đoạn huấn luyện để tối ưu hóa các hệ số β. Thay vào đó, mô hình chỉ đơn giản là "lưu trữ" toàn bộ tập dữ liệu lịch sử vào bộ nhớ.
Nó chỉ thực sự vận hành và thực hiện các phép tính toán học khi có một quan sát mới cần dự báo xuất hiện. Lúc này, tư duy cốt lõi của KNN hoàn toàn khớp với "Phương pháp định giá tham chiếu" trong kinh tế học, dựa trên tiên đề hình học: "Các đối tượng có đặc tính tương đồng sẽ nằm gần nhau trong không gian đa chiều".
Cơ chế toán học của KNN Regressor Khi xuất hiện một điểm dữ liệu mới cần dự báo (ký hiệu là x*), mô hình KNN sẽ vận hành một cách tuần tự qua 3 bước cốt lõi:
Đo lường khoảng cách: Thuật toán sẽ quét qua toàn bộ không gian tập dữ liệu huấn luyện để tính toán khoảng cách hình học từ điểm mới x* đến tất cả các điểm dữ liệu lịch sử. Thước đo phổ biến nhất thường được áp dụng là khoảng cách đường thẳng Euclidean:
Trích xuất K láng giềng: Sau khi thiết lập được các khoảng cách, thuật toán tiến hành sắp xếp chúng theo thứ tự từ thấp đến cao. Từ đó, mô hình sẽ lọc ra một tập hợp gồm đúng K quan sát (được gọi là láng giềng) có khoảng cách ngắn nhất so với điểm cần dự báo x*
Tổng hợp giá trị dự báo: Khác với bài toán phân lớp dùng cơ chế bỏ phiếu số đông, đối với bài toán Hồi quy (KNN Regressor), giá trị dự báo cuối cùng đơn giản là trung bình cộng giá trị biến mục tiêu của K láng giềng gần nhất vừa tìm được:
(Lưu ý mở rộng: Trong các phiên bản thuật toán nâng cao, KNN có thể tích hợp thêm trọng số tỷ lệ nghịch với khoảng cách (Distance-weighted KNN). Cơ chế này đảm bảo rằng những láng giềng nào nằm càng sát với điểm x* thì sẽ có tiếng nói mạnh mẽ hơn và mức độ đóng góp cao hơn vào kết quả dự báo cuối cùng).
Sức mạnh và độ tin cậy của thuật toán KNN phụ thuộc gần như hoàn toàn vào một siêu tham số (hyperparameter) duy nhất: K (số lượng láng giềng). Thuật toán KNN không tự học ra tham số K trong quá trình huấn luyện; thay vào đó, người phân tích dữ liệu phải chủ động thiết lập con số này. Việc lựa chọn giá trị K chính là hiện thân rõ nét nhất của nguyên lý kinh điển trong học máy: Sự đánh đổi Bias - Variance (Bias-Variance Tradeoff).
Tác động của siêu tham số K:
Khi K quá nhỏ (Ví dụ K = 1): Mô hình trở nên cực kỳ nhạy cảm với dữ liệu cục bộ. Nó sẽ dự báo dựa trên đúng một điểm láng giềng gần nhất, dẫn đến việc "học thuộc lòng" từng điểm dữ liệu huấn luyện, bao gồm cả những điểm nhiễu (noise) hay các giá trị ngoại lệ (outliers). Trạng thái này tạo ra một mô hình có độ chệch thấp (Low Bias) nhưng phương sai cực cao (High Variance), dẫn đến thảm họa Overfitting (Quá khớp / Học vẹt). Mô hình rất khớp với dữ liệu quá khứ nhưng sẽ thất bại khi dự báo dữ liệu tương lai.
Khi K quá lớn (Ví dụ K tiến gần đến tổng số quan sát N): Mô hình sẽ lấy trung bình của một khu vực quá rộng, bất chấp các điểm dữ liệu đó có thực sự tương đồng với đối tượng cần dự báo hay không. Ranh giới dự báo bị "cào bằng", làm mờ các cấu trúc cục bộ và những đặc tính quan trọng của dữ liệu. Lúc này, mô hình rơi vào trạng thái độ chệch rất cao (High Bias) và phương sai thấp (Low Variance), gây ra hiện tượng underfitting (Kém khớp / học chưa đủ).
Giải pháp thực chiến: Dò tìm tự động bằng GridSearchCV
Trong phân tích kinh tế thực chứng, giá trị K tối ưu không bao giờ được chọn bằng cảm tính. Dù có một số quy tắc ngón tay cái (ví dụ: chọn K căn bậc 2 của n với n là số lượng quan sát), phương pháp khoa học và chính xác nhất bắt buộc phải sử dụng là Kiểm định chéo (Cross-Validation).
Thay vì phải lập trình thử nghiệm thủ công từng giá trị K, chúng ta sẽ sử dụng công cụ GridSearchCV từ thư viện scikit-learn. Công cụ này sẽ tạo ra một mạng lưới (grid) các giá trị K tiềm năng, tự động chia tập dữ liệu huấn luyện thành nhiều phần (K-Fold), sau đó huấn luyện và kiểm tra chéo liên tục trên từng phần để tìm ra con số K mang lại sai số trung bình thấp nhất và ổn định nhất.
💻 Cú pháp lập trình cơ bản: Đoạn mã dưới đây minh họa tư duy thiết lập lưới tìm kiếm tự động để rà soát siêu tham số K từ 1 đến 30:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import StandardScaler
# =====================================================================
# 1. TỰ TẠO DỮ LIỆU MÔ PHỎNG ĐỂ CÓ DỮ LIỆU ĐẦU VÀO
# =====================================================================
np.random.seed(42)
# Giả lập 200 quan sát với 2 biến đầu vào
X_mock = pd.DataFrame({
'Bien_1': np.random.uniform(10, 100, 200),
'Bien_2': np.random.uniform(1, 10, 200)
})
# Biến mục tiêu bị ảnh hưởng bởi 2 biến trên cộng thêm nhiễu
y_mock = 0.5 * X_mock['Bien_1'] + 2.5 * X_mock['Bien_2'] + np.random.normal(0, 5, 200)
# Chia dữ liệu thành tập Huấn luyện (Train) và Kiểm tra (Test)
X_train, X_test, y_train, y_test = train_test_split(X_mock, y_mock, test_size=0.2, random_state=42)
# =====================================================================
# 2. CHUẨN HÓA DỮ LIỆU (KỶ LUẬT BẮT BUỘC CỦA KNN)
# =====================================================================
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # Chỉ fit trên tập Train để tránh rò rỉ dữ liệu
X_test_scaled = scaler.transform(X_test) # Chỉ transform trên tập Test
# =====================================================================
# 3. TÌM K TỐI ƯU BẰNG GRID SEARCH CV VÀ 5-FOLD CROSS-VALIDATION
# =====================================================================
# Khởi tạo mô hình KNN gốc
knn_model = KNeighborsRegressor()
# Thiết lập không gian tìm kiếm (Thử nghiệm K từ 1 đến 30)
param_grid = {'n_neighbors': np.arange(1, 31)}
# Cấu hình GridSearchCV với 5-fold Cross-Validation
# Hàm scoring='neg_mean_squared_error' sẽ cố gắng tìm K để sai số MSE là nhỏ nhất
grid_search = GridSearchCV(estimator=knn_model,
param_grid=param_grid,
cv=5,
scoring='neg_mean_squared_error')
# Kích hoạt quá trình rà soát tự động trên tập dữ liệu huấn luyện
grid_search.fit(X_train_scaled, y_train)
# Trích xuất giá trị K tối ưu nhất
print("--- KẾT QUẢ TỐI ƯU HÓA ---")
print(f"Giá trị K tối ưu nhất tìm được là: {grid_search.best_params_['n_neighbors']}")
Ghi chú: Việc tìm ra tham số K tối ưu giúp hệ thống duy trì được sự cân bằng hoàn hảo: vừa đủ linh hoạt để nắm bắt các biến động thực tế (giảm Bias), vừa đủ ổn định để không bị đánh lừa bởi dữ liệu nhiễu (giảm Variance).
💡 Góc nhìn chuyên sâu: Bản chất việc "Huấn luyện" KNN và Sức mạnh của K-Fold Cross-Validation
Để hiểu thấu đáo cách vận hành của thuật toán K-Nearest Neighbors (KNN) trong thực tế, chúng ta cần làm rõ một nghịch lý thú vị: Làm thế nào để "huấn luyện" một mô hình vốn được mệnh danh là "không cần huấn luyện"? Câu trả lời nằm ở sự kết hợp giữa việc dò tìm Siêu tham số (Hyperparameter Tuning) và kỹ thuật Kiểm định chéo (Cross-Validation).
1. Nghịch lý "Huấn luyện" của thuật toán Học lười (Lazy Learning) Như đã phân tích, KNN là một thuật toán phi tham số. Khi bạn gọi hàm .fit(X_train, y_train) trong Python, máy tính hoàn toàn không thực hiện bất kỳ phương trình tối ưu hóa nào để tìm ra các trọng số toán học (như hệ số $\beta$ trong Hồi quy tuyến tính). Nó chỉ đơn giản là "lưu trữ" hoặc lập chỉ mục không gian cho toàn bộ tập dữ liệu lịch sử vào bộ nhớ (RAM).
Vậy nếu máy tính không tự học, thì người phân tích dữ liệu đang thực sự "huấn luyện" cái gì? Câu trả lời chính là đi tìm Siêu tham số K (số lượng láng giềng) tối ưu nhất. Việc chọn K quyết định trực tiếp đến sự đánh đổi Bias - Variance: K quá nhỏ (ví dụ $K=1$) dẫn đến Overfitting (Quá khớp), trong khi K quá lớn dẫn đến Underfitting (Kém khớp).
2. Rào cản: Tại sao không thử K trực tiếp trên tập Test? Để tìm K, một sai lầm chết người mà người mới học thường mắc phải là: Thử lần lượt $K=1, 2, 3...$ sau đó áp dụng luôn lên tập kiểm tra (Test Set) xem K nào cho điểm cao nhất thì chọn.
Hành động này vi phạm nguyên tắc tối kỵ trong Học máy: Rò rỉ dữ liệu (Data Leakage),. Tập Test sinh ra với sứ mệnh duy nhất là làm một bài thi khách quan ở phút cuối cùng để nghiệm thu mô hình. Nếu bạn liên tục thay đổi K để "chiều lòng" tập Test, bạn đã biến tập Test thành tập Validation (Kiểm định). Lúc này, kết quả đánh giá sẽ bị lạc quan giả tạo và mô hình sẽ thất bại khi đối mặt với dữ liệu thực tế trong tương lai,.
3. Giải pháp vẹn toàn: K-Fold Cross-Validation Để giải quyết bài toán "tìm K tối ưu mà không được đụng vào tập Test", kỹ thuật K-Fold Cross-Validation (Kiểm định chéo K-lần) được áp dụng như một tiêu chuẩn vàng. Thay vì chỉ chia dữ liệu huấn luyện một lần dễ dẫn đến may rủi, phương pháp này (phổ biến nhất là 5-Fold) hoạt động theo cơ chế sau:
Chia nhỏ: Tập dữ liệu huấn luyện (Training Set) được cắt thành 5 phần (fold) bằng nhau.
Thi thử 5 vòng: Với một giá trị K cụ thể (ví dụ $K=3$), mô hình sẽ được huấn luyện và kiểm tra 5 lần. Ở mỗi lần, nó lấy 4 phần để "học" và dùng 1 phần còn lại để "thi thử" (Validation),.
Xoay vòng: Quá trình này lặp lại xoay vòng sao cho mỗi phần dữ liệu đều được làm bài thi đúng một lần.
Đánh giá: Điểm số của cấu hình $K=3$ chính là trung bình cộng của cả 5 lần thi đó,.
Hệ thống sẽ lặp lại toàn bộ quy trình trên cho hàng loạt các giá trị K khác nhau (từ 1 đến 30) thông qua công cụ tìm kiếm lưới (GridSearchCV).
4. Ý nghĩa tổng hợp đối với thuật toán KNN Việc áp dụng K-Fold Cross-Validation vào quá trình tìm K cho KNN mang lại 3 giá trị thực tiễn vô cùng to lớn:
Tạo thước đo ổn định, triệt tiêu sự ngẫu nhiên: Điểm số trung bình qua 5 vòng kiểm tra chéo giúp loại bỏ hoàn toàn rủi ro bị phụ thuộc vào một lần chia dữ liệu "ăn may" hay "xui xẻo", mang lại đánh giá khách quan nhất về năng lực của từng con số K.
Tối ưu hóa tối đa tài nguyên dữ liệu: 100% dữ liệu huấn luyện đều được luân phiên tận dụng để vừa đóng vai trò học tập, vừa đóng vai trò kiểm định, cực kỳ hữu ích khi doanh nghiệp có nguồn dữ liệu giới hạn.
Bảo vệ sự "trong trắng" của tập Test: K-Fold tạo ra các tập Validation nội bộ, đóng vai trò như một "hội đồng giám khảo" giúp chọn ra con số K xuất sắc nhất. Nhờ đó, tập Test cuối cùng được bảo vệ hoàn toàn độc lập, đảm bảo kết quả nghiệm thu dự báo phản ánh đúng thực tế,.
Thuật toán KNN tuy mạnh mẽ và linh hoạt nhờ triết lý phi tham số, nhưng nó lại là một thuật toán cực kỳ "nhạy cảm" với chất lượng và hình thái của dữ liệu đầu vào. Khi áp dụng KNN vào dữ liệu kinh tế vĩ mô, người phân tích phải đối mặt với hai rào cản kỹ thuật nghiêm trọng có thể làm sụp đổ hoàn toàn độ chính xác của mô hình nếu không được xử lý đúng đắn.
1. Kỷ luật Chuẩn hóa (StandardScaler) - Sự bắt buộc trước độ chệch quy mô
Bản chất hàm mục tiêu của KNN là đo lường khoảng cách trong không gian hình học (phổ biến nhất là khoảng cách Euclidean). Khoảng cách này được tính bằng tổng bình phương độ lệch của tất cả các biến đặc trưng.
Hãy tưởng tượng bạn đang dự báo lượng phát thải $CO_2$ dựa trên hai biến: GDP thực tế và Tỷ lệ đô thị hóa. Trong không gian toán học, GDP được tính bằng hàng tỷ hoặc hàng nghìn tỷ USD (thang đo $10^9$), trong khi tỷ lệ đô thị hóa chỉ là một số phần trăm dao động từ 0 đến 100 (thang đo $10^{-2}$). Nếu đưa trực tiếp dữ liệu thô này vào thuật toán, chênh lệch khổng lồ của GDP sẽ ngay lập tức "nuốt chửng" hoàn toàn sự biến thiên của tỷ lệ đô thị hóa. KNN sẽ bị "mù" trước đặc trưng đô thị hóa và chỉ tìm kiếm láng giềng dựa duy nhất vào quy mô GDP.
Giải pháp: Áp dụng kỷ luật chuẩn hóa StandardScaler (Z-score Normalization) là yêu cầu bắt buộc. Phương pháp này đưa tất cả các biến về cùng một hệ quy chiếu có giá trị trung bình bằng 0 và độ lệch chuẩn bằng 1. Tuy nhiên, nguyên tắc tối kỵ cần tuân thủ là ngăn chặn rò rỉ dữ liệu (Data Leakage): Bạn chỉ được phép tính toán trung bình và độ lệch chuẩn (hàm fit_transform) trên tập Huấn luyện (Train), và chỉ áp dụng bộ thông số đó (hàm transform) lên tập Kiểm tra (Test).
2. Cảnh báo điền khuyết: Thảm họa của việc dùng trung bình toàn cầu
Trong dữ liệu vĩ mô, hiện tượng khuyết thiếu dữ liệu (Missing Values) là rất phổ biến. Một thói quen nguy hiểm của nhiều nhà phân tích dữ liệu thiếu kinh nghiệm là sử dụng lệnh fillna(mean) hoặc SimpleImputer(strategy='mean') để điền giá trị trung bình của toàn cầu vào các ô bị trống.
Trong kinh tế học vĩ mô, hành vi phát thải hoặc cấu trúc kinh tế của các siêu cường công nghiệp (như Hoa Kỳ, Trung Quốc) có sự chênh lệch mang tính vực thẳm so với các quốc gia đang phát triển hoặc các đảo quốc nhỏ. Việc lấy giá trị trung bình của toàn thế giới để điền vào dữ liệu bị thiếu của một quốc gia nhỏ sẽ tạo ra một sai số hệ thống (bias) nghiêm trọng.
Hành động này làm phẳng hóa một cách khiên cưỡng sự phân hóa kinh tế, làm mờ đi các đặc thù cấu trúc và tạo ra các xu hướng kinh tế nhân tạo (artificial economic patterns) không hề tồn tại trong thực tế.
💻 Ví dụ Code thực chiến: Kỷ luật Tiền xử lý dữ liệu vĩ mô
Đoạn code dưới đây minh họa cách xử lý khuyết thiếu theo nhóm (Group-based Imputation) thay vì trung bình toàn cục, và áp dụng kỷ luật chuẩn hóa nghiêm ngặt:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# =====================================================================
# BƯỚC 0: TỰ TẠO DỮ LIỆU MÔ PHỎNG VĨ MÔ CÓ KHUYẾT THIẾU
# =====================================================================
np.random.seed(42)
# Giả lập 200 quốc gia thuộc 3 khu vực địa lý khác nhau
n_countries = 200
khu_vuc = np.random.choice(['Châu Á', 'Châu Âu', 'Châu Phi'], n_countries)
quoc_gia = [f"QuocGia_{i}" for i in range(n_countries)]
# Tạo dữ liệu GDP và Đô thị hóa theo đặc thù khu vực
gdp = []
do_thi_hoa = []
for kv in khu_vuc:
if kv == 'Châu Á':
gdp.append(np.random.uniform(1000, 20000))
do_thi_hoa.append(np.random.uniform(0.3, 0.7))
elif kv == 'Châu Âu':
gdp.append(np.random.uniform(20000, 60000))
do_thi_hoa.append(np.random.uniform(0.6, 0.9))
else: # Châu Phi
gdp.append(np.random.uniform(500, 5000))
do_thi_hoa.append(np.random.uniform(0.1, 0.5))
gdp = np.array(gdp)
do_thi_hoa = np.array(do_thi_hoa)
# Tạo biến mục tiêu CO2 có quan hệ với GDP và Đô thị hóa
co2 = 2 * np.log(gdp) + 15 * (do_thi_hoa ** 2) + np.random.normal(0, 1.5, n_countries)
# Tạo DataFrame
df = pd.DataFrame({
'Quoc_gia': quoc_gia,
'Khu_vuc': khu_vuc,
'GDP': gdp,
'Do_thi_hoa': do_thi_hoa,
'CO2': co2
})
# Cố tình chèn ngẫu nhiên các giá trị khuyết thiếu (NaN)
missing_indices_gdp = np.random.choice(n_countries, 20, replace=False)
missing_indices_dothi = np.random.choice(n_countries, 15, replace=False)
df.loc[missing_indices_gdp, 'GDP'] = np.nan
df.loc[missing_indices_dothi, 'Do_thi_hoa'] = np.nan
print("--- KIỂM TRA DỮ LIỆU TRƯỚC KHI ĐIỀN KHUYẾT ---")
print(df.isnull().sum())
# =====================================================================
# BƯỚC 1: ĐIỀN KHUYẾT THIẾU CÓ CHỌN LỌC (Tránh trung bình toàn cầu)
# =====================================================================
# Thay vì điền trung bình toàn cầu, ta điền bằng trung vị của KHU VỰC ĐỊA LÝ đó
df['GDP'] = df.groupby('Khu_vuc')['GDP'].transform(lambda x: x.fillna(x.median()))
df['Do_thi_hoa'] = df.groupby('Khu_vuc')['Do_thi_hoa'].transform(lambda x: x.fillna(x.median()))
print("\n--- KIỂM TRA DỮ LIỆU SAU KHI ĐIỀN KHUYẾT ---")
print(df.isnull().sum())
X = df[['GDP', 'Do_thi_hoa']]
y = df['CO2']
# =====================================================================
# BƯỚC 2: CHIA TÁCH DỮ LIỆU BẮT BUỘC TRƯỚC KHI CHUẨN HÓA
# =====================================================================
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# =====================================================================
# BƯỚC 3: KỶ LUẬT CHUẨN HÓA (Tránh rò rỉ dữ liệu)
# =====================================================================
scaler = StandardScaler()
# CHỈ 'fit_transform' trên tập Train
X_train_scaled = scaler.fit_transform(X_train)
# CHỈ 'transform' trên tập Test
X_test_scaled = scaler.transform(X_test)
print("\n--- KẾT QUẢ CHUẨN HÓA (5 dòng đầu của tập Train) ---")
print(pd.DataFrame(X_train_scaled, columns=['GDP_scaled', 'Do_thi_hoa_scaled']).head())
💡 Insights và Ý nghĩa quản trị vĩ mô: Sự cẩu thả trong khâu tiền xử lý dữ liệu có thể dẫn đến những báo cáo ảo tưởng. Nếu một mô hình KNN báo cáo mức độ chính xác R^2 lên tới 98% nhưng lại sử dụng phương pháp điền khuyết trung bình toàn cầu cho các chỉ số vĩ mô, thì độ chính xác đó thực chất là giả tạo. Đối với các nhà hoạch định chính sách, tuyệt đối không được phép tin vào các chỉ số hiệu năng đẹp đẽ nếu khâu xử lý dữ liệu nền tảng chưa tôn trọng và đặc thù hóa được sự khác biệt về nhóm thu nhập cũng như thể chế kinh tế của từng quốc gia.
Để chứng minh sức mạnh từ triết lý "tương đồng cục bộ" của KNN, chúng ta sẽ xây dựng một mô hình dự báo lượng phát thải $CO_2$ bình quân đầu người dựa trên hai biến vĩ mô: Quy mô kinh tế (GDP) và Tỷ lệ đô thị hóa.
Đoạn mã Python dưới đây đặc biệt nhấn mạnh vào hai thao tác sống còn của thuật toán này: (1) Kỷ luật chuẩn hóa dữ liệu bắt buộc (StandardScaler) để tránh việc biến GDP khổng lồ "nuốt chửng" hoàn toàn ý nghĩa của biến đô thị hóa, và (2) sử dụng GridSearchCV kết hợp Kiểm định chéo để tự động dò tìm số láng giềng $K$ tối ưu,.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score
# =====================================================================
# BƯỚC 1: TỰ TẠO DỮ LIỆU MÔ PHỎNG VĨ MÔ
# =====================================================================
np.random.seed(42)
n_countries = 250
# GDP thực tế (Tỷ USD) dao động chênh lệch cực lớn giữa các quốc gia
gdp = np.random.uniform(1, 25000, n_countries)
# Tỷ lệ đô thị hóa (%) dao động từ 10% đến 100%
urbanization = np.random.uniform(10, 100, n_countries)
# Lượng phát thải CO2 bình quân đầu người là một hàm phi tuyến phức tạp của GDP và đô thị hóa
co2_emissions = 1.5 * np.log1p(gdp) + 0.05 * (urbanization ** 1.5) + np.random.normal(0, 1.5, n_countries)
df_macro = pd.DataFrame({'GDP': gdp, 'Urbanization': urbanization, 'CO2': co2_emissions})
X = df_macro[['GDP', 'Urbanization']]
y = df_macro['CO2']
# =====================================================================
# BƯỚC 2: CHIA TẬP VÀ KỶ LUẬT CHUẨN HÓA DỮ LIỆU (BẮT BUỘC VỚI KNN)
# =====================================================================
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Khởi tạo và áp dụng StandardScaler chỉ fit trên tập Train để tránh rò rỉ dữ liệu tương lai
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# =====================================================================
# BƯỚC 3: DÙNG GRIDSEARCHCV VÀ K-FOLD CV ĐỂ DÒ TÌM K TỐI ƯU
# =====================================================================
# Thiết lập không gian tìm kiếm K từ 1 đến 30
param_grid = {'n_neighbors': np.arange(1, 31)}
knn_model = KNeighborsRegressor()
# Cấu hình GridSearchCV với 5-Fold Cross Validation
grid_search = GridSearchCV(estimator=knn_model,
param_grid=param_grid,
cv=5,
scoring='neg_mean_squared_error',
n_jobs=-1)
# Rà soát K tự động
grid_search.fit(X_train_scaled, y_train)
best_k = grid_search.best_params_['n_neighbors']
print("--- KẾT QUẢ TỐI ƯU HÓA SIÊU THAM SỐ ---")
print(f"Số láng giềng K tối ưu nhất tìm được: {best_k}")
# =====================================================================
# BƯỚC 4: ĐÁNH GIÁ MÔ HÌNH VỚI K TỐI ƯU NHẤT
# =====================================================================
best_knn = grid_search.best_estimator_
y_pred = best_knn.predict(X_test_scaled)
print(f"\nR-squared trên tập Test : {r2_score(y_test, y_pred):.4f}")
print(f"RMSE trên tập Test : {np.sqrt(mean_squared_error(y_test, y_pred)):.4f}")
Sự thất bại của các phương trình tuyến tính trước dữ liệu vĩ mô đa dạng: Khi phân tích lượng phát thải $CO_2$ bình quân đầu người, chúng ta đang đối mặt với một hệ thống phức tạp mà phương trình chi phối nó hoàn toàn là một ẩn số. Lượng phát thải của một quốc gia là hệ quả của sự tương tác đan xen giữa vô số biến số vĩ mô như GDP thực tế, tốc độ đô thị hóa, dòng vốn FDI và cơ cấu năng lượng. Nếu cố tình áp đặt mô hình Hồi quy tuyến tính (Linear Regression) lên tập dữ liệu này, thuật toán sẽ cố gắng vẽ ra một đường thẳng "trung bình hóa" duy nhất, áp dụng chung cho mọi quốc gia trên thế giới — từ những nước carbon-neutral (trung hòa carbon) như Bhutan cho đến những quốc gia phát thải cao nhất như Qatar. Hành động gò ép một phương trình toàn cục này là một sự bóp méo thực tế rõ ràng, khiến Hồi quy tuyến tính trở nên mù lòa trước các khúc quanh phi tuyến tính và dẫn đến điểm $R^2$ cực kỳ thấp kèm theo sai số (MSE) khổng lồ.
Giá trị của KNN dưới góc độ "Phương pháp định giá tham chiếu": Để bứt phá khỏi giới hạn của phương trình toàn cục, thuật toán K-Nearest Neighbors (KNN) mang đến một triết lý khác biệt hoàn toàn: tìm kiếm sự tương đồng cục bộ (local similarity). Trong thực tiễn kinh tế phát triển và tài chính, tư duy của KNN chính là hiện thân toán học của "Phương pháp định giá tham chiếu" (Reference Group / Peer-group Analysis).
Cụ thể, để dự báo mức phát thải $CO_2$ của Việt Nam trong năm tới, mô hình KNN không hề cố gắng tìm ra hay áp dụng một công thức vật lý chung của toàn thế giới. Thay vào đó, nó sẽ quét qua dữ liệu lịch sử để tìm đúng $K$ quốc gia có cơ cấu kinh tế (quy mô GDP, tỷ lệ đô thị hóa) giống hệt với Việt Nam nhất, sau đó lấy giá trị trung bình lượng phát thải của nhóm tham chiếu này để đưa ra kết luận dự báo.
Tư duy phi tham số linh hoạt này giúp mô hình không bị đóng khung vào bất kỳ giả định phân phối cứng nhắc nào, dễ dàng uốn lượn theo các cụm dữ liệu phân tán đa dạng, biến KNN thành một công cụ phân tích thực chứng vô cùng sắc bén cho các nhà hoạch định chính sách vĩ mô.