You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyTorch实现SGD的系数与OLS不匹配,问题出在哪?

问题:SGD拟合结果与OLS系数差距大的原因及修正

问题背景

业务场景中需要用SGD得到接近普通最小二乘法(OLS)的回归系数,但当前分别用OLS、未缩放数据的SGD、缩放数据的SGD拟合后,系数差异极大,需要定位原因并修正。

核心原因分析

1. 模型设定完全不匹配

OLS使用的是无截距模型(公式中-1),特征是x1到x16共16个变量;但原PyTorch代码中:

  • 输入X取的是df.iloc[:,2:],跳过了原始数据中的x1列,只用到x2到x16
  • 模型nn.Linear默认添加了截距(bias),相当于把OLS中的x1替换成了模型截距,其他特征对应错位,自然系数完全不对

2. 缩放后系数未做逆转换

用StandardScaler缩放特征后,SGD学到的系数是针对标准化后特征的,不能直接和OLS(基于原始特征)的系数对比,必须将系数转换回原始特征空间才能对齐。

3. SGD训练参数不合理

  • 学习率0.1过大:原始特征量级差异极大(比如x1是-0.2左右,x7可达几千),大学习率会导致参数更新震荡,无法收敛到最优解
  • 迭代次数不足:5000次迭代对于这种病态条件数(OLS结果中Cond. No.为2.14e+07,说明特征高度共线性)的回归问题,可能不够收敛
  • 未针对病态数据做优化:高条件数的回归问题,SGD需要更小的学习率或正则化辅助收敛

修正方案与代码

步骤1:对齐模型设定

去掉PyTorch模型的截距,输入包含全部16个特征(x1到x16),和OLS保持一致。

步骤2:处理缩放后系数的逆转换

对标准化后的特征系数,用Scaler的均值和标准差转换回原始特征的系数。

步骤3:优化SGD训练参数

降低学习率,增加迭代次数,添加学习率衰减辅助收敛。

以下是修正后的完整代码:

import pandas as pd
from statsmodels.formula.api import ols
import torch.optim as optim
from sklearn.preprocessing import StandardScaler
import torch
import torch.nn as nn

# 1. OLS拟合(保持原逻辑)
df = pd.read_csv('https://gist.githubusercontent.com/alexhallam/820baf95a6f29f8d032dadd384dc4f6b/raw/b3e6a6829a8ce94349acfc775801d426fd05d08b/test_ols_sgd_data.csv')
fit = ols('z ~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16 - 1', data=df).fit()
print("OLS系数:")
print(fit.params)

# 2. 修正后的PyTorch线性回归模型(无截距)
class LinearRegressionNoBias(nn.Module):
    def __init__(self, input_dim):
        super().__init__()
        self.linear = nn.Linear(input_dim, 1, bias=False)  # 无截距,和OLS对齐
        self.linear.weight = nn.Parameter(self.linear.weight.to(torch.float64))

    def forward(self, x):
        return self.linear(x)

def fit_by_SGD(X, y, scaler=None, learning_rate=1e-5, num_epochs=50000, weight_decay=1e-4, debug=True):
    input_dim = X.shape[1]
    model = LinearRegressionNoBias(input_dim)
    criterion = nn.MSELoss()
    # 添加学习率衰减,优化收敛过程
    optimizer = optim.SGD(model.parameters(), lr=learning_rate, weight_decay=weight_decay)
    scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10000, gamma=0.5)
    
    prev_loss = float('inf')
    for epoch in range(num_epochs):
        outputs = model(X.to(torch.float64))
        loss = criterion(outputs, y.view(-1, 1))  # 确保y的形状与输出匹配
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        scheduler.step()
        
        # 收敛判断
        if abs(loss.item() - prev_loss) < 1e-10:
            print(f"已收敛,迭代次数:{epoch+1}")
            break
        prev_loss = loss.item()
    
    coeffs = model.linear.weight.data.view(-1).numpy()
    # 如果是缩放后的特征,转换回原始特征空间的系数
    if scaler is not None:
        coeffs = coeffs / scaler.scale_
    
    if debug:
        x_names = [f'x{i+1}' for i in range(input_dim)]
        print("\nSGD拟合系数:")
        print(pd.DataFrame({'特征': x_names, '系数': coeffs}))
    return coeffs

# 3. 基于原始数据的SGD拟合
X_raw = torch.tensor(df.iloc[:, 1:17].values)  # 取x1到x16,共16个特征
y = torch.tensor(df.iloc[:, 0].values, dtype=torch.float64)
print("\n=== 原始数据SGD拟合 ===")
sgd_raw_coeffs = fit_by_SGD(X_raw, y)

# 4. 基于缩放数据的SGD拟合并转换系数
scaler = StandardScaler()
X_scaled_np = scaler.fit_transform(df.iloc[:, 1:17].values)
X_scaled = torch.tensor(X_scaled_np)
print("\n=== 缩放数据SGD拟合(转换回原始系数) ===")
sgd_scaled_coeffs = fit_by_SGD(X_scaled, y, scaler=scaler)

# 5. 对比OLS和SGD系数
print("\n=== 系数对比 ===")
compare_df = pd.DataFrame({
    'OLS系数': fit.params.values,
    '原始数据SGD系数': sgd_raw_coeffs,
    '缩放数据SGD系数': sgd_scaled_coeffs
}, index=fit.params.index)
print(compare_df)

修正后的效果

  • 模型设定对齐后,SGD的系数会和OLS高度接近
  • 缩放数据的系数经过逆转换后,也能匹配OLS结果
  • 优化后的SGD参数(小学习率+学习率衰减+正则化)能有效处理高条件数的回归问题,收敛到最优解

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 21:30:56