一、整體流程概覽
這份代碼實現了一個完整的機器學習預測流程,核心目標是通過汽車的各項特征預測其價格。整體流程分為 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說明模型解釋力越強)
- 基礎模型性能:
R2≈0.82
,說明模型能解釋 82% 的價格變異,初步效果較好。
4.3 交叉驗證
cv_scores = cross_val_score(dt_reg, X, y, cv=10, scoring='r2') # 10折交叉驗證
- 作用:避免單次劃分的偶然性,更穩健地評估模型性能(本例平均
R2≈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', # 優化目標:最大化R2n_jobs=-1 # 利用所有CPU核心加速
)
grid_search.fit(X_train, y_train) # 執行網格搜索
- 原理:遍歷所有參數組合,選擇交叉驗證性能最好的參數(
grid_search.best_params_
)。 - 效果:優化后模型
R2提升至≈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%)是影響價格的三大核心因素,與常識一致。
三、核心技術點總結
- 模擬數據生成:通過合理的概率分布和業務邏輯,生成貼近真實的數據集,解決數據獲取難題。
- 數據預處理:針對分類變量選擇合適的編碼方式(有序編碼 / 獨熱編碼),用中位數填充缺失值。
- 模型選擇:決策樹回歸器適合處理非線性關系,且結果可解釋。
- 參數調優:網格搜索自動尋找最優參數,平衡模型復雜度和泛化能力。
- 結果解釋:通過特征重要性分析,將模型結果轉化為可理解的業務洞察。