看到一个有意思的题目:仅使用python的标准库,完成一个小批量梯度下降的线性回归算法
平常使用numpy这样的计算库习惯了,只允许使用标准库还有点不习惯,下面就使用这个过程来写一个。
import random from typing import List # 生成测试数据 def generate_data(num_samples: int, weights: List[float], bias: float, noise=0.1) -> (List[List[float]], List[float]): X = [[random.uniform(-10, 10) for _ in range(len(weights))] for _ in range(num_samples)] y = [sum(w * x for w, x in zip(weights, x_i)) + bias + random.uniform(-noise, noise) for x_i in X] return X, y # 计算损失 def mse(y_true: List[float], y_pred: List[float]): return 0.5 * sum((yt - yp) for yt, yp in zip(y_true, y_pred)) ** 2 # 将矩阵转置 def transpose(mat: List[List[float]]): row, col = len(mat), len(mat[0]) # 固定列,访问行 result = [ [mat[r][c] for r in range(row)] for c in range(col) ] return result # 计算矩阵乘法 def matmul(mat: List[List[float]], vec: List[float]): return [sum(r * c for r, c in zip(row, vec)) for row in mat] # 计算梯度 def compute_grad(y_true_batch: List[float], y_pred_batch: List[float], x_batch: List[List[float]]): batch_size = len(y_true_batch) residual = [yt - yp for yt, yp in zip(y_true_batch, y_pred_batch)] # 根据 y = x @ w + b # grad_w = -x.T @ residual grad_w = matmul(transpose(x_batch), residual) grad_w = [-gw / batch_size for gw in grad_w] grad_b = -sum(residual) / batch_size # grad_w: List[float] # grad_b: float return grad_w, grad_b # 开启训练 def train(): lr = 0.01 epochs = 50 batch_size = 16 dim_feat = 3 num_samples = 500 weights = [random.random() * 0.1 for _ in range(dim_feat)] bias = random.random() * 0.1 print('original params') print('w:', weights) print('b:', bias) X, y = generate_data(num_samples, weights, bias, noise=0.1) for epoch in range(epochs): for i in range(0, num_samples, batch_size): x_batch = X[i:i+batch_size] y_batch = y[i:i+batch_size] y_pred = [item + bias for item in matmul(x_batch, weights)] loss = mse(y_batch, y_pred) grad_w, grad_b = compute_grad(y_batch, y_pred, x_batch) weights = [w - lr * gw for w, gw in zip(weights, grad_w)] bias -= lr * grad_b print(f'Epoch: {epoch + 1}, Loss = {loss:.3f}') print('trained params') print('w:', weights) print('b:', bias) train()
'运行
输出结果如下
original params
w: [0.04845598598148951, 0.007741816562531545, 0.02436678108587098]
b: 0.01644073086522535
Epoch: 1, Loss = 0.000
Epoch: 2, Loss = 0.000
Epoch: 3, Loss = 0.000
Epoch: 4, Loss = 0.000
Epoch: 5, Loss = 0.000
Epoch: 6, Loss = 0.000
Epoch: 7, Loss = 0.000
Epoch: 8, Loss = 0.000
Epoch: 9, Loss = 0.000
Epoch: 10, Loss = 0.000
Epoch: 11, Loss = 0.000
Epoch: 12, Loss = 0.000
Epoch: 13, Loss = 0.000
Epoch: 14, Loss = 0.000
Epoch: 15, Loss = 0.000
Epoch: 16, Loss = 0.000
Epoch: 17, Loss = 0.000
Epoch: 18, Loss = 0.000
Epoch: 19, Loss = 0.000
Epoch: 20, Loss = 0.000
Epoch: 21, Loss = 0.000
Epoch: 22, Loss = 0.000
Epoch: 23, Loss = 0.000
Epoch: 24, Loss = 0.000
Epoch: 25, Loss = 0.000
Epoch: 26, Loss = 0.000
Epoch: 27, Loss = 0.000
Epoch: 28, Loss = 0.000
Epoch: 29, Loss = 0.000
Epoch: 30, Loss = 0.000
Epoch: 31, Loss = 0.000
Epoch: 32, Loss = 0.000
Epoch: 33, Loss = 0.000
Epoch: 34, Loss = 0.000
Epoch: 35, Loss = 0.000
Epoch: 36, Loss = 0.000
Epoch: 37, Loss = 0.000
Epoch: 38, Loss = 0.000
Epoch: 39, Loss = 0.000
Epoch: 40, Loss = 0.000
Epoch: 41, Loss = 0.000
Epoch: 42, Loss = 0.000
Epoch: 43, Loss = 0.000
Epoch: 44, Loss = 0.000
Epoch: 45, Loss = 0.000
Epoch: 46, Loss = 0.000
Epoch: 47, Loss = 0.000
Epoch: 48, Loss = 0.000
Epoch: 49, Loss = 0.000
Epoch: 50, Loss = 0.000
trained params
w: [0.05073234817652038, 0.007306286342947243, 0.023218625946243507]
b: 0.016648404245261664
可以看到,结果还是不错的