ML基础:从线性拟合看样本影响
ML基础:线性模型
Introduction
回想一年前刚开始学ML的时候,打的第一个模型就是线性拟合 \(y=kx+b\)
现在在测试多线程训练,发现一些之前没有想清楚的问题:
- 出现了NaN(小样本和不当学习率)
- 数据集里个别样本对参数准确度的影响比较大
Main part
先给出代码:
# 定义线性回归模型 (y = kx + b)
class LinearRegression(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(1, 1) # 输入维度为1,输出维度为1
def forward(self, x):
return self.linear(x)
结构:
- nn.Linear(1, 1) 表示输入维度为 1,输出维度为 1
- 它有两个参数:
- weight:权重,对应斜率 k,形状为 (1, 1)(因为输入和输出都是 1 维)
- bias:偏置,对应截距 b,形状为 (1,) (形状为 (1,) 表示一个张量是一维的,且包含 1 个元素)
NaN问题
看面试题的时候经常会问到这个基础问题,刚好在这个简单的情境下可以静下心来好好考虑
首先给出代码:
# 用户提供的数据(一维向量 x 和 y)
x = torch.tensor([0.05 ,1.0, -2.0, 5.0,5.5,9.0,-30.0, 42.0], dtype=torch.float32).reshape(-1, 1) # 形状: (n_samples, 1)
y = torch.tensor([0.1,2.0, -4.0,10.0,11.0,18.0, -60.0, 84.0], dtype=torch.float32).reshape(-1, 1) # 形状: (n_samples, 1)
# 创建数据集和数据加载器
dataset = TensorDataset(x, y)
data_loader = DataLoader(dataset, batch_size=2, shuffle=True)
# 定义线性回归模型 (y = kx + b)
class LinearRegression(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(1, 1) # 输入维度为1,输出维度为1
def forward(self, x):
return self.linear(x)
# 训练函数
def train_model(model, data_loader, epochs=100):
optimizer = torch.optim.SGD(model.parameters(), lr=0.005)
criterion = nn.MSELoss()
for epoch in range(epochs):
for x_batch, y_batch in data_loader:
optimizer.zero_grad()
output = model(x_batch)
loss = criterion(output, y_batch)
loss.backward()
optimizer.step()
# 返回斜率 k 和截距 b
k = model.linear.weight.detach().numpy()[0][0] # 权重即斜率 k
b = model.linear.bias.detach().numpy()[0] # 偏置即截距 b
return k, b
# 多线程训练并收集参数
def multithread_training(num_threads=2):
models = [LinearRegression() for _ in range(num_threads)]
# 将数据分成多个子集
data_splits = [DataLoader(dataset, batch_size=2, sampler=torch.utils.data.SubsetRandomSampler(
range(i * len(dataset) // num_threads, (i + 1) * len(dataset) // num_threads)))
for i in range(num_threads)]
# 使用线程池并行训练
results = []
with ThreadPoolExecutor(max_workers=num_threads) as executor:
results = list(executor.map(train_model, models, data_splits))
# 打印每个模型的拟合参数
for i, (k, b) in enumerate(results):
print(f"模型 {i+1} 的拟合直线: y = {k:.4f}x + {b:.4f}")
if __name__ == "__main__":
multithread_training(num_threads=2)
下面是ds给出的原因
这通常是由于训练过程中梯度不稳定或数值溢出导致的
可能的原因:
- 学习率过大:
- 学习率(
lr=0.005)可能对某些数据子集来说太大,导致梯度更新时权重值爆炸(特别是模型2的数据子集中可能有较大的输入值或梯度)。 - 模型1的数据子集可能更“温和”,因此没有出现NaN。
- 学习率(
- 数据分布不均匀:
- 你的数据
x的范围差异较大(从 -30.0 到 42.0),而y是x的线性变换(y = 2x)。 - 如果模型2的数据子集中包含极端值(如
x = -30.0或x = 42.0),梯度可能会变得非常大,导致权重更新时数值溢出。 - 模型1的数据子集可能没有包含这些极端值。
- 你的数据
- 随机数据划分:
- 你使用了
SubsetRandomSampler随机划分数据,模型2可能不幸地分到了某些极端值组合,导致梯度爆炸。 - 模型1的数据子集可能更“温和”。
- 你使用了
- 未归一化数据:
- 输入数据
x的范围差异较大(-30.0 到 42.0),而未进行归一化(如缩放到 [-1, 1] 或 [0, 1]),这可能导致梯度不稳定。
- 输入数据
解决方法:
-
降低学习率:
- 尝试将学习率调小(如
lr=0.001或更小),观察是否还会出现NaN。
- 尝试将学习率调小(如
-
数据归一化:
-
对输入数据
x进行归一化(如缩放到 [-1, 1] 或 [0, 1]):x = (x - x.mean()) / x.std() # 标准化 # 或 x = (x - x.min()) / (x.max() - x.min()) # 缩放到 [0, 1] -
这样可以让梯度更稳定。
-
-
梯度裁剪:
-
在训练时限制梯度的大小,防止梯度爆炸:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
-
-
检查数据划分:
-
打印模型1和模型2的数据子集,看看是否有极端值:
for i, split in enumerate(data_splits): print(f"模型 {i+1} 的数据子集:") for x_batch, y_batch in split: print(x_batch, y_batch)
-
-
初始化权重:
- 检查模型的权重初始化是否合理(
nn.Linear默认使用均匀初始化,通常没问题)。
- 检查模型的权重初始化是否合理(
修改后的代码示例:
# 数据归一化(标准化)
x_mean, x_std = x.mean(), x.std()
x_normalized = (x - x_mean) / x_std
# 更新数据集
dataset = TensorDataset(x_normalized, y)
# 训练时添加梯度裁剪
def train_model(model, data_loader, epochs=100):
optimizer = torch.optim.SGD(model.parameters(), lr=0.001) # 降低学习率
criterion = nn.MSELoss()
for epoch in range(epochs):
for x_batch, y_batch in data_loader:
optimizer.zero_grad()
output = model(x_batch)
loss = criterion(output, y_batch)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # 梯度裁剪
optimizer.step()
k = model.linear.weight.detach().numpy()[0][0]
b = model.linear.bias.detach().numpy()[0]
return k, b
现在直观理解了为什么会用到那么多技巧
参数准确度的影响比较大的样本点
困扰我一年的问题:
x = torch.tensor([1.0, 2.0, 5.0 ,4.0], dtype=torch.float32).reshape(-1, 1)
y = torch.tensor([2.0, 4.0,10.0 ,8.0], dtype=torch.float32).reshape(-1, 1)
x = torch.tensor([-1.0, 2.0, 5.0,4.0], dtype=torch.float32).reshape(-1, 1)
y = torch.tensor([-2.0, 4.0,10.0,8.0], dtype=torch.float32).reshape(-1, 1)
为什么有一个在y轴左侧的数据会让拟合结果更接近 \(y=2x\)
杠杆效应
指在线性回归中,某些数据点(尤其是远离 x 均值的点)对拟合直线的斜率和截距有更大的影响。
这些点被称为高杠杆点,因为它们对回归结果的“拉动”作用更强。
杠杆效应的深层原理根植于线性回归的数学机制,特别是最小二乘法优化过程中数据点对拟合参数(斜率和截距)的影响
杠杆效应定义:
杠杆效应源于数据点对拟合参数的影响力差异,量化通过帽子矩阵(Hat Matrix)的定义
\(H=X(X^TX)^{−1}X^T\)
\(\hat{Y} = HY\)
帽子矩阵的的对角元素 \(h_{ii}\) 称为第 i 个数据点的杠杆值
\(h_{ii}=\frac{1}{n}+\frac{(x_{i}-\bar{x})^2}{\sum_{j=1}^{n}(x_{j}-\bar{x})^2}\)
\(x_{i}\)离均值越远,\(h_{ii}\)就越大,表明该点具有高杠杆效应。

浙公网安备 33010602011771号