实验一 支持向量机¶
代码¶
python¶
import pandas as pd
import numpy as np
from sklearn.model_selection import KFold
from sklearn.linear_model import LinearRegression
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import mean_squared_error, r2_score # 用于比较模型
import matplotlib.pyplot as plt
import tkinter as tk
from tkinter import filedialog
import warnings
# 忽略一些sklearn的FutureWarning,如果出现的话
warnings.filterwarnings('ignore', category=FutureWarning)
warnings.filterwarnings('ignore', category=UserWarning) # Matplotlib 可能的 UserWarning
# --- 0. Matplotlib 中文显示配置 ---
try:
#plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体:黑体
#plt.rcParams['font.sans-serif'] = ['NotoSansCJKsc-Regular'] # Specify default font: NotoSansCJKsc-Regular
plt.rcParams['font.sans-serif'] = ['STHeiti'] # Specify default font: PingFang SC
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
except Exception as e:
print(f"设置中文字体失败: {e}. 图表中的中文可能无法正确显示。")
print("请确保已安装 SimHei 字体,或修改为系统中存在的其他中文字体(如 Microsoft YaHei, WenQuanYi Micro Hei 等)。")
# --- 1. 数据加载和初步处理函数 ---
def load_and_preprocess_data():
"""
使用Tkinter让用户选择CSV文件,加载数据并进行预处理。
预处理包括:
- 处理'horsepower'列的'?'为NaN,转换为数值型,并用均值填充NaN。
- 删除'MPG', 'weight', 'horsepower'中任何包含NaN的行。
返回:
X (pd.DataFrame): 特征 ('weight', 'horsepower')
y (pd.Series): 目标变量 ('MPG')
df_cleaned (pd.DataFrame): 清理后的完整DataFrame,用于选择x0
"""
root = tk.Tk()
root.withdraw() # 隐藏Tkinter主窗口
file_path = filedialog.askopenfilename(
title="请选择 汽车MPG.CSV 文件",
filetypes=[("CSV files", "*.csv")]
)
if not file_path:
print("未选择文件,程序退出。")
return None, None, None
try:
df = pd.read_csv(file_path)
print(f"成功从 '{file_path}' 加载数据。")
except Exception as e:
print(f"读取文件 '{file_path}' 失败: {e}")
return None, None, None
# 数据预处理
# 1. 处理 'horsepower'
if 'horsepower' in df.columns:
# 将非数值(如 '?')替换为 NaN
df['horsepower'] = pd.to_numeric(df['horsepower'], errors='coerce')
if df['horsepower'].isnull().sum() > 0:
print(f"列 'horsepower' 中发现 {df['horsepower'].isnull().sum()} 个非数值/缺失值,将用该列均值填充。")
horsepower_mean = df['horsepower'].mean()
df['horsepower'].fillna(horsepower_mean, inplace=True)
print(f"使用 'horsepower' 均值 {horsepower_mean:.2f} 填充缺失值。")
else:
print("错误:数据集中未找到 'horsepower' 列。程序退出。")
return None, None, None
# 2. 确保核心列存在且无缺失值
required_cols = ['MPG', 'weight', 'horsepower']
for col in required_cols:
if col not in df.columns:
print(f"错误:数据集中缺少必需列 '{col}'。程序退出。")
return None, None, None
# 删除这些核心列中任何包含NaN的行
initial_rows = len(df)
df.dropna(subset=required_cols, inplace=True) # inplace=True 直接修改df
rows_dropped = initial_rows - len(df)
if rows_dropped > 0:
print(f"因 'MPG', 'weight', 'horsepower' 列存在缺失值,已删除 {rows_dropped} 行。")
if df.empty:
print("错误:预处理后数据为空。请检查CSV文件内容。程序退出。")
return None, None, None
X = df[['weight', 'horsepower']].copy() # 使用 .copy() 避免 SettingWithCopyWarning
y = df['MPG'].copy()
print(f"数据加载和预处理完成。最终数据集包含 {len(df)} 条记录。")
return X, y, df # 返回清理后的df,其索引可能已改变
# --- 2. 交叉验证、模型训练与绘图函数 ---
def train_eval_and_plot_cv(X_full, y_full, model_template, model_name, n_splits=2):
"""
执行n折交叉验证,训练模型,并绘制散点图。
参数:
X_full (pd.DataFrame): 全部特征数据
y_full (pd.Series): 全部目标数据
model_template: sklearn兼容的模型实例 (如LinearRegression(), Pipeline(SVR()))
model_name (str): 模型名称,用于图表标题和打印输出
n_splits (int): 交叉验证的折数
返回:
all_actuals (np.array): 所有折叠中测试集的实际MPG值
all_predictions (np.array): 所有折叠中测试集的预测MPG值
"""
kf = KFold(n_splits=n_splits, shuffle=True, random_state=42) # random_state保证可复现性
fig_weight, axs_weight = plt.subplots(n_splits, 1, figsize=(12, 5 * n_splits), squeeze=False)
fig_hp, axs_hp = plt.subplots(n_splits, 1, figsize=(12, 5 * n_splits), squeeze=False)
fig_weight.suptitle(f'{model_name} - MPG vs 自重 (Weight) - {n_splits}折交叉验证', fontsize=16)
fig_hp.suptitle(f'{model_name} - MPG vs 马力 (Horsepower) - {n_splits}折交叉验证', fontsize=16)
fold_num = 0
all_actuals_list = []
all_predictions_list = []
for train_index, test_index in kf.split(X_full):
X_train_fold, X_test_fold = X_full.iloc[train_index], X_full.iloc[test_index]
y_train_fold, y_test_fold = y_full.iloc[train_index], y_full.iloc[test_index]
# 每次都用模板创建一个新的模型实例并训练
# 对于Pipeline和简单模型,直接fit即可,它们会被重置/新训练
current_model = model_template
current_model.fit(X_train_fold, y_train_fold)
y_pred_fold = current_model.predict(X_test_fold)
all_actuals_list.extend(y_test_fold)
all_predictions_list.extend(y_pred_fold)
# --- 绘制 MPG vs Weight ---
ax_w = axs_weight[fold_num, 0] # axs是2D数组,即使只有一列
ax_w.scatter(X_train_fold['weight'], y_train_fold, color='lightblue', label='训练集点', alpha=0.6, s=30)
ax_w.scatter(X_test_fold['weight'], y_test_fold, color='green', label='测试集实际值', alpha=0.8, s=50, marker='o')
ax_w.scatter(X_test_fold['weight'], y_pred_fold, color='red', label='测试集预测值', alpha=0.8, s=50, marker='x')
weight_range = np.linspace(X_full['weight'].min(), X_full['weight'].max(), 100)
hp_mean_train = X_train_fold['horsepower'].mean()
X_plot_w = pd.DataFrame({'weight': weight_range, 'horsepower': hp_mean_train})
y_plot_pred_w = current_model.predict(X_plot_w) # 使用当前训练好的模型
ax_w.plot(weight_range, y_plot_pred_w, color='darkorange', linestyle='--', linewidth=2, label=f'回归线 (HP={hp_mean_train:.0f})')
ax_w.set_title(f'第 {fold_num + 1} 折', fontsize=12)
ax_w.set_xlabel('自重 (Weight)', fontsize=10)
ax_w.set_ylabel('MPG', fontsize=10)
ax_w.legend(fontsize=8)
ax_w.grid(True, linestyle=':', alpha=0.7)
# --- 绘制 MPG vs Horsepower ---
ax_h = axs_hp[fold_num, 0]
ax_h.scatter(X_train_fold['horsepower'], y_train_fold, color='lightblue', label='训练集点', alpha=0.6, s=30)
ax_h.scatter(X_test_fold['horsepower'], y_test_fold, color='green', label='测试集实际值', alpha=0.8, s=50, marker='o')
ax_h.scatter(X_test_fold['horsepower'], y_pred_fold, color='red', label='测试集预测值', alpha=0.8, s=50, marker='x')
hp_range = np.linspace(X_full['horsepower'].min(), X_full['horsepower'].max(), 100)
weight_mean_train = X_train_fold['weight'].mean()
X_plot_h = pd.DataFrame({'weight': weight_mean_train, 'horsepower': hp_range})
y_plot_pred_h = current_model.predict(X_plot_h) # 使用当前训练好的模型
ax_h.plot(hp_range, y_plot_pred_h, color='purple', linestyle='--', linewidth=2, label=f'回归线 (W={weight_mean_train:.0f})')
ax_h.set_title(f'第 {fold_num + 1} 折', fontsize=12)
ax_h.set_xlabel('马力 (Horsepower)', fontsize=10)
ax_h.set_ylabel('MPG', fontsize=10)
ax_h.legend(fontsize=8)
ax_h.grid(True, linestyle=':', alpha=0.7)
pred_mean_fold = np.mean(y_pred_fold)
pred_var_fold = np.var(y_pred_fold)
error_fold = y_test_fold.values - y_pred_fold #确保都是numpy array
error_mean_fold = np.mean(error_fold)
error_var_fold = np.var(error_fold)
r2_fold = r2_score(y_test_fold, y_pred_fold)
mse_fold = mean_squared_error(y_test_fold, y_pred_fold)
print(f" {model_name} - 第 {fold_num + 1} 折:")
print(f" 测试集预测MPG: 均值={pred_mean_fold:.2f}, 方差={pred_var_fold:.2f}")
print(f" 测试集预测误差: 均值={error_mean_fold:.2f}, 方差={error_var_fold:.2f}")
print(f" 测试集R²得分: {r2_fold:.3f}, MSE: {mse_fold:.2f}")
fold_num += 1
fig_weight.tight_layout(rect=[0, 0, 1, 0.96])
fig_hp.tight_layout(rect=[0, 0, 1, 0.96])
return np.array(all_actuals_list), np.array(all_predictions_list)
# --- 3. 特定数据点 x0 的预测均值和方差函数 ---
def predict_for_x0(X_full, y_full, x0_features_df, model_template, model_name, n_splits=2):
"""
对指定数据点x0,使用交叉验证中每折训练的模型进行预测,并计算预测的均值和方差。
"""
predictions_on_x0 = []
kf = KFold(n_splits=n_splits, shuffle=True, random_state=42)
for train_index, _ in kf.split(X_full):
X_train_fold, y_train_fold = X_full.iloc[train_index], y_full.iloc[train_index]
current_model_instance = model_template
current_model_instance.fit(X_train_fold, y_train_fold)
pred_x0 = current_model_instance.predict(x0_features_df)[0]
predictions_on_x0.append(pred_x0)
mean_pred_x0 = np.mean(predictions_on_x0)
var_pred_x0 = np.var(predictions_on_x0)
print(f"\n--- 对 x0 ({x0_features_df.iloc[0].to_dict()}) 使用 {model_name} 的预测 ({n_splits}折模型) ---")
if not predictions_on_x0: # 以防万一
print(" 未能生成任何预测值。")
return np.nan, np.nan
print(f" 各折模型对 x0 的预测值: {[f'{p:.2f}' for p in predictions_on_x0]}")
print(f" 对 x0 的预测均值: {mean_pred_x0:.2f} MPG")
print(f" 对 x0 的预测方差: {var_pred_x0:.2f}")
return mean_pred_x0, var_pred_x0
# --- 4. 主程序 ---
def main():
X, y, df_cleaned = load_and_preprocess_data()
if X is None or y is None or df_cleaned is None:
return
lr_model_template = LinearRegression()
svr_model_template = Pipeline([
('scaler', StandardScaler()),
('svr', SVR(kernel='rbf'))
])
n_cv_splits = 2
print(f"\n=== 开始执行 {n_cv_splits}折交叉验证:线性回归 ===")
lr_actuals, lr_predictions = train_eval_and_plot_cv(
X, y, lr_model_template, "线性回归 (LR)", n_splits=n_cv_splits
)
overall_lr_mse = mean_squared_error(lr_actuals, lr_predictions)
overall_lr_r2 = r2_score(lr_actuals, lr_predictions)
print(f"线性回归 ({n_cv_splits}-fold CV 总体性能):")
print(f" MSE: {overall_lr_mse:.2f}")
print(f" R² score: {overall_lr_r2:.3f}")
print(f"\n=== 开始执行 {n_cv_splits}折交叉验证:支持向量回归 ===")
svr_actuals, svr_predictions = train_eval_and_plot_cv(
X, y, svr_model_template, "支持向量回归 (SVR)", n_splits=n_cv_splits
)
overall_svr_mse = mean_squared_error(svr_actuals, svr_predictions)
overall_svr_r2 = r2_score(svr_actuals, svr_predictions)
print(f"支持向量回归 ({n_cv_splits}-fold CV 总体性能):")
print(f" MSE: {overall_svr_mse:.2f}")
print(f" R² score: {overall_svr_r2:.3f}")
print("\n=== 模型比较总结 (基于整体交叉验证结果) ===")
print(f"线性回归 (LR): MSE = {overall_lr_mse:.2f}, R² = {overall_lr_r2:.3f}")
print(f"支持向量回归 (SVR): MSE = {overall_svr_mse:.2f}, R² = {overall_svr_r2:.3f}")
if overall_lr_mse < overall_svr_mse:
print("在此数据集和评估下,线性回归的MSE较低,表现相对较好。")
elif overall_svr_mse < overall_lr_mse:
print("在此数据集和评估下,支持向量回归的MSE较低,表现相对较好。")
else:
print("在此数据集和评估下,两种模型的MSE表现相似。")
# --- 对指定数据点 x0 进行预测 ---
if len(X) > 0:
# 随机选择一个数据点作为 x0 (基于处理后的X/y数据集)
idx_pos_x0 = np.random.randint(0, len(X))
print(f"\n随机选择数据行(基于处理后的X/y数据集的位置索引 {idx_pos_x0})作为 x0。")
x0_features_df = X.iloc[[idx_pos_x0]] # DataFrame格式 (1 row, 2 cols)
x0_actual_mpg = y.iloc[idx_pos_x0] # Series value
original_index_label = X.index[idx_pos_x0]
print(f"选定的 x0 (原始DataFrame索引标签: {original_index_label}): 特征 {x0_features_df.iloc[0].to_dict()}")
print(f"x0 的实际 MPG: {x0_actual_mpg:.2f}")
predict_for_x0(X, y, x0_features_df, lr_model_template, "线性回归 (LR)", n_splits=n_cv_splits)
predict_for_x0(X, y, x0_features_df, svr_model_template, "支持向量回归 (SVR)", n_splits=n_cv_splits)
else:
print("数据为空,无法选择 x0 进行预测。")
print("\n所有图表已生成。关闭图表窗口后程序结束。")
plt.show()
if __name__ == '__main__':
main()
实验任务¶
名称为汽车MPG.CSV的文件记录了不同车型的各种汽车的行驶测试数据和客观指 标值,包括汽车在城市道路上
每加仑汽油可行使的英里数MPG、
气缸数cylinders、
排气量displacement、
发动机马力horsepower、
车的自重weight、
百公里加速时间acceleration
车名car name。
请选取自重和马力两个特征,并根据数据集建立油耗 MPG(越大则油耗越低)预测的回归预测模型。
具体要求:¶
按照下发的实验报告模板独立完成实验报告,独立完成、加入代码注释。
采用一般线性回归和支持向量回归建立回归预测模型,借助2折交叉验证法对两种 方法进行比较,对得到的图表和数值给出必要说明和讨论。
分别绘制不同测试集和训练集、两种方法下的MPG与自重、MPG与马力的回归预 测结果,并用图例来说明不同点的含义。
在支持向量回归中,对比4组惩罚参数C和epsilon取值的模型训练效果。
分别针对自重数据和马力数据,输出两种方法的预测均值和方差。
报告材料¶
实验名称:
基于支持向量机的回归预测
实验目的:
理解支持向量机模型实现回归任务的基本原理,能够在有限时间
内自主编写Python程序解决一个面向实际应用的基于支持向量机的
回归预测问题。
实验设备:
运行Sublime Text 3代码编辑器和Python 3.9.1编译环境的计算机。