机器学习概述-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.dot、torch.matmul),而不是*。
1.3 学习准则:均方误差
回归任务是对连续值的预测,希望模型能根据数据特征输出一个连续值作为预测值。因此回归任务中常用的学习准则是均方误差(Mean Squared Error,MSE)。
令 、 分别为 个样本的真实标签和预测标签,均方误差定义为
其中 为所有元素均为 的 维向量。
对应的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 优化算法:最小二乘法与梯度法
对于线性回归 + 均方误差,最优参数可以直接令偏导数等于 得到解析解。这种方式叫最小二乘法(Least Square Method,LSM)。
对均方误差关于 求偏导并令其为 ,可以解出
其中 和 分别是标签和特征向量的平均值。把 代回去再对 求偏导,可以解出
若加上 正则化(结构风险),最优解变为
下面把 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}')