华为AI机试编程题算法模板(NumPy手写)

华为AI机试编程题算法模板(300分保命板子)

一、逻辑回归(梯度下降)⭐⭐⭐⭐⭐

完整实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import numpy as np

class LogisticRegression:
def __init__(self, lr=0.01, epochs=1000):
self.lr = lr
self.epochs = epochs
self.w = None
self.b = None

def sigmoid(self, z):
"""Sigmoid激活函数,带数值稳定处理"""
return np.where(z >= 0,
1 / (1 + np.exp(-z)),
np.exp(z) / (1 + np.exp(z)))

def fit(self, X, y):
"""训练模型"""
n_samples, n_features = X.shape

# 初始化权重
self.w = np.zeros(n_features)
self.b = 0

# 梯度下降
for epoch in range(self.epochs):
# 前向传播
linear = np.dot(X, self.w) + self.b
y_pred = self.sigmoid(linear)

# 计算梯度
dw = (1 / n_samples) * np.dot(X.T, (y_pred - y))
db = (1 / n_samples) * np.sum(y_pred - y)

# 更新参数
self.w -= self.lr * dw
self.b -= self.lr * db

# 可选:打印损失
if epoch % 100 == 0:
loss = self.compute_loss(y, y_pred)
print(f"Epoch {epoch}, Loss: {loss:.4f}")

def compute_loss(self, y_true, y_pred):
"""二元交叉熵损失"""
epsilon = 1e-15 # 防止log(0)
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))

def predict_proba(self, X):
"""预测概率"""
linear = np.dot(X, self.w) + self.b
return self.sigmoid(linear)

def predict(self, X):
"""预测类别"""
return (self.predict_proba(X) >= 0.5).astype(int)

# 使用示例
if __name__ == "__main__":
# 生成模拟数据
np.random.seed(42)
X = np.random.randn(100, 2)
y = (X[:, 0] + X[:, 1] > 0).astype(int)

# 训练
model = LogisticRegression(lr=0.1, epochs=1000)
model.fit(X, y)

# 预测
predictions = model.predict(X)
accuracy = np.mean(predictions == y)
print(f"Accuracy: {accuracy:.4f}")

简化版(考试快写版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import numpy as np

def sigmoid(z):
return 1 / (1 + np.exp(-np.clip(z, -500, 500)))

def logistic_regression(X, y, lr=0.01, epochs=1000):
n, d = X.shape
w = np.zeros(d)
b = 0

for _ in range(epochs):
z = X @ w + b
y_pred = sigmoid(z)

# 梯度
dw = X.T @ (y_pred - y) / n
db = np.mean(y_pred - y)

# 更新
w -= lr * dw
b -= lr * db

return w, b

# 预测
def predict(X, w, b):
return (sigmoid(X @ w + b) >= 0.5).astype(int)

复杂度分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"""
逻辑回归复杂度分析:

时间复杂度:
- 训练阶段:O(epochs * n * d)
其中 n=样本数, d=特征数
- 前向传播 X @ w: O(n * d)
- 梯度计算 X.T @ (y_pred - y): O(n * d)
- 总共迭代 epochs 次

- 预测阶段:O(m * d)
其中 m=测试样本数

空间复杂度:O(d)
- 权重向量 w: O(d)
- 偏置 b: O(1)
- 中间变量 y_pred: O(n) 但会被覆盖

实际考试建议:
- 小数据集(n<10000): epochs=1000, lr=0.1
- 大数据集(n>10000): epochs=100-500, lr=0.01
"""

常见Bug及调试技巧 🐛

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Bug 1: 数值溢出 - Sigmoid函数
# 错误写法:
def sigmoid_wrong(z):
return 1 / (1 + np.exp(-z)) # z很大时exp(-z)会溢出

# 正确写法:
def sigmoid_correct(z):
# 方法1: clip限制范围
return 1 / (1 + np.exp(-np.clip(z, -500, 500)))

# 方法2: 分段计算(更稳定)
return np.where(z >= 0,
1 / (1 + np.exp(-z)),
np.exp(z) / (1 + np.exp(z)))

# Bug 2: 维度不匹配
# 错误:y是(n,),y_pred是(n,1)
y = np.array([0, 1, 0]) # shape: (3,)
y_pred = sigmoid(X @ w + b) # shape可能是(3,1)
error = y_pred - y # 广播错误!

# 正确:确保维度一致
y = y.ravel() # 展平为(n,)
y_pred = y_pred.ravel() # 展平为(n,)

# Bug 3: 学习率过大导致发散
# 症状:loss变成nan或inf
# 解决:降低学习率或添加梯度裁剪
dw = np.clip(dw, -1, 1) # 梯度裁剪

# Bug 4: 忘记归一化数据
# 特征尺度差异大时收敛慢
X = (X - X.mean(axis=0)) / (X.std(axis=0) + 1e-8)

完整测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import numpy as np

def test_logistic_regression():
"""完整的测试用例"""
print("=" * 50)
print("测试1: 线性可分数据")
print("=" * 50)

# 生成线性可分数据
np.random.seed(42)
n_samples = 200

# 类别0: 均值[-2, -2]
X0 = np.random.randn(n_samples // 2, 2) + np.array([-2, -2])
y0 = np.zeros(n_samples // 2)

# 类别1: 均值[2, 2]
X1 = np.random.randn(n_samples // 2, 2) + np.array([2, 2])
y1 = np.ones(n_samples // 2)

X = np.vstack([X0, X1])
y = np.hstack([y0, y1])

# 打乱数据
indices = np.random.permutation(n_samples)
X, y = X[indices], y[indices]

# 训练
model = LogisticRegression(lr=0.1, epochs=1000)
model.fit(X, y)

# 预测
predictions = model.predict(X)
accuracy = np.mean(predictions == y)
print(f"训练集准确率: {accuracy:.4f}")
assert accuracy > 0.9, "准确率应该大于90%"

print("\n" + "=" * 50)
print("测试2: 非线性可分数据(XOR问题)")
print("=" * 50)

# XOR数据(逻辑回归无法完美分类)
X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_xor = np.array([0, 1, 1, 0])

# 扩展为多项式特征
X_xor_poly = np.c_[X_xor, X_xor[:, 0] * X_xor[:, 1]]

model_xor = LogisticRegression(lr=0.5, epochs=5000)
model_xor.fit(X_xor_poly, y_xor)

predictions_xor = model_xor.predict(X_xor_poly)
accuracy_xor = np.mean(predictions_xor == y_xor)
print(f"XOR准确率(加多项式特征): {accuracy_xor:.4f}")

print("\n" + "=" * 50)
print("测试3: 边界情况")
print("=" * 50)

# 单样本
X_single = np.array([[1, 2]])
y_single = np.array([1])
model_single = LogisticRegression(lr=0.1, epochs=100)
model_single.fit(X_single, y_single)
print(f"单样本预测: {model_single.predict(X_single)}")

# 所有样本同类
X_same = np.random.randn(50, 3)
y_same = np.ones(50)
model_same = LogisticRegression(lr=0.1, epochs=100)
model_same.fit(X_same, y_same)
print(f"同类样本预测: {np.unique(model_same.predict(X_same))}")

print("\n✅ 所有测试通过!")

# 运行测试
if __name__ == "__main__":
test_logistic_regression()

真题示例:华为2024春招

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
"""
题目:实现逻辑回归分类器,预测客户流失

输入格式:
第一行:n(样本数), d(特征数)
接下来n行:每行d个特征值,最后一列是标签(0/1)
最后一行:测试样本特征值

输出格式:
测试样本的预测类别(0或1)

示例输入:
6 2
1.2 2.3 0
2.1 3.4 0
3.5 4.2 0
5.1 6.2 1
6.3 7.1 1
7.2 8.5 1
4.5 5.5

示例输出:
1
"""

def solve():
# 读取数据
n, d = map(int, input().split())

X_train = []
y_train = []

for _ in range(n):
line = list(map(float, input().split()))
X_train.append(line[:-1]) # 前d个是特征
y_train.append(line[-1]) # 最后一个是标签

X_train = np.array(X_train)
y_train = np.array(y_train)

# 测试样本
X_test = np.array([list(map(float, input().split()))])

# 标准化
mean = X_train.mean(axis=0)
std = X_train.std(axis=0) + 1e-8
X_train = (X_train - mean) / std
X_test = (X_test - mean) / std

# 训练
w, b = logistic_regression(X_train, y_train, lr=0.1, epochs=1000)

# 预测
prediction = predict(X_test, w, b)
print(int(prediction[0]))

# solve() # 取消注释以运行

二、线性回归(最小二乘法)⭐⭐⭐⭐

解析解(闭式解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np

def linear_regression_analytical(X, y):
"""
线性回归解析解: w = (X^T X)^(-1) X^T y
"""
# 添加偏置项
X_b = np.c_[np.ones(X.shape[0]), X]

# 计算权重
w = np.linalg.inv(X_b.T @ X_b) @ X_b.T @ y

return w

def predict(X, w):
X_b = np.c_[np.ones(X.shape[0]), X]
return X_b @ w

# 使用示例
X = np.array([[1], [2], [3], [4]])
y = np.array([2, 4, 6, 8])
w = linear_regression_analytical(X, y)
print(f"权重: {w}") # [0, 2] 表示 y = 2x

梯度下降法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np

def linear_regression_gd(X, y, lr=0.01, epochs=1000):
"""梯度下降法"""
n, d = X.shape
w = np.zeros(d)
b = 0

for _ in range(epochs):
# 预测
y_pred = X @ w + b

# 梯度
dw = -2 * X.T @ (y - y_pred) / n
db = -2 * np.mean(y - y_pred)

# 更新
w -= lr * dw
b -= lr * db

return w, b

def mse(y_true, y_pred):
"""均方误差"""
return np.mean((y_true - y_pred) ** 2)

复杂度分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"""
线性回归复杂度分析:

解析解方法(最小二乘法):
时间复杂度:O(d³ + nd²)
- X^T @ X: O(nd²)
- 矩阵求逆: O(d³)
- 适用场景:d较小时(d < 1000)

空间复杂度:O(d²)
- 存储 X^T @ X 矩阵

梯度下降法:
时间复杂度:O(epochs * nd)
- 每次迭代: O(nd)
- 适用场景:d很大或在线学习

空间复杂度:O(d)
- 只需存储权重向量

选择建议:
- 小数据集且特征少:用解析解(快速、精确)
- 大数据集或特征多:用梯度下降(可扩展)
- X^T @ X 不可逆:必须用梯度下降
"""

岭回归(Ridge Regression,带L2正则化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np

def ridge_regression(X, y, alpha=1.0):
"""
岭回归解析解: w = (X^T X + αI)^(-1) X^T y
alpha: 正则化系数,防止过拟合
"""
n, d = X.shape

# 添加偏置项
X_b = np.c_[np.ones(n), X]

# 岭回归公式
I = np.eye(d + 1)
I[0, 0] = 0 # 不正则化偏置项

w = np.linalg.inv(X_b.T @ X_b + alpha * I) @ X_b.T @ y

return w

# 使用示例
X = np.random.randn(100, 5)
y = X @ np.array([1, 2, 3, 4, 5]) + np.random.randn(100) * 0.1
w = ridge_regression(X, y, alpha=0.1)
print(f"岭回归权重: {w}")

完整测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import numpy as np

def test_linear_regression():
"""线性回归完整测试"""
print("=" * 50)
print("测试1: 完美线性关系")
print("=" * 50)

# y = 2x + 3
X = np.array([[1], [2], [3], [4], [5]])
y = 2 * X.ravel() + 3

# 解析解
w_analytical = linear_regression_analytical(X, y)
print(f"解析解权重: {w_analytical}") # 应接近[3, 2]

# 梯度下降
w_gd, b_gd = linear_regression_gd(X, y, lr=0.01, epochs=1000)
print(f"梯度下降权重: w={w_gd}, b={b_gd}") # 应接近w=[2], b=3

# 预测
y_pred = X @ w_gd + b_gd
mse_value = mse(y, y_pred)
print(f"MSE: {mse_value:.6f}")
assert mse_value < 0.01, "完美线性关系MSE应接近0"

print("\n" + "=" * 50)
print("测试2: 带噪声的线性关系")
print("=" * 50)

np.random.seed(42)
X_noise = np.random.randn(100, 3)
w_true = np.array([1.5, -2.0, 3.0])
y_noise = X_noise @ w_true + np.random.randn(100) * 0.5

# 训练
w_estimated, b_estimated = linear_regression_gd(X_noise, y_noise, lr=0.1, epochs=1000)
print(f"真实权重: {w_true}")
print(f"估计权重: {w_estimated}")
print(f"误差: {np.abs(w_estimated - w_true)}")

# 预测
y_pred_noise = X_noise @ w_estimated + b_estimated
mse_noise = mse(y_noise, y_pred_noise)
print(f"MSE: {mse_noise:.4f}")

print("\n" + "=" * 50)
print("测试3: 多重共线性(岭回归优势)")
print("=" * 50)

# 创建高度相关的特征
X_corr = np.random.randn(50, 1)
X_corr = np.c_[X_corr, X_corr + np.random.randn(50, 1) * 0.01] # 高度相关
y_corr = X_corr @ np.array([1, 1]) + np.random.randn(50) * 0.1

# 普通最小二乘(可能不稳定)
try:
w_ols = linear_regression_analytical(X_corr, y_corr)
print(f"OLS权重: {w_ols}")
except np.linalg.LinAlgError:
print("OLS失败:矩阵奇异")

# 岭回归(更稳定)
w_ridge = ridge_regression(X_corr, y_corr, alpha=1.0)
print(f"岭回归权重: {w_ridge}")

print("\n✅ 所有测试通过!")

# test_linear_regression()

常见Bug

1
2
3
4
5
6
7
8
9
10
11
12
13
# Bug 1: X^T X 矩阵奇异(不可逆)
# 原因:特征完全线性相关或样本数 < 特征数
# 解决:使用伪逆或岭回归
w = np.linalg.pinv(X_b.T @ X_b) @ X_b.T @ y # 伪逆
w = ridge_regression(X, y, alpha=0.1) # 岭回归

# Bug 2: 特征尺度差异导致收敛慢
X_scaled = (X - X.mean(axis=0)) / X.std(axis=0)

# Bug 3: 学习率设置不当
# 太大:震荡不收敛
# 太小:收敛极慢
# 建议:先用0.01尝试,观察loss曲线调整

三、K近邻(KNN)⭐⭐⭐⭐⭐

完整实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import numpy as np
from collections import Counter

class KNN:
def __init__(self, k=3):
self.k = k
self.X_train = None
self.y_train = None

def fit(self, X, y):
"""存储训练数据"""
self.X_train = X
self.y_train = y

def euclidean_distance(self, x1, x2):
"""欧氏距离"""
return np.sqrt(np.sum((x1 - x2) ** 2, axis=1))

def predict(self, X):
"""预测"""
predictions = []
for x in X:
# 计算与所有训练样本的距离
distances = self.euclidean_distance(self.X_train, x)

# 找到k个最近邻
k_indices = np.argsort(distances)[:self.k]
k_nearest_labels = self.y_train[k_indices]

# 投票
most_common = Counter(k_nearest_labels).most_common(1)[0][0]
predictions.append(most_common)

return np.array(predictions)

# 使用示例
X_train = np.array([[1, 2], [2, 3], [3, 1], [6, 5], [7, 7], [8, 6]])
y_train = np.array([0, 0, 0, 1, 1, 1])
X_test = np.array([[5, 5], [1, 1]])

knn = KNN(k=3)
knn.fit(X_train, y_train)
predictions = knn.predict(X_test)
print(predictions) # [1, 0]

向量化优化版(快速计算距离矩阵)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import numpy as np

def knn_predict_vectorized(X_train, y_train, X_test, k=3):
"""
向量化KNN,适合大数据量
使用广播计算所有距离
"""
# 计算距离矩阵: (n_test, n_train)
# ||a - b||^2 = ||a||^2 + ||b||^2 - 2*a·b
X_train_sq = np.sum(X_train ** 2, axis=1) # (n_train,)
X_test_sq = np.sum(X_test ** 2, axis=1) # (n_test,)

# 距离矩阵
distances = np.sqrt(
X_test_sq[:, np.newaxis] + X_train_sq[np.newaxis, :]
- 2 * X_test @ X_train.T
)

# 找到k个最近邻的索引
k_indices = np.argsort(distances, axis=1)[:, :k]

# 获取k个最近邻的标签
k_nearest_labels = y_train[k_indices]

# 投票(每行找众数)
predictions = []
for labels in k_nearest_labels:
predictions.append(np.bincount(labels).argmax())

return np.array(predictions)

加权KNN(距离加权)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def weighted_knn(X_train, y_train, X_test, k=3):
"""距离加权KNN,近的样本权重大"""
distances = np.sqrt(((X_test[:, np.newaxis] - X_train) ** 2).sum(axis=2))

predictions = []
for dist_row in distances:
# 找k个最近邻
k_indices = np.argsort(dist_row)[:k]
k_distances = dist_row[k_indices]
k_labels = y_train[k_indices]

# 距离加权(距离越小权重越大)
weights = 1 / (k_distances + 1e-8)

# 加权投票
unique_labels = np.unique(k_labels)
weighted_votes = {}
for label in unique_labels:
mask = (k_labels == label)
weighted_votes[label] = weights[mask].sum()

pred = max(weighted_votes, key=weighted_votes.get)
predictions.append(pred)

return np.array(predictions)

KNN变体:加权KNN与KD树优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import numpy as np

class WeightedKNN:
"""距离加权KNN - 距离越近权重越大"""
def __init__(self, k=3):
self.k = k

def fit(self, X, y):
self.X_train = X
self.y_train = y

def predict(self, X_test):
predictions = []

for x in X_test:
# 计算距离
distances = np.sqrt(np.sum((self.X_train - x) ** 2, axis=1))

# 找k个最近邻
k_indices = np.argsort(distances)[:self.k]
k_distances = distances[k_indices]
k_labels = self.y_train[k_indices]

# 距离加权(距离越小权重越大)
weights = 1 / (k_distances + 1e-8)

# 加权投票
unique_labels = np.unique(k_labels)
weighted_votes = {}
for label in unique_labels:
mask = (k_labels == label)
weighted_votes[label] = weights[mask].sum()

pred = max(weighted_votes, key=weighted_votes.get)
predictions.append(pred)

return np.array(predictions)

# KNN回归版本
def knn_regression(X_train, y_train, X_test, k=3):
"""
KNN用于回归:预测连续值
返回k个近邻的平均值
"""
predictions = []

for x in X_test:
distances = np.sqrt(np.sum((X_train - x) ** 2, axis=1))
k_indices = np.argsort(distances)[:k]
k_values = y_train[k_indices]

# 平均值预测
pred = np.mean(k_values)
predictions.append(pred)

return np.array(predictions)

完整测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def test_knn():
"""KNN完整测试"""
print("=" * 50)
print("测试1: Iris数据集简化版")
print("=" * 50)

# 模拟鸢尾花数据
np.random.seed(42)

# 三个类别
X_class0 = np.random.randn(20, 2) + np.array([0, 0])
X_class1 = np.random.randn(20, 2) + np.array([3, 3])
X_class2 = np.random.randn(20, 2) + np.array([0, 3])

X_train = np.vstack([X_class0, X_class1, X_class2])
y_train = np.array([0]*20 + [1]*20 + [2]*20)

# 测试样本
X_test = np.array([[0, 0], [3, 3], [0, 3], [1.5, 1.5]])
y_test_expected = np.array([0, 1, 2, 0]) # 期望预测

# KNN预测
knn = KNN(k=5)
knn.fit(X_train, y_train)
predictions = knn.predict(X_test)

print(f"预测结果: {predictions}")
print(f"期望结果: {y_test_expected}")
accuracy = np.mean(predictions == y_test_expected)
print(f"准确率: {accuracy:.2%}")

print("\n" + "=" * 50)
print("测试2: KNN回归")
print("=" * 50)

# 回归数据
X_reg = np.linspace(0, 10, 50).reshape(-1, 1)
y_reg = np.sin(X_reg).ravel() + np.random.randn(50) * 0.1

X_test_reg = np.array([[2.5], [5.0], [7.5]])
y_pred_reg = knn_regression(X_reg, y_reg, X_test_reg, k=5)

print(f"测试点: {X_test_reg.ravel()}")
print(f"预测值: {y_pred_reg}")
print(f"真实值: {np.sin(X_test_reg.ravel())}")

print("\n" + "=" * 50)
print("测试3: 不同k值的影响")
print("=" * 50)

for k in [1, 3, 5, 10]:
knn_k = KNN(k=k)
knn_k.fit(X_train, y_train)
pred_k = knn_k.predict(X_test)
acc_k = np.mean(pred_k == y_test_expected)
print(f"k={k}: 准确率 {acc_k:.2%}")

print("\n✅ 所有测试通过!")

# test_knn()

复杂度分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"""
KNN复杂度分析:

训练阶段:O(1)
- 只是存储数据,无需训练

预测阶段:O(n * d * m)
- n: 训练样本数
- d: 特征维度
- m: 测试样本数
- 对每个测试样本计算与所有训练样本的距离

空间复杂度:O(n * d)
- 需要存储所有训练数据

优化方法:
1. KD树:预测降至O(log n),但仅适用于低维数据(d<20)
2. Ball Tree:适用于高维数据
3. LSH(局部敏感哈希):适用于超高维

选择k值的技巧:
- k太小:过拟合,对噪声敏感
- k太大:欠拟合,边界模糊
- 常用:k = sqrt(n) 或通过交叉验证选择
- 分类问题:k最好选奇数(避免平局)
"""

常见Bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Bug 1: 忘记标准化特征
# 不同特征尺度差异大时,欧氏距离会被大尺度特征主导
# 错误:
distances = np.sqrt(np.sum((X_train - x) ** 2, axis=1))

# 正确:先标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# Bug 2: k值设置不当
# k > n(训练样本数)会报错
k = min(k, len(X_train))

# Bug 3: 距离计算维度错误
# 确保x和X_train[i]维度一致
x = x.reshape(1, -1) if len(x.shape) == 1 else x

# Bug 4: 投票时平局处理
# 使用Counter.most_common()可能出现平局
# 解决:使用距离加权或选择距离最近的类

四、多层感知机(MLP)前向传播⭐⭐⭐⭐⭐

双层MLP(输入→隐藏→输出)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import numpy as np

class MLP:
def __init__(self, input_size, hidden_size, output_size):
"""
初始化双层MLP
input_size: 输入特征维度
hidden_size: 隐藏层神经元数量
output_size: 输出维度
"""
# He初始化(适合ReLU)
self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2 / input_size)
self.b1 = np.zeros(hidden_size)

self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(2 / hidden_size)
self.b2 = np.zeros(output_size)

def relu(self, x):
"""ReLU激活函数"""
return np.maximum(0, x)

def relu_derivative(self, x):
"""ReLU导数"""
return (x > 0).astype(float)

def softmax(self, x):
"""Softmax激活函数(数值稳定版)"""
exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
return exp_x / np.sum(exp_x, axis=1, keepdims=True)

def forward(self, X):
"""
前向传播
X: (batch_size, input_size)
"""
# 第一层
self.z1 = X @ self.W1 + self.b1 # (batch, hidden)
self.a1 = self.relu(self.z1) # (batch, hidden)

# 第二层
self.z2 = self.a1 @ self.W2 + self.b2 # (batch, output)
self.a2 = self.softmax(self.z2) # (batch, output)

return self.a2

def backward(self, X, y, learning_rate=0.01):
"""
反向传播
X: (batch_size, input_size)
y: (batch_size, output_size) one-hot编码
"""
batch_size = X.shape[0]

# 输出层梯度
dz2 = self.a2 - y # Softmax + CrossEntropy的导数
dW2 = self.a1.T @ dz2 / batch_size
db2 = np.sum(dz2, axis=0) / batch_size

# 隐藏层梯度
da1 = dz2 @ self.W2.T
dz1 = da1 * self.relu_derivative(self.z1)
dW1 = X.T @ dz1 / batch_size
db1 = np.sum(dz1, axis=0) / batch_size

# 更新参数
self.W2 -= learning_rate * dW2
self.b2 -= learning_rate * db2
self.W1 -= learning_rate * dW1
self.b1 -= learning_rate * db1

def train(self, X, y, epochs=100, lr=0.01):
"""训练"""
for epoch in range(epochs):
# 前向传播
output = self.forward(X)

# 反向传播
self.backward(X, y, lr)

# 计算损失
if epoch % 10 == 0:
loss = -np.mean(y * np.log(output + 1e-8))
print(f"Epoch {epoch}, Loss: {loss:.4f}")

def predict(self, X):
"""预测"""
output = self.forward(X)
return np.argmax(output, axis=1)

# 使用示例
if __name__ == "__main__":
# 生成模拟数据
np.random.seed(42)
X = np.random.randn(100, 4) # 100样本,4特征
y_labels = np.random.randint(0, 3, 100) # 3分类
y = np.eye(3)[y_labels] # one-hot编码

# 训练
mlp = MLP(input_size=4, hidden_size=8, output_size=3)
mlp.train(X, y, epochs=100, lr=0.1)

# 预测
predictions = mlp.predict(X)
accuracy = np.mean(predictions == y_labels)
print(f"Accuracy: {accuracy:.4f}")

多输出回归MLP(考试常见)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np

def mlp_regression_forward(X, W1, b1, W2, b2):
"""
回归任务的MLP前向传播
输出层不用激活函数(或用线性激活)
"""
# 隐藏层
z1 = X @ W1 + b1
a1 = np.maximum(0, z1) # ReLU

# 输出层(回归,无激活)
z2 = a1 @ W2 + b2

return z2

# 示例:预测多个连续值
X = np.array([[1, 2], [3, 4]])
W1 = np.random.randn(2, 5)
b1 = np.zeros(5)
W2 = np.random.randn(5, 3) # 输出3个值
b2 = np.zeros(3)

output = mlp_regression_forward(X, W1, b1, W2, b2)
print(output.shape) # (2, 3)

激活函数汇总

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import numpy as np

# 1. Sigmoid(用于二分类输出层)
def sigmoid(x):
return 1 / (1 + np.exp(-np.clip(x, -500, 500)))

def sigmoid_derivative(x):
s = sigmoid(x)
return s * (1 - s)

# 2. Tanh(中心化的Sigmoid,范围[-1,1])
def tanh(x):
return np.tanh(x)

def tanh_derivative(x):
return 1 - np.tanh(x) ** 2

# 3. ReLU(最常用,解决梯度消失)
def relu(x):
return np.maximum(0, x)

def relu_derivative(x):
return (x > 0).astype(float)

# 4. Leaky ReLU(解决ReLU的死神经元问题)
def leaky_relu(x, alpha=0.01):
return np.where(x > 0, x, alpha * x)

def leaky_relu_derivative(x, alpha=0.01):
return np.where(x > 0, 1, alpha)

# 5. Softmax(多分类输出层)
def softmax(x):
"""数值稳定版本"""
exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
return exp_x / np.sum(exp_x, axis=-1, keepdims=True)

MLP完整测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def test_mlp():
"""MLP完整测试"""
print("=" * 50)
print("测试1: 多分类问题(鸢尾花)")
print("=" * 50)

np.random.seed(42)

# 生成三分类数据
n_per_class = 40
X_class0 = np.random.randn(n_per_class, 4) + np.array([0, 0, 0, 0])
X_class1 = np.random.randn(n_per_class, 4) + np.array([2, 2, 2, 2])
X_class2 = np.random.randn(n_per_class, 4) + np.array([4, 0, 4, 0])

X = np.vstack([X_class0, X_class1, X_class2])
y_labels = np.array([0]*n_per_class + [1]*n_per_class + [2]*n_per_class)
y = np.eye(3)[y_labels] # one-hot编码

# 标准化
X = (X - X.mean(axis=0)) / X.std(axis=0)

# 训练
mlp = MLP(input_size=4, hidden_size=10, output_size=3)
mlp.train(X, y, epochs=500, lr=0.1)

# 预测
predictions = mlp.predict(X)
accuracy = np.mean(predictions == y_labels)
print(f"训练集准确率: {accuracy:.2%}")
assert accuracy > 0.8, "准确率应该大于80%"

print("\n" + "=" * 50)
print("测试2: 二分类问题")
print("=" * 50)

# 生成二分类数据
X_binary = np.random.randn(100, 3)
y_binary_labels = (X_binary[:, 0] + X_binary[:, 1] > 0).astype(int)
y_binary = np.eye(2)[y_binary_labels]

mlp_binary = MLP(input_size=3, hidden_size=5, output_size=2)
mlp_binary.train(X_binary, y_binary, epochs=300, lr=0.1)

pred_binary = mlp_binary.predict(X_binary)
acc_binary = np.mean(pred_binary == y_binary_labels)
print(f"二分类准确率: {acc_binary:.2%}")

print("\n" + "=" * 50)
print("测试3: XOR问题(非线性可分)")
print("=" * 50)

# XOR数据
X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_xor_labels = np.array([0, 1, 1, 0])
y_xor = np.eye(2)[y_xor_labels]

# 复制数据增加样本量
X_xor = np.tile(X_xor, (25, 1))
y_xor = np.tile(y_xor, (25, 1))
y_xor_labels = np.tile(y_xor_labels, 25)

mlp_xor = MLP(input_size=2, hidden_size=4, output_size=2)
mlp_xor.train(X_xor, y_xor, epochs=1000, lr=0.5)

# 测试原始4个点
X_test_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
pred_xor = mlp_xor.predict(X_test_xor)
print(f"XOR预测: {pred_xor}")
print(f"XOR真实: {np.array([0, 1, 1, 0])}")

print("\n✅ 所有测试通过!")

# test_mlp()

复杂度分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"""
MLP复杂度分析(双层网络):

前向传播:
时间复杂度:O(d*h + h*c)
- 输入层→隐藏层: O(d * h),d=输入维度,h=隐藏层大小
- 隐藏层→输出层: O(h * c),c=输出类别数
- 总计: O(d*h + h*c) ≈ O(max(d,c) * h)

反向传播:
时间复杂度:O(d*h + h*c)
- 与前向传播相同

训练总时间:O(epochs * n * (d*h + h*c))
- n: 样本数

空间复杂度:O(d*h + h*c)
- 权重矩阵: W1(d×h) + W2(h×c)
- 中间激活值: O(n*h)(batch)

参数量:d*h + h + h*c + c
- W1: d × h
- b1: h
- W2: h × c
- b2: c

隐藏层大小选择建议:
- 小数据集: h = 2d 到 10d
- 大数据集: h = 100 到 1000
- 经验法则: h = (d + c) / 2 或 h = sqrt(d * c)
"""

常见Bug及解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Bug 1: 梯度爆炸/消失
# 症状:loss变成nan或不下降
# 解决方案:
# 1) 梯度裁剪
def clip_gradients(grads, max_norm=5.0):
total_norm = np.sqrt(sum(np.sum(g**2) for g in grads))
clip_coef = max_norm / (total_norm + 1e-8)
if clip_coef < 1:
grads = [g * clip_coef for g in grads]
return grads

# 2) 使用更好的初始化(He初始化)
W = np.random.randn(input_size, hidden_size) * np.sqrt(2 / input_size)

# 3) 使用Batch Normalization
def batch_norm(x, gamma=1, beta=0, eps=1e-8):
mean = np.mean(x, axis=0)
var = np.var(x, axis=0)
x_norm = (x - mean) / np.sqrt(var + eps)
return gamma * x_norm + beta

# Bug 2: 过拟合
# 症状:训练准确率高,测试准确率低
# 解决方案:
# 1) Dropout
def dropout(x, drop_rate=0.5, training=True):
if training:
mask = np.random.binomial(1, 1-drop_rate, size=x.shape) / (1-drop_rate)
return x * mask
return x

# 2) L2正则化
def compute_loss_with_l2(y_true, y_pred, weights, lambda_=0.01):
ce_loss = -np.mean(y_true * np.log(y_pred + 1e-8))
l2_loss = lambda_ * sum(np.sum(w**2) for w in weights)
return ce_loss + l2_loss

# Bug 3: 学习率设置不当
# 使用学习率衰减
def lr_schedule(epoch, initial_lr=0.1):
# 指数衰减
return initial_lr * 0.95 ** epoch

# 或阶梯衰减
# return initial_lr * 0.1 ** (epoch // 100)

# Bug 4: 死ReLU问题
# 症状:很多神经元输出恒为0
# 解决:使用Leaky ReLU或降低学习率
def leaky_relu(x, alpha=0.01):
return np.where(x > 0, x, alpha * x)

真题示例:手写数字识别(简化版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
"""
题目:实现MLP识别手写数字(28×28图像,10分类)

输入格式:
第一行:n(样本数)
接下来n行:每行784个像素值(0-255),最后一个数是标签(0-9)
最后一行:测试样本的784个像素值

输出格式:
预测的数字(0-9)
"""

def solve_mnist():
import sys
input = sys.stdin.readline

n = int(input())

X_train = []
y_train = []

for _ in range(n):
line = list(map(float, input().split()))
X_train.append(line[:-1]) # 前784个是像素
y_train.append(int(line[-1])) # 最后是标签

X_train = np.array(X_train)
y_train = np.array(y_train)

# 测试样本
X_test = np.array([list(map(float, input().split()))])

# 归一化到[0,1]
X_train = X_train / 255.0
X_test = X_test / 255.0

# One-hot编码
y_train_onehot = np.eye(10)[y_train]

# 训练MLP
mlp = MLP(input_size=784, hidden_size=128, output_size=10)
mlp.train(X_train, y_train_onehot, epochs=100, lr=0.1)

# 预测
prediction = mlp.predict(X_test)
print(prediction[0])

# solve_mnist()

五、数据预处理⭐⭐⭐⭐⭐

1. 标准化(Z-score Normalization)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np

def standardize(X):
"""
标准化: (x - μ) / σ
使数据均值为0,标准差为1
"""
mean = np.mean(X, axis=0)
std = np.std(X, axis=0)
return (X - mean) / (std + 1e-8)

# 示例
X = np.array([[1, 2], [3, 4], [5, 6]])
X_std = standardize(X)
print(X_std)
print(f"Mean: {X_std.mean(axis=0)}") # 接近[0, 0]
print(f"Std: {X_std.std(axis=0)}") # 接近[1, 1]

2. 归一化(Min-Max Scaling)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def normalize(X, min_val=0, max_val=1):
"""
归一化到[min_val, max_val]
常用[0, 1]或[-1, 1]
"""
X_min = np.min(X, axis=0)
X_max = np.max(X, axis=0)

X_scaled = (X - X_min) / (X_max - X_min + 1e-8)
X_scaled = X_scaled * (max_val - min_val) + min_val

return X_scaled

# 示例
X = np.array([[1, 2], [3, 4], [5, 6]])
X_norm = normalize(X)
print(X_norm)
print(f"Min: {X_norm.min(axis=0)}") # [0, 0]
print(f"Max: {X_norm.max(axis=0)}") # [1, 1]

3. 缺失值处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import numpy as np

def fill_missing_mean(X):
"""用均值填充缺失值(NaN)"""
X_filled = X.copy()
for col in range(X.shape[1]):
col_data = X[:, col]
mask = ~np.isnan(col_data)
if mask.any():
mean_val = np.mean(col_data[mask])
X_filled[~mask, col] = mean_val
return X_filled

def fill_missing_median(X):
"""用中位数填充(对异常值鲁棒)"""
X_filled = X.copy()
for col in range(X.shape[1]):
col_data = X[:, col]
mask = ~np.isnan(col_data)
if mask.any():
median_val = np.median(col_data[mask])
X_filled[~mask, col] = median_val
return X_filled

def fill_missing_forward(X):
"""前向填充(时间序列常用)"""
X_filled = X.copy()
for col in range(X.shape[1]):
mask = np.isnan(X_filled[:, col])
idx = np.where(~mask, np.arange(len(mask)), 0)
np.maximum.accumulate(idx, out=idx)
X_filled[:, col] = X_filled[idx, col]
return X_filled

# 示例
X = np.array([[1, 2], [np.nan, 4], [5, np.nan]])
X_filled = fill_missing_mean(X)
print(X_filled)
# [[1. 2.]
# [3. 4.] # 第一列用(1+5)/2=3填充
# [5. 3.]] # 第二列用(2+4)/2=3填充

4. 异常值检测与处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def detect_outliers_iqr(X):
"""
使用IQR方法检测异常值
异常值定义:< Q1 - 1.5*IQR 或 > Q3 + 1.5*IQR
"""
Q1 = np.percentile(X, 25, axis=0)
Q3 = np.percentile(X, 75, axis=0)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = (X < lower_bound) | (X > upper_bound)
return outliers

def remove_outliers(X, y=None):
"""移除异常值样本"""
outliers = detect_outliers_iqr(X)
# 任何特征有异常值的样本都移除
mask = ~np.any(outliers, axis=1)

if y is not None:
return X[mask], y[mask]
return X[mask]

def clip_outliers(X):
"""将异常值裁剪到边界"""
Q1 = np.percentile(X, 25, axis=0)
Q3 = np.percentile(X, 75, axis=0)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

return np.clip(X, lower_bound, upper_bound)

# 示例
X = np.array([[1], [2], [3], [100], [2], [3]]) # 100是异常值
outliers = detect_outliers_iqr(X)
print(f"异常值位置: {np.where(outliers)[0]}") # [3]

X_clipped = clip_outliers(X)
print(f"裁剪后: {X_clipped.ravel()}")

六、相似度计算⭐⭐⭐⭐

1. 余弦相似度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import numpy as np

def cosine_similarity(a, b):
"""
余弦相似度: cos(θ) = (a·b) / (||a|| * ||b||)
返回值范围: [-1, 1]
"""
dot_product = np.dot(a, b)
norm_a = np.linalg.norm(a)
norm_b = np.linalg.norm(b)
return dot_product / (norm_a * norm_b + 1e-8)

def cosine_similarity_matrix(X, Y=None):
"""
计算矩阵间的余弦相似度
X: (n, d)
Y: (m, d) 如果为None,则计算X与自己
返回: (n, m) 相似度矩阵
"""
if Y is None:
Y = X

# 归一化
X_norm = X / (np.linalg.norm(X, axis=1, keepdims=True) + 1e-8)
Y_norm = Y / (np.linalg.norm(Y, axis=1, keepdims=True) + 1e-8)

# 矩阵乘法
return X_norm @ Y_norm.T

# 示例
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(f"余弦相似度: {cosine_similarity(a, b):.4f}")

# 批量计算
X = np.array([[1, 0], [0, 1], [1, 1]])
sim_matrix = cosine_similarity_matrix(X)
print("相似度矩阵:\n", sim_matrix)

2. 欧氏距离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def euclidean_distance(a, b):
"""欧氏距离"""
return np.sqrt(np.sum((a - b) ** 2))

def euclidean_distance_matrix(X, Y=None):
"""
计算距离矩阵
使用 ||a-b||^2 = ||a||^2 + ||b||^2 - 2a·b 加速
"""
if Y is None:
Y = X

X_sq = np.sum(X ** 2, axis=1, keepdims=True) # (n, 1)
Y_sq = np.sum(Y ** 2, axis=1, keepdims=True) # (m, 1)

distances = np.sqrt(X_sq + Y_sq.T - 2 * X @ Y.T)
return distances

3. 曼哈顿距离

1
2
3
def manhattan_distance(a, b):
"""曼哈顿距离(L1距离)"""
return np.sum(np.abs(a - b))

4. 杰卡德相似度(Jaccard)

1
2
3
4
5
6
7
8
9
10
11
12
13
def jaccard_similarity(set_a, set_b):
"""
杰卡德相似度: |A ∩ B| / |A ∪ B|
适用于集合、二值特征
"""
intersection = np.sum(set_a & set_b)
union = np.sum(set_a | set_b)
return intersection / (union + 1e-8)

# 示例:文档词集合
doc1 = np.array([1, 0, 1, 1, 0]) # 词1,3,4存在
doc2 = np.array([1, 1, 0, 1, 0]) # 词1,2,4存在
print(f"Jaccard相似度: {jaccard_similarity(doc1, doc2):.4f}")

七、时间窗口处理⭐⭐⭐

滑动窗口特征提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import numpy as np

def sliding_window(data, window_size, stride=1):
"""
滑动窗口切分时间序列
data: (n,) 或 (n, features)
window_size: 窗口大小
stride: 步长
返回: (num_windows, window_size) 或 (num_windows, window_size, features)
"""
if len(data.shape) == 1:
data = data.reshape(-1, 1)

n, features = data.shape
num_windows = (n - window_size) // stride + 1

windows = []
for i in range(num_windows):
start = i * stride
end = start + window_size
windows.append(data[start:end])

return np.array(windows)

# 示例
data = np.array([1, 2, 3, 4, 5, 6, 7, 8])
windows = sliding_window(data, window_size=3, stride=2)
print(windows)
# [[[1], [2], [3]],
# [[3], [4], [5]],
# [[5], [6], [7]]]

时间窗口特征统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def window_features(data, window_size):
"""
计算窗口内的统计特征
返回:均值、标准差、最大值、最小值
"""
windows = sliding_window(data, window_size)

features = {
'mean': np.mean(windows, axis=1),
'std': np.std(windows, axis=1),
'max': np.max(windows, axis=1),
'min': np.min(windows, axis=1)
}

return features

# 示例
data = np.array([1, 5, 3, 7, 2, 8, 4])
features = window_features(data, window_size=3)
print("窗口均值:", features['mean'])

八、文档检索与排序⭐⭐⭐

TF-IDF相似度检索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import numpy as np

def compute_tf(doc):
"""计算词频TF"""
tf = {}
total_words = len(doc)
for word in doc:
tf[word] = tf.get(word, 0) + 1
# 归一化
for word in tf:
tf[word] /= total_words
return tf

def compute_idf(docs):
"""计算逆文档频率IDF"""
n_docs = len(docs)
idf = {}

# 统计每个词出现在多少文档中
for doc in docs:
unique_words = set(doc)
for word in unique_words:
idf[word] = idf.get(word, 0) + 1

# 计算IDF
for word in idf:
idf[word] = np.log(n_docs / idf[word])

return idf

def compute_tfidf(docs):
"""计算TF-IDF矩阵"""
idf = compute_idf(docs)

tfidf_vectors = []
vocab = sorted(idf.keys())

for doc in docs:
tf = compute_tf(doc)
tfidf_vector = []
for word in vocab:
tfidf_value = tf.get(word, 0) * idf[word]
tfidf_vector.append(tfidf_value)
tfidf_vectors.append(tfidf_vector)

return np.array(tfidf_vectors), vocab

def search_documents(query_doc, docs):
"""
基于TF-IDF的文档检索
返回:按相似度排序的文档索引
"""
all_docs = docs + [query_doc]
tfidf_matrix, vocab = compute_tfidf(all_docs)

# 查询向量是最后一个
query_vector = tfidf_matrix[-1]
doc_vectors = tfidf_matrix[:-1]

# 计算余弦相似度
similarities = cosine_similarity_matrix(
query_vector.reshape(1, -1),
doc_vectors
)[0]

# 排序
ranked_indices = np.argsort(similarities)[::-1]

return ranked_indices, similarities[ranked_indices]

# 示例
docs = [
['机器', '学习', '算法'],
['深度', '学习', '神经网络'],
['自然', '语言', '处理']
]
query = ['深度', '学习']

ranked, scores = search_documents(query, docs)
print("相关文档排序:", ranked)
print("相似度分数:", scores)

九、决策树(ID3算法)⭐⭐⭐

信息熵与信息增益

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import numpy as np

def entropy(y):
"""
计算信息熵: H(Y) = -Σ p(y) * log2(p(y))
"""
_, counts = np.unique(y, return_counts=True)
probs = counts / len(y)
return -np.sum(probs * np.log2(probs + 1e-10))

def information_gain(X_column, y, threshold):
"""
计算信息增益
X_column: 某个特征列
y: 标签
threshold: 分割阈值
"""
# 父节点熵
parent_entropy = entropy(y)

# 分割
left_mask = X_column <= threshold
right_mask = ~left_mask

if np.sum(left_mask) == 0 or np.sum(right_mask) == 0:
return 0

# 子节点加权熵
n = len(y)
left_entropy = entropy(y[left_mask])
right_entropy = entropy(y[right_mask])

weighted_entropy = (np.sum(left_mask) / n * left_entropy +
np.sum(right_mask) / n * right_entropy)

# 信息增益
return parent_entropy - weighted_entropy

def find_best_split(X, y):
"""找到最佳分割特征和阈值"""
best_gain = 0
best_feature = None
best_threshold = None

for feature_idx in range(X.shape[1]):
values = np.unique(X[:, feature_idx])

for value in values:
gain = information_gain(X[:, feature_idx], y, value)

if gain > best_gain:
best_gain = gain
best_feature = feature_idx
best_threshold = value

return best_feature, best_threshold, best_gain

# 示例
X = np.array([[1, 2], [2, 3], [3, 1], [4, 2]])
y = np.array([0, 0, 1, 1])

print(f"熵: {entropy(y):.4f}")
feature, threshold, gain = find_best_split(X, y)
print(f"最佳分割: 特征{feature}, 阈值{threshold}, 增益{gain:.4f}")

十、决策树完整实现(ID3/CART)⭐⭐⭐⭐

完整决策树类(CART分类树)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import numpy as np

class TreeNode:
"""决策树节点"""
def __init__(self, feature=None, threshold=None, left=None, right=None, value=None):
self.feature = feature # 分裂特征索引
self.threshold = threshold # 分裂阈值
self.left = left # 左子树
self.right = right # 右子树
self.value = value # 叶节点的预测值

class DecisionTreeClassifier:
"""CART分类决策树(使用基尼指数)"""
def __init__(self, max_depth=10, min_samples_split=2):
"""
max_depth: 最大深度,防止过拟合
min_samples_split: 最小分裂样本数
"""
self.max_depth = max_depth
self.min_samples_split = min_samples_split
self.root = None

def gini_impurity(self, y):
"""
计算基尼不纯度: Gini = 1 - Σ(p_i^2)
值越小表示纯度越高
"""
_, counts = np.unique(y, return_counts=True)
probs = counts / len(y)
return 1 - np.sum(probs ** 2)

def entropy(self, y):
"""
计算信息熵: H = -Σ(p_i * log2(p_i))
用于ID3算法
"""
_, counts = np.unique(y, return_counts=True)
probs = counts / len(y)
return -np.sum(probs * np.log2(probs + 1e-10))

def split_data(self, X, y, feature, threshold):
"""根据特征和阈值分裂数据"""
left_mask = X[:, feature] <= threshold
right_mask = ~left_mask

X_left, y_left = X[left_mask], y[left_mask]
X_right, y_right = X[right_mask], y[right_mask]

return X_left, y_left, X_right, y_right

def find_best_split(self, X, y):
"""
找到最佳分裂点
遍历所有特征和所有可能的阈值
"""
best_gain = -1
best_feature = None
best_threshold = None

parent_gini = self.gini_impurity(y)
n_samples = len(y)

# 遍历每个特征
for feature in range(X.shape[1]):
# 获取该特征的所有唯一值作为候选阈值
thresholds = np.unique(X[:, feature])

for threshold in thresholds:
# 分裂数据
X_left, y_left, X_right, y_right = self.split_data(X, y, feature, threshold)

if len(y_left) == 0 or len(y_right) == 0:
continue

# 计算加权基尼指数
n_left, n_right = len(y_left), len(y_right)
gini_left = self.gini_impurity(y_left)
gini_right = self.gini_impurity(y_right)

weighted_gini = (n_left / n_samples) * gini_left + \
(n_right / n_samples) * gini_right

# 计算信息增益(基尼减少量)
gain = parent_gini - weighted_gini

if gain > best_gain:
best_gain = gain
best_feature = feature
best_threshold = threshold

return best_feature, best_threshold, best_gain

def build_tree(self, X, y, depth=0):
"""
递归构建决策树
"""
n_samples, n_features = X.shape
n_classes = len(np.unique(y))

# 停止条件
if depth >= self.max_depth or \
n_samples < self.min_samples_split or \
n_classes == 1:
# 创建叶节点,返回众数
leaf_value = np.bincount(y.astype(int)).argmax()
return TreeNode(value=leaf_value)

# 找最佳分裂点
best_feature, best_threshold, best_gain = self.find_best_split(X, y)

# 如果没有有效分裂,创建叶节点
if best_feature is None or best_gain <= 0:
leaf_value = np.bincount(y.astype(int)).argmax()
return TreeNode(value=leaf_value)

# 分裂数据
X_left, y_left, X_right, y_right = self.split_data(
X, y, best_feature, best_threshold
)

# 递归构建左右子树
left_child = self.build_tree(X_left, y_left, depth + 1)
right_child = self.build_tree(X_right, y_right, depth + 1)

return TreeNode(
feature=best_feature,
threshold=best_threshold,
left=left_child,
right=right_child
)

def fit(self, X, y):
"""训练决策树"""
self.root = self.build_tree(X, y)

def predict_sample(self, x, node):
"""预测单个样本"""
# 到达叶节点
if node.value is not None:
return node.value

# 根据特征值决定走左子树还是右子树
if x[node.feature] <= node.threshold:
return self.predict_sample(x, node.left)
else:
return self.predict_sample(x, node.right)

def predict(self, X):
"""预测多个样本"""
return np.array([self.predict_sample(x, self.root) for x in X])

def print_tree(self, node=None, depth=0):
"""打印树结构(调试用)"""
if node is None:
node = self.root

indent = " " * depth

if node.value is not None:
print(f"{indent}预测: {node.value}")
else:
print(f"{indent}特征{node.feature} <= {node.threshold:.2f}")
print(f"{indent}├─ 左:")
self.print_tree(node.left, depth + 1)
print(f"{indent}└─ 右:")
self.print_tree(node.right, depth + 1)

# 使用示例
if __name__ == "__main__":
# 生成数据
np.random.seed(42)
X = np.random.randn(100, 2)
y = ((X[:, 0] > 0) & (X[:, 1] > 0)).astype(int) # AND逻辑

# 训练
tree = DecisionTreeClassifier(max_depth=5)
tree.fit(X, y)

# 预测
predictions = tree.predict(X)
accuracy = np.mean(predictions == y)
print(f"准确率: {accuracy:.2%}")

# 打印树结构
print("\n树结构:")
tree.print_tree()

决策树回归(CART回归树)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class DecisionTreeRegressor:
"""CART回归树(使用均方误差)"""
def __init__(self, max_depth=10, min_samples_split=2):
self.max_depth = max_depth
self.min_samples_split = min_samples_split
self.root = None

def mse(self, y):
"""计算均方误差"""
if len(y) == 0:
return 0
mean = np.mean(y)
return np.mean((y - mean) ** 2)

def find_best_split(self, X, y):
"""找最佳分裂点(最小化MSE)"""
best_mse = float('inf')
best_feature = None
best_threshold = None

parent_mse = self.mse(y)
n_samples = len(y)

for feature in range(X.shape[1]):
thresholds = np.unique(X[:, feature])

for threshold in thresholds:
left_mask = X[:, feature] <= threshold
right_mask = ~left_mask

if np.sum(left_mask) == 0 or np.sum(right_mask) == 0:
continue

y_left, y_right = y[left_mask], y[right_mask]

# 加权MSE
weighted_mse = (len(y_left) / n_samples) * self.mse(y_left) + \
(len(y_right) / n_samples) * self.mse(y_right)

if weighted_mse < best_mse:
best_mse = weighted_mse
best_feature = feature
best_threshold = threshold

return best_feature, best_threshold

def build_tree(self, X, y, depth=0):
"""递归构建回归树"""
n_samples = len(y)

# 停止条件
if depth >= self.max_depth or n_samples < self.min_samples_split:
return TreeNode(value=np.mean(y))

best_feature, best_threshold = self.find_best_split(X, y)

if best_feature is None:
return TreeNode(value=np.mean(y))

# 分裂
left_mask = X[:, best_feature] <= best_threshold
right_mask = ~left_mask

left_child = self.build_tree(X[left_mask], y[left_mask], depth + 1)
right_child = self.build_tree(X[right_mask], y[right_mask], depth + 1)

return TreeNode(
feature=best_feature,
threshold=best_threshold,
left=left_child,
right=right_child
)

def fit(self, X, y):
self.root = self.build_tree(X, y)

def predict_sample(self, x, node):
if node.value is not None:
return node.value

if x[node.feature] <= node.threshold:
return self.predict_sample(x, node.left)
else:
return self.predict_sample(x, node.right)

def predict(self, X):
return np.array([self.predict_sample(x, self.root) for x in X])

复杂度分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"""
决策树复杂度分析:

训练阶段:
时间复杂度:O(n * d * log(n) * depth)
- n: 样本数
- d: 特征数
- 每层遍历所有特征和阈值: O(n * d)
- 树的深度: O(log n) 到 O(n)(平衡/不平衡)

预测阶段:
时间复杂度:O(depth)
- 从根节点走到叶节点
- 平衡树: O(log n)
- 最坏: O(n)

空间复杂度:O(n)
- 最坏情况下每个样本一个节点

优点:
✓ 可解释性强(白盒模型)
✓ 不需要特征标准化
✓ 能处理非线性关系
✓ 同时处理数值和类别特征

缺点:
✗ 容易过拟合(需要剪枝)
✗ 对数据变化敏感(不稳定)
✗ 贪心算法(局部最优)
"""

完整测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def test_decision_tree():
"""决策树完整测试"""
print("=" * 50)
print("测试1: 分类任务 - AND逻辑")
print("=" * 50)

np.random.seed(42)
X = np.random.randn(200, 2)
y = ((X[:, 0] > 0) & (X[:, 1] > 0)).astype(int)

tree = DecisionTreeClassifier(max_depth=5)
tree.fit(X, y)
predictions = tree.predict(X)
accuracy = np.mean(predictions == y)
print(f"AND逻辑准确率: {accuracy:.2%}")
assert accuracy > 0.95

print("\n" + "=" * 50)
print("测试2: 回归任务 - 正弦函数")
print("=" * 50)

X_reg = np.linspace(0, 10, 100).reshape(-1, 1)
y_reg = np.sin(X_reg).ravel() + np.random.randn(100) * 0.1

tree_reg = DecisionTreeRegressor(max_depth=10)
tree_reg.fit(X_reg, y_reg)
predictions_reg = tree_reg.predict(X_reg)
mse_value = np.mean((predictions_reg - y_reg) ** 2)
print(f"回归MSE: {mse_value:.4f}")

print("\n" + "=" * 50)
print("测试3: 多分类 - 鸢尾花")
print("=" * 50)

# 三分类数据
X_multi = np.random.randn(150, 4)
y_multi = np.random.randint(0, 3, 150)

tree_multi = DecisionTreeClassifier(max_depth=5)
tree_multi.fit(X_multi, y_multi)
pred_multi = tree_multi.predict(X_multi)
acc_multi = np.mean(pred_multi == y_multi)
print(f"三分类准确率: {acc_multi:.2%}")

print("\n✅ 所有测试通过!")

# test_decision_tree()

十一、朴素贝叶斯分类器⭐⭐⭐⭐

1. NumPy常用函数速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 数组操作
np.array() # 创建数组
np.zeros((n, m)) # 全0矩阵
np.ones((n, m)) # 全1矩阵
np.eye(n) # 单位矩阵
np.random.randn(n,m) # 标准正态分布

# 数学运算
np.dot(a, b) 或 a @ b # 矩阵乘法
np.sum(a, axis=0) # 按列求和
np.mean(), np.std() # 均值、标准差
np.max(), np.min() # 最大、最小值
np.exp(), np.log() # 指数、对数
np.sqrt() # 平方根

# 形状操作
a.shape # 查看形状
a.reshape(n, m) # 重塑
a.T # 转置
np.concatenate() # 拼接
np.expand_dims() # 增加维度

# 逻辑操作
np.where(condition, x, y) # 条件选择
np.argmax(), np.argmin() # 最大最小值索引
np.argsort() # 排序索引
np.unique() # 去重

# 广播机制
a = np.array([[1], [2], [3]]) # (3, 1)
b = np.array([10, 20, 30]) # (3,)
c = a + b # 广播为(3, 3)

2. 常见Bug及解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Bug 1: 维度不匹配
# 错误: (n,) 和 (n, 1) 不同
a = np.array([1, 2, 3]) # (3,)
b = a.reshape(-1, 1) # (3, 1)

# Bug 2: 整数除法
# Python 3中要注意
loss = loss / n # 正确(浮点除法)
# 避免 loss // n(整数除法)

# Bug 3: 原地修改
a = np.array([1, 2, 3])
b = a # b和a指向同一对象
b = a.copy() # 正确:深拷贝

# Bug 4: 梯度消失
# 使用clip避免
z = np.clip(z, -500, 500)
output = 1 / (1 + np.exp(-z))

3. 打印调试模板

1
2
3
4
5
6
7
8
9
10
def debug_shapes(*arrays, names=None):
"""调试时打印所有数组的形状"""
if names is None:
names = [f"Array{i}" for i in range(len(arrays))]

for name, arr in zip(names, arrays):
print(f"{name}: shape={arr.shape}, dtype={arr.dtype}")

# 使用
debug_shapes(X, y, W, b, names=['X', 'y', 'W', 'b'])

4. 考试时间分配建议

  • 前5分钟:通读题目,理解输入输出格式
  • 选择题(30分钟):快速答题,不确定的先跳过
  • 第一题(40分钟):必须AC,分数最容易拿
  • 第二题(40分钟):尽力而为,部分通过也有分
  • 最后5分钟:检查ACM输入输出格式

5. 代码模板保存建议

考前将所有模板整理到一个Python文件,考试时可以直接复制粘贴:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# my_templates.py
# 逻辑回归
def logistic_regression(...): pass

# KNN
def knn(...): pass

# MLP
class MLP: pass

# 数据预处理
def standardize(...): pass
def fill_missing(...): pass

# 相似度
def cosine_similarity(...): pass

十二、K-Means聚类⭐⭐⭐⭐⭐

完整实现(手把手教学版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import numpy as np

class KMeans:
"""
K-Means聚类算法

原理:
1. 随机初始化k个聚类中心
2. 迭代执行:
a) 分配:每个样本分配到最近的聚类中心
b) 更新:重新计算每个聚类的中心点
3. 直到收敛(中心点不再变化)
"""
def __init__(self, n_clusters=3, max_iters=100, tol=1e-4, random_state=None):
"""
n_clusters: 聚类数量k
max_iters: 最大迭代次数
tol: 收敛阈值(中心点变化小于此值则停止)
random_state: 随机种子
"""
self.n_clusters = n_clusters
self.max_iters = max_iters
self.tol = tol
self.random_state = random_state
self.centers = None # 聚类中心
self.labels = None # 每个样本的类别标签
self.inertia = None # 样本到聚类中心的平方距离之和

def euclidean_distance(self, X, centers):
"""
计算样本到所有中心的距离矩阵
X: (n_samples, n_features)
centers: (n_clusters, n_features)
返回: (n_samples, n_clusters) 距离矩阵
"""
# 使用广播高效计算
# ||X - C||^2 = ||X||^2 + ||C||^2 - 2X·C^T
X_sq = np.sum(X ** 2, axis=1, keepdims=True) # (n, 1)
C_sq = np.sum(centers ** 2, axis=1, keepdims=True).T # (1, k)
distances = np.sqrt(X_sq + C_sq - 2 * X @ centers.T) # (n, k)
return distances

def initialize_centers(self, X):
"""
初始化聚类中心
方法1: 随机选择k个样本作为初始中心(简单但可能不好)
方法2: K-Means++(更好的初始化,考试可选)
"""
if self.random_state is not None:
np.random.seed(self.random_state)

# 随机选择k个样本
n_samples = X.shape[0]
indices = np.random.choice(n_samples, self.n_clusters, replace=False)
return X[indices].copy()

def initialize_centers_plus(self, X):
"""
K-Means++初始化(考试加分项)

步骤:
1. 随机选择第一个中心
2. 对于剩余中心:
- 计算每个点到已选中心的最小距离
- 距离越远的点被选中概率越大
"""
if self.random_state is not None:
np.random.seed(self.random_state)

n_samples, n_features = X.shape
centers = np.zeros((self.n_clusters, n_features))

# 第一个中心:随机选择
centers[0] = X[np.random.randint(n_samples)]

# 选择剩余中心
for i in range(1, self.n_clusters):
# 计算每个点到已选中心的最小距离
distances = self.euclidean_distance(X, centers[:i])
min_distances = np.min(distances, axis=1)

# 距离平方作为概率权重
probabilities = min_distances ** 2
probabilities /= probabilities.sum()

# 按概率选择下一个中心
next_center_idx = np.random.choice(n_samples, p=probabilities)
centers[i] = X[next_center_idx]

return centers

def assign_clusters(self, X, centers):
"""
分配步骤:将每个样本分配到最近的聚类中心
返回: 每个样本的类别标签 (n_samples,)
"""
distances = self.euclidean_distance(X, centers)
return np.argmin(distances, axis=1)

def update_centers(self, X, labels):
"""
更新步骤:计算每个聚类的新中心(均值)
返回: 新的聚类中心 (n_clusters, n_features)
"""
n_features = X.shape[1]
new_centers = np.zeros((self.n_clusters, n_features))

for k in range(self.n_clusters):
# 找到属于聚类k的所有样本
cluster_samples = X[labels == k]

if len(cluster_samples) > 0:
# 计算均值作为新中心
new_centers[k] = np.mean(cluster_samples, axis=0)
else:
# 如果某个聚类没有样本,重新随机初始化
new_centers[k] = X[np.random.randint(len(X))]

return new_centers

def compute_inertia(self, X, labels, centers):
"""
计算惯性(样本到聚类中心的平方距离之和)
用于评估聚类质量,值越小越好
"""
inertia = 0
for k in range(self.n_clusters):
cluster_samples = X[labels == k]
if len(cluster_samples) > 0:
distances = np.sum((cluster_samples - centers[k]) ** 2)
inertia += distances
return inertia

def fit(self, X):
"""
训练K-Means模型
X: (n_samples, n_features)
"""
# 初始化聚类中心
self.centers = self.initialize_centers(X)
# 或使用K-Means++: self.centers = self.initialize_centers_plus(X)

# 迭代优化
for iteration in range(self.max_iters):
# 保存旧中心用于判断收敛
old_centers = self.centers.copy()

# 分配样本到最近的聚类
self.labels = self.assign_clusters(X, self.centers)

# 更新聚类中心
self.centers = self.update_centers(X, self.labels)

# 检查收敛
center_shift = np.sum((self.centers - old_centers) ** 2)

if center_shift < self.tol:
print(f"收敛于第 {iteration + 1} 次迭代")
break

# 计算最终惯性
self.inertia = self.compute_inertia(X, self.labels, self.centers)

return self

def predict(self, X):
"""
预测新样本的聚类标签
X: (n_samples, n_features)
"""
return self.assign_clusters(X, self.centers)

def fit_predict(self, X):
"""训练并返回标签"""
self.fit(X)
return self.labels

# 使用示例
if __name__ == "__main__":
# 生成模拟数据(3个聚类)
np.random.seed(42)

cluster1 = np.random.randn(50, 2) + np.array([0, 0])
cluster2 = np.random.randn(50, 2) + np.array([5, 5])
cluster3 = np.random.randn(50, 2) + np.array([0, 5])

X = np.vstack([cluster1, cluster2, cluster3])

# 训练
kmeans = KMeans(n_clusters=3, max_iters=100, random_state=42)
labels = kmeans.fit_predict(X)

print(f"聚类中心:\n{kmeans.centers}")
print(f"惯性: {kmeans.inertia:.2f}")
print(f"每个聚类的样本数: {np.bincount(labels)}")

考试速写版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import numpy as np

def kmeans(X, k, max_iters=100):
"""
极简K-Means实现(考试快速版)
X: (n, d) 数据
k: 聚类数
返回: labels, centers
"""
n, d = X.shape

# 随机初始化中心
centers = X[np.random.choice(n, k, replace=False)]

for _ in range(max_iters):
# 分配:计算距离,找最近的中心
distances = np.sqrt(((X[:, np.newaxis] - centers) ** 2).sum(axis=2))
labels = np.argmin(distances, axis=1)

# 更新:计算新中心
new_centers = np.array([X[labels == i].mean(axis=0)
if np.sum(labels == i) > 0
else centers[i]
for i in range(k)])

# 检查收敛
if np.allclose(centers, new_centers):
break

centers = new_centers

return labels, centers

复杂度分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
"""
K-Means复杂度分析:

时间复杂度:O(iterations * n * k * d)
- iterations: 迭代次数(通常10-100次)
- n: 样本数
- k: 聚类数
- d: 特征维度
- 每次迭代:
* 计算距离: O(n * k * d)
* 分配标签: O(n * k)
* 更新中心: O(n * d)

空间复杂度:O(n + k*d)
- 存储标签: O(n)
- 存储中心: O(k * d)

优点:
✓ 简单易实现
✓ 速度快,可扩展到大数据
✓ 适合球形聚类

缺点:
✗ 需要预先指定k
✗ 对初始值敏感(可能局部最优)
✗ 对异常值敏感
✗ 假设聚类是凸形的

选择k的方法:
1. 肘部法则(Elbow Method):画k-惯性曲线
2. 轮廓系数(Silhouette Score)
3. Gap统计量
"""

肘部法则选择k

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def elbow_method(X, k_range=range(2, 11)):
"""
肘部法则:找最优k值
绘制k vs 惯性曲线,找拐点
"""
inertias = []

for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans.fit(X)
inertias.append(kmeans.inertia)

# 打印结果
print("k\t惯性")
for k, inertia in zip(k_range, inertias):
print(f"{k}\t{inertia:.2f}")

return inertias

# 使用
# inertias = elbow_method(X, range(2, 10))
# 观察曲线,找"肘部"(惯性下降变缓的点)

完整测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def test_kmeans():
"""K-Means完整测试"""
print("=" * 50)
print("测试1: 明显分离的聚类")
print("=" * 50)

np.random.seed(42)

# 生成三个明显分离的聚类
cluster1 = np.random.randn(50, 2) + np.array([0, 0])
cluster2 = np.random.randn(50, 2) + np.array([10, 0])
cluster3 = np.random.randn(50, 2) + np.array([5, 10])

X = np.vstack([cluster1, cluster2, cluster3])
true_labels = np.array([0]*50 + [1]*50 + [2]*50)

# 训练
kmeans = KMeans(n_clusters=3, random_state=42)
pred_labels = kmeans.fit_predict(X)

print(f"聚类中心:\n{kmeans.centers}")
print(f"惯性: {kmeans.inertia:.2f}")
print(f"每个聚类样本数: {np.bincount(pred_labels)}")

print("\n" + "=" * 50)
print("测试2: 不同k值的影响")
print("=" * 50)

for k in [2, 3, 4, 5]:
kmeans_k = KMeans(n_clusters=k, random_state=42)
kmeans_k.fit(X)
print(f"k={k}: 惯性={kmeans_k.inertia:.2f}")

print("\n" + "=" * 50)
print("测试3: 预测新样本")
print("=" * 50)

# 训练模型
kmeans = KMeans(n_clusters=3, random_state=42)
kmeans.fit(X)

# 预测新样本
X_new = np.array([[0, 0], [10, 0], [5, 10]])
pred_new = kmeans.predict(X_new)
print(f"新样本预测: {pred_new}")

print("\n✅ 所有测试通过!")

# test_kmeans()

常见Bug及调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Bug 1: 空聚类问题
# 某个聚类没有样本分配
# 解决:重新随机初始化该中心
if len(cluster_samples) == 0:
new_centers[k] = X[np.random.randint(len(X))]

# Bug 2: 数值不稳定
# 距离计算时可能出现负数(浮点误差)
distances = np.sqrt(np.maximum(distances_sq, 0))

# Bug 3: k值选择不当
# k > n(样本数)会报错
k = min(k, len(X))

# Bug 4: 未标准化特征
# 特征尺度差异大时,大尺度特征主导距离计算
X_scaled = (X - X.mean(axis=0)) / X.std(axis=0)

十三、主成分分析(PCA)降维⭐⭐⭐⭐

十四、支持向量机(SVM)基础⭐⭐⭐

线性SVM(简化版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np

class LinearSVM:
def __init__(self, C=1.0, lr=0.001, epochs=1000):
self.C = C
self.lr = lr
self.epochs = epochs
self.w = None
self.b = None

def fit(self, X, y):
n_samples, n_features = X.shape
self.w = np.zeros(n_features)
self.b = 0

for epoch in range(self.epochs):
for i in range(n_samples):
margin = y[i] * (X[i] @ self.w + self.b)
if margin < 1:
self.w -= self.lr * (self.C * self.w - y[i] * X[i])
self.b -= self.lr * (-y[i])
else:
self.w -= self.lr * self.C * self.w

def predict(self, X):
return np.sign(X @ self.w + self.b)

十五、容易出错的地方 ⚠️

最高频Bug TOP10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 1. 维度不匹配
a = np.array([1, 2, 3]) # (3,)
a = a.reshape(-1, 1) # (3, 1)

# 2. Softmax溢出
def softmax(x):
exp_x = np.exp(x - np.max(x))
return exp_x / np.sum(exp_x)

# 3. 除零错误
std = np.std(X) + 1e-8

# 4. 数据泄露
mean = X_train.mean() # 只用训练集
X_test = (X_test - mean) / std

# 5. 学习率不当
lr = 0.01 # 从小开始

# 6. 全零初始化
W = np.random.randn(n, m) * np.sqrt(2/n)

# 7. 边界条件
k = min(k, len(X_train))

# 8. 原地修改
b = a.copy()

# 9. log(0)
loss = -np.log(y_pred + 1e-15)

# 10. 忘记标准化
X = (X - X.mean(axis=0)) / X.std(axis=0)

十六、真题模拟

真题1:逻辑回归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def solve():
n, m = map(int, input().split())
X_train, y_train = [], []
for _ in range(n):
line = list(map(float, input().split()))
X_train.append(line[:-1])
y_train.append(int(line[-1]))

X_train = np.array(X_train)
y_train = np.array(y_train)
X_test = np.array([list(map(float, input().split()))])

# 标准化
mean = X_train.mean(axis=0)
std = X_train.std(axis=0) + 1e-8
X_train = (X_train - mean) / std
X_test = (X_test - mean) / std

# 训练
w, b = logistic_regression(X_train, y_train, lr=0.1, epochs=1000)
pred = predict(X_test, w, b)
print(int(pred[0]))

十七、考试策略

时间分配(150分钟)

  • 前5分钟:通读题目
  • 选择题:30-40分钟
  • 编程题1:40-50分钟(必须AC)
  • 编程题2:50-60分钟(尽力而为)
  • 最后10分钟:检查格式

300分保命口诀

1
2
3
4
5
6
7
8
必背5算法:逻辑回归、KNN、线性回归、MLPK-Means
必会5技能:标准化、填充、梯度下降、损失函数、准确率
必查5Bug:维度、溢出、除零、泄露、学习率

读题仔细别慌张,输入输出看清楚
先写框架再填空,模板代码记心中
标准化后再训练,学习率从小调整
第一题必须拿下,第二题尽力而为

🎉 恭喜完成学习!本文档已从1026行扩充到2900+行,增加3倍内容!

包含:

  • ✅ 15+核心算法完整实现
  • ✅ 教学版+速写版双版本
  • ✅ 详细复杂度分析
  • ✅ 完整测试用例
  • ✅ Bug调试技巧
  • ✅ 真题解析
  • ✅ 考试策略

祝你300分保底,冲击满分!💪🚀

下一篇:ACM输入输出大全

十四、支持向量机(SVM)基础⭐⭐⭐

线性SVM(简化版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np

class LinearSVM:
def __init__(self, C=1.0, lr=0.001, epochs=1000):
self.C = C
self.lr = lr
self.epochs = epochs
self.w = None
self.b = None

def fit(self, X, y):
n_samples, n_features = X.shape
self.w = np.zeros(n_features)
self.b = 0

for epoch in range(self.epochs):
for i in range(n_samples):
margin = y[i] * (X[i] @ self.w + self.b)
if margin < 1:
self.w -= self.lr * (self.C * self.w - y[i] * X[i])
self.b -= self.lr * (-y[i])
else:
self.w -= self.lr * self.C * self.w

def predict(self, X):
return np.sign(X @ self.w + self.b)

十五、容易出错的地方 ⚠️

最高频Bug TOP10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 1. 维度不匹配
a = np.array([1, 2, 3]) # (3,)
a = a.reshape(-1, 1) # (3, 1)

# 2. Softmax溢出
def softmax(x):
exp_x = np.exp(x - np.max(x))
return exp_x / np.sum(exp_x)

# 3. 除零错误
std = np.std(X) + 1e-8

# 4. 数据泄露
mean = X_train.mean() # 只用训练集
X_test = (X_test - mean) / std

# 5. 学习率不当
lr = 0.01 # 从小开始

# 6. 全零初始化
W = np.random.randn(n, m) * np.sqrt(2/n)

# 7. 边界条件
k = min(k, len(X_train))

# 8. 原地修改
b = a.copy()

# 9. log(0)
loss = -np.log(y_pred + 1e-15)

# 10. 忘记标准化
X = (X - X.mean(axis=0)) / X.std(axis=0)

十六、真题模拟

真题1:逻辑回归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def solve():
n, m = map(int, input().split())
X_train, y_train = [], []
for _ in range(n):
line = list(map(float, input().split()))
X_train.append(line[:-1])
y_train.append(int(line[-1]))

X_train = np.array(X_train)
y_train = np.array(y_train)
X_test = np.array([list(map(float, input().split()))])

# 标准化
mean = X_train.mean(axis=0)
std = X_train.std(axis=0) + 1e-8
X_train = (X_train - mean) / std
X_test = (X_test - mean) / std

# 训练
w, b = logistic_regression(X_train, y_train, lr=0.1, epochs=1000)
pred = predict(X_test, w, b)
print(int(pred[0]))

十七、考试策略

时间分配(150分钟)

  • 前5分钟:通读题目
  • 选择题:30-40分钟
  • 编程题1:40-50分钟(必须AC)
  • 编程题2:50-60分钟(尽力而为)
  • 最后10分钟:检查格式

300分保命口诀

1
2
3
4
5
6
7
8
必背5算法:逻辑回归、KNN、线性回归、MLPK-Means
必会5技能:标准化、填充、梯度下降、损失函数、准确率
必查5Bug:维度、溢出、除零、泄露、学习率

读题仔细别慌张,输入输出看清楚
先写框架再填空,模板代码记心中
标准化后再训练,学习率从小调整
第一题必须拿下,第二题尽力而为

🎉 恭喜完成学习!本文档已从1026行扩充到2900+行,增加3倍内容!

包含:

  • ✅ 15+核心算法完整实现
  • ✅ 教学版+速写版双版本
  • ✅ 详细复杂度分析
  • ✅ 完整测试用例
  • ✅ Bug调试技巧
  • ✅ 真题解析
  • ✅ 考试策略

祝你300分保底,冲击满分!💪🚀

下一篇:ACM输入输出大全


华为AI机试编程题算法模板(NumPy手写)
https://whyalwaysme.lol/2026/09/01/华为AI机试-编程题模板/
作者
Cassiur
发布于
2026年9月1日
许可协议