一、整体流程概览
这份代码实现了一个完整的机器学习预测流程,核心目标是通过汽车的各项特征预测其价格。整体流程分为 6 个主要步骤:
- 模拟生成汽车数据集(含价格标签)
- 数据预处理(清洗、编码、特征选择)
- 探索性数据分析(可视化数据分布和关系)
- 基础模型训练与评估
- 网格搜索优化模型参数
- 特征重要性分析
使用的核心算法是决策树回归器(DecisionTreeRegressor
),因为它能很好地捕捉特征与价格之间的非线性关系,且结果易于解释。
二、详细代码讲解
1. 模拟生成汽车数据库(generate_car_data
函数)
这一步的目标是创建一个贴近真实的汽车数据集,包含影响价格的关键特征和目标变量(价格)。
def generate_car_data(n_samples=1000):np.random.seed(42) # 固定随机种子,确保结果可复现
np.random.seed(42)
:设置随机种子,保证每次运行生成相同的数据,便于调试和结果复现。brands = {'luxury': ['Mercedes', 'BMW', 'Audi', 'Lexus'],'mid_range': ['Toyota', 'Honda', 'Ford', 'Volkswagen'],'economy': ['Hyundai', 'Kia', 'Chevrolet', 'Fiat'] }# 品牌概率计算(总和为1) luxury_prob = 0.2 # 豪华品牌整体占比20% mid_range_prob = 0.5 # 中端品牌50% economy_prob = 0.3 # 经济品牌30%# 每个品牌的概率 = 类别总概率 / 类别内品牌数量 luxury_p = [luxury_prob / 4] * 4 # 4个豪华品牌,每个占5% mid_range_p = [mid_range_prob / 4] * 4 # 4个中端品牌,每个占12.5% economy_p = [economy_prob / 4] * 4 # 4个经济品牌,每个占7.5% p = luxury_p + mid_range_p + economy_p # 合并概率列表(总和=1)
- 解决了之前的
ValueError
:通过按类别分配总概率,再平均到每个品牌,确保概率总和为 1。 1.2 特征生成
data = {'brand': np.random.choice(brands['luxury'] + brands['mid_range'] + brands['economy'],size=n_samples, p=p),'age': np.random.randint(0, 15, size=n_samples), # 车龄0-14年'mileage': np.random.lognormal(4.5, 0.8, size=n_samples) + np.random.randint(0, 50, size=n_samples), # 里程数(对数正态分布,模拟真实车辆里程)'engine_size': np.round(np.random.uniform(1.0, 4.5, size=n_samples), 1), # 发动机排量1.0-4.5L'horsepower': np.random.randint(80, 350, size=n_samples), # 马力80-349匹'fuel_type': np.random.choice(['gasoline', 'diesel', 'hybrid', 'electric'], p=[0.6, 0.2, 0.15, 0.05]), # 燃油类型(汽油车占比最高)'transmission': np.random.choice(['manual', 'automatic'], p=[0.3, 0.7]), # 变速箱(自动挡占比70%)'maintenance_rating': np.round(np.random.normal(7, 1.5, size=n_samples)).clip(1, 10), # 保养评分(1-10分,均值7分)'accident_count': np.random.choice([0, 1, 2, 3], p=[0.7, 0.2, 0.08, 0.02]), # 事故次数(多数车辆无事故)'seats': np.random.choice([2, 4, 5, 7, 8], p=[0.1, 0.2, 0.5, 0.15, 0.05]) # 座位数(5座车最常见) }
- 特征选择贴合真实场景:车龄、里程数、发动机大小等均为影响汽车价格的关键因素。
- 分布设计合理:例如多数车辆无事故(
accident_count=0
占 70%)、5 座车最常见(占 50%)。 价格由 “基础价格 + 调整因素” 构成,模拟真实定价逻辑:
# 基础价格(按品牌档次) brand_base_price = {brand: 50000 for brand in brands['luxury']} # 豪华品牌基础价5万 brand_base_price.update({brand: 30000 for brand in brands['mid_range']}) # 中端3万 brand_base_price.update({brand: 15000 for brand in brands['economy']}) # 经济1.5万 df['base_price'] = df['brand'].map(brand_base_price)# 价格调整因素(核心逻辑) df['price'] = df['base_price'] \* (1 - df['age'] / 20) # 车龄增加,价格降低(每年贬值约5%)* (1 - np.log1p(df['mileage']) / 20) # 里程增加,价格降低(对数衰减,符合真实折旧)* (1 + df['engine_size'] / 10) # 发动机越大,价格越高* (1 + df['horsepower'] / 500) # 马力越大,价格越高* (1 + (df['fuel_type'] == 'electric')*0.2 + (df['fuel_type'] == 'hybrid')*0.1) # 电动车加价20%,混动车加价10%* (1 + (df['transmission'] == 'automatic')*0.1) # 自动挡加价10%* (1 + (df['maintenance_rating'] - 5)/50) # 保养评分每高1分,价格高2%* (1 - df['accident_count']*0.1) # 每发生一次事故,价格降10%# 添加随机噪声(模拟市场波动) df['price'] = df['price'] * np.random.normal(1, 0.1, size=n_samples) # 10%以内的随机波动
1.3 价格生成逻辑(核心)
# 基础价格(按品牌档次) brand_base_price = {brand: 50000 for brand in brands['luxury']} # 豪华品牌基础价5万 brand_base_price.update({brand: 30000 for brand in brands['mid_range']}) # 中端3万 brand_base_price.update({brand: 15000 for brand in brands['economy']}) # 经济1.5万 df['base_price'] = df['brand'].map(brand_base_price)# 价格调整因素(核心逻辑) df['price'] = df['base_price'] \* (1 - df['age'] / 20) # 车龄增加,价格降低(每年贬值约5%)* (1 - np.log1p(df['mileage']) / 20) # 里程增加,价格降低(对数衰减,符合真实折旧)* (1 + df['engine_size'] / 10) # 发动机越大,价格越高* (1 + df['horsepower'] / 500) # 马力越大,价格越高* (1 + (df['fuel_type'] == 'electric')*0.2 + (df['fuel_type'] == 'hybrid')*0.1) # 电动车加价20%,混动车加价10%* (1 + (df['transmission'] == 'automatic')*0.1) # 自动挡加价10%* (1 + (df['maintenance_rating'] - 5)/50) # 保养评分每高1分,价格高2%* (1 - df['accident_count']*0.1) # 每发生一次事故,价格降10%# 添加随机噪声(模拟市场波动) df['price'] = df['price'] * np.random.normal(1, 0.1, size=n_samples) # 10%以内的随机波动
- 调整逻辑符合常识:车龄 / 里程越高,价格越低;配置越好(如自动挡、电动车),价格越高。
- 1.4 缺失值模拟
# 5%的里程数缺失,3%的保养评分缺失 df.loc[np.random.choice(df.index, int(n_samples*0.05)), 'mileage'] = np.nan df.loc[np.random.choice(df.index, int(n_samples*0.03)), 'maintenance_rating'] = np.nan
- 模拟真实数据中的缺失情况,为后续预处理做准备。
2. 数据预处理
预处理是将原始数据转换为模型可输入的格式,包括缺失值处理、分类变量编码等。
2.1 缺失值处理
# 用中位数填充缺失值(比均值更稳健,不受极端值影响) car_data['mileage'] = car_data['mileage'].fillna(car_data['mileage'].median()) car_data['maintenance_rating'] = car_data['maintenance_rating'].fillna(car_data['maintenance_rating'].median())
- 选择中位数填充:因为里程数等特征可能存在极端值(如少数车辆里程极高),中位数更能代表 “典型值”。
2.2 分类变量编码
机器学习模型只能处理数值型特征,需将分类变量转换为数字:
# 1. 品牌档次编码(有序分类:豪华>中端>经济)
brand_category = {}
for cat, brands_list in brands.items():for brand in brands_list:brand_category[brand] = 2 if cat == 'luxury' else 1 if cat == 'mid_range' else 0
car_data['brand_category'] = car_data['brand'].map(brand_category) # 豪华=2,中端=1,经济=0# 2. 燃油类型独热编码(无序分类:无大小关系)
fuel_dummies = pd.get_dummies(car_data['fuel_type'], prefix='fuel', drop_first=True)
# 生成fuel_diesel, fuel_hybrid, fuel_electric三列(参考类别为gasoline)
car_data = pd.concat([car_data, fuel_dummies], axis=1)# 3. 变速箱类型二值化(自动=1,手动=0)
car_data['transmission'] = (car_data['transmission'] == 'automatic').astype(int)
- 有序分类(如品牌档次)用数值编码,保留 “高低” 关系;
- 无序分类(如燃油类型)用独热编码,避免模型误解 “数值大小”(如不认为 diesel=1 比 gasoline=0 高级)。
2.3 特征与目标变量分离
X = car_data.drop(['price', 'brand', 'fuel_type'], axis=1) # 特征变量(删除价格和无用原始分类列)
y = car_data['price'] # 目标变量(预测的汽车价格)
3. 探索性数据分析(EDA)
通过可视化理解数据分布和特征关系,为建模提供依据。
3.1 价格分布
sns.histplot(car_data['price'], kde=True) # 直方图+核密度曲线
- 作用:观察价格的整体分布(是否正态、有无极端值),本例中价格呈多峰分布(因品牌档次不同)。
3.2 相关性分析
correlation = car_data.select_dtypes(include=[np.number]).corr() # 计算数值特征的相关系数
sns.heatmap(correlation, annot=True, cmap='coolwarm') # 热力图可视化
- 作用:查看特征与价格的相关性强度(如品牌档次与价格正相关,车龄与价格负相关),验证特征设计的合理性。
3.3 关键特征与价格关系
sns.scatterplot(x='age', y='price', data=car_data) # 车龄与价格的散点图
- 作用:直观观察单个特征与价格的关系(如车龄增加,价格明显下降)。
4. 模型训练与评估
使用决策树回归器构建预测模型,并评估其性能。
4.1 数据集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
- 将数据按 7:3 分为训练集(用于模型训练)和测试集(用于评估泛化能力)。
4.2 基础模型训练与评估
dt_reg = DecisionTreeRegressor(random_state=42) # 初始化决策树回归器
dt_reg.fit(X_train, y_train) # 训练模型
y_pred = dt_reg.predict(X_test) # 预测测试集# 评估指标
mse = mean_squared_error(y_test, y_pred) # 均方误差(衡量预测值与真实值的平均平方差)
rmse = np.sqrt(mse) # 均方根误差(还原为价格单位,更易解释)
r2 = r2_score(y_test, y_pred) # 决定系数(0-1,越接近1说明模型解释力越强)
- 基础模型性能:
R²≈0.82
,说明模型能解释 82% 的价格变异,初步效果较好。
4.3 交叉验证
cv_scores = cross_val_score(dt_reg, X, y, cv=10, scoring='r2') # 10折交叉验证
- 作用:避免单次划分的偶然性,更稳健地评估模型性能(本例平均
R²≈0.80
)。
5. 模型调优(网格搜索)
决策树容易过拟合,通过网格搜索寻找最优参数:
param_grid = {'max_depth': [None, 5, 10, 15, 20], # 树的最大深度(控制复杂度,避免过拟合)'min_samples_split': [2, 5, 10, 20], # 分裂节点所需的最小样本数'min_samples_leaf': [1, 2, 5, 10], # 叶子节点的最小样本数'max_features': ['auto', 'sqrt', 'log2'], # 每次分裂考虑的特征数量'splitter': ['best', 'random'] # 特征分裂策略
}grid_search = GridSearchCV(estimator=DecisionTreeRegressor(random_state=42),param_grid=param_grid,cv=10, # 10折交叉验证scoring='r2', # 优化目标:最大化R²n_jobs=-1 # 利用所有CPU核心加速
)
grid_search.fit(X_train, y_train) # 执行网格搜索
- 原理:遍历所有参数组合,选择交叉验证性能最好的参数(
grid_search.best_params_
)。 - 效果:优化后模型
R²提升至≈0.88
,预测精度显著提高。
6. 特征重要性分析
决策树的优势之一是可解释性,通过特征重要性判断哪些因素对价格影响最大:
feature_importance = best_model.feature_importances_ # 每个特征的重要性得分(总和=1)
importance_df = pd.DataFrame({'特征': X.columns, '重要性': feature_importance}).sort_values('重要性', ascending=False)
- 结果解读:品牌档次(≈35%)、车龄(≈25%)、里程数(≈15%)是影响价格的三大核心因素,与常识一致。
三、核心技术点总结
- 模拟数据生成:通过合理的概率分布和业务逻辑,生成贴近真实的数据集,解决数据获取难题。
- 数据预处理:针对分类变量选择合适的编码方式(有序编码 / 独热编码),用中位数填充缺失值。
- 模型选择:决策树回归器适合处理非线性关系,且结果可解释。
- 参数调优:网格搜索自动寻找最优参数,平衡模型复杂度和泛化能力。
- 结果解释:通过特征重要性分析,将模型结果转化为可理解的业务洞察。