ML03

ML03

多特征

  • Fw,b(x)=w1x1+w2x2+…+wnxn+b

向量化

  • f = np.dot(w,x) + b(可以理解为点向量,这里使用的是python中的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
    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
    import numpy as np
    import time
    import sys

    # 定义一个辅助函数来测量代码执行时间并打印结果
    def measure_performance(func, *args, method_name=""):
    start_time = time.perf_counter()
    result = func(*args)
    end_time = time.perf_counter()
    duration = (end_time - start_time) * 1000 # 转换为毫秒

    # 注意:sys.getsizeof() 测量的是 Python 对象本身的内存大小,
    # 对于 NumPy 数组,它不包含底层实际存储数据所占用的内存。
    # NumPy 的核心优势在于其底层数据存储和运算的高度优化(通常由 C/Fortran 实现)。
    result_memory_mb = sys.getsizeof(result) / (1024 * 1024)

    print(f"--- 方法: {method_name} ---")
    print(f"时间消耗: {duration:.4f} 毫秒")
    print(f"结果对象大小 (Python层): {result_memory_mb:.4f} MB")
    print("(请注意:NumPy 数组的实际数据存储在底层,通常更高效且不计入此处的 Python 对象大小。)")
    return result

    print("我们将通过经典的向量和矩阵运算示例,对比普通 Python 方法和 NumPy dot 函数的性能差异(主要是时间)。")
    # 修正了之前的语法错误,确保字符串在单行内正确闭合
    print("NumPy 库的底层是高度优化的 C/Fortran 代码,所以在内存使用和计算速度上通常远优于纯 Python 实现。\n")

    # --- 示例 1: 两个向量的点积 (Vector Dot Product) ---
    print("--- 示例 1: 两个向量的点积 ---")
    N_vec = 5000 # 向量维度

    vec_a = np.random.rand(N_vec)
    vec_b = np.random.rand(N_vec)

    # 普通 Python 方法计算点积
    def normal_dot_product(v1, v2):
    return sum(v1[i] * v2[i] for i in range(len(v1)))

    # 将 NumPy 数组转换为列表,以模拟纯 Python 操作的开销
    list_a = vec_a.tolist()
    list_b = vec_b.tolist()

    result_normal_vec = measure_performance(normal_dot_product, list_a, list_b, method_name="纯 Python 循环计算向量点积")

    # NumPy dot 函数计算点积
    result_numpy_vec = measure_performance(np.dot, vec_a, vec_b, method_name="NumPy dot 函数计算向量点积")

    print(f"两个结果是否接近: {np.isclose(result_normal_vec, result_numpy_vec)}\n")

    # --- 示例 2: 矩阵与向量相乘 (Matrix-Vector Multiplication) ---
    print("--- 示例 2: 矩阵与向量相乘 ---")
    N_mat_row = 1000 # 矩阵行数
    N_mat_col = 500 # 矩阵列数 (也为向量维度)

    matrix = np.random.rand(N_mat_row, N_mat_col)
    vector = np.random.rand(N_mat_col)

    # 普通 Python 方法计算矩阵与向量相乘
    def normal_mat_vec_multiply(mat, vec):
    rows = len(mat)
    cols = len(mat[0])
    result = [0] * rows
    for i in range(rows):
    for j in range(cols):
    result[i] += mat[i][j] * vec[j]
    return result

    # 将 NumPy 数组转换为列表的列表,以模拟纯 Python 操作的开销
    list_matrix = matrix.tolist()
    list_vector = vector.tolist()

    result_normal_mat_vec = measure_performance(normal_mat_vec_multiply, list_matrix, list_vector, method_name="纯 Python 循环计算矩阵-向量乘法")

    # NumPy dot 函数计算矩阵与向量相乘
    result_numpy_mat_vec = measure_performance(np.dot, matrix, vector, method_name="NumPy dot 函数计算矩阵-向量乘法")

    print(f"两个结果是否接近: {np.allclose(result_normal_mat_vec, result_numpy_mat_vec)}\n")


    # --- 示例 3: 两个矩阵相乘 (Matrix-Matrix Multiplication) ---
    print("--- 示例 3: 两个矩阵相乘 ---")
    N_mat1_row = 500
    N_common = 300
    N_mat2_col = 400

    mat_A = np.random.rand(N_mat1_row, N_common)
    mat_B = np.random.rand(N_common, N_mat2_col)

    # 普通 Python 方法计算矩阵相乘
    def normal_mat_mat_multiply(mat1, mat2):
    rows_a = len(mat1)
    cols_a = len(mat1[0])
    rows_b = len(mat2)
    cols_b = len(mat2[0])

    if cols_a != rows_b:
    raise ValueError("矩阵维度不匹配,无法相乘")

    result = [[0 for _ in range(cols_b)] for _ in range(rows_a)]

    for i in range(rows_a):
    for j in range(cols_b):
    for k in range(cols_a): # 或者 rows_b
    result[i][j] += mat1[i][k] * mat2[k][j]
    return result

    # 将 NumPy 数组转换为列表的列表
    list_mat_A = mat_A.tolist()
    list_mat_B = mat_B.tolist()

    result_normal_mat_mat = measure_performance(normal_mat_mat_multiply, list_mat_A, list_mat_B, method_name="纯 Python 循环计算矩阵乘法")

    # NumPy dot 函数计算矩阵相乘
    result_numpy_mat_mat = measure_performance(np.dot, mat_A, mat_B, method_name="NumPy dot 函数计算矩阵乘法")

    # 将 NumPy 结果转换为列表进行比较
    print(f"两个结果是否接近: {np.allclose(result_normal_mat_mat, result_numpy_mat_mat)}\n")

    print("总结:从上述示例可以看出,NumPy 的 dot 函数在处理大型多参数向量和矩阵运算时,相比纯 Python 循环具有压倒性的性能优势(尤其体现在时间消耗上)。")
    print("这是因为它底层使用了高度优化的 C/Fortran 库(如 BLAS),能够高效地处理连续内存块和并行计算。")
    print("在实际的机器学习和科学计算中,强烈建议使用 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
33
34
35
36
37
38
39
我们将通过经典的向量和矩阵运算示例,对比普通 Python 方法和 NumPy dot 函数的性能差异(主要是时间)。
NumPy 库的底层是高度优化的 C/Fortran 代码,所以在内存使用和计算速度上通常远优于纯 Python 实现。

--- 示例 1: 两个向量的点积 ---
--- 方法: 纯 Python 循环计算向量点积 ---
时间消耗: 0.4017 毫秒
结果对象大小 (Python层): 0.0000 MB
(请注意:NumPy 数组的实际数据存储在底层,通常更高效且不计入此处的 Python 对象大小。)
--- 方法: NumPy dot 函数计算向量点积 ---
时间消耗: 0.0356 毫秒
结果对象大小 (Python层): 0.0000 MB
(请注意:NumPy 数组的实际数据存储在底层,通常更高效且不计入此处的 Python 对象大小。)
两个结果是否接近: True

--- 示例 2: 矩阵与向量相乘 ---
--- 方法: 纯 Python 循环计算矩阵-向量乘法 ---
时间消耗: 39.5623 毫秒
结果对象大小 (Python层): 0.0077 MB
(请注意:NumPy 数组的实际数据存储在底层,通常更高效且不计入此处的 Python 对象大小。)
--- 方法: NumPy dot 函数计算矩阵-向量乘法 ---
时间消耗: 0.7684 毫秒
结果对象大小 (Python层): 0.0077 MB
(请注意:NumPy 数组的实际数据存储在底层,通常更高效且不计入此处的 Python 对象大小。)
两个结果是否接近: True

--- 示例 3: 两个矩阵相乘 ---
--- 方法: 纯 Python 循环计算矩阵乘法 ---
时间消耗: 10004.3874 毫秒
结果对象大小 (Python层): 0.0040 MB
(请注意:NumPy 数组的实际数据存储在底层,通常更高效且不计入此处的 Python 对象大小。)
--- 方法: NumPy dot 函数计算矩阵乘法 ---
时间消耗: 19.0441 毫秒
结果对象大小 (Python层): 1.5260 MB
(请注意:NumPy 数组的实际数据存储在底层,通常更高效且不计入此处的 Python 对象大小。)
两个结果是否接近: True

总结:从上述示例可以看出,NumPy 的 dot 函数在处理大型多参数向量和矩阵运算时,相比纯 Python 循环具有压倒性的性能优势(尤其体现在时间消耗上)。
这是因为它底层使用了高度优化的 C/Fortran 库(如 BLAS),能够高效地处理连续内存块和并行计算。
在实际的机器学习和科学计算中,强烈建议使用 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
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
import numpy as np
import matplotlib.pyplot as plt

# 设置随机种子以保证结果可复现
np.random.seed(42)

# 数据量
m = 100
# 特征数量
n_features = 3

# 生成随机特征数据 X
# 加上一列1用于偏置项b的计算,这样可以将b合并到权重w中,简化计算
X = np.random.rand(m, n_features)

# 真实的权重 (w_true) 和偏置 (b_true)
w_true = np.array([2.5, -1.0, 0.8]).reshape(n_features, 1) # 列向量
b_true = 3.0

# 生成目标变量 y
# y = Xw + b + noise
noise = np.random.randn(m, 1) * 0.5 # 添加一些噪声
y = X @ w_true + b_true + noise

print("特征矩阵 X 的形状:", X.shape)
print("目标变量 y 的形状:", y.shape)
print("真实权重 w_true:", w_true.T)
print("真实偏置 b_true:", b_true)

# 初始化权重 w 和偏置 b
# 为了方便计算,我们将偏置 b 作为权重向量 w 的一部分,并在特征矩阵 X 中添加一列1
X_b = np.c_[np.ones((m, 1)), X] # 在 X 前面添加一列 1

# 初始化权重向量 theta (包含 b 和 w)
theta = np.random.randn(n_features + 1, 1)

# 设置超参数
learning_rate = 0.01
n_iterations = 1000

# 存储每次迭代的损失值
loss_history = []

# 梯度下降训练
for iteration in range(n_iterations):
# 1. 预测值
y_pred = X_b @ theta

# 2. 计算误差
errors = y_pred - y

# 3. 计算梯度
# 损失函数 L = 1/m * sum((y_pred - y)^2)
# 梯度 dL/d(theta) = 2/m * X_b.T @ (y_pred - y)
gradients = (2/m) * X_b.T @ errors

# 4. 更新权重
theta = theta - learning_rate * gradients

# 记录当前迭代的损失(均方误差 MSE)
mse = np.mean(errors**2)
loss_history.append(mse)

# 提取学习到的偏置和权重
b_learned = theta[0, 0]
w_learned = theta[1:, 0]

print("梯度下降训练完成。")
print(f"学习到的偏置 b: {b_learned:.4f}")
print(f"学习到的权重 w: {w_learned}")

plt.figure(figsize=(10, 6))
plt.plot(range(n_iterations), loss_history)
plt.xlabel("迭代次数")
plt.ylabel("均方误差 (MSE)")
plt.title("梯度下降过程中损失曲线")
plt.grid(True)
plt.show()

print("--- 参数比较 ---")
print(f"真实偏置 b_true: {b_true:.4f}")
print(f"学习到的偏置 b_learned: {b_learned:.4f}")
print("\n真实权重 w_true:", w_true.flatten())
print("学习到的权重 w_learned:", w_learned)

# 可以计算学习到的参数与真实参数之间的L2范数(欧几里得距离)来衡量接近程度
weight_diff = np.linalg.norm(w_learned - w_true.flatten())
bias_diff = np.abs(b_learned - b_true)

print(f"\n权重向量差异 (L2 范数): {weight_diff:.4f}")
print(f"偏置差异 (绝对值): {bias_diff:.4f}")

print("从结果可以看出,经过足够次数的迭代,梯度下降算法能够有效地学习到接近真实值的模型参数。")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
特征矩阵 X 的形状: (100, 3)
目标变量 y 的形状: (100, 1)
真实权重 w_true: [[ 2.5 -1. 0.8]]
真实偏置 b_true: 3.0

梯度下降训练完成。
学习到的偏置 b: 2.7316
学习到的权重 w: [ 2.23075147 -0.47932172 1.11275231]

--- 参数比较 ---
真实偏置 b_true: 3.0000
学习到的偏置 b_learned: 2.7316

真实权重 w_true: [ 2.5 -1. 0.8]
学习到的权重 w_learned: [ 2.23075147 -0.47932172 1.11275231]

权重向量差异 (L2 范数): 0.6644
偏置差异 (绝对值): 0.2684
从结果可以看出,经过足够次数的迭代,梯度下降算法能够有效地学习到接近真实值的模型参数。