Bạn có bao giờ thắc mắc tại sao khi học Machine Learning, người ta luôn nhấn mạnh về "normalization" (chuẩn hóa dữ liệu)? Và tại sao khi code, nhiều người bị "kẹt" ở mảng 2D, 3D trong C# hay các ngôn ngữ khác mà không biết cách tư duy về không gian nhiều chiều hơn?
Bài viết này sẽ giúp bạn hiểu rõ vấn đề thông qua một ví dụ đơn giản nhưng rất trực quan.
Bài toán: Dự đoán người mua nhà
Giả sử chúng ta muốn dự đoán một người có mua nhà hay không dựa trên:
- Chiều cao (Height): từ 165cm đến 180cm
- Thu nhập (Income): từ 8 triệu đến 20 triệu VNĐ/tháng
Lưu ý: Ví dụ này không nhằm hợp lý về mặt kinh tế (chiều cao không liên quan đến việc mua nhà), mà chỉ để minh họa tác động của normalization lên không gian dữ liệu.
Dataset mẫu
Height,Income,BuyHouse
165,8000000,false
168,9000000,false
170,10000000,false
172,12000000,false
175,15000000,true
178,18000000,true
180,20000000,true
Phần 1: Không gian 1 Chiều (1D)
Giả sử ban đầu chúng ta chỉ xét một yếu tố duy nhất: Thu nhập.
Code Python trong Google Colab
# Cell 1: Import thư viện
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.preprocessing import StandardScaler, MinMaxScaler
%matplotlib inline
print("✓ Import thành công!")
# Cell 2: Tạo dataset
data = {
'Height': [165, 168, 170, 172, 175, 178, 180],
'Income': [8000000, 9000000, 10000000, 12000000, 15000000, 18000000, 20000000],
'BuyHouse': [False, False, False, False, True, True, True]
}
df = pd.DataFrame(data)
print("Dataset gốc:")
print(df)
Đối chiếu với .NET
| Python | .NET |
| --------------- | -------------- |
| DataFrame | DataTable |
| df["Age"] | row["Age"] |
| df.head() | SELECT TOP |
| df[df.Age > 20] | WHERE Age > 20 |
# Cell 3: Phân tích không gian 1D - chỉ xét Thu nhập
plt.figure(figsize=(12, 4))
# Vẽ trên trục số
plt.subplot(1, 2, 1)
colors = ['red' if not buy else 'green' for buy in df['BuyHouse']]
plt.scatter(df['Income'], [0]*len(df), c=colors, s=200, alpha=0.6)
plt.xlabel('Thu nhập (VNĐ)', fontsize=12)
plt.yticks([])
plt.title('Không gian 1D: Chỉ có Thu nhập', fontsize=14, fontweight='bold')
plt.axvline(x=13000000, color='blue', linestyle='--', linewidth=2, label='Ngưỡng quyết định')
plt.legend()
plt.grid(True, alpha=0.3)
# Vẽ dạng bar chart
plt.subplot(1, 2, 2)
plt.bar(range(len(df)), df['Income'], color=colors, alpha=0.6)
plt.xlabel('Người', fontsize=12)
plt.ylabel('Thu nhập (VNĐ)', fontsize=12)
plt.title('Thu nhập từng người', fontsize=14, fontweight='bold')
plt.axhline(y=13000000, color='blue', linestyle='--', linewidth=2, label='Ngưỡng')
plt.legend()
plt.tight_layout()
plt.show()
print("Đỏ = Không mua nhà | Xanh = Mua nhà")
print("Trong không gian 1D, chúng ta chỉ có 1 trục duy nhất để phân loại.")
Nhận xét về 1D
- Dễ visualize (chỉ là một đường thẳng)
- Dễ tìm ngưỡng quyết định (threshold)
- Nhưng thiếu thông tin (bỏ qua chiều cao)
Phần 2: Không gian 2 Chiều (2D) - CHƯA Normalization
Bây giờ chúng ta xét cả hai yếu tố: Chiều cao và Thu nhập.
# Cell 4: Vẽ không gian 2D - CHƯA chuẩn hóa
plt.figure(figsize=(14, 6))
# Biểu đồ 1: Dữ liệu gốc
plt.subplot(1, 2, 1)
for i, row in df.iterrows():
color = 'red' if not row['BuyHouse'] else 'green'
plt.scatter(row['Height'], row['Income'], c=color, s=150, alpha=0.7)
plt.text(row['Height']+0.5, row['Income']+200000, f"P{i+1}", fontsize=9)
plt.xlabel('Chiều cao (cm)', fontsize=12)
plt.ylabel('Thu nhập (VNĐ)', fontsize=12)
plt.title('Không gian 2D - CHƯA Normalization', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3)
# Biểu đồ 2: Zoom vào để thấy vấn đề
plt.subplot(1, 2, 2)
for i, row in df.iterrows():
color = 'red' if not row['BuyHouse'] else 'green'
plt.scatter(row['Height'], row['Income'], c=color, s=150, alpha=0.7)
plt.xlabel('Chiều cao (cm)', fontsize=12)
plt.ylabel('Thu nhập (VNĐ)', fontsize=12)
plt.title('Nhìn kỹ hơn - Thấy vấn đề gì không?', fontsize=14, fontweight='bold')
plt.xlim(160, 185)
plt.ylim(0, 25000000)
plt.grid(True, alpha=0.3)
# Vẽ tỷ lệ scale
plt.axhline(y=20000000, xmin=0.7, xmax=0.9, color='orange', linewidth=3)
plt.text(175, 21000000, '← Thu nhập: 20M', fontsize=10, color='orange')
plt.axvline(x=180, ymin=0.7, ymax=0.9, color='purple', linewidth=3)
plt.text(181, 15000000, 'Chiều cao:\n15cm →', fontsize=10, color='purple')
plt.tight_layout()
plt.show()
print("\n⚠️ VẤN ĐỀ:")
print(f"Chiều cao: {df['Height'].min()}-{df['Height'].max()} (khoảng {df['Height'].max()-df['Height'].min()} đơn vị)")
print(f"Thu nhập: {df['Income'].min():,}-{df['Income'].max():,} (khoảng {df['Income'].max()-df['Income'].min():,} đơn vị)")
print(f"\nTỷ lệ: Thu nhập lớn hơn Chiều cao {(df['Income'].max()-df['Income'].min())/(df['Height'].max()-df['Height'].min()):,.0f} lần!")
Vấn đề của dữ liệu CHƯA chuẩn hóa
Hãy tính khoảng cách giữa 2 điểm trong không gian 2D:
# Cell 5: Tính khoảng cách Euclidean
def euclidean_distance(p1, p2):
return np.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)
# Người 1 vs Người 5
p1 = [df.loc[0, 'Height'], df.loc[0, 'Income']] # [165, 8000000]
p5 = [df.loc[4, 'Height'], df.loc[4, 'Income']] # [175, 15000000]
distance = euclidean_distance(p1, p5)
print("Khoảng cách giữa Người 1 và Người 5:")
print(f"Δ Chiều cao: {p5[0]-p1[0]} cm")
print(f"Δ Thu nhập: {p5[1]-p1[1]:,} VNĐ")
print(f"Khoảng cách Euclidean: {distance:,.2f}")
print(f"\n⚠️ Khoảng cách bị chi phối gần như HOÀN TOÀN bởi Thu nhập!")
print(f" Chiều cao hầu như KHÔNG ảnh hưởng: {((p5[0]-p1[0])**2 / distance**2 * 100):.6f}%")
Kết quả: Khoảng cách ~7,000,007. Trong đó:
- Chiều cao đóng góp: 10² = 100
- Thu nhập đóng góp: 7,000,000² = 49,000,000,000,000
→ Chiều cao hầu như KHÔNG đóng góp gì!
Phần 3: Normalization - Giải pháp
Phương pháp 1: Min-Max Scaling (0-1)
Công thức: x_scaled = (x - min) / (max - min)
# Cell 6: Min-Max Normalization
scaler_minmax = MinMaxScaler()
df_minmax = df.copy()
df_minmax[['Height', 'Income']] = scaler_minmax.fit_transform(df[['Height', 'Income']])
print("Sau khi Min-Max Scaling (0-1):")
print(df_minmax)
Kết quả
Sau khi Min-Max Scaling (0-1):
Height Income BuyHouse
0 0.000000 0.000000 False
1 0.200000 0.083333 False
2 0.333333 0.166667 False
3 0.466667 0.333333 False
4 0.666667 0.583333 True
5 0.866667 0.833333 True
6 1.000000 1.000000 True
Phương pháp 2: Standardization (Z-score)
Công thức: x_scaled = (x - mean) / std
# Cell 7: Standardization
scaler_standard = StandardScaler()
df_standard = df.copy()
df_standard[['Height', 'Income']] = scaler_standard.fit_transform(df[['Height', 'Income']])
print("Sau khi Standardization (Z-score):")
print(df_standard)
Sau khi Standardization (Z-score):
Height Income BuyHouse
0 -1.511205 -1.198669 False
1 -0.912426 -0.965594 False
2 -0.513239 -0.732520 False
3 -0.114053 -0.266371 False
4 0.484726 0.432853 True
5 1.083505 1.132076 True
6 1.482691 1.598225 True
So sánh trực quan
# Cell 8: So sánh 3 trường hợp
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
datasets = [
(df, 'Dữ liệu GỐC (CHƯA chuẩn hóa)', 'Height', 'Income'),
(df_minmax, 'Min-Max Scaling (0-1)', 'Height', 'Income'),
(df_standard, 'Standardization (Z-score)', 'Height', 'Income')
]
for idx, (data, title, xcol, ycol) in enumerate(datasets):
ax = axes[idx]
for i, row in data.iterrows():
color = 'red' if not row['BuyHouse'] else 'green'
ax.scatter(row[xcol], row[ycol], c=color, s=150, alpha=0.7)
ax.text(row[xcol]+0.01, row[ycol]+0.01, f"P{i+1}", fontsize=8)
ax.set_xlabel(xcol, fontsize=11)
ax.set_ylabel(ycol, fontsize=11)
ax.set_title(title, fontsize=13, fontweight='bold')
ax.grid(True, alpha=0.3)
ax.set_aspect('equal', adjustable='box')
plt.tight_layout()
plt.show()
print("Đỏ = Không mua nhà | Xanh = Mua nhà")
print("\nChú ý: Sau khi chuẩn hóa, CẢ HAI trục có cùng tỷ lệ ảnh hưởng!")
Tính lại khoảng cách sau normalization
# Cell 9: So sánh khoảng cách
p1_original = [df.loc[0, 'Height'], df.loc[0, 'Income']]
p5_original = [df.loc[4, 'Height'], df.loc[4, 'Income']]
p1_minmax = [df_minmax.loc[0, 'Height'], df_minmax.loc[0, 'Income']]
p5_minmax = [df_minmax.loc[4, 'Height'], df_minmax.loc[4, 'Income']]
p1_standard = [df_standard.loc[0, 'Height'], df_standard.loc[0, 'Income']]
p5_standard = [df_standard.loc[4, 'Height'], df_standard.loc[4, 'Income']]
dist_original = euclidean_distance(p1_original, p5_original)
dist_minmax = euclidean_distance(p1_minmax, p5_minmax)
dist_standard = euclidean_distance(p1_standard, p5_standard)
print("KHOẢNG CÁCH GIỮA NGƯỜI 1 VÀ NGƯỜI 5:")
print("="*60)
print(f"Dữ liệu gốc: {dist_original:,.2f}")
print(f" → Chiều cao đóng góp: {((p5_original[0]-p1_original[0])**2 / dist_original**2 * 100):.6f}%")
print(f" → Thu nhập đóng góp: {((p5_original[1]-p1_original[1])**2 / dist_original**2 * 100):.6f}%")
print()
print(f"Min-Max (0-1): {dist_minmax:.4f}")
print(f" → Chiều cao đóng góp: {((p5_minmax[0]-p1_minmax[0])**2 / dist_minmax**2 * 100):.2f}%")
print(f" → Thu nhập đóng góp: {((p5_minmax[1]-p1_minmax[1])**2 / dist_minmax**2 * 100):.2f}%")
print()
print(f"Standardization: {dist_standard:.4f}")
print(f" → Chiều cao đóng góp: {((p5_standard[0]-p1_standard[0])**2 / dist_standard**2 * 100):.2f}%")
print(f" → Thu nhập đóng góp: {((p5_standard[1]-p1_standard[1])**2 / dist_standard**2 * 100):.2f}%")
print()
print("✅ Sau normalization, CẢ HAI chiều đều đóng góp vào khoảng cách!")
KHOẢNG CÁCH GIỮA NGƯỜI 1 VÀ NGƯỜI 5:
============================================================
Dữ liệu gốc: 7,000,000.00
→ Chiều cao đóng góp: 0.000000%
→ Thu nhập đóng góp: 100.000000%
Min-Max (0-1): 0.8858
→ Chiều cao đóng góp: 56.64%
→ Thu nhập đóng góp: 43.36%
Standardization: 2.5779
→ Chiều cao đóng góp: 59.95%
→ Thu nhập đóng góp: 40.05%
✅ Sau normalization, CẢ HAI chiều đều đóng góp vào khoảng cách!
Phần 4: Không gian 3 Chiều (3D)
Giả sử chúng ta thêm một yếu tố thứ 3: Tuổi
# Cell 10: Thêm chiều thứ 3 - Tuổi
df['Age'] = [25, 28, 30, 32, 35, 38, 40]
print("Dataset với 3 chiều:")
print(df)
# Chuẩn hóa 3 chiều
df_3d_norm = df.copy()
df_3d_norm[['Height', 'Income', 'Age']] = StandardScaler().fit_transform(
df[['Height', 'Income', 'Age']]
)
print("\nSau khi chuẩn hóa:")
print(df_3d_norm[['Height', 'Income', 'Age', 'BuyHouse']])
Kết quả
Dataset với 3 chiều:
Height Income BuyHouse Age
0 165 8000000 False 25
1 168 9000000 False 28
2 170 10000000 False 30
3 172 12000000 False 32
4 175 15000000 True 35
5 178 18000000 True 38
6 180 20000000 True 40
Sau khi chuẩn hóa:
Height Income Age BuyHouse
0 -1.511205 -1.198669 -1.511205 False
1 -0.912426 -0.965594 -0.912426 False
2 -0.513239 -0.732520 -0.513239 False
3 -0.114053 -0.266371 -0.114053 False
4 0.484726 0.432853 0.484726 True
5 1.083505 1.132076 1.083505 True
6 1.482691 1.598225 1.482691 True
Vẽ biểu đồ 3D
# Cell 11: Vẽ biểu đồ 3D
fig = plt.figure(figsize=(16, 6))
# Biểu đồ 1: Dữ liệu gốc (3D)
ax1 = fig.add_subplot(1, 2, 1, projection='3d')
for i, row in df.iterrows():
color = 'red' if not row['BuyHouse'] else 'green'
ax1.scatter(row['Height'], row['Income'], row['Age'],
c=color, s=150, alpha=0.7)
ax1.set_xlabel('Chiều cao (cm)', fontsize=10)
ax1.set_ylabel('Thu nhập (VNĐ)', fontsize=10)
ax1.set_zlabel('Tuổi', fontsize=10)
ax1.set_title('3D - Dữ liệu GỐC (CHƯA chuẩn hóa)', fontsize=13, fontweight='bold')
# Biểu đồ 2: Dữ liệu đã chuẩn hóa (3D)
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
for i, row in df_3d_norm.iterrows():
color = 'red' if not row['BuyHouse'] else 'green'
ax2.scatter(row['Height'], row['Income'], row['Age'],
c=color, s=150, alpha=0.7)
ax2.set_xlabel('Chiều cao (chuẩn hóa)', fontsize=10)
ax2.set_ylabel('Thu nhập (chuẩn hóa)', fontsize=10)
ax2.set_zlabel('Tuổi (chuẩn hóa)', fontsize=10)
ax2.set_title('3D - Sau Standardization', fontsize=13, fontweight='bold')
# Set aspect ratio cho biểu đồ 2
ax2.set_box_aspect([1,1,1])
plt.tight_layout()
plt.show()
print("Trong không gian 3D, dữ liệu chuẩn hóa giúp các chiều cân bằng với nhau.")
Phần 5: Tư duy về N-Chiều (Beyond 3D)
Đây là phần quan trọng nhất: Làm sao thoát khỏi tư duy mảng 2D, 3D?
Vấn đề khi code C# (hoặc bất kỳ ngôn ngữ nào)
Nhiều người bị "kẹt" vì nghĩ:
// C# - Tư duy SAI
int[] array1D = new int[10]; // 1 chiều
int[,] array2D = new int[10, 10]; // 2 chiều
int[,,] array3D = new int[10, 10, 10]; // 3 chiều
// Còn 4 chiều, 5 chiều thì sao??? ❌
Giải pháp: Tư duy về VECTOR và MATRIX
Thay vì nghĩ về "mảng nhiều chiều", hãy nghĩ về:
- Mỗi điểm dữ liệu = 1 vector
- Toàn bộ dataset = 1 matrix (mảng 2D) với shape (n_samples, n_features)
# Cell 12: Tư duy đúng về N-chiều
print("TƯ DUY VỀ N-CHIỀU")
print("="*70)
# Ví dụ với 2 chiều
print("\n1. Dataset 2 chiều (Height, Income):")
print(f" Shape: {df[['Height', 'Income']].values.shape}")
print(f" → (7 người, 2 features)")
print(df[['Height', 'Income']].values)
# Ví dụ với 3 chiều
print("\n2. Dataset 3 chiều (Height, Income, Age):")
print(f" Shape: {df[['Height', 'Income', 'Age']].values.shape}")
print(f" → (7 người, 3 features)")
print(df[['Height', 'Income', 'Age']].values)
# Ví dụ với 10 chiều
print("\n3. Giả sử có 10 features:")
# Tạo thêm 7 features giả
for i in range(1, 8):
df[f'Feature{i}'] = np.random.rand(7)
all_features = [col for col in df.columns if col != 'BuyHouse']
print(f" Shape: {df[all_features].values.shape}")
print(f" → (7 người, {len(all_features)} features)")
print(f"\n Đây vẫn chỉ là MẢNG 2D trong code!")
print(f" Nhưng biểu diễn dữ liệu {len(all_features)}-chiều trong không gian toán học!")
Kết quả
TƯ DUY VỀ N-CHIỀU
======================================================================
1. Dataset 2 chiều (Height, Income):
Shape: (7, 2)
→ (7 người, 2 features)
[[ 165 8000000]
[ 168 9000000]
[ 170 10000000]
[ 172 12000000]
[ 175 15000000]
[ 178 18000000]
[ 180 20000000]]
2. Dataset 3 chiều (Height, Income, Age):
Shape: (7, 3)
→ (7 người, 3 features)
[[ 165 8000000 25]
[ 168 9000000 28]
[ 170 10000000 30]
[ 172 12000000 32]
[ 175 15000000 35]
[ 178 18000000 38]
[ 180 20000000 40]]
3. Giả sử có 10 features:
Shape: (7, 10)
→ (7 người, 10 features)
Đây vẫn chỉ là MẢNG 2D trong code!
Nhưng biểu diễn dữ liệu 10-chiều trong không gian toán học!
Code mẫu trong C#
// C# - Tư duy ĐÚNG
public class DataPoint
{
public double[] Features { get; set; } // N chiều
public bool Label { get; set; }
}
// Ví dụ sử dụng
var person1 = new DataPoint
{
Features = new double[] { 165, 8000000 }, // 2 chiều
Label = false
};
var person2 = new DataPoint
{
Features = new double[] { 165, 8000000, 25, 70, 1.2 }, // 5 chiều
Label = false
};
// Hoặc dùng List<List<double>> cho toàn bộ dataset
List<List<double>> dataset = new List<List<double>>
{
new List<double> { 165, 8000000, 25 }, // Người 1
new List<double> { 168, 9000000, 28 }, // Người 2
// ... có thể có bao nhiêu chiều cũng được
};
Code mẫu trong Python
# Cell 13: Tư duy đúng trong Python
# Dùng NumPy array - LUÔN LUÔN là mảng 2D
# Dù dữ liệu của bạn có 2, 3, 10, 100, 1000 features
# Ví dụ 1: 2 features
X_2d = np.array([
[165, 8000000],
[168, 9000000],
[170, 10000000]
])
print(f"2 features: shape = {X_2d.shape}") # (3, 2)
# Ví dụ 2: 5 features
X_5d = np.array([
[165, 8000000, 25, 70, 1.75],
[168, 9000000, 28, 72, 1.78],
[170, 10000000, 30, 75, 1.80]
])
print(f"5 features: shape = {X_5d.shape}") # (3, 5)
# Ví dụ 3: 100 features
X_100d = np.random.rand(50, 100) # 50 người, 100 features
print(f"100 features: shape = {X_100d.shape}") # (50, 100)
print("\n✅ Trong code, tất cả đều là MẢNG 2D!")
print("✅ Chiều thứ 1: số lượng samples (người)")
print("✅ Chiều thứ 2: số lượng features (thuộc tính)")
Kết quả
2 features: shape = (3, 2)
5 features: shape = (3, 5)
100 features: shape = (50, 100)
✅ Trong code, tất cả đều là MẢNG 2D!
✅ Chiều thứ 1: số lượng samples (người)
✅ Chiều thứ 2: số lượng features (thuộc tính)
Phần 6: Visualize N-chiều
Nếu không thể vẽ được 4D, 5D trở lên thì làm sao visualize?
Kỹ thuật 1: Pair Plot (Biểu đồ cặp)
# Cell 14: Pair plot cho nhiều chiều
import seaborn as sns
# Chỉ dùng 4 features để demo
df_4d = df[['Height', 'Income', 'Age', 'BuyHouse']].copy()
# Tạo pair plot
sns.pairplot(df_4d, hue='BuyHouse', palette={False: 'red', True: 'green'})
plt.suptitle('Pair Plot: Xem tất cả các cặp chiều', y=1.02, fontsize=16, fontweight='bold')
plt.show()
print("Pair plot giúp bạn xem mối quan hệ giữa TẤT CẢ các cặp features.")
Kỹ thuật 2: PCA - Giảm chiều để visualize
# Cell 15: Dùng PCA để giảm từ N-chiều về 2D/3D
from sklearn.decomposition import PCA
# Tạo dataset 10 chiều
X_10d = df[all_features].values
y = df['BuyHouse'].values
# Chuẩn hóa
X_10d_scaled = StandardScaler().fit_transform(X_10d)
# Giảm từ 10 chiều về 2 chiều
pca = PCA(n_components=2)
X_2d_pca = pca.fit_transform(X_10d_scaled)
# Vẽ
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.title(f'Dữ liệu gốc: {X_10d_scaled.shape[1]} chiều\n(không thể vẽ được!)',
fontsize=13, fontweight='bold')
plt.text(0.5, 0.5, f'Shape: {X_10d_scaled.shape}\n{X_10d_scaled.shape[0]} người\n{X_10d_scaled.shape[1]} features',
ha='center', va='center', fontsize=20, bbox=dict(boxstyle='round', facecolor='wheat'))
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.axis('off')
plt.subplot(1, 2, 2)
for i in range(len(X_2d_pca)):
color = 'red' if not y[i] else 'green'
plt.scatter(X_2d_pca[i, 0], X_2d_pca[i, 1], c=color, s=150, alpha=0.7)
plt.xlabel(f'PC1 ({pca.explained_variance_ratio_[0]*100:.1f}% variance)', fontsize=11)
plt.ylabel(f'PC2 ({pca.explained_variance_ratio_[1]*100:.1f}% variance)', fontsize=11)
plt.title(f'Sau PCA: 2 chiều\n(có thể visualize!)', fontsize=13, fontweight='bold')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print(f"PCA giải thích: {sum(pca.explained_variance_ratio_)*100:.2f}% variance của dữ liệu gốc")
Kỹ thuật 3: Dùng màu sắc, kích thước để thể hiện chiều thứ 4, 5
# Cell 16: Dùng màu và size để thể hiện thêm chiều
fig = plt.figure(figsize=(14, 6))
# Vẽ 3D với màu sắc và kích thước
ax = fig.add_subplot(1, 2, 1, projection='3d')
# Giả sử thêm 2 chiều nữa
df['Savings'] = [5, 8, 10, 15, 20, 25, 30] # Tiết kiệm (triệu)
df['CreditScore'] = [600, 620, 650, 680, 720, 750, 780] # Điểm tín dụng
# Normalize để làm màu và size
savings_norm = (df['Savings'] - df['Savings'].min()) / (df['Savings'].max() - df['Savings'].min())
credit_norm = (df['CreditScore'] - df['CreditScore'].min()) / (df['CreditScore'].max() - df['CreditScore'].min())
for i, row in df.iterrows():
color_intensity = savings_norm[i] # Chiều thứ 4: màu sắc
size = 50 + credit_norm[i] * 200 # Chiều thứ 5: kích thước
base_color = 'red' if not row['BuyHouse'] else 'green'
ax.scatter(row['Height'], row['Income'], row['Age'],
c=base_color, s=size, alpha=0.3 + color_intensity*0.5)
ax.set_xlabel('Chiều cao', fontsize=10)
ax.set_ylabel('Thu nhập', fontsize=10)
ax.set_zlabel('Tuổi', fontsize=10)
ax.set_title('5 Chiều: 3D + Màu (Savings) + Size (Credit)', fontsize=12, fontweight='bold')
# Vẽ legend
ax2 = fig.add_subplot(1, 2, 2)
ax2.text(0.5, 0.7, 'CHIỀU THỨ 1-3:', ha='center', fontsize=12, fontweight='bold')
ax2.text(0.5, 0.6, 'X, Y, Z = Chiều cao, Thu nhập, Tuổi', ha='center', fontsize=10)
ax2.text(0.5, 0.45, 'CHIỀU THỨ 4:', ha='center', fontsize=12, fontweight='bold')
ax2.text(0.5, 0.35, 'Màu sáng/tối = Tiết kiệm nhiều/ít', ha='center', fontsize=10)
ax2.text(0.5, 0.2, 'CHIỀU THỨ 5:', ha='center', fontsize=12, fontweight='bold')
ax2.text(0.5, 0.1, 'Kích thước lớn/nhỏ = Credit score cao/thấp', ha='center', fontsize=10)
ax2.axis('off')
plt.tight_layout()
plt.show()
print("Bằng cách sử dụng màu sắc và kích thước, ta có thể visualize 5 chiều!")
Tổng kết: Roadmap tư duy về chiều không gian
| Chiều | Biểu diễn toán học | Biểu diễn trong code | Cách visualize |
|---|---|---|---|
| 1D | Một số thực x | double x = 5.0; | Trục số / Line chart |
| 2D | Cặp (x, y) | double[] point = {x, y}; | Scatter plot 2D |
| 3D | Bộ ba (x, y, z) | double[] point = {x, y, z}; | Scatter plot 3D |
| 4D | Bộ bốn (x₁, x₂, x₃, x₄) | double[] point = {x1, x2, x3, x4}; | 3D + màu sắc |
| 5D | Bộ năm (x₁, ..., x₅) | double[] point = {x1, ..., x5}; | 3D + màu + kích thước |
| N-D | Vector x ∈ ℝⁿ | double[] point = new double[n]; | PCA / t-SNE / Pair plot |
Nguyên tắc vàng
Trong code: Luôn dùng mảng 2D với shape (n_samples, n_features)
Trong toán: Mỗi hàng là 1 vector n-chiều
Khi visualize: Dùng PCA hoặc chọn 2-3 chiều quan trọng nhất
Bài tập thực hành
# Cell 17: Bài tập - Thử với dataset của bạn
print("BÀI TẬP:")
print("1. Tạo dataset của riêng bạn với ít nhất 5 features")
print("2. Chuẩn hóa dữ liệu bằng StandardScaler")
print("3. Vẽ pair plot để xem mối quan hệ")
print("4. Dùng PCA giảm về 2D và visualize")
print()
print("Template code:")
print("""
# Tạo dataset
my_data = pd.DataFrame({
'Feature1': [...],
'Feature2': [...],
# Thêm features khác
'Label': [...]
})
# Chuẩn hóa
X = my_data.drop('Label', axis=1)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
# Visualize
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=my_data['Label'])
plt.show()
""")
Kết luận
Qua bài viết này, bạn đã học được:
- Tại sao cần normalization: Để các features có tỷ lệ đóng góp cân bằng
- Cách tư duy về N-chiều: Không phải mảng 3D, 4D... mà là vector
- Cách code đúng: Luôn dùng mảng 2D (n_samples, n_features)
- Cách visualize N-chiều: PCA, pair plot, màu sắc, kích thước
Điều quan trọng nhất: Đừng bị giới hạn bởi khả năng visualize 3D! Trong Machine Learning, dữ liệu có thể có hàng trăm, hàng nghìn features (chiều), nhưng cách xử lý vẫn giống nhau.
"Dữ liệu 1000 chiều không khó hơn dữ liệu 3 chiều. Nó chỉ là 1 mảng 2D với shape (n, 1000) thôi!"
Bài viết sử dụng Python 3.x, NumPy, Pandas, Matplotlib, Scikit-learn. Code có thể chạy trực tiếp trên Google Colab.
Nhận xét
Đăng nhận xét