基于ElasticNet网格搜索的汽车燃油效率预测

1.作者介绍

任鑫,男,西安工程大学电子信息学院,2025级研究生

研究方向:深度学习、目标检测

电子邮件:renx17811@163.com

胥乾信,男,西安工程大学电子信息学院,2025级研究生,张宏伟人工智能课题组

研究方向:机器视觉与人工智能

电子邮件:2692797728@qq.com

2.弹性网络回归(Elastic Net Regression)介绍

2.1 回归与正则化基础

线性回归的本质是找到特征与目标值的线性映射关系,公式为:

模型训练的目标是找到最优的w和b,让预测值与真实值的均方误差(MSE)最小。
2.2弹性网络回归的数学原理

普通线性回归易出现过拟合和多重共线性问题,因此引入L1、L2两种正则化,二者作用互补:既实现特征选择(L1正则化),又处理多重共线性(L2正则化),兼顾模型的稀疏性和稳定性。

1)弹性网络回归的损失函数是数据误差项+L1正则化项+L2正则化项的组合,标准形式为:

2)有两个核心的超参数:

3)模型的优化目标:

弹性网络回归的本质是带约束的凸优化问题,目标是找到最优的回归系数w,让损失函数最小:

图1 不同的ρ对弹性网络回归的影响

当ρ逐渐增大时,L1正则项占据主导地位,代价函数越接近Lasso回归;ρ逐渐减小时,L2正则项占据主导地位,代价函数越接近Ridge回归。

2.3 弹性网络回归的求解方法

由于L1正则化项的存在,损失函数在处不可导,因此无法直接用普通梯度下降法求解,常用两种优化方法。

2.4弹性网络回归的案例

1)预测水果店供应商综合满意度

有价格(x1)、配送时间(x2)、水果质量(x3)3个特征,预测供应商的综合满意度(y,1-10分)。

单一正则化的问题:用Lasso:可能随机将"配送时间"的系数压缩为0,丢失重要特征;用Ridge:保留所有3个特征,若存在冗余特征(如后续加入"运费",与价格高度相关),模型会冗余。

弹性网络的优势:既会将真正无关的特征(如后续加入的"供应商名称")系数压缩为0,又会对高度相关的特征(价格、运费)做系数平滑,让模型既稀疏又稳定。

2)使用模拟高维数据集(500样本+10特征)进行弹性网络回归,步骤包括:数据生成、预处理、建模、评估、可视化。

图3 模拟高维数据集可视化图

3.基于ElasticNet网格搜索的汽车燃油效率预测

3.1 Auto MPG数据集

MPG汽车油耗数据集源自1983年的美国统计协会博览会,包含398个样本,9个特征,用于回归任务。

使用Seaborn的MPG数据集:https://raw.githubusercontent.com/mwaskom/seaborn-data/master/mpg.csv

Github链接:seaborn-data/mpg.csv at master · mwaskom/seaborn-data

图4 数据集介绍
3.2 代码调试

1)导入必要库

python 复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 导入数据集生成、模型、评估指标、数据处理工具
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import ElasticNet
from sklearn.metrics import r2_score, mean_squared_error
import urllib.request
import os

2)加载Auto MPG数据集

python 复制代码
try:
    # 直接从GitHub加载数据
    github_url = 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/mpg.csv'
    data = pd.read_csv(github_url)
    print("成功从GitHub加载MPG数据集")
    print(f"原始数据形状: {data.shape}")
    print(f"数据列: {list(data.columns)}")
except:
    print("无法从GitHub加载数据,正在使用模拟数据")
    # 如果无法获取真实数据,则生成模拟数据作为备用
    X_sim, y_sim, coef_sim = make_regression(n_samples=400, n_features=7, noise=10, random_state=42, coef=True)
    # 创建模拟数据框,对应MPG数据集的特征
    sim_columns = ["cylinders", "displacement", "horsepower", "weight", "acceleration", "model_year", "origin","name"]
    data = pd.DataFrame(X_sim, columns=sim_columns)
    data['mpg'] = y_sim

# 数据预处理
print("\n数据预处理开始...")

# 查看缺失值情况
print(f"缺失值统计:\n{data.isnull().sum()}")

# 删除含有缺失值的行(主要针对horsepower列)
data = data.dropna()

# 查看数据类型
print(f"\n数据类型:\n{data.dtypes}")

# 查看分类列的唯一值
print(f"\norigin列的唯一值: {data['origin'].unique()}")
if 'country' in data.columns:
    print(f"country列的唯一值: {data['country'].unique()}")

# 处理分类变量 - 对origin列进行独热编码
origin_encoded = pd.get_dummies(data['origin'], prefix='origin')
print(f"独热编码后的origin列: {list(origin_encoded.columns)}")

# 合并独热编码后的origin列和原始数据
data_processed = pd.concat([data, origin_encoded], axis=1)

# 选择数值型特征用于建模(排除原始的分类列)
feature_cols = []
for col in ['cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'model_year', 
            'origin_1', 'origin_2', 'origin_3']:  # 包含独热编码后的origin列
    if col in data_processed.columns:
        feature_cols.append(col)

X = data_processed[feature_cols]
y = data_processed['mpg']

print(f"处理后数据形状: {X.shape}")
print(f"特征列: {list(X.columns)}")
print(f"前5行数据:\n{X.head()}")

# 确保所有特征列都是数值型
for col in X.columns:
    X[col] = pd.to_numeric(X[col], errors='coerce')

# 再次删除可能产生的NaN值
data_clean = pd.concat([X, y], axis=1).dropna()
X = data_clean[X.columns]
y = data_clean['mpg']

print(f"清理后数据形状: {X.shape}")

3)数据预处理

python 复制代码
# 1. 划分训练集(80%)和测试集(20%)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 2. 特征标准化(正则化回归模型必做,消除量纲影响)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # 训练集拟合+转换
X_test_scaled = scaler.transform(X_test)        # 测试集仅转换,避免数据泄露

print(f"训练集形状: {X_train_scaled.shape}")
print(f"测试集形状: {X_test_scaled.shape}")

4)使用网格搜索优化弹性网络回归模型

python 复制代码
# 定义超参数搜索空间
param_grid = {
    'alpha': [0.001, 0.01, 0.1, 1, 10, 100],    #α值,控制正则化强度,越大正则化越强
    'l1_ratio': [0.1, 0.3, 0.5, 0.7, 0.9]      #ρ值,控制L1和L2的权重
}
# 创建ElasticNet模型
elastic_net = ElasticNet(random_state=42)

# 使用网格搜索寻找最佳参数
grid_search = GridSearchCV(
    estimator=elastic_net,
    param_grid=param_grid,
    cv=5,  # 5折交叉验证
    scoring='r2',  # 使用R²作为评分标准
    n_jobs=-1,  # 使用所有CPU核心
    verbose=1   # 显示进度`在这里插入代码片`
)

print("开始网格搜索...")
grid_search.fit(X_train_scaled, y_train)  # 执行网格搜索

# 获取最佳模型和参数
best_elastic_net = grid_search.best_estimator_
best_params = grid_search.best_params_

print(f"最佳参数: {best_params}")
print(f"最佳交叉验证得分: {grid_search.best_score_:.4f}")

5)模型预测与评估

python 复制代码
# 对训练集和测试集进行预测
y_pred_train = best_elastic_net.predict(X_train_scaled)
y_pred_test = best_elastic_net.predict(X_test_scaled)

# 计算评估指标:R²(越接近1越好)、MSE(越小越好)
r2_train = r2_score(y_train, y_pred_train)
r2_test = r2_score(y_test, y_pred_test)
mse_train = mean_squared_error(y_train, y_pred_train)
mse_test = mean_squared_error(y_test, y_pred_test)

# 输出评估结果
print("========== 弹性网络回归模型性能 ==========")
print(f"最佳参数: {best_params}")
print(f"训练集R²:{r2_train:.4f},训练集MSE:{mse_train:.4f}")
print(f"测试集R²:{r2_test:.4f},测试集MSE:{mse_test:.4f}")

# 比较网格搜索前后的性能(使用默认参数)
default_elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5, random_state=42)
default_elastic_net.fit(X_train_scaled, y_train)
y_pred_default_test = default_elastic_net.predict(X_test_scaled)
r2_default_test = r2_score(y_test, y_pred_default_test)
mse_default_test = mean_squared_error(y_test, y_pred_default_test)

print(f"\n默认参数模型测试集R²:{r2_default_test:.4f},MSE:{mse_default_test:.4f}")
print(f"网格搜索提升: R² 提升 {r2_test - r2_default_test:.4f}, MSE 改善 {mse_default_test - mse_test:.4f}")

6)多维度可视化分析

python 复制代码
plt.figure(figsize=(15, 10))  # 设置画布大小

# 子图1:特征重要性(回归系数值,系数为0表示被筛选掉)
plt.subplot(2, 2, 1)
coef = best_elastic_net.coef_  # 获取回归系数
colors = sns.color_palette("coolwarm", len(coef))
plt.barh(range(len(X.columns)), coef, color=colors)
plt.yticks(range(len(X.columns)), X.columns)
plt.title('Feature Importance (ElasticNet Coefficients)', fontsize=16)
plt.xlabel('Coefficient Value', fontsize=12)
plt.ylabel('Feature', fontsize=12)

# 添加网格线以便更好地读取数值
plt.grid(axis='x', linestyle='--', alpha=0.6)

# 子图2:测试集真实值 vs 预测值(点越靠近红线,拟合效果越好)
plt.subplot(2, 2, 2)
plt.scatter(y_test, y_pred_test, c=y_pred_test, cmap='cool', alpha=0.7, edgecolor='k')
plt.plot([min(y_test), max(y_test)], [min(y_test), max(y_test)], color='red', linewidth=2)
plt.title('Predicted vs True Values', fontsize=16)
plt.xlabel('True Values', fontsize=12)
plt.ylabel('Predicted Values', fontsize=12)
plt.colorbar(label="Predicted Value Intensity")

# 子图3:残差分布(残差=真实值-预测值,越靠近0线,模型越稳定)
plt.subplot(2, 2, 3)
residuals = y_test - y_pred_test
plt.scatter(y_pred_test, residuals, c=residuals, cmap='Spectral', alpha=0.7, edgecolor='k')
plt.axhline(y=0, color='red', linestyle='--', linewidth=2)
plt.title('Residuals vs Predicted Values', fontsize=16)
plt.xlabel('Predicted Values', fontsize=12)
plt.ylabel('Residuals', fontsize=12)
plt.colorbar(label="Residual Intensity")

# 子图4:模型性能指标对比(训练集vs测试集)
plt.subplot(2, 2, 4)
metrics = ['Train R²', 'Test R²', 'Train MSE', 'Test MSE']
values = [r2_train, r2_test, mse_train, mse_test]
colors = sns.color_palette("viridis", len(metrics))
bars = plt.bar(metrics, values, color=colors)
plt.title('Model Performance Metrics', fontsize=16)
plt.ylabel('Score / Error', fontsize=12)

# 在柱状图上添加数值标签
for bar, value in zip(bars, values):
    height = bar.get_height()
    plt.text(bar.get_x() + bar.get_width()/2., height,
             f'{value:.3f}',
             ha='center', va='bottom')

# 调整子图间距,显示图像
plt.tight_layout()
plt.show()

# 输出被筛选掉的特征(系数为0的特征)
zero_coef_features = X.columns[coef == 0].tolist()
if zero_coef_features:
    print(f"\n被弹性网络筛选掉的无关特征:{zero_coef_features}")
else:
    print("\n无特征被筛选,所有特征均为重要特征")

# 分析origin特征的重要性
origin_features = [col for col in X.columns if col.startswith('origin')]
if origin_features:
    origin_coefs = [coef[list(X.columns).index(col)] for col in origin_features]
    print(f"\nOrigin特征的系数: {dict(zip(origin_features, origin_coefs))}")
    
    # 可视化origin特征的影响
    plt.figure(figsize=(10, 6))
    plt.bar(origin_features, origin_coefs)
    plt.title('Origin Features Coefficients')
    plt.xlabel('Origin Categories')
    plt.ylabel('Coefficient Value')
    plt.show()

# 可视化网格搜索结果
plt.figure(figsize=(12, 5))

# 显示网格搜索的参数组合得分
plt.subplot(1, 2, 1)
results_df = pd.DataFrame(grid_search.cv_results_)
pivot_table = results_df.pivot(index='param_alpha', columns='param_l1_ratio', values='mean_test_score')
sns.heatmap(pivot_table, annot=True, fmt='.3f', cmap='viridis')
plt.title('Grid Search: Alpha vs L1 Ratio Heatmap')

# 显示最佳参数附近的得分情况
plt.subplot(1, 2, 2)
scores_by_alpha = results_df.groupby('param_alpha')['mean_test_score'].max()
plt.plot(scores_by_alpha.index.astype(str), scores_by_alpha.values, marker='o')
plt.title('Best Score by Alpha Value')
plt.xlabel('Alpha')
plt.ylabel('Best Cross-Validation Score')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

3.3完整代码

python 复制代码
#1)使用独热编码法
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 导入数据集生成、模型、评估指标、数据处理工具
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import ElasticNet
from sklearn.metrics import r2_score, mean_squared_error
import urllib.request
import os

# ===================== 步骤1:加载Auto MPG数据集 =====================
# 使用Seaborn的MPG数据集   https://blog.csdn.net/DeepModel/article/details/158181189
try:
    # 直接从GitHub加载数据
    github_url = 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/mpg.csv'
    data = pd.read_csv(github_url)
    print("成功从GitHub加载MPG数据集")
    print(f"原始数据形状: {data.shape}")
    print(f"数据列: {list(data.columns)}")
except:
    print("无法从GitHub加载数据,正在使用模拟数据")
    # 如果无法获取真实数据,则生成模拟数据作为备用
    X_sim, y_sim, coef_sim = make_regression(n_samples=400, n_features=7, noise=10, random_state=42, coef=True)
    # 创建模拟数据框,对应MPG数据集的特征
    sim_columns = ["cylinders", "displacement", "horsepower", "weight", "acceleration", "model_year", "origin","name"]
    data = pd.DataFrame(X_sim, columns=sim_columns)
    data['mpg'] = y_sim

# 数据预处理
print("\n数据预处理开始...")

# 查看缺失值情况
print(f"缺失值统计:\n{data.isnull().sum()}")

# 删除含有缺失值的行(主要针对horsepower列)
data = data.dropna()

# 查看数据类型
print(f"\n数据类型:\n{data.dtypes}")

# 查看分类列的唯一值
print(f"\norigin列的唯一值: {data['origin'].unique()}")
if 'country' in data.columns:
    print(f"country列的唯一值: {data['country'].unique()}")

# 处理分类变量 - 对origin列进行独热编码
origin_encoded = pd.get_dummies(data['origin'], prefix='origin')
print(f"独热编码后的origin列: {list(origin_encoded.columns)}")

# 合并独热编码后的origin列和原始数据
data_processed = pd.concat([data, origin_encoded], axis=1)

# 选择数值型特征用于建模(排除原始的分类列)
feature_cols = []
for col in ['cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'model_year', 
            'origin_1', 'origin_2', 'origin_3']:  # 包含独热编码后的origin列
    if col in data_processed.columns:
        feature_cols.append(col)

X = data_processed[feature_cols]
y = data_processed['mpg']

print(f"处理后数据形状: {X.shape}")
print(f"特征列: {list(X.columns)}")
print(f"前5行数据:\n{X.head()}")

# 确保所有特征列都是数值型
for col in X.columns:
    X[col] = pd.to_numeric(X[col], errors='coerce')

# 再次删除可能产生的NaN值
data_clean = pd.concat([X, y], axis=1).dropna()
X = data_clean[X.columns]
y = data_clean['mpg']

print(f"清理后数据形状: {X.shape}")

# ===================== 步骤2:数据预处理 =====================
# 1. 划分训练集(80%)和测试集(20%)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 2. 特征标准化(正则化回归模型必做,消除量纲影响)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # 训练集拟合+转换
X_test_scaled = scaler.transform(X_test)        # 测试集仅转换,避免数据泄露

print(f"训练集形状: {X_train_scaled.shape}")
print(f"测试集形状: {X_test_scaled.shape}")

# ===================== 步骤3:使用网格搜索优化弹性网络回归模型 =====================
# 定义超参数搜索空间
param_grid = {
    'alpha': [0.001, 0.01, 0.1, 1, 10, 100],    #α值,控制正则化强度,越大正则化越强
    'l1_ratio': [0.1, 0.3, 0.5, 0.7, 0.9]      #ρ值,控制L1和L2的权重
}
# 创建ElasticNet模型
elastic_net = ElasticNet(random_state=42)

# 使用网格搜索寻找最佳参数
grid_search = GridSearchCV(
    estimator=elastic_net,
    param_grid=param_grid,
    cv=5,  # 5折交叉验证
    scoring='r2',  # 使用R²作为评分标准
    n_jobs=-1,  # 使用所有CPU核心
    verbose=1   # 显示进度
)

print("开始网格搜索...")
grid_search.fit(X_train_scaled, y_train)  # 执行网格搜索

# 获取最佳模型和参数
best_elastic_net = grid_search.best_estimator_
best_params = grid_search.best_params_

print(f"最佳参数: {best_params}")
print(f"最佳交叉验证得分: {grid_search.best_score_:.4f}")

# ===================== 步骤4:模型预测与评估 =====================
# 对训练集和测试集进行预测
y_pred_train = best_elastic_net.predict(X_train_scaled)
y_pred_test = best_elastic_net.predict(X_test_scaled)

# 计算评估指标:R²(越接近1越好)、MSE(越小越好)
r2_train = r2_score(y_train, y_pred_train)
r2_test = r2_score(y_test, y_pred_test)
mse_train = mean_squared_error(y_train, y_pred_train)
mse_test = mean_squared_error(y_test, y_pred_test)

# 输出评估结果
print("========== 弹性网络回归模型性能 ==========")
print(f"最佳参数: {best_params}")
print(f"训练集R²:{r2_train:.4f},训练集MSE:{mse_train:.4f}")
print(f"测试集R²:{r2_test:.4f},测试集MSE:{mse_test:.4f}")

# 比较网格搜索前后的性能(使用默认参数)
default_elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5, random_state=42)
default_elastic_net.fit(X_train_scaled, y_train)
y_pred_default_test = default_elastic_net.predict(X_test_scaled)
r2_default_test = r2_score(y_test, y_pred_default_test)
mse_default_test = mean_squared_error(y_test, y_pred_default_test)

print(f"\n默认参数模型测试集R²:{r2_default_test:.4f},MSE:{mse_default_test:.4f}")
print(f"网格搜索提升: R² 提升 {r2_test - r2_default_test:.4f}, MSE 改善 {mse_default_test - mse_test:.4f}")

# ===================== 步骤5:多维度可视化分析 =====================
plt.figure(figsize=(15, 10))  # 设置画布大小

# 子图1:特征重要性(回归系数值,系数为0表示被筛选掉)
plt.subplot(2, 2, 1)
coef = best_elastic_net.coef_  # 获取回归系数
colors = sns.color_palette("coolwarm", len(coef))
plt.barh(range(len(X.columns)), coef, color=colors)
plt.yticks(range(len(X.columns)), X.columns)
plt.title('Feature Importance (ElasticNet Coefficients)', fontsize=16)
plt.xlabel('Coefficient Value', fontsize=12)
plt.ylabel('Feature', fontsize=12)

# 添加网格线以便更好地读取数值
plt.grid(axis='x', linestyle='--', alpha=0.6)

# 子图2:测试集真实值 vs 预测值(点越靠近红线,拟合效果越好)
plt.subplot(2, 2, 2)
plt.scatter(y_test, y_pred_test, c=y_pred_test, cmap='cool', alpha=0.7, edgecolor='k')
plt.plot([min(y_test), max(y_test)], [min(y_test), max(y_test)], color='red', linewidth=2)
plt.title('Predicted vs True Values', fontsize=16)
plt.xlabel('True Values', fontsize=12)
plt.ylabel('Predicted Values', fontsize=12)
plt.colorbar(label="Predicted Value Intensity")

# 子图3:残差分布(残差=真实值-预测值,越靠近0线,模型越稳定)
plt.subplot(2, 2, 3)
residuals = y_test - y_pred_test
plt.scatter(y_pred_test, residuals, c=residuals, cmap='Spectral', alpha=0.7, edgecolor='k')
plt.axhline(y=0, color='red', linestyle='--', linewidth=2)
plt.title('Residuals vs Predicted Values', fontsize=16)
plt.xlabel('Predicted Values', fontsize=12)
plt.ylabel('Residuals', fontsize=12)
plt.colorbar(label="Residual Intensity")

# 子图4:模型性能指标对比(训练集vs测试集)
plt.subplot(2, 2, 4)
metrics = ['Train R²', 'Test R²', 'Train MSE', 'Test MSE']
values = [r2_train, r2_test, mse_train, mse_test]
colors = sns.color_palette("viridis", len(metrics))
bars = plt.bar(metrics, values, color=colors)
plt.title('Model Performance Metrics', fontsize=16)
plt.ylabel('Score / Error', fontsize=12)
# 在柱状图上添加数值标签
for bar, value in zip(bars, values):
    height = bar.get_height()
    plt.text(bar.get_x() + bar.get_width()/2., height,
             f'{value:.3f}',
             ha='center', va='bottom')
# 调整子图间距,显示图像
plt.tight_layout()
plt.show()
# 输出被筛选掉的特征(系数为0的特征)
zero_coef_features = X.columns[coef == 0].tolist()
if zero_coef_features:
    print(f"\n被弹性网络筛选掉的无关特征:{zero_coef_features}")
else:
    print("\n无特征被筛选,所有特征均为重要特征")
# 分析origin特征的重要性
origin_features = [col for col in X.columns if col.startswith('origin')]
if origin_features:
    origin_coefs = [coef[list(X.columns).index(col)] for col in origin_features]
    print(f"\nOrigin特征的系数: {dict(zip(origin_features, origin_coefs))}")
    # 可视化origin特征的影响
    plt.figure(figsize=(10, 6))
    plt.bar(origin_features, origin_coefs)
    plt.title('Origin Features Coefficients')
    plt.xlabel('Origin Categories')
    plt.ylabel('Coefficient Value')
    plt.show()
# 可视化网格搜索结果
plt.figure(figsize=(12, 5))

# 显示网格搜索的参数组合得分
plt.subplot(1, 2, 1)
results_df = pd.DataFrame(grid_search.cv_results_)
pivot_table = results_df.pivot(index='param_alpha', columns='param_l1_ratio', values='mean_test_score')
sns.heatmap(pivot_table, annot=True, fmt='.3f', cmap='viridis')
plt.title('Grid Search: Alpha vs L1 Ratio Heatmap')

# 显示最佳参数附近的得分情况
plt.subplot(1, 2, 2)
scores_by_alpha = results_df.groupby('param_alpha')['mean_test_score'].max()
plt.plot(scores_by_alpha.index.astype(str), scores_by_alpha.values, marker='o')
plt.title('Best Score by Alpha Value')
plt.xlabel('Alpha')
plt.ylabel('Best Cross-Validation Score')
plt.xticks(rotation=45)

plt.tight_layout()
plt.show()
python 复制代码
#2)使用数值映射法
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 导入数据集生成、模型、评估指标、数据处理工具
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import ElasticNet
from sklearn.metrics import r2_score, mean_squared_error
import urllib.request
import os

# ===================== 步骤1:加载Auto MPG数据集 =====================
# 使用Seaborn的MPG数据集
try:
    # 直接从GitHub加载数据
    github_url = 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/mpg.csv'
    data = pd.read_csv(github_url)
    print("成功从GitHub加载MPG数据集")
    print(f"原始数据形状: {data.shape}")
    print(f"数据列: {list(data.columns)}")
except:
    print("无法从GitHub加载数据,正在使用模拟数据")
    # 如果无法获取真实数据,则生成模拟数据作为备用
    X_sim, y_sim, coef_sim = make_regression(n_samples=400, n_features=7, noise=10, random_state=42, coef=True)
    # 创建模拟数据框,对应MPG数据集的特征
    sim_columns = ["cylinders", "displacement", "horsepower", "weight", "acceleration", "model_year", "origin","name"]
    data = pd.DataFrame(X_sim, columns=sim_columns)
    data['mpg'] = y_sim

# 数据预处理
print("\n数据预处理开始...")

# 查看缺失值情况
print(f"缺失值统计:\n{data.isnull().sum()}")

# 删除含有缺失值的行(主要针对horsepower列)
data = data.dropna()

# 查看数据类型
print(f"\n数据类型:\n{data.dtypes}")

# 查看分类列的唯一值
print(f"\norigin列的唯一值: {data['origin'].unique()}")
if 'country' in data.columns:
    print(f"country列的唯一值: {data['country'].unique()}")

# 处理分类变量 - 将origin映射为数值
data['origin_numeric'] = data['origin'].map({'usa': 1, 'europe': 2, 'japan': 3})
# 如果origin列已经是数值,则直接使用
if data['origin'].dtype == 'object':
    data['origin'] = data['origin'].map({'usa': 1, 'europe': 2, 'japan': 3})

# 如果存在country列,也需要处理
if 'country' in data.columns:
    data['country'] = data['country'].map({'usa': 1, 'europe': 2, 'japan': 3})

# 选择数值型特征用于建模
# 根据实际数据列来选择特征
feature_cols = []
for col in ['cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'model_year', 'origin']:
    if col in data.columns:
        feature_cols.append(col)

X = data[feature_cols]
y = data['mpg']

print(f"处理后数据形状: {X.shape}")
print(f"特征列: {list(X.columns)}")
print(f"前5行数据:\n{X.head()}")

# 确保所有特征列都是数值型
for col in X.columns:
    X[col] = pd.to_numeric(X[col], errors='coerce')

# 再次删除可能产生的NaN值
data_clean = pd.concat([X, y], axis=1).dropna()
X = data_clean[X.columns]
y = data_clean['mpg']

print(f"清理后数据形状: {X.shape}")

# ===================== 步骤2:数据预处理 =====================
# 1. 划分训练集(80%)和测试集(20%)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 2. 特征标准化(正则化回归模型必做,消除量纲影响)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # 训练集拟合+转换
X_test_scaled = scaler.transform(X_test)        # 测试集仅转换,避免数据泄露

print(f"训练集形状: {X_train_scaled.shape}")
print(f"测试集形状: {X_test_scaled.shape}")

# ===================== 步骤3:使用网格搜索优化弹性网络回归模型 =====================
# 定义超参数搜索空间
param_grid = {
    'alpha': [0.001, 0.01, 0.1, 1, 10, 100],    #α值,控制正则化强度,越大正则化越强
    'l1_ratio': [0.1, 0.3, 0.5, 0.7, 0.9]      #ρ值,控制L1和L2的权重
}
# 创建ElasticNet模型
elastic_net = ElasticNet(random_state=42)

# 使用网格搜索寻找最佳参数
grid_search = GridSearchCV(
    estimator=elastic_net,
    param_grid=param_grid,
    cv=5,  # 5折交叉验证
    scoring='r2',  # 使用R²作为评分标准
    n_jobs=-1,  # 使用所有CPU核心
    verbose=1   # 显示进度
)

print("开始网格搜索...")
grid_search.fit(X_train_scaled, y_train)  # 执行网格搜索

# 获取最佳模型和参数
best_elastic_net = grid_search.best_estimator_
best_params = grid_search.best_params_

print(f"最佳参数: {best_params}")
print(f"最佳交叉验证得分: {grid_search.best_score_:.4f}")

# ===================== 步骤4:模型预测与评估 =====================
# 对训练集和测试集进行预测
y_pred_train = best_elastic_net.predict(X_train_scaled)
y_pred_test = best_elastic_net.predict(X_test_scaled)

# 计算评估指标:R²(越接近1越好)、MSE(越小越好)
r2_train = r2_score(y_train, y_pred_train)
r2_test = r2_score(y_test, y_pred_test)
mse_train = mean_squared_error(y_train, y_pred_train)
mse_test = mean_squared_error(y_test, y_pred_test)

# 输出评估结果
print("========== 弹性网络回归模型性能 ==========")
print(f"最佳参数: {best_params}")
print(f"训练集R²:{r2_train:.4f},训练集MSE:{mse_train:.4f}")
print(f"测试集R²:{r2_test:.4f},测试集MSE:{mse_test:.4f}")

# 比较网格搜索前后的性能(使用默认参数)
default_elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5, random_state=42)
default_elastic_net.fit(X_train_scaled, y_train)
y_pred_default_test = default_elastic_net.predict(X_test_scaled)
r2_default_test = r2_score(y_test, y_pred_default_test)
mse_default_test = mean_squared_error(y_test, y_pred_default_test)

print(f"\n默认参数模型测试集R²:{r2_default_test:.4f},MSE:{mse_default_test:.4f}")
print(f"网格搜索提升: R² 提升 {r2_test - r2_default_test:.4f}, MSE 改善 {mse_default_test - mse_test:.4f}")

# ===================== 步骤5:多维度可视化分析 =====================
plt.figure(figsize=(15, 10))  # 设置画布大小

# 子图1:特征重要性(回归系数值,系数为0表示被筛选掉)
plt.subplot(2, 2, 1)
coef = best_elastic_net.coef_  # 获取回归系数
colors = sns.color_palette("coolwarm", len(coef))
plt.barh(range(len(X.columns)), coef, color=colors)
plt.yticks(range(len(X.columns)), X.columns)
plt.title('Feature Importance (ElasticNet Coefficients)', fontsize=16)
plt.xlabel('Coefficient Value', fontsize=12)
plt.ylabel('Feature', fontsize=12)

# 添加网格线以便更好地读取数值
plt.grid(axis='x', linestyle='--', alpha=0.6)

# 子图2:测试集真实值 vs 预测值(点越靠近红线,拟合效果越好)
plt.subplot(2, 2, 2)
plt.scatter(y_test, y_pred_test, c=y_pred_test, cmap='cool', alpha=0.7, edgecolor='k')
plt.plot([min(y_test), max(y_test)], [min(y_test), max(y_test)], color='red', linewidth=2)
plt.title('Predicted vs True Values', fontsize=16)
plt.xlabel('True Values', fontsize=12)
plt.ylabel('Predicted Values', fontsize=12)
plt.colorbar(label="Predicted Value Intensity")

# 子图3:残差分布(残差=真实值-预测值,越靠近0线,模型越稳定)
plt.subplot(2, 2, 3)
residuals = y_test - y_pred_test
plt.scatter(y_pred_test, residuals, c=residuals, cmap='Spectral', alpha=0.7, edgecolor='k')
plt.axhline(y=0, color='red', linestyle='--', linewidth=2)
plt.title('Residuals vs Predicted Values', fontsize=16)
plt.xlabel('Predicted Values', fontsize=12)
plt.ylabel('Residuals', fontsize=12)
plt.colorbar(label="Residual Intensity")

# 子图4:模型性能指标对比(训练集vs测试集)
plt.subplot(2, 2, 4)
metrics = ['Train R²', 'Test R²', 'Train MSE', 'Test MSE']
values = [r2_train, r2_test, mse_train, mse_test]
colors = sns.color_palette("viridis", len(metrics))
bars = plt.bar(metrics, values, color=colors)
plt.title('Model Performance Metrics', fontsize=16)
plt.ylabel('Score / Error', fontsize=12)

# 在柱状图上添加数值标签
for bar, value in zip(bars, values):
    height = bar.get_height()
    plt.text(bar.get_x() + bar.get_width()/2., height,
             f'{value:.3f}',
             ha='center', va='bottom')

# 调整子图间距,显示图像
plt.tight_layout()
plt.show()

# 输出被筛选掉的特征(系数为0的特征)
zero_coef_features = X.columns[coef == 0].tolist()
if zero_coef_features:
    print(f"\n被弹性网络筛选掉的无关特征:{zero_coef_features}")
else:
    print("\n无特征被筛选,所有特征均为重要特征")

# 可视化网格搜索结果
plt.figure(figsize=(12, 5))

# 显示网格搜索的参数组合得分
plt.subplot(1, 2, 1)
results_df = pd.DataFrame(grid_search.cv_results_)
pivot_table = results_df.pivot(index='param_alpha', columns='param_l1_ratio', values='mean_test_score')
sns.heatmap(pivot_table, annot=True, fmt='.3f', cmap='viridis')
plt.title('Grid Search: Alpha vs L1 Ratio Heatmap')

# 显示最佳参数附近的得分情况
plt.subplot(1, 2, 2)
scores_by_alpha = results_df.groupby('param_alpha')['mean_test_score'].max()
plt.plot(scores_by_alpha.index.astype(str), scores_by_alpha.values, marker='o')
plt.title('Best Score by Alpha Value')
plt.xlabel('Alpha')
plt.ylabel('Best Cross-Validation Score')
plt.xticks(rotation=45)

plt.tight_layout()
plt.show()

3.4结果展示

1)成功加载数据集

图5 MPG数据集结果展示

成功加载MPG数据集,列出9种数据列,且horsepower缺失6个。

2)代码运行结果

图6 独热编码法

模型仅使用数值型特征(气缸数、排量、马力、重量、加速度、年份),最佳参数:alpha=0.1、l1_ratio=0.9,测试集R²=0.7950,MSE=10.4647。弹性网络筛选掉的无关特征:acceleration(加速度),模型表现良好。

图7 数值映射法

在原有特征基础上增加了制造商地区(origin),并直接赋予数值 1、2、3。测试集R²=0.7858,MSE=10.9339,性能反而下降。筛选掉的无关特征增多:cylinders,displacement,acceleration。原因:origin是无序类别变量(如美国、欧洲、日本),直接映射为 1、2、3会引入虚假的顺序关系,误导模型,导致预测能力下降。

3)多维度可视化分析

图8 特征重要性、测试集真实值vs预测值散点图、残差分布、模型性能指标对比

1 特征重要性:weight(重量)和 model_year(年份)的系数绝对值最大,表明它们是预测油耗最关键的变量;acceleration(加速度)的系数压缩为0,被筛选掉。可以理解为使用更轻、马力更小、排量小、气缸量小、年份更新的车辆通常具有更高的燃油效率。

2 预测值vs真实值:散点基本围绕对角线分布,但存在一定离散,表明模型预测能力中等。

3 残差vs预测值:残差点在零线附近随机分布,无明显漏斗形或趋势,说明模型满足线性回归的方差齐性假设,稳定性较好。

4 性能指标:训练集R²约0.810,测试集R²约0.795,训练MSE约11.99,测试MSE约10.46,说明模型未出现严重过拟合,泛化能力良好。

图9 网格搜索热力图和最佳得分随Alpha变化折线图

网格搜索热力图:

1 当alpha很小时(0.001-0.1),得分较高(约0.80-0.82),且对不同l1_ratio不敏感。

2 当alpha=1时,得分开始下降。

3 当alpha=10或100时,得分急剧下降到接近0或负数,说明正则化过强,模型欠拟合。

4 最佳参数通常出现在alpha=0.1或0.01,l1_ratio=0.9。

最佳分数与Alpha:

1 曲线在alpha较小时保持平稳高位。

2 在alpha=1处开始下降,alpha=10之后急剧下滑。

该图表明模型对正则化强度敏感,不宜使用过大的alpha。

4.问题分析

1)数值映射vs独热编码

1数值映射

优点:不增加特征维度,保持原特征数量。

缺点:引入虚假顺序关系(如usa=1,europe=2,japan=3),暗示类别间存在大小等级,与实际无序类别矛盾。

2 独热编码

优点:每个类别独立为一个二值特征,消除虚假顺序,真实表达平等类别关系。

配合L1正则化可自动对冗余虚拟变量进行特征选择。

本数据集实践:对origin(美国、欧洲、日本)采用独热编码,正确表达了三个地区的平等属性。

2)固定参数训练vs超参数优化策略

1 固定参数训练

直接使用默认或经验参数,缺乏合理性验证。

无法保证找到最优超参数,易导致次优性能。

2 网格搜索(GridSearchCV)

自动遍历指定超参数组合,结合交叉验证评估性能。

先通过交叉验证确定最佳参数,再训练最终模型,确保参数选择科学可靠。

后续使用更高效的搜索策略,在更大参数空间内快速找到全局最优解。

5.参考链接

1)机器学习算法系列(六)- 弹性网络回归算法

2)弹性网络回归(Elastic Net Regression)详解

相关推荐
在坚持一下我可没意见1 小时前
Python 修仙修炼录 05:循环神通,省去无用苦修
开发语言·python·面试·入门·循环·复习
PNP Robotics1 小时前
【荣誉时刻】PnP机器人荣获「具身智能跨界融合创新奖」,以硬核实力引领产业融合新范式
人工智能·深度学习·机器学习·机器人
大飞记Python1 小时前
从“驱动地狱”到一行代码:WebDriverManager使用手记(附模板)
python·测试
Cloud_Shy6182 小时前
Python 数据分析基础入门:《Excel Python:飞速搞定数据分析与处理》学习笔记系列(第九章 Excel 自动化 上篇)
python·数据分析·excel·numpy·pandas
子午2 小时前
基于YOLO的玫瑰叶片检测系统~Python+深度学习+人工智能+目标检测+YOLOV8算法
人工智能·python·yolo
阳明山水2 小时前
基于静态属性的聚类预测新商品销量
人工智能·机器学习·微信·微信公众平台·微信开放平台
Hesionberger2 小时前
LeetCode 101:对称二叉树(多语言解法)
开发语言·python
小陈的进阶之路2 小时前
Python系列课(11)——PySpark
开发语言·python·ajax
WL_Aurora2 小时前
备战蓝桥杯国赛【Day 12】
python·蓝桥杯