张天乙 · 博客

机器学习5大要素和线性回归案例

2026年7月3日

机器学习概述-5大要素和线性回归案例


1. 机器学习5大要素

  • 数据
  • 模型
  • 学习准则
  • 优化算法
  • 评价指标

1.1 数据-构造用于线性回归的数据集

创建一个符合y=wx+b,并且包含高斯噪声的数据,代码:

import torch
import matplotlib.pyplot as plt
torch.manual_seed(0)

def linear_data(n, w=1.2, b=0.5, noise=2.0, interval=(-10,10)):
    x = torch.empty(n,1).uniform_(*interval)
    y = w*x+b+noise*torch.randn_like(x)
    return x,y

X_train, y_train = linear_data(100)
X_test,  y_test  = linear_data(50)
X_large, y_large = linear_data(5000)

xs = torch.linspace(-10, 10, 200).unsqueeze(1)
plt.scatter(X_train.numpy(), y_train.numpy(), s=15, alpha=0.6, label='train (100)')
plt.scatter(X_test.numpy(),  y_test.numpy(),  s=15, alpha=0.6, label='test (50)')
plt.plot(xs.numpy(), (1.2*xs + 0.5).numpy(), 'k-', lw=1, label='underlying')
plt.legend(); plt.xlabel('x'); plt.ylabel('y'); plt.title('ToyLinear150'); plt.show()

运行结果:

注:
  • torch.empty(n,1)创建一个n行1列的二维张量
  • uniform_()是python的in-place方法,直接修改原有对象的内容,而不创建新的副本,例如
    import torch
    x = torch.tensor([1,2,3])
    y = torch.tensor([1,2,3])
    x1 = x.add(1)
    #x = tensor([1,2,3]), x1 = tensor([2,3,4])
    y1 = y.add_(1)
    #y = tensor([2,3,4]), y1 = tensor([2,3,4])
    
  • *interval是python的解包操作,(*interval)等价于(-10,10)
    两种解包操作符***
    单星号*解包位置参数。基本用法是解包到函数参数,例子如下:
    def add(a, b, c):
      return a + b + c
      # 不使用解包
      numbers = [1, 2, 3]
      result = add(numbers[0], numbers[1], numbers[2])  # 6
      # 使用解包
      result = add(*numbers)  # 等价于 add(1, 2, 3)
      print(result)  # 6
    
    支持的数据类型有:
    # 元组
    args = (1, 2, 3)
    func(*args)
    # 列表
    args = [1, 2, 3]
    func(*args)
    # 字符串(拆成单个字符)
    chars = "abc"
    print(*chars)  # a b c
    # 集合
    args = {1, 2, 3}  # 注意:无序
    func(*args)
    # 生成器
    args = range(3)  # 0, 1, 2
    func(*args)
    
    双星号**解包关键字参数。例子如下:
      def introduce(name, age, city):
          print(f"{name} is {age} years old from {city}")
      # 不使用解包
      person = {"name": "Alice", "age": 30, "city": "New York"}
      introduce(person["name"], person["age"], person["city"])
      # 使用解包
      introduce(**person)  # 等价于 introduce(name="Alice", age=30, city="New York")
      # 输出: Alice is 30 years old from New York
    
    支持的数据类型有:字典。
    解包符号也可以在函数定义中使用,*args - 收集多余的位置参数,**kwargs - 收集多余的关键字参数。例如
    def func(a, b, *args, c=10, **kwargs):
      print(f"a={a}, b={b}, args={args}, c={c}, kwargs={kwargs}")
    
    func(1, 2, 3, 4, c=20, d=5, e=6)
    # a=1, b=2, args=(3, 4), c=20, kwargs={'d': 5, 'e': 6}
    #顺序原则:必填位置参数 → 可选位置参数(*args) → 默认参数 → 可选关键字参数(**kwargs)
    
  • unsqueeze() 的详细用法:
    tensor.unsqueeze(dim)  # 在 dim 位置插入一个维度`
    

1.2 模型-线性算子

PyTorch 已经内置了 nn.Linear,但为了更深入地理解算子是如何工作的,本节定义一个继承自Op 接口的 Linear 算子。它把权重和偏置作为成员变量 params 持有,便于后面手写最小二乘法直接赋值。

class Op(object):
  def __init__(self):
    pass

  def __call__(self,inputs):
    return self.forward(inputs)

  def forward(self, inputs):
    raise NotImplementedError

  def backward(self, outputs_grads):
    raise NotImplementedError

class Linear(Op):
  """y=Xw+b的线性算子"""
  def __init__(self, input_size):
    self.input_size = input_size
    # 模型参数
    self.params = {
      'w': torch.randn(input_size, 1),
      'b': torch.zeros(1),
    }
  def __call__(self, X):
    return self.forward(X)
  def forward(self, X):
    # X: [N, D]   ->   y_pred: [N, 1]
    assert X.shape[1] == self.input_size
    return X @ self.params['w']+self.params['b']

# 简单测试
torch.manual_seed(0)
model_demo = Linear(input_size = 3)
X_demo = torch.randn(2,3)
print('y_pred:', model_demo(X_demo))

运行结果:
y_pred: tensor([[4.2414], [1.9428]])

注:
  • __call__ 让 Op 的实例变成了可调用的函数式对象,它内部仅仅是把调用转发给 forward 方法,目的是提升使用时的语法简洁性和语义直观性,并为框架后续的功能扩展(如自动梯度追踪、钩子注入)预留接口位置。
  • NotImplementedError 是 Python 内置的一个异常类,它的核心作用是标记某个方法尚未实现,强制子类必须重写该方法。
  • @ 是矩阵乘法(线性代数标准运算,用于特征变换),* 是逐元素乘法(对应位置相乘,仅用于元素级操作)。如果想在神经网络中实现正确的线性变换,必须使用 @(或 np.dottorch.matmul),而不是 *

1.3 学习准则:均方误差

回归任务是对连续值的预测,希望模型能根据数据特征输出一个连续值作为预测值。因此回归任务中常用的学习准则是均方误差(Mean Squared Error,MSE)。
yRN\boldsymbol{y}\in\mathbb{R}^Ny^RN\hat{\boldsymbol{y}}\in\mathbb{R}^N 分别为 NN 个样本的真实标签和预测标签,均方误差定义为
L(y,y^)=12Ny^y2=12NXw+by2,\mathcal{L}(\boldsymbol{y},\hat{\boldsymbol{y}})=\frac{1}{2N}\|\hat{\boldsymbol{y}}-\boldsymbol{y}\|^2=\frac{1}{2N}\|\boldsymbol{X}\boldsymbol{w}+\boldsymbol{b}-\boldsymbol{y}\|^2,
其中 b\boldsymbol{b} 为所有元素均为 bbNN 维向量。
对应的python代码为:

def mean_squared_error(y_true, y_pred):
  assert y_true.shape[0] == y_pred.shape[0]
  return 0.5*((y_true - y_pred) ** 2).mean()
注:
  • assert 是 Python 中的断言语句,用于调试阶段检查程序中的假设条件是否成立。如果条件为 False,程序会抛出 AssertionError 异常并终止运行。

1.4 优化算法:最小二乘法与梯度法

对于线性回归 + 均方误差,最优参数可以直接令偏导数等于 00 得到解析解。这种方式叫最小二乘法(Least Square Method,LSM)。

对均方误差关于 bb 求偏导并令其为 00,可以解出

b=yˉxˉw,b^* = \bar y - \bar{\boldsymbol{x}}^\top \boldsymbol{w},

其中 yˉ\bar yxˉ\bar{\boldsymbol{x}} 分别是标签和特征向量的平均值。把 bb^* 代回去再对 w\boldsymbol{w} 求偏导,可以解出

w=((Xxˉ)(Xxˉ))1(Xxˉ)(yyˉ).\boldsymbol{w}^* = \big((\boldsymbol{X}-\bar{\boldsymbol{x}}^\top)^\top(\boldsymbol{X}-\bar{\boldsymbol{x}}^\top)\big)^{-1}(\boldsymbol{X}-\bar{\boldsymbol{x}}^\top)^\top(\boldsymbol{y}-\bar y).

若加上 2\ell_2 正则化(结构风险),最优解变为

w=((Xxˉ)(Xxˉ)+λI)1(Xxˉ)(yyˉ).\boldsymbol{w}^* = \big((\boldsymbol{X}-\bar{\boldsymbol{x}}^\top)^\top(\boldsymbol{X}-\bar{\boldsymbol{x}}^\top)+\lambda \boldsymbol{I}\big)^{-1}(\boldsymbol{X}-\bar{\boldsymbol{x}}^\top)^\top(\boldsymbol{y}-\bar y).

下面把 optimizer_lsm 用 PyTorch 实现出来。

def optimizer_lsm(model, X, y, reg_lambda = 0.0):
  """最小二乘法(可带 L2 正则化)求解 Linear 算子的参数。"""
  N, D = X.shape
  x_bar = X.mean(dim=0, keepdim = True)
  y_bar = y.mean()
  x_sub = X-x_bar
  if torch.all(x_sub == 0):
    model.params['b'] = y_bar.unsqueeze(0)
    model.params['w'] = torch.zeros(D, 1)
    return model
  A = x_sub.T @ x_sub + reg_lambda * torch.eye(D, dtype=X.dtype, device=X.device)
  rhs = x_sub.T @ (y - y_bar)
  w = torch.linalg.solve(A, rhs)
  b = y_bar - x_bar @ w                       # [1, 1]

  model.params['w'] = w                       # [D, 1]
  model.params['b'] = b.squeeze(0)            # [1]
  return model
注:
  • L2正则化是机器学习中用来防止过拟合的一种技术手段,通过在损失函数中添加权重的平方和作为惩罚项,迫使模型的权重趋向于较小的值。它的另一个常见名字叫 "权重衰减"(Weight Decay)

1.5 模型训练 + 评价

在准备了数据、模型、损失和优化器后,我们开始训练。对线性回归而言,最小二乘法一步就能解出最优参数;模型的评价指标和损失函数一致,都用 MSE。对应的python代码为:

model = Linear(input_size = 1)
optimizer_lsm(model, X_train, y_train)
print(f"w_pred: {model.params['w'].item():.4f}   b_pred: {model.params['b'].item():.4f}")
y_train_pred = model(X_train)
train_error  = mean_squared_error(y_train, y_train_pred).item()
y_test_pred  = model(X_test)
test_error   = mean_squared_error(y_test,  y_test_pred).item()
print(f'train error: {train_error:.4f}')
print(f'test  error: {test_error:.4f}')

输出结果为:

w_pred: 1.1760   b_pred: 1.2056
train error: 1.9685
test  error: 2.1148

总结

将整个线性回归过程整合到一起的代码为:

import torch
import matplotlib.pyplot as plt
torch.manual_seed(0)

# 1.数据
def linear_data(n, w=1.2, b=0.5, noise=2.0, interval=(-10,10)):
  x = torch.empty(n,1).uniform_(*interval)
  y = w*x+b+noise*torch.randn_like(x)
  return x,y
X_train, y_train = linear_data(100)
X_test,  y_test  = linear_data(50)
X_large, y_large = linear_data(5000)
xs = torch.linspace(-10, 10, 200).unsqueeze(1)
plt.scatter(X_train.numpy(), y_train.numpy(), s=15, alpha=0.6, label='train (100)')
plt.scatter(X_test.numpy(),  y_test.numpy(),  s=15, alpha=0.6, label='test (50)')
plt.plot(xs.numpy(), (1.2*xs + 0.5).numpy(), 'k-', lw=1, label='underlying')
plt.legend(); plt.xlabel('x'); plt.ylabel('y'); plt.title('ToyLinear150'); plt.show()

# 2.模型
class Op(object):
  def __init__(self):
    pass
  def __call__(self,inputs):
    return self.forward(inputs)
  def forward(self, inputs):
    raise NotImplementedError
  def backward(self, outputs_grads):
    raise NotImplementedError
class Linear(Op):
  """y=Xw+b的线性算子"""
  def __init__(self, input_size):
    self.input_size = input_size
    # 模型参数
    self.params = {
      'w': torch.randn(input_size, 1),
      'b': torch.zeros(1),
    }
  def __call__(self, X):
    return self.forward(X)
  def forward(self, X):
    # X: [N, D]   ->   y_pred: [N, 1]
    assert X.shape[1] == self.input_size
    return X @ self.params['w']+self.params['b']

# 3.学习准则
def mean_squared_error(y_true, y_pred):
  assert y_true.shape[0] == y_pred.shape[0]
  return 0.5*((y_true - y_pred) ** 2).mean()

# 4.优化方法
def optimizer_lsm(model, X, y, reg_lambda = 0.0):
  """最小二乘法(可带 L2 正则化)求解 Linear 算子的参数。"""
  N, D = X.shape
  x_bar = X.mean(dim=0, keepdim = True)
  y_bar = y.mean()
  x_sub = X-x_bar
  if torch.all(x_sub == 0):
    model.params['b'] = y_bar.unsqueeze(0)
    model.params['w'] = torch.zeros(D, 1)
    return model
  A = x_sub.T @ x_sub + reg_lambda * torch.eye(D, dtype=X.dtype, device=X.device)
  rhs = x_sub.T @ (y - y_bar)
  w = torch.linalg.solve(A, rhs)
  b = y_bar - x_bar @ w                       # [1, 1]
  model.params['w'] = w                       # [D, 1]
  model.params['b'] = b.squeeze(0)            # [1]
  return model

# 5.模型训练+评价
model = Linear(input_size = 1)
optimizer_lsm(model, X_train, y_train)
print(f"w_pred: {model.params['w'].item():.4f}   b_pred: {model.params['b'].item():.4f}")
y_train_pred = model(X_train)
train_error  = mean_squared_error(y_train, y_train_pred).item()
y_test_pred  = model(X_test)
test_error   = mean_squared_error(y_test,  y_test_pred).item()
print(f'train error: {train_error:.4f}')
print(f'test  error: {test_error:.4f}')

← 返回人工智能学