预测锻炼期间燃烧卡路里的数据分析与建模

一、确定业务目标

本项目旨在通过分析锻炼相关数据,建立模型预测锻炼期间燃烧的卡路里量。这项研究具有重要的现实意义:

  1. 个性化健康管理:准确预测卡路里消耗可以帮助个人调整锻炼计划,达到健康减重或保持体重的目标。
  2. 健身效果评估:为健身爱好者提供量化的锻炼效果评估,优化锻炼方案。
  3. 智能健康设备开发:为智能手环、手表等健康监测设备提供更准确的卡路里消耗算法。
  4. 健康应用支持:为健康和健身应用提供更精准的能量消耗预测功能。

本项目的具体目标是:

  • 分析各项身体指标和运动特征与卡路里消耗的关系
  • 构建高精度的卡路里消耗预测模型
  • 评估不同机器学习算法的预测效果
  • 提供可用于实际应用的预测模型

二、获取数据

数据来源于Kaggle平台的"Calories Burnt Prediction"数据集。该数据集包含以下文件:

  1. train.csv:训练数据集,包含锻炼相关特征和卡路里消耗量
  2. test.csv:测试数据集,包含锻炼相关特征,需要预测卡路里消耗量
  3. sample_submission.csv:提交格式样例

数据集包含以下特征:

  • id:记录ID
  • Sex:性别(male/female)
  • Age:年龄
  • Height:身高(厘米)
  • Weight:体重(千克)
  • Duration:锻炼持续时间(分钟)
  • Heart_Rate:心率(次/分钟)
  • Body_Temp:体温(摄氏度)
  • Calories:燃烧的卡路里(仅在训练集中提供)

这个是一个当前正在举办的比赛,地址:Predict Calorie Expenditure | Kaggle

三、数据预处理和探索性分析

3.1 数据预处理

数据预处理阶段包括以下步骤:

  1. 数据加载与检查

    • 加载训练和测试数据集
    • 检查数据集大小和基本信息
    • 查看数据类型和统计摘要
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
     # 1. 数据获取
    train_data, test_data = load_data()

    # 2. 数据预处理
    train_data = preprocess_data(train_data)
    test_data = preprocess_data(test_data, is_train=False)

    # 加载数据
    def load_data():
    """
    加载训练集和测试集数据

    Returns:
    tuple: (训练数据, 测试数据)
    """
    try:
    print("正在加载数据...")
    train_data = pd.read_csv('train.csv')
    test_data = pd.read_csv('test.csv')
    print(f"训练集大小:{train_data.shape}, 测试集大小:{test_data.shape}")
    return train_data, test_data
    except Exception as e:
    print(f"加载数据时出错:{e}")
    raise

    image-20250528101850943

    读取这个数据的前五行:

    1
    2
    3
    4
    5
    # 读取数据
    train_data = pd.read_csv('train.csv')

    # 显示前五行
    print(train_data.head())

    image-20250528101622498

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    print("正在进行数据预处理...")

    # 创建数据副本,避免修改原始数据
    df = data.copy()

    # 显示数据基本信息
    print("\n数据基本信息:")
    print(df.info())

    # 显示数据统计摘要
    print("\n数据统计摘要:")
    print(df.describe())

    image-20250528102208243

  2. 缺失值处理

    • 检查各特征的缺失值
    • 使用适当的方法填充缺失值(若有)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 检查缺失值
    print("\n检查缺失值:")
    missing_values = df.isnull().sum()
    print(missing_values[missing_values > 0])

    # 处理缺失值(如果有)
    if df.isnull().sum().sum() > 0:
    # 对数值型特征使用均值填充,分类特征使用众数填充
    num_features = df.select_dtypes(include=['float64', 'int64']).columns
    cat_features = df.select_dtypes(include=['object']).columns

    for col in num_features:
    if df[col].isnull().sum() > 0:
    df[col].fillna(df[col].mean(), inplace=True)

    for col in cat_features:
    if df[col].isnull().sum() > 0:
    df[col].fillna(df[col].mode()[0], inplace=True)
  3. 特征编码

    • 将分类特征(如性别)编码为数值形式
    • 男性编码为1,女性编码为0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     # 性别编码:将性别特征转换为数值
    if 'Sex' in df.columns:
    df['Sex'] = df['Sex'].map({'male': 1, 'female': 0})

    # 删除ID列,因为它不是预测的特征
    if 'id' in df.columns:
    df = df.drop('id', axis=1)

    # 显示处理后的数据信息
    print("\n预处理后的数据信息:")
    print(df.info())

    image-20250528103836338

  4. 特征工程

    • 使用StandardScaler对数值特征进行标准化处理(模型训练和评估当中)
    • 使模型训练更稳定,提高收敛速度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 特征工程
def feature_engineering(data):
"""
创建新特征以提高模型性能

Args:
data (DataFrame): 预处理后的数据

Returns:
DataFrame: 包含新特征的数据
"""
try:
print("正在进行特征工程...")
print(f"特征工程前数据形状: {data.shape}")

# 创建数据副本
df = data.copy()

# 记录原始特征列表
original_features = df.columns.tolist()

# 1. 创建BMI特征(体重指数)
df['BMI'] = df['Weight'] / ((df['Height']/100) ** 2)

# 2. 创建心率与年龄的比率
df['Heart_Rate_Age_Ratio'] = df['Heart_Rate'] / df['Age']

# 3. 创建锻炼强度指标
df['Exercise_Intensity'] = df['Heart_Rate'] * df['Duration'] / 100

# 4. 创建体温与心率的比率
df['Temp_Heart_Ratio'] = df['Body_Temp'] / df['Heart_Rate']

# 5. 体重与身高的比率
df['Weight_Height_Ratio'] = df['Weight'] / (df['Height']/100)

# 获取新创建的特征列表
new_features = [col for col in df.columns if col not in original_features]

print(f"特征工程完成,创建了 {len(new_features)} 个新特征:")
for feature in new_features:
print(f" - {feature}: 均值={df[feature].mean():.4f}, 标准差={df[feature].std():.4f}")

print(f"特征工程后数据形状: {df.shape}")

return df

except Exception as e:
print(f"特征工程过程中出错:{e}")
raise

image-20250528104847152

3.2 探索性数据分析

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 探索性数据分析
def exploratory_data_analysis(data):
"""
进行探索性数据分析

Args:
data (DataFrame): 需要分析的数据
"""
try:
print("正在进行探索性数据分析...")

# 创建保存图形的文件夹
if not os.path.exists('plots'):
os.makedirs('plots')

# 对于大数据集,可以使用采样减少计算量
sample_size = min(10000, len(data))
data_sample = data.sample(n=sample_size, random_state=42) if len(data) > 10000 else data
print(f"使用{'采样数据' if len(data) > 10000 else '完整数据'}进行可视化分析,样本大小: {len(data_sample)}")

# 分阶段执行可视化
plot_basic_distributions(data_sample)
plot_correlations(data_sample)
plot_feature_relationships(data_sample)
plot_categorical_analysis(data_sample)

print("探索性数据分析完成,图表已保存到 'plots' 文件夹")

except Exception as e:
print(f"探索性数据分析过程中出错:{e}")
raise

探索性数据分析阶段包括以下内容:

  1. 目标变量分析

    • 卡路里消耗的分布情况
    • 异常值检测
  2. 特征分析

    • 各特征的分布情况
    • 箱线图检查异常值
  3. 相关性分析

    • 特征间的相关性热力图
    • 各特征与卡路里消耗的相关性
  4. 特征与目标变量的关系

    • 各特征与卡路里消耗的散点图
    • 性别对卡路里消耗的影响
    • 年龄与卡路里消耗的关系
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    def plot_basic_distributions(data):
    """
    绘制基本分布图

    Args:
    data (DataFrame): 数据
    """
    try:
    print("正在生成基本分布图...")

    # 1. 目标变量分布
    plt.figure(figsize=(10, 6))
    sns.histplot(data['Calories'], kde=True)
    plt.title('卡路里消耗分布')
    plt.xlabel('卡路里')
    plt.ylabel('频率')
    plt.savefig('plots/calories_distribution.png')
    plt.close()

    # 2. 特征分布图(在一个图中展示所有数值特征)
    numerical_features = data.select_dtypes(include=['float64', 'int64']).columns
    numerical_features = [col for col in numerical_features if col != 'Calories']

    fig, axes = plt.subplots(nrows=(len(numerical_features)//3) + (1 if len(numerical_features)%3 > 0 else 0),
    ncols=3, figsize=(15, 3*((len(numerical_features)//3) + (1 if len(numerical_features)%3 > 0 else 0))))
    axes = axes.flatten()

    for i, feature in enumerate(numerical_features):
    if i < len(axes):
    sns.histplot(data[feature], kde=True, ax=axes[i])
    axes[i].set_title(f'{feature} 分布')

    # 隐藏未使用的子图
    for j in range(i+1, len(axes)):
    axes[j].set_visible(False)

    plt.tight_layout()
    plt.savefig('plots/feature_distributions.png')
    plt.close()

    print("基本分布图生成完成")
    except Exception as e:
    print(f"生成基本分布图时出错:{e}")
    plt.close('all') # 确保关闭所有图形,防止内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def plot_correlations(data):
"""
绘制相关性分析图

Args:
data (DataFrame): 数据
"""
try:
print("正在生成相关性分析图...")

# 相关性热力图
plt.figure(figsize=(12, 10))
correlation_matrix = data.corr()
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt='.2f')
plt.title('特征相关性热力图')
plt.tight_layout()
plt.savefig('plots/correlation_heatmap.png')
plt.close()

# 特征与目标变量的相关性条形图
correlations = data.corr()['Calories'].drop('Calories').sort_values(ascending=False)

plt.figure(figsize=(10, 8))
sns.barplot(x=correlations.values, y=correlations.index)
plt.title('特征与卡路里消耗的相关性')
plt.xlabel('相关系数')
plt.tight_layout()
plt.savefig('plots/feature_target_correlation.png')
plt.close()

print("相关性分析图生成完成")
except Exception as e:
print(f"生成相关性分析图时出错:{e}")
plt.close('all') # 确保关闭所有图形,防止内存泄漏

image-20250528105807741

image-20250528110104710

发现新特征没啥用,后续训练模型的时候也就没使用这些新的特征。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def plot_feature_relationships(data):
"""
绘制特征关系图

Args:
data (DataFrame): 数据
"""
try:
print("正在生成特征关系图...")

# 只为相关性最高的前5个特征生成散点图
correlation_with_target = data.corr()['Calories'].abs().sort_values(ascending=False)
top_features = correlation_with_target.index[1:6] # 排除Calories自身

for feature in tqdm(top_features, desc="生成特征散点图"):
try:
plt.figure(figsize=(10, 6))
sns.scatterplot(x=feature, y='Calories', data=data, hue='Sex')
plt.title(f'{feature} 与卡路里消耗的关系')
plt.savefig(f'plots/{feature}_vs_calories.png')
plt.close()
except Exception as e:
print(f" 生成 {feature} 散点图时出错:{e}")
plt.close()
continue # 继续处理下一个特征

# 生成一个包含所有重要特征的配对图
try:
print("正在生成特征配对图(这可能需要一些时间)...")
sns.pairplot(data[list(top_features) + ['Calories', 'Sex']],
hue='Sex',
diag_kind='kde',
plot_kws={'alpha': 0.6},
height=2.5)
plt.savefig('plots/features_pairplot.png')
plt.close()
except Exception as e:
print(f" 生成特征配对图时出错:{e}")
plt.close()

print("特征关系图生成完成")
except Exception as e:
print(f"生成特征关系图时出错:{e}")
plt.close('all') # 确保关闭所有图形,防止内存泄漏

image-20250528110659212

它直观验证了 “时长是卡路里消耗的核心驱动”,同时暴露了 “性别影响弱”“异常点风险”

image-20250528110952874

心率是卡路里消耗的核心驱动

image-20250528111148683

image-20250528111234824

image-20250528111310170

image-20250528111347335

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def plot_categorical_analysis(data):
"""
绘制分类特征分析图

Args:
data (DataFrame): 数据
"""
try:
print("正在生成分类特征分析图...")

# 性别与卡路里消耗关系
plt.figure(figsize=(10, 6))
sns.boxplot(x='Sex', y='Calories', data=data)
plt.title('不同性别的卡路里消耗分布')
plt.savefig('plots/sex_vs_calories.png')
plt.close()

# 创建年龄分组
data_copy = data.copy()
data_copy['Age_Group'] = pd.cut(data_copy['Age'], bins=[0, 30, 45, 60, 100], labels=['<30', '30-45', '45-60', '>60'])

# 年龄组与卡路里消耗关系
plt.figure(figsize=(10, 6))
sns.boxplot(x='Age_Group', y='Calories', data=data_copy, hue='Sex')
plt.title('不同年龄组的卡路里消耗分布')
plt.savefig('plots/age_group_vs_calories.png')
plt.close()

print("分类特征分析图生成完成")
except Exception as e:
print(f"生成分类特征分析图时出错:{e}")
plt.close('all') # 确保关闭所有图形,防止内存泄漏

image-20250528111535038

性别单独对卡路里消耗的区分度极弱

image-20250528111613739

年龄对卡路里消耗的影响随性别变化,且高龄组存在特殊高消耗模式

四、建模和模型评价

4.1 建模策略

我们选择了以下四种回归算法进行建模:

  1. 决策树回归

    • 优点:易于理解和解释,可捕捉非线性关系
    • 缺点:可能过拟合,预测精度有限
  2. 随机森林回归

    • 优点:集成多个决策树,降低方差,提高稳定性
    • 缺点:计算开销大,模型解释性较差
  3. XGBoost回归

    • 优点:梯度提升框架,处理复杂非线性关系效果好
    • 缺点:调参复杂,计算资源需求高
  4. 线性回归

    • 优点:简单易懂,计算效率高
    • 缺点:无法捕捉复杂的非线性关系

4.2 模型训练与评估

对于每个模型,我们采用以下步骤:

  1. 将数据分割为训练集(80%)和验证集(20%)
  2. 使用训练集训练模型
  3. 在验证集上评估模型性能
  4. 比较不同模型的性能指标

导出图片

评估指标包括:

  • 均方误差(MSE)
  • 均方根误差(RMSE)
  • 平均绝对误差(MAE)
  • 决定系数(R²)

image-20250515154202938

image-20250515154219339

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
"""
卡路里消耗预测 - 数据分析课程设计
本代码用于预测锻炼期间燃烧了多少卡路里
"""

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from xgboost import XGBRegressor
from sklearn.linear_model import LinearRegression
import warnings
import os
import time # 导入时间模块
from tqdm import tqdm # 导入tqdm用于进度显示
import joblib

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题

# 忽略警告
warnings.filterwarnings('ignore')

# 设置随机种子,确保结果可重现
np.random.seed(42)

# 主函数
def main(use_sampling=False):
print("开始卡路里消耗预测数据分析...")

# 1. 数据获取
train_data, test_data = load_data()

# 2. 数据预处理
train_data = preprocess_data(train_data)
test_data = preprocess_data(test_data, is_train=False)

# 3. 特征工程(新增步骤)
train_data = feature_engineering(train_data)
test_data = feature_engineering(test_data)

# 3.1 绘制工程特征与目标变量的关系(仅使用训练集)
print("正在可视化工程特征与目标变量的关系...")
if not os.path.exists('plots'):
os.makedirs('plots')

# 获取工程特征列表
engineered_features = ['BMI', 'Heart_Rate_Age_Ratio', 'Exercise_Intensity',
'Temp_Heart_Ratio', 'Weight_Height_Ratio']

# 计算工程特征与目标变量的相关性
feature_correlations = train_data[engineered_features + ['Calories']].corr()['Calories'].drop('Calories')

# 绘制相关性条形图
plt.figure(figsize=(10, 6))
sns.barplot(x=feature_correlations.values, y=feature_correlations.index)
plt.title('工程特征与卡路里消耗的相关性')
plt.xlabel('相关系数')
plt.tight_layout()
plt.savefig('plots/engineered_features_correlation.png')
plt.close()

# 4. 探索性数据分析
exploratory_data_analysis(train_data)

# 5. 特征与目标变量分离
X_train, y_train = train_data.drop('Calories', axis=1), train_data['Calories']

# 6. 建模和模型评价
sample_limit = 100000 if use_sampling else None # 如果使用采样,设置为10万条记录
best_model, best_score = model_training_and_evaluation(X_train, y_train, sample_limit)

# 7. 使用最佳模型进行预测并生成提交文件
generate_submission(best_model, test_data)

print("卡路里消耗预测数据分析完成!")

# 加载数据
def load_data():
"""
加载训练集和测试集数据

Returns:
tuple: (训练数据, 测试数据)
"""
try:
print("正在加载数据...")
train_data = pd.read_csv('train.csv')
test_data = pd.read_csv('test.csv')
print(f"训练集大小:{train_data.shape}, 测试集大小:{test_data.shape}")
return train_data, test_data
except Exception as e:
print(f"加载数据时出错:{e}")
raise

# 数据预处理
def preprocess_data(data, is_train=True):
"""
对数据进行预处理

Args:
data (DataFrame): 需要处理的数据
is_train (bool): 是否为训练数据

Returns:
DataFrame: 预处理后的数据
"""
try:
print("正在进行数据预处理...")

# 创建数据副本,避免修改原始数据
df = data.copy()

# 显示数据基本信息
print("\n数据基本信息:")
print(df.info())

# 显示数据统计摘要
print("\n数据统计摘要:")
print(df.describe())

# 检查缺失值
print("\n检查缺失值:")
missing_values = df.isnull().sum()
print(missing_values[missing_values > 0])

# 处理缺失值(如果有)
if df.isnull().sum().sum() > 0:
# 对数值型特征使用均值填充,分类特征使用众数填充
num_features = df.select_dtypes(include=['float64', 'int64']).columns
cat_features = df.select_dtypes(include=['object']).columns

for col in num_features:
if df[col].isnull().sum() > 0:
df[col].fillna(df[col].mean(), inplace=True)

for col in cat_features:
if df[col].isnull().sum() > 0:
df[col].fillna(df[col].mode()[0], inplace=True)

# 性别编码:将性别特征转换为数值
if 'Sex' in df.columns:
df['Sex'] = df['Sex'].map({'male': 1, 'female': 0})

# 删除ID列,因为它不是预测的特征
if 'id' in df.columns:
df = df.drop('id', axis=1)

# 显示处理后的数据信息
print("\n预处理后的数据信息:")
print(df.info())

return df

except Exception as e:
print(f"数据预处理过程中出错:{e}")
raise

# 探索性数据分析
def exploratory_data_analysis(data):
"""
进行探索性数据分析

Args:
data (DataFrame): 需要分析的数据
"""
try:
print("正在进行探索性数据分析...")

# 创建保存图形的文件夹
if not os.path.exists('plots'):
os.makedirs('plots')

# 对于大数据集,可以使用采样减少计算量(可选)
sample_size = min(10000, len(data))
data_sample = data.sample(n=sample_size, random_state=42) if len(data) > 10000 else data
print(f"使用{'采样数据' if len(data) > 10000 else '完整数据'}进行可视化分析,样本大小: {len(data_sample)}")

# 分阶段执行可视化
plot_basic_distributions(data_sample)
plot_correlations(data_sample)
plot_feature_relationships(data_sample)
plot_categorical_analysis(data_sample)

print("探索性数据分析完成,图表已保存到 'plots' 文件夹")

except Exception as e:
print(f"探索性数据分析过程中出错:{e}")
raise

def plot_basic_distributions(data):
"""
绘制基本分布图

Args:
data (DataFrame): 数据
"""
try:
print("正在生成基本分布图...")

# 1. 目标变量分布
plt.figure(figsize=(10, 6))
sns.histplot(data['Calories'], kde=True)
plt.title('卡路里消耗分布')
plt.xlabel('卡路里')
plt.ylabel('频率')
plt.savefig('plots/calories_distribution.png')
plt.close()

# 2. 特征分布图(在一个图中展示所有数值特征)
numerical_features = data.select_dtypes(include=['float64', 'int64']).columns
numerical_features = [col for col in numerical_features if col != 'Calories']

fig, axes = plt.subplots(nrows=(len(numerical_features)//3) + (1 if len(numerical_features)%3 > 0 else 0),
ncols=3, figsize=(15, 3*((len(numerical_features)//3) + (1 if len(numerical_features)%3 > 0 else 0))))
axes = axes.flatten()

for i, feature in enumerate(numerical_features):
if i < len(axes):
sns.histplot(data[feature], kde=True, ax=axes[i])
axes[i].set_title(f'{feature} 分布')

# 隐藏未使用的子图
for j in range(i+1, len(axes)):
axes[j].set_visible(False)

plt.tight_layout()
plt.savefig('plots/feature_distributions.png')
plt.close()

print("基本分布图生成完成")
except Exception as e:
print(f"生成基本分布图时出错:{e}")
plt.close('all') # 确保关闭所有图形,防止内存泄漏

def plot_correlations(data):
"""
绘制相关性分析图

Args:
data (DataFrame): 数据
"""
try:
print("正在生成相关性分析图...")

# 相关性热力图
plt.figure(figsize=(12, 10))
correlation_matrix = data.corr()
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt='.2f')
plt.title('特征相关性热力图')
plt.tight_layout()
plt.savefig('plots/correlation_heatmap.png')
plt.close()

# 特征与目标变量的相关性条形图
correlations = data.corr()['Calories'].drop('Calories').sort_values(ascending=False)

plt.figure(figsize=(10, 8))
sns.barplot(x=correlations.values, y=correlations.index)
plt.title('特征与卡路里消耗的相关性')
plt.xlabel('相关系数')
plt.tight_layout()
plt.savefig('plots/feature_target_correlation.png')
plt.close()

print("相关性分析图生成完成")
except Exception as e:
print(f"生成相关性分析图时出错:{e}")
plt.close('all') # 确保关闭所有图形,防止内存泄漏

def plot_feature_relationships(data):
"""
绘制特征关系图

Args:
data (DataFrame): 数据
"""
try:
print("正在生成特征关系图...")

# 只为相关性最高的前5个特征生成散点图
correlation_with_target = data.corr()['Calories'].abs().sort_values(ascending=False)
top_features = correlation_with_target.index[1:6] # 排除Calories自身

for feature in tqdm(top_features, desc="生成特征散点图"):
try:
plt.figure(figsize=(10, 6))
sns.scatterplot(x=feature, y='Calories', data=data, hue='Sex')
plt.title(f'{feature} 与卡路里消耗的关系')
plt.savefig(f'plots/{feature}_vs_calories.png')
plt.close()
except Exception as e:
print(f" 生成 {feature} 散点图时出错:{e}")
plt.close()
continue # 继续处理下一个特征

# 生成一个包含所有重要特征的配对图
try:
print("正在生成特征配对图(这可能需要一些时间)...")
sns.pairplot(data[list(top_features) + ['Calories', 'Sex']],
hue='Sex',
diag_kind='kde',
plot_kws={'alpha': 0.6},
height=2.5)
plt.savefig('plots/features_pairplot.png')
plt.close()
except Exception as e:
print(f" 生成特征配对图时出错:{e}")
plt.close()

print("特征关系图生成完成")
except Exception as e:
print(f"生成特征关系图时出错:{e}")
plt.close('all') # 确保关闭所有图形,防止内存泄漏

def plot_categorical_analysis(data):
"""
绘制分类特征分析图

Args:
data (DataFrame): 数据
"""
try:
print("正在生成分类特征分析图...")

# 性别与卡路里消耗关系
plt.figure(figsize=(10, 6))
sns.boxplot(x='Sex', y='Calories', data=data)
plt.title('不同性别的卡路里消耗分布')
plt.savefig('plots/sex_vs_calories.png')
plt.close()

# 创建年龄分组
data_copy = data.copy()
data_copy['Age_Group'] = pd.cut(data_copy['Age'], bins=[0, 30, 45, 60, 100], labels=['<30', '30-45', '45-60', '>60'])

# 年龄组与卡路里消耗关系
plt.figure(figsize=(10, 6))
sns.boxplot(x='Age_Group', y='Calories', data=data_copy, hue='Sex')
plt.title('不同年龄组的卡路里消耗分布')
plt.savefig('plots/age_group_vs_calories.png')
plt.close()

print("分类特征分析图生成完成")
except Exception as e:
print(f"生成分类特征分析图时出错:{e}")
plt.close('all') # 确保关闭所有图形,防止内存泄漏

# 模型训练和评估
def model_training_and_evaluation(X, y, sample_limit=None):
"""
训练多个模型并评估性能

Args:
X (DataFrame): 特征数据,包括原始特征和通过特征工程创建的新特征
y (Series): 目标变量(卡路里消耗量)
sample_limit (int, optional): 可选的数据采样限制,如果指定,将随机采样数据

Returns:
tuple: (最佳模型, 最佳得分)
"""
try:
print("正在进行模型训练和评估...")

# 可选的数据采样
if sample_limit is not None and len(X) > sample_limit:
print(f"数据集较大,进行随机采样({sample_limit}/{len(X)}条记录)...")
sample_idx = np.random.choice(len(X), sample_limit, replace=False)
X = X.iloc[sample_idx].copy()
y = y.iloc[sample_idx].copy()
print(f"采样后数据形状: X={X.shape}, y={len(y)}")
else:
print(f"使用完整数据集: X={X.shape}, y={len(y)}")

# 分割数据为训练集和验证集
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)

# 特征缩放
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_val_scaled = scaler.transform(X_val)

# 保存特征名称,用于后续的特征重要性分析
feature_names = X.columns.tolist()

# 定义要训练的模型
models = {
"决策树回归": DecisionTreeRegressor(random_state=42),
"随机森林回归": RandomForestRegressor(
random_state=42,
n_estimators=50, # 减少树的数量(从100减少到50)
max_depth=20, # 限制树的最大深度
min_samples_split=10,# 增加分裂所需的最小样本数
n_jobs=-1, # 使用所有可用的CPU核心
verbose=1 # 显示训练进度
),
"XGBoost回归": XGBRegressor(random_state=42),
"线性回归": LinearRegression()
}

# 用于存储各模型评估结果
results = {}
best_model = None
best_score = float('inf') # 使用RMSE作为评估指标,值越小越好

# 训练和评估每个模型
for name, model in models.items():
print(f"\n正在训练 {name} 模型...")

# 训练模型
start_time = time.time()
model.fit(X_train_scaled, y_train)
end_time = time.time()
training_time = end_time - start_time

# 在验证集上进行预测
y_pred = model.predict(X_val_scaled)

# 计算评估指标
mse = mean_squared_error(y_val, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_val, y_pred)
r2 = r2_score(y_val, y_pred)

# 存储结果
results[name] = {
'MSE': mse,
'RMSE': rmse,
'MAE': mae,
'R²': r2,
'model': model,
'training_time': training_time
}

# 输出评估结果
print(f"{name} 评估结果:")
print(f" 均方误差 (MSE): {mse:.4f}")
print(f" 均方根误差 (RMSE): {rmse:.4f}")
print(f" 平均绝对误差 (MAE): {mae:.4f}")
print(f" R² 分数: {r2:.4f}")
print(f" 训练时间: {training_time:.2f}秒")

# 更新最佳模型
if rmse < best_score:
best_score = rmse
best_model = model

# 比较模型性能并可视化
print("\n模型性能比较:")
metrics = ['MSE', 'RMSE', 'MAE', 'R²']

for metric in metrics:
plt.figure(figsize=(10, 6))
model_names = list(results.keys())
if metric == 'R²':
# R²越高越好
metric_values = [results[name][metric] for name in model_names]
colors = ['green' if val == max([results[name][metric] for name in model_names]) else 'blue' for val in metric_values]
else:
# 其他指标越低越好
metric_values = [results[name][metric] for name in model_names]
colors = ['green' if val == min([results[name][metric] for name in model_names]) else 'blue' for val in metric_values]

plt.bar(model_names, metric_values, color=colors)
plt.title(f'不同模型的{metric}比较')
plt.xlabel('模型')
plt.ylabel(metric)
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig(f'plots/model_comparison_{metric}.png')
plt.close()

# 可视化模型训练时间
plt.figure(figsize=(10, 6))
model_names = list(results.keys())
training_times = [results[name]['training_time'] for name in model_names]
colors = ['green' if t == min(training_times) else 'blue' for t in training_times]

plt.bar(model_names, training_times, color=colors)
plt.title('不同模型的训练时间比较')
plt.xlabel('模型')
plt.ylabel('训练时间 (秒)')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('plots/model_comparison_training_time.png')
plt.close()

# 时间-性能权衡分析(RMSE/时间)
plt.figure(figsize=(10, 6))
model_names = list(results.keys())
rmse_values = [results[name]['RMSE'] for name in model_names]
efficiency = [rmse / time for rmse, time in zip(rmse_values, training_times)]
colors = ['green' if e == min(efficiency) else 'blue' for e in efficiency]

plt.bar(model_names, efficiency, color=colors)
plt.title('模型效率分析 (RMSE/训练时间)')
plt.xlabel('模型')
plt.ylabel('效率指标 (越低越好)')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('plots/model_comparison_efficiency.png')
plt.close()

# 分析特征重要性(仅对支持特征重要性的模型)
best_model_name = [name for name, res in results.items() if res['model'] == best_model][0]
print(f"\n最佳模型是: {best_model_name}")
print(f"最佳RMSE: {best_score:.4f}")

# 分析最佳模型的特征重要性
if hasattr(best_model, 'feature_importances_'):
plt.figure(figsize=(12, 8))
importances = best_model.feature_importances_
indices = np.argsort(importances)[::-1]

plt.title(f'{best_model_name}模型的特征重要性')
plt.bar(range(X.shape[1]), importances[indices], align='center')
plt.xticks(range(X.shape[1]), [feature_names[i] for i in indices], rotation=90)
plt.tight_layout()
plt.savefig('plots/best_model_feature_importance.png')
plt.close()

print("\n特征重要性排序:")
for i, idx in enumerate(indices):
print(f"{i+1}. {feature_names[idx]}: {importances[idx]:.4f}")

# 保存最佳模型
try:
if not os.path.exists('models'):
os.makedirs('models')
joblib.dump(best_model, 'models/best_model.pkl')
print("\n最佳模型已保存到: models/best_model.pkl")
except Exception as e:
print(f"保存模型时出错: {e}")

return best_model, best_score

except Exception as e:
print(f"模型训练和评估过程中出错:{e}")
raise

# 生成提交文件
def generate_submission(model, test_data):
"""
使用训练好的模型生成提交文件

Args:
model: 训练好的模型
test_data (DataFrame): 测试数据
"""
try:
print("正在生成提交文件...")

# 保存测试集的ID
test_ids = pd.read_csv('test.csv')['id']

# 特征缩放
scaler = StandardScaler()
X_test_scaled = scaler.fit_transform(test_data)

# 预测
predictions = model.predict(X_test_scaled)

# 确保预测值为非负数(卡路里不可能为负)
predictions = np.maximum(predictions, 0)

# 创建提交文件
submission = pd.DataFrame({
'id': test_ids,
'Calories': predictions
})

# 保存为CSV文件
submission.to_csv('submission.csv', index=False)

print(f"提交文件已生成: submission.csv,包含 {len(submission)} 个预测结果")

# 显示预测值的基本统计信息
print("\n预测结果统计信息:")
print(f"最小值: {predictions.min():.2f}")
print(f"最大值: {predictions.max():.2f}")
print(f"平均值: {predictions.mean():.2f}")
print(f"中位数: {np.median(predictions):.2f}")
print(f"标准差: {predictions.std():.2f}")

except Exception as e:
print(f"生成提交文件过程中出错:{e}")
raise

# 特征工程
def feature_engineering(data):
"""
创建新特征以提高模型性能

Args:
data (DataFrame): 预处理后的数据

Returns:
DataFrame: 包含新特征的数据
"""
try:
print("正在进行特征工程...")
print(f"特征工程前数据形状: {data.shape}")

# 创建数据副本
df = data.copy()

# 记录原始特征列表
original_features = df.columns.tolist()

# 1. 创建BMI特征(体重指数)
df['BMI'] = df['Weight'] / ((df['Height']/100) ** 2)

# 2. 创建心率与年龄的比率
df['Heart_Rate_Age_Ratio'] = df['Heart_Rate'] / df['Age']

# 3. 创建锻炼强度指标
df['Exercise_Intensity'] = df['Heart_Rate'] * df['Duration'] / 100

# 4. 创建体温与心率的比率
df['Temp_Heart_Ratio'] = df['Body_Temp'] / df['Heart_Rate']

# 5. 体重与身高的比率
df['Weight_Height_Ratio'] = df['Weight'] / (df['Height']/100)

# 获取新创建的特征列表
new_features = [col for col in df.columns if col not in original_features]

print(f"特征工程完成,创建了 {len(new_features)} 个新特征:")
for feature in new_features:
print(f" - {feature}: 均值={df[feature].mean():.4f}, 标准差={df[feature].std():.4f}")

print(f"特征工程后数据形状: {df.shape}")

return df

except Exception as e:
print(f"特征工程过程中出错:{e}")
raise

# 程序入口
if __name__ == "__main__":
# 是否使用数据采样来加速训练(开发阶段设为True,最终模型训练设为False)
USE_SAMPLING = False
main(use_sampling=USE_SAMPLING)

五、模型优化

5.1 超参数调优

针对不同算法,我们使用网格搜索或随机搜索进行超参数调优:

  1. 决策树优化

    • 参数:max_depth, min_samples_split, min_samples_leaf, max_features
    • 使用GridSearchCV进行网格搜索
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    def optimize_decision_tree(X_train, X_val, y_train, y_val):
    """
    优化决策树回归模型

    Args:
    X_train, X_val, y_train, y_val: 训练和验证数据

    Returns:
    tuple: (最佳模型, 最佳参数, 验证集RMSE, 验证集RMSLE)
    """
    try:
    print("开始优化决策树回归模型...")

    # 参数网格 - 适度减少参数空间但保留关键选项
    param_grid = {
    'max_depth': [None, 10, 15, 20, 25], # 恢复None选项,对大数据集可能有益
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4],
    'max_features': ['auto', 'sqrt', 'log2'] # 保留所有特征选择方法
    }

    # 基础模型
    dt = DecisionTreeRegressor(random_state=42)

    # 对于决策树,恢复使用GridSearchCV以确保找到最优参数
    # 决策树训练速度相对较快,即使数据量大也可接受网格搜索
    grid_search = GridSearchCV(
    estimator=dt,
    param_grid=param_grid,
    cv=3, # 保持减少的交叉验证折数以节省时间
    scoring='neg_mean_squared_error',
    n_jobs=-1,
    verbose=1
    )

    # 开始计时
    start_time = time.time()

    # 训练
    grid_search.fit(X_train, y_train)

    # 结束计时
    end_time = time.time()
    print(f"决策树网格搜索耗时: {end_time - start_time:.2f} 秒")

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

    # 在验证集上评估
    y_pred = best_dt.predict(X_val)
    rmse = np.sqrt(mean_squared_error(y_val, y_pred))
    r2 = r2_score(y_val, y_pred)
    rmsle_score = rmsle(y_val, y_pred) # 计算RMSLE

    print(f"决策树最佳参数: {best_params}")
    print(f"验证集RMSE: {rmse:.4f}")
    print(f"验证集R²: {r2:.4f}")
    print(f"验证集RMSLE: {rmsle_score:.4f} (竞赛评估指标)")

    # 绘制特征重要性
    feature_importances = best_dt.feature_importances_
    features = ['Sex', 'Age', 'Height', 'Weight', 'Duration', 'Heart_Rate', 'Body_Temp']

    plt.figure(figsize=(10, 6))
    sns.barplot(x=feature_importances, y=features)
    plt.title('决策树 - 特征重要性')
    plt.tight_layout()
    plt.savefig('plots/dt_feature_importance.png')
    plt.close()

    # 保存模型
    joblib.dump(best_dt, 'models/decision_tree_best.joblib')

    return best_dt, best_params, rmse, rmsle_score

    except Exception as e:
    print(f"优化决策树模型时出错:{e}")
    raise

  2. 随机森林优化

    • 参数:n_estimators, max_depth, min_samples_split, min_samples_leaf, max_features
    • 使用RandomizedSearchCV进行随机搜索
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    def optimize_random_forest(X_train, X_val, y_train, y_val):
    """
    优化随机森林回归模型

    Args:
    X_train, X_val, y_train, y_val: 训练和验证数据

    Returns:
    tuple: (最佳模型, 最佳参数, 验证集RMSE, 验证集RMSLE)
    """
    try:
    print("开始优化随机森林回归模型...")

    # 由于随机森林计算开销大,即使数据量大也使用随机搜索而非网格搜索
    param_distributions = {
    'n_estimators': [50, 100, 150, 200], # 恢复200作为选项
    'max_depth': [None, 10, 20, 30], # 恢复None和30选项,对大数据集可能有益
    'min_samples_split': [2, 5, 10], # 恢复10作为选项
    'min_samples_leaf': [1, 2, 4], # 恢复4作为选项
    'max_features': ['auto', 'sqrt', 'log2'], # 恢复log2作为选项
    'bootstrap': [True], # 使用bootstrap抽样
    'max_samples': [0.7, 0.8, 0.9] # 控制每棵树使用的样本比例
    }

    # 基础模型 - 添加n_jobs参数使用多核CPU
    rf = RandomForestRegressor(random_state=42, n_jobs=-1)

    # 随机搜索 - 增加n_iter以提高搜索质量
    random_search = RandomizedSearchCV(
    estimator=rf,
    param_distributions=param_distributions,
    n_iter=20, # 恢复原始的20次尝试以提高搜索质量
    cv=3, # 保持减少的交叉验证折数以节省时间
    scoring='neg_mean_squared_error',
    n_jobs=-1,
    verbose=1,
    random_state=42
    )

    # 开始计时
    start_time = time.time()

    # 训练
    random_search.fit(X_train, y_train)

    # 结束计时
    end_time = time.time()
    print(f"随机森林随机搜索耗时: {end_time - start_time:.2f} 秒")

    # 获取最佳参数和模型
    best_params = random_search.best_params_
    best_rf = random_search.best_estimator_

    # 在验证集上评估
    y_pred = best_rf.predict(X_val)
    rmse = np.sqrt(mean_squared_error(y_val, y_pred))
    r2 = r2_score(y_val, y_pred)
    rmsle_score = rmsle(y_val, y_pred) # 计算RMSLE

    print(f"随机森林最佳参数: {best_params}")
    print(f"验证集RMSE: {rmse:.4f}")
    print(f"验证集R²: {r2:.4f}")
    print(f"验证集RMSLE: {rmsle_score:.4f} (竞赛评估指标)")

    # 绘制特征重要性
    feature_importances = best_rf.feature_importances_
    features = ['Sex', 'Age', 'Height', 'Weight', 'Duration', 'Heart_Rate', 'Body_Temp']

    plt.figure(figsize=(10, 6))
    sns.barplot(x=feature_importances, y=features)
    plt.title('随机森林 - 特征重要性')
    plt.tight_layout()
    plt.savefig('plots/rf_feature_importance.png')
    plt.close()

    # 保存模型
    joblib.dump(best_rf, 'models/random_forest_best.joblib')

    return best_rf, best_params, rmse, rmsle_score

    except Exception as e:
    print(f"优化随机森林模型时出错:{e}")
    raise
  3. XGBoost优化

    • 参数:n_estimators, max_depth, learning_rate, subsample, colsample_bytree, gamma
    • 使用RandomizedSearchCV进行随机搜索
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    def optimize_xgboost(X_train, X_val, y_train, y_val):
    """
    优化XGBoost回归模型

    Args:
    X_train, X_val, y_train, y_val: 训练和验证数据

    Returns:
    tuple: (最佳模型, 最佳参数, 验证集RMSE, 验证集RMSLE)
    """
    try:
    print("开始优化XGBoost回归模型...")

    # 参数网格
    param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [3, 5, 7, 9],
    'learning_rate': [0.01, 0.05, 0.1, 0.2],
    'subsample': [0.8, 0.9, 1.0],
    'colsample_bytree': [0.8, 0.9, 1.0],
    'gamma': [0, 0.1, 0.2]
    }

    # 基础模型 - 添加n_jobs参数使用多核CPU和更快的tree_method
    xgb_model = xgb.XGBRegressor(random_state=42, n_jobs=-1, tree_method='hist')

    # 随机搜索
    random_search = RandomizedSearchCV(
    estimator=xgb_model,
    param_distributions=param_grid,
    n_iter=20, # 尝试20种组合
    cv=5,
    scoring='neg_mean_squared_error',
    n_jobs=-1,
    verbose=1,
    random_state=42
    )

    # 开始计时
    start_time = time.time()

    # 训练
    random_search.fit(X_train, y_train)

    # 结束计时
    end_time = time.time()
    print(f"XGBoost随机搜索耗时: {end_time - start_time:.2f} 秒")

    # 获取最佳参数和模型
    best_params = random_search.best_params_
    best_xgb = random_search.best_estimator_

    # 在验证集上评估
    y_pred = best_xgb.predict(X_val)
    rmse = np.sqrt(mean_squared_error(y_val, y_pred))
    r2 = r2_score(y_val, y_pred)
    rmsle_score = rmsle(y_val, y_pred) # 计算RMSLE

    print(f"XGBoost最佳参数: {best_params}")
    print(f"验证集RMSE: {rmse:.4f}")
    print(f"验证集R²: {r2:.4f}")
    print(f"验证集RMSLE: {rmsle_score:.4f} (竞赛评估指标)")

    # 绘制特征重要性
    feature_importances = best_xgb.feature_importances_
    features = ['Sex', 'Age', 'Height', 'Weight', 'Duration', 'Heart_Rate', 'Body_Temp']

    plt.figure(figsize=(10, 6))
    sns.barplot(x=feature_importances, y=features)
    plt.title('XGBoost - 特征重要性')
    plt.tight_layout()
    plt.savefig('plots/xgb_feature_importance.png')
    plt.close()

    # 保存模型
    joblib.dump(best_xgb, 'models/xgboost_best.joblib')

    return best_xgb, best_params, rmse, rmsle_score

    except Exception as e:
    print(f"优化XGBoost模型时出错:{e}")
    raise

  4. 线性回归

    • 线性回归没有需要调优的超参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    def optimize_linear_regression(X_train, X_val, y_train, y_val):
    """
    优化线性回归模型

    Args:
    X_train, X_val, y_train, y_val: 训练和验证数据

    Returns:
    tuple: (训练好的模型, None, 验证集RMSE, 验证集RMSLE)
    """
    try:
    print("开始优化线性回归模型...")

    # 线性回归没有超参数需要调优,直接训练模型
    lr = LinearRegression()

    # 开始计时
    start_time = time.time()

    # 训练
    lr.fit(X_train, y_train)

    # 结束计时
    end_time = time.time()
    print(f"线性回归训练耗时: {end_time - start_time:.2f} 秒")

    # 在验证集上评估
    y_pred = lr.predict(X_val)
    rmse = np.sqrt(mean_squared_error(y_val, y_pred))
    r2 = r2_score(y_val, y_pred)
    rmsle_score = rmsle(y_val, y_pred) # 计算RMSLE

    print(f"验证集RMSE: {rmse:.4f}")
    print(f"验证集R²: {r2:.4f}")
    print(f"验证集RMSLE: {rmsle_score:.4f} (竞赛评估指标)")

    # 绘制系数
    coefficients = lr.coef_
    features = ['Sex', 'Age', 'Height', 'Weight', 'Duration', 'Heart_Rate', 'Body_Temp']

    plt.figure(figsize=(10, 6))
    sns.barplot(x=coefficients, y=features)
    plt.title('线性回归 - 特征系数')
    plt.tight_layout()
    plt.savefig('plots/lr_coefficients.png')
    plt.close()

    # 保存模型
    joblib.dump(lr, 'models/linear_regression.joblib')

    return lr, None, rmse, rmsle_score

    except Exception as e:
    print(f"优化线性回归模型时出错:{e}")
    raise

5.2 特征重要性分析

通过分析各模型的特征重要性,我们发现:

image-20250515152921306

5.3 优化后的模型比较

我们对比了所有优化后的模型性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def compare_optimized_models(results):
"""
比较优化后的模型性能

Args:
results (dict): 包含各模型结果的字典

Returns:
str: 基于RMSLE的最佳模型名称
"""
try:
print("比较优化后的模型性能...")

# 提取模型名称和评估指标
model_names = list(results.keys())
rmse_values = [results[name]['rmse'] for name in model_names]
rmsle_values = [results[name]['rmsle'] for name in model_names]

# 找出基于RMSE的最佳模型
best_rmse_idx = np.argmin(rmse_values)
rmse_colors = ['green' if i == best_rmse_idx else 'blue' for i in range(len(model_names))]

# 找出基于RMSLE的最佳模型(竞赛评估指标)
best_rmsle_idx = np.argmin(rmsle_values)
rmsle_colors = ['green' if i == best_rmsle_idx else 'blue' for i in range(len(model_names))]

# 绘制RMSE比较图
plt.figure(figsize=(10, 6))
plt.bar(model_names, rmse_values, color=rmse_colors)
plt.title('优化后的模型性能比较 (RMSE)')
plt.xlabel('模型')
plt.ylabel('RMSE (越低越好)')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('plots/optimized_models_comparison_rmse.png')
plt.close()

# 绘制RMSLE比较图(竞赛评估指标)
plt.figure(figsize=(10, 6))
plt.bar(model_names, rmsle_values, color=rmsle_colors)
plt.title('优化后的模型性能比较 (RMSLE) - 竞赛评估指标')
plt.xlabel('模型')
plt.ylabel('RMSLE (越低越好)')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('plots/optimized_models_comparison_rmsle.png')
plt.close()

# 输出比较结果
print("\n模型性能比较 (RMSE):")
for i, name in enumerate(model_names):
print(f"{name}: {rmse_values[i]:.4f}" + (" (最佳)" if i == best_rmse_idx else ""))

print("\n模型性能比较 (RMSLE) - 竞赛评估指标:")
for i, name in enumerate(model_names):
print(f"{name}: {rmsle_values[i]:.4f}" + (" (最佳)" if i == best_rmsle_idx else ""))

# 如果RMSE和RMSLE选出的最佳模型不同,输出说明
if best_rmse_idx != best_rmsle_idx:
print(f"\n注意: 基于RMSE的最佳模型是 {model_names[best_rmse_idx]},而基于RMSLE的最佳模型是 {model_names[best_rmsle_idx]}。")
print("由于竞赛使用RMSLE作为评估指标,我们将选择基于RMSLE的最佳模型。")

# 返回基于RMSLE的最佳模型名称
return model_names[best_rmsle_idx]

except Exception as e:
print(f"比较优化后的模型时出错:{e}")
raise

image-20250515153858400

image-20250515153916182

image-20250515153934775

优化的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
卡路里消耗预测 - 模型优化
本代码用于优化预测模型的超参数
"""

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, GridSearchCV, RandomizedSearchCV, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
import xgboost as xgb
from sklearn.linear_model import LinearRegression
import warnings
import os
import joblib
import time

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题

# 忽略警告
warnings.filterwarnings('ignore')

# 设置随机种子,确保结果可重现
np.random.seed(42)

def rmsle(y_true, y_pred):
"""
计算Root Mean Squared Logarithmic Error (RMSLE)

Args:
y_true: 真实值
y_pred: 预测值

Returns:
float: RMSLE值
"""
# 确保输入值为正数(避免对负数取对数)
y_true = np.maximum(y_true, 0)
y_pred = np.maximum(y_pred, 0)

# 计算RMSLE
return np.sqrt(np.mean(np.power(np.log1p(y_pred) - np.log1p(y_true), 2)))

def load_preprocessed_data():
"""
加载预处理后的训练数据

Returns:
tuple: (X_train, X_val, y_train, y_val) - 训练特征、验证特征、训练目标、验证目标
"""
try:
# 加载训练数据
train_data = pd.read_csv('train.csv')

# 数据预处理
# 删除id列
if 'id' in train_data.columns:
train_data.drop('id', axis=1, inplace=True)

# 性别编码
if 'Sex' in train_data.columns:
train_data['Sex'] = train_data['Sex'].map({'male': 1, 'female': 0})

# 分离特征和目标变量
X = train_data.drop('Calories', axis=1)
y = train_data['Calories']

# 分割数据为训练集和验证集
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)

# 特征缩放
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_val_scaled = scaler.transform(X_val)

# 保存scaler,用于后续预测
joblib.dump(scaler, 'scaler.joblib')

return X_train_scaled, X_val_scaled, y_train, y_val

except Exception as e:
print(f"加载和预处理数据时出错:{e}")
raise

def optimize_decision_tree(X_train, X_val, y_train, y_val):
"""
优化决策树回归模型

Args:
X_train, X_val, y_train, y_val: 训练和验证数据

Returns:
tuple: (最佳模型, 最佳参数, 验证集RMSE, 验证集RMSLE)
"""
try:
print("开始优化决策树回归模型...")

# 参数网格 - 适度减少参数空间但保留关键选项
param_grid = {
'max_depth': [None, 10, 15, 20, 25], # 恢复None选项,对大数据集可能有益
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4],
'max_features': ['auto', 'sqrt', 'log2'] # 保留所有特征选择方法
}

# 基础模型
dt = DecisionTreeRegressor(random_state=42)

# 对于决策树,恢复使用GridSearchCV以确保找到最优参数
# 决策树训练速度相对较快,即使数据量大也可接受网格搜索
grid_search = GridSearchCV(
estimator=dt,
param_grid=param_grid,
cv=3, # 保持减少的交叉验证折数以节省时间
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=1
)

# 开始计时
start_time = time.time()

# 训练
grid_search.fit(X_train, y_train)

# 结束计时
end_time = time.time()
print(f"决策树网格搜索耗时: {end_time - start_time:.2f} 秒")

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

# 在验证集上评估
y_pred = best_dt.predict(X_val)
rmse = np.sqrt(mean_squared_error(y_val, y_pred))
r2 = r2_score(y_val, y_pred)
rmsle_score = rmsle(y_val, y_pred) # 计算RMSLE

print(f"决策树最佳参数: {best_params}")
print(f"验证集RMSE: {rmse:.4f}")
print(f"验证集R²: {r2:.4f}")
print(f"验证集RMSLE: {rmsle_score:.4f} (竞赛评估指标)")

# 绘制特征重要性
feature_importances = best_dt.feature_importances_
features = ['Sex', 'Age', 'Height', 'Weight', 'Duration', 'Heart_Rate', 'Body_Temp']

plt.figure(figsize=(10, 6))
sns.barplot(x=feature_importances, y=features)
plt.title('决策树 - 特征重要性')
plt.tight_layout()
plt.savefig('plots/dt_feature_importance.png')
plt.close()

# 保存模型
joblib.dump(best_dt, 'models/decision_tree_best.joblib')

return best_dt, best_params, rmse, rmsle_score

except Exception as e:
print(f"优化决策树模型时出错:{e}")
raise

def optimize_random_forest(X_train, X_val, y_train, y_val):
"""
优化随机森林回归模型

Args:
X_train, X_val, y_train, y_val: 训练和验证数据

Returns:
tuple: (最佳模型, 最佳参数, 验证集RMSE, 验证集RMSLE)
"""
try:
print("开始优化随机森林回归模型...")

# 由于随机森林计算开销大,即使数据量大也使用随机搜索而非网格搜索
param_distributions = {
'n_estimators': [50, 100, 150, 200], # 恢复200作为选项
'max_depth': [None, 10, 20, 30], # 恢复None和30选项,对大数据集可能有益
'min_samples_split': [2, 5, 10], # 恢复10作为选项
'min_samples_leaf': [1, 2, 4], # 恢复4作为选项
'max_features': ['auto', 'sqrt', 'log2'], # 恢复log2作为选项
'bootstrap': [True], # 使用bootstrap抽样
'max_samples': [0.7, 0.8, 0.9] # 控制每棵树使用的样本比例
}

# 基础模型 - 添加n_jobs参数使用多核CPU
rf = RandomForestRegressor(random_state=42, n_jobs=-1)

# 随机搜索 - 增加n_iter以提高搜索质量
random_search = RandomizedSearchCV(
estimator=rf,
param_distributions=param_distributions,
n_iter=20, # 恢复原始的20次尝试以提高搜索质量
cv=3, # 保持减少的交叉验证折数以节省时间
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=1,
random_state=42
)

# 开始计时
start_time = time.time()

# 训练
random_search.fit(X_train, y_train)

# 结束计时
end_time = time.time()
print(f"随机森林随机搜索耗时: {end_time - start_time:.2f} 秒")

# 获取最佳参数和模型
best_params = random_search.best_params_
best_rf = random_search.best_estimator_

# 在验证集上评估
y_pred = best_rf.predict(X_val)
rmse = np.sqrt(mean_squared_error(y_val, y_pred))
r2 = r2_score(y_val, y_pred)
rmsle_score = rmsle(y_val, y_pred) # 计算RMSLE

print(f"随机森林最佳参数: {best_params}")
print(f"验证集RMSE: {rmse:.4f}")
print(f"验证集R²: {r2:.4f}")
print(f"验证集RMSLE: {rmsle_score:.4f} (竞赛评估指标)")

# 绘制特征重要性
feature_importances = best_rf.feature_importances_
features = ['Sex', 'Age', 'Height', 'Weight', 'Duration', 'Heart_Rate', 'Body_Temp']

plt.figure(figsize=(10, 6))
sns.barplot(x=feature_importances, y=features)
plt.title('随机森林 - 特征重要性')
plt.tight_layout()
plt.savefig('plots/rf_feature_importance.png')
plt.close()

# 保存模型
joblib.dump(best_rf, 'models/random_forest_best.joblib')

return best_rf, best_params, rmse, rmsle_score

except Exception as e:
print(f"优化随机森林模型时出错:{e}")
raise

def optimize_xgboost(X_train, X_val, y_train, y_val):
"""
优化XGBoost回归模型

Args:
X_train, X_val, y_train, y_val: 训练和验证数据

Returns:
tuple: (最佳模型, 最佳参数, 验证集RMSE, 验证集RMSLE)
"""
try:
print("开始优化XGBoost回归模型...")

# 参数网格
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 7, 9],
'learning_rate': [0.01, 0.05, 0.1, 0.2],
'subsample': [0.8, 0.9, 1.0],
'colsample_bytree': [0.8, 0.9, 1.0],
'gamma': [0, 0.1, 0.2]
}

# 基础模型 - 添加n_jobs参数使用多核CPU和更快的tree_method
xgb_model = xgb.XGBRegressor(random_state=42, n_jobs=-1, tree_method='hist')

# 随机搜索
random_search = RandomizedSearchCV(
estimator=xgb_model,
param_distributions=param_grid,
n_iter=20, # 尝试20种组合
cv=5,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=1,
random_state=42
)

# 开始计时
start_time = time.time()

# 训练
random_search.fit(X_train, y_train)

# 结束计时
end_time = time.time()
print(f"XGBoost随机搜索耗时: {end_time - start_time:.2f} 秒")

# 获取最佳参数和模型
best_params = random_search.best_params_
best_xgb = random_search.best_estimator_

# 在验证集上评估
y_pred = best_xgb.predict(X_val)
rmse = np.sqrt(mean_squared_error(y_val, y_pred))
r2 = r2_score(y_val, y_pred)
rmsle_score = rmsle(y_val, y_pred) # 计算RMSLE

print(f"XGBoost最佳参数: {best_params}")
print(f"验证集RMSE: {rmse:.4f}")
print(f"验证集R²: {r2:.4f}")
print(f"验证集RMSLE: {rmsle_score:.4f} (竞赛评估指标)")

# 绘制特征重要性
feature_importances = best_xgb.feature_importances_
features = ['Sex', 'Age', 'Height', 'Weight', 'Duration', 'Heart_Rate', 'Body_Temp']

plt.figure(figsize=(10, 6))
sns.barplot(x=feature_importances, y=features)
plt.title('XGBoost - 特征重要性')
plt.tight_layout()
plt.savefig('plots/xgb_feature_importance.png')
plt.close()

# 保存模型
joblib.dump(best_xgb, 'models/xgboost_best.joblib')

return best_xgb, best_params, rmse, rmsle_score

except Exception as e:
print(f"优化XGBoost模型时出错:{e}")
raise

def optimize_linear_regression(X_train, X_val, y_train, y_val):
"""
优化线性回归模型

Args:
X_train, X_val, y_train, y_val: 训练和验证数据

Returns:
tuple: (训练好的模型, None, 验证集RMSE, 验证集RMSLE)
"""
try:
print("开始优化线性回归模型...")

# 线性回归没有超参数需要调优,直接训练模型
lr = LinearRegression()

# 开始计时
start_time = time.time()

# 训练
lr.fit(X_train, y_train)

# 结束计时
end_time = time.time()
print(f"线性回归训练耗时: {end_time - start_time:.2f} 秒")

# 在验证集上评估
y_pred = lr.predict(X_val)
rmse = np.sqrt(mean_squared_error(y_val, y_pred))
r2 = r2_score(y_val, y_pred)
rmsle_score = rmsle(y_val, y_pred) # 计算RMSLE

print(f"验证集RMSE: {rmse:.4f}")
print(f"验证集R²: {r2:.4f}")
print(f"验证集RMSLE: {rmsle_score:.4f} (竞赛评估指标)")

# 绘制系数
coefficients = lr.coef_
features = ['Sex', 'Age', 'Height', 'Weight', 'Duration', 'Heart_Rate', 'Body_Temp']

plt.figure(figsize=(10, 6))
sns.barplot(x=coefficients, y=features)
plt.title('线性回归 - 特征系数')
plt.tight_layout()
plt.savefig('plots/lr_coefficients.png')
plt.close()

# 保存模型
joblib.dump(lr, 'models/linear_regression.joblib')

return lr, None, rmse, rmsle_score

except Exception as e:
print(f"优化线性回归模型时出错:{e}")
raise

def compare_optimized_models(results):
"""
比较优化后的模型性能

Args:
results (dict): 包含各模型结果的字典

Returns:
str: 基于RMSLE的最佳模型名称
"""
try:
print("比较优化后的模型性能...")

# 提取模型名称和评估指标
model_names = list(results.keys())
rmse_values = [results[name]['rmse'] for name in model_names]
rmsle_values = [results[name]['rmsle'] for name in model_names]

# 找出基于RMSE的最佳模型
best_rmse_idx = np.argmin(rmse_values)
rmse_colors = ['green' if i == best_rmse_idx else 'blue' for i in range(len(model_names))]

# 找出基于RMSLE的最佳模型(竞赛评估指标)
best_rmsle_idx = np.argmin(rmsle_values)
rmsle_colors = ['green' if i == best_rmsle_idx else 'blue' for i in range(len(model_names))]

# 绘制RMSE比较图
plt.figure(figsize=(10, 6))
plt.bar(model_names, rmse_values, color=rmse_colors)
plt.title('优化后的模型性能比较 (RMSE)')
plt.xlabel('模型')
plt.ylabel('RMSE (越低越好)')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('plots/optimized_models_comparison_rmse.png')
plt.close()

# 绘制RMSLE比较图(竞赛评估指标)
plt.figure(figsize=(10, 6))
plt.bar(model_names, rmsle_values, color=rmsle_colors)
plt.title('优化后的模型性能比较 (RMSLE) - 竞赛评估指标')
plt.xlabel('模型')
plt.ylabel('RMSLE (越低越好)')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('plots/optimized_models_comparison_rmsle.png')
plt.close()

# 输出比较结果
print("\n模型性能比较 (RMSE):")
for i, name in enumerate(model_names):
print(f"{name}: {rmse_values[i]:.4f}" + (" (最佳)" if i == best_rmse_idx else ""))

print("\n模型性能比较 (RMSLE) - 竞赛评估指标:")
for i, name in enumerate(model_names):
print(f"{name}: {rmsle_values[i]:.4f}" + (" (最佳)" if i == best_rmsle_idx else ""))

# 如果RMSE和RMSLE选出的最佳模型不同,输出说明
if best_rmse_idx != best_rmsle_idx:
print(f"\n注意: 基于RMSE的最佳模型是 {model_names[best_rmse_idx]},而基于RMSLE的最佳模型是 {model_names[best_rmsle_idx]}。")
print("由于竞赛使用RMSLE作为评估指标,我们将选择基于RMSLE的最佳模型。")

# 返回基于RMSLE的最佳模型名称
return model_names[best_rmsle_idx]

except Exception as e:
print(f"比较优化后的模型时出错:{e}")
raise

def generate_submission_with_best_model(best_model_name):
"""
使用最佳模型生成提交文件

Args:
best_model_name (str): 最佳模型的名称
"""
try:
print(f"使用最佳模型 {best_model_name} 生成提交文件...")

# 加载最佳模型
model_file = f'models/{best_model_name}.joblib'
if not os.path.exists(model_file):
print(f"错误:找不到模型文件 {model_file}")
return

best_model = joblib.load(model_file)

# 加载测试数据
test_data = pd.read_csv('test.csv')
test_ids = test_data['id'].copy()

# 预处理测试数据
if 'id' in test_data.columns:
test_data.drop('id', axis=1, inplace=True)

if 'Sex' in test_data.columns:
test_data['Sex'] = test_data['Sex'].map({'male': 1, 'female': 0})

# 加载scaler
scaler = joblib.load('scaler.joblib')
X_test_scaled = scaler.transform(test_data)

# 预测
predictions = best_model.predict(X_test_scaled)

# 创建提交文件
submission = pd.DataFrame({
'id': test_ids,
'Calories': predictions
})

# 保存为CSV文件
submission_file = f'submission_{best_model_name}.csv'
submission.to_csv(submission_file, index=False)

print(f"提交文件已生成: {submission_file}")

except Exception as e:
print(f"生成提交文件过程中出错:{e}")
raise

def main():
"""
主函数
"""
try:
print("开始模型优化流程...")

# 创建保存模型和图形的文件夹
if not os.path.exists('models'):
os.makedirs('models')
if not os.path.exists('plots'):
os.makedirs('plots')

# 加载预处理后的数据
X_train, X_val, y_train, y_val = load_preprocessed_data()

# 优化各模型并存储结果
results = {}

# 决策树
dt_model, dt_params, dt_rmse, dt_rmsle = optimize_decision_tree(X_train, X_val, y_train, y_val)
results['decision_tree_best'] = {'model': dt_model, 'params': dt_params, 'rmse': dt_rmse, 'rmsle': dt_rmsle}

# 随机森林
rf_model, rf_params, rf_rmse, rf_rmsle = optimize_random_forest(X_train, X_val, y_train, y_val)
results['random_forest_best'] = {'model': rf_model, 'params': rf_params, 'rmse': rf_rmse, 'rmsle': rf_rmsle}

# XGBoost
xgb_model, xgb_params, xgb_rmse, xgb_rmsle = optimize_xgboost(X_train, X_val, y_train, y_val)
results['xgboost_best'] = {'model': xgb_model, 'params': xgb_params, 'rmse': xgb_rmse, 'rmsle': xgb_rmsle}

# 线性回归
lr_model, lr_params, lr_rmse, lr_rmsle = optimize_linear_regression(X_train, X_val, y_train, y_val)
results['linear_regression'] = {'model': lr_model, 'params': lr_params, 'rmse': lr_rmse, 'rmsle': lr_rmsle}

# 比较优化后的模型
best_model_name = compare_optimized_models(results)

# 使用最佳模型生成提交文件
generate_submission_with_best_model(best_model_name)

print("模型优化流程完成!")

# 绘制流程图的代码(由于Mermaid需要在Markdown中呈现,这里只生成文本文件)
generate_flow_diagrams()

except Exception as e:
print(f"模型优化过程中出错:{e}")
raise

def generate_flow_diagrams():
"""
生成各算法的流程图文本文件
"""
# 决策树流程图
dt_flow = """
```mermaid
flowchart TD
A[开始] --> B[加载数据]
B --> C[数据预处理]
C --> D[特征缩放]
D --> E[定义参数网格]
E --> F[创建决策树基础模型]
F --> G[使用GridSearchCV进行网格搜索]
G --> H[获取最佳参数]
H --> I[使用最佳参数训练模型]
I --> J[在验证集上评估]
J --> K[计算RMSE和R²]
K --> K1[计算RMSLE(竞赛评估指标)]
K1 --> L[绘制特征重要性]
L --> M[保存最佳模型]
M --> N[结束]

style A fill:#f9d5e5,stroke:#333,stroke-width:2px
style N fill:#f9d5e5,stroke:#333,stroke-width:2px
style G fill:#eeeeee,stroke:#333,stroke-width:2px
style I fill:#b5ead7,stroke:#333,stroke-width:2px
style K1 fill:#ffcc99,stroke:#333,stroke-width:2px
style M fill:#b5ead7,stroke:#333,stroke-width:2px
```
"""

# 随机森林流程图
rf_flow = """
```mermaid
flowchart TD
A[开始] --> B[加载数据]
B --> C[数据预处理]
C --> D[特征缩放]
D --> E[定义参数分布]
E --> F[创建随机森林基础模型]
F --> G[使用RandomizedSearchCV进行随机搜索]
G --> H[获取最佳参数]
H --> I[使用最佳参数训练模型]
I --> J[在验证集上评估]
J --> K[计算RMSE和R²]
K --> K1[计算RMSLE(竞赛评估指标)]
K1 --> L[绘制特征重要性]
L --> M[保存最佳模型]
M --> N[结束]

style A fill:#f9d5e5,stroke:#333,stroke-width:2px
style N fill:#f9d5e5,stroke:#333,stroke-width:2px
style G fill:#eeeeee,stroke:#333,stroke-width:2px
style I fill:#b5ead7,stroke:#333,stroke-width:2px
style K1 fill:#ffcc99,stroke:#333,stroke-width:2px
style M fill:#b5ead7,stroke:#333,stroke-width:2px
```
"""

# XGBoost流程图
xgb_flow = """
```mermaid
flowchart TD
A[开始] --> B[加载数据]
B --> C[数据预处理]
C --> D[特征缩放]
D --> E[定义参数网格]
E --> F[创建XGBoost基础模型]
F --> G[使用RandomizedSearchCV进行随机搜索]
G --> H[获取最佳参数]
H --> I[使用最佳参数训练模型]
I --> J[在验证集上评估]
J --> K[计算RMSE和R²]
K --> K1[计算RMSLE(竞赛评估指标)]
K1 --> L[绘制特征重要性]
L --> M[保存最佳模型]
M --> N[结束]

style A fill:#f9d5e5,stroke:#333,stroke-width:2px
style N fill:#f9d5e5,stroke:#333,stroke-width:2px
style G fill:#eeeeee,stroke:#333,stroke-width:2px
style I fill:#b5ead7,stroke:#333,stroke-width:2px
style K1 fill:#ffcc99,stroke:#333,stroke-width:2px
style M fill:#b5ead7,stroke:#333,stroke-width:2px
```
"""

# 线性回归流程图
lr_flow = """
```mermaid
flowchart TD
A[开始] --> B[加载数据]
B --> C[数据预处理]
C --> D[特征缩放]
D --> F[创建线性回归模型]
F --> I[训练模型]
I --> J[在验证集上评估]
J --> K[计算RMSE和R²]
K --> K1[计算RMSLE(竞赛评估指标)]
K1 --> L[绘制特征系数]
L --> M[保存模型]
M --> N[结束]

style A fill:#f9d5e5,stroke:#333,stroke-width:2px
style N fill:#f9d5e5,stroke:#333,stroke-width:2px
style I fill:#b5ead7,stroke:#333,stroke-width:2px
style K1 fill:#ffcc99,stroke:#333,stroke-width:2px
style M fill:#b5ead7,stroke:#333,stroke-width:2px
```
"""

# 模型比较流程图
compare_flow = """
```mermaid
flowchart TD
A[开始] --> B[加载所有优化后的模型]
B --> C[计算每个模型的RMSE]
C --> D[计算每个模型的RMSLE]
D --> E[绘制RMSE比较图]
E --> F[绘制RMSLE比较图]
F --> G[基于RMSLE选择最佳模型]
G --> H[使用最佳模型生成提交文件]
H --> I[结束]

style A fill:#f9d5e5,stroke:#333,stroke-width:2px
style I fill:#f9d5e5,stroke:#333,stroke-width:2px
style D fill:#ffcc99,stroke:#333,stroke-width:2px
style F fill:#ffcc99,stroke:#333,stroke-width:2px
style G fill:#ffcc99,stroke:#333,stroke-width:2px
```
"""

# 保存流程图文本
os.makedirs('flowcharts', exist_ok=True)

with open('flowcharts/decision_tree_flow.md', 'w', encoding='utf-8') as f:
f.write(dt_flow)

with open('flowcharts/random_forest_flow.md', 'w', encoding='utf-8') as f:
f.write(rf_flow)

with open('flowcharts/xgboost_flow.md', 'w', encoding='utf-8') as f:
f.write(xgb_flow)

with open('flowcharts/linear_regression_flow.md', 'w', encoding='utf-8') as f:
f.write(lr_flow)

with open('flowcharts/model_comparison_flow.md', 'w', encoding='utf-8') as f:
f.write(compare_flow)

print("流程图文本文件已生成在 'flowcharts' 文件夹中")

if __name__ == "__main__":
main()

六、模型应用

6.1 最终模型选择

XGBoost

6.2 模型实际应用场景

该预测模型可应用于以下场景:

  1. 健身应用中的卡路里消耗预测功能
  2. 智能手表、手环等可穿戴设备的能量消耗算法
  3. 个性化健身计划制定工具
  4. 健康管理系统的锻炼评估组件

七、数据分析结论

7.1 主要发现

通过本项目的数据分析和建模,我们得出以下主要发现:同上面

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env python
# coding: utf-8

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.font_manager import FontProperties
import matplotlib as mpl

# 设置中文显示
try:
# 尝试设置中文字体
font = FontProperties(fname=r"C:\Windows\Fonts\SimHei.ttf", size=14)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
except:
print("警告: 未能设置中文字体,图表中的中文可能显示为方块")

# 模型性能数据
models = ['XGBoost', '随机森林', '决策树', '线性回归'] # 调整顺序,确保随机森林显示正确
rmse_scores = [3.6246, 3.7526, 5.9721, 11.0588] # 对应调整顺序
r2_scores = [0.9966, 0.9964, 0.9908, 0.9684] # 对应调整顺序
rmsle_scores = [0.0622, 0.0627, 0.0896, 0.5652] # 对应调整顺序

# 创建DataFrame以便于数据处理
results_df = pd.DataFrame({
'模型': models,
'RMSE': rmse_scores,
'R平方': r2_scores,
'RMSLE (竞赛评估指标)': rmsle_scores
})

# 打印比较表格
print("模型性能比较表:")
print(results_df.to_string(index=False))

# 找出每个指标的最佳模型
best_rmse_model = models[np.argmin(rmse_scores)]
best_r2_model = models[np.argmax(r2_scores)]
best_rmsle_model = models[np.argmin(rmsle_scores)]

print("\n各指标最佳模型:")
print(f"RMSE 最低的模型: {best_rmse_model} (RMSE = {min(rmse_scores):.4f})")
print(f"R平方 最高的模型: {best_r2_model} (R平方 = {max(r2_scores):.4f})")
print(f"RMSLE 最低的模型: {best_rmsle_model} (RMSLE = {min(rmsle_scores):.4f})")

# 计算各模型的综合排名
# 为每个指标计算排名(RMSE和RMSLE越低越好,R²越高越好)
rmse_rank = np.argsort(rmse_scores).argsort()
r2_rank = np.argsort(-np.array(r2_scores)).argsort() # 负号使得R²越高排名越靠前
rmsle_rank = np.argsort(rmsle_scores).argsort()

# 计算平均排名
avg_rank = (rmse_rank + r2_rank + rmsle_rank) / 3.0

# 添加排名信息到DataFrame
results_df['RMSE排名'] = rmse_rank + 1 # +1使排名从1开始
results_df['R平方排名'] = r2_rank + 1
results_df['RMSLE排名'] = rmsle_rank + 1
results_df['平均排名'] = avg_rank + 1
results_df = results_df.sort_values('平均排名')

print("\n综合排名(考虑所有指标):")
print(results_df[['模型', 'RMSE排名', 'R平方排名', 'RMSLE排名', '平均排名']].to_string(index=False))

# 定义更好的颜色方案
colors = ['#3498db', '#2ecc71', '#9b59b6', '#e74c3c']

# 可视化比较
plt.figure(figsize=(18, 12))

# 1. RMSE比较
plt.subplot(2, 2, 1)
ax1 = sns.barplot(x='模型', y='RMSE', data=results_df, hue='模型', palette=colors, legend=False)
plt.title('各模型RMSE比较(越低越好)', fontproperties=font, fontsize=14)
plt.ylabel('RMSE', fontsize=12)
plt.xlabel('模型', fontsize=12)

# 获取Y轴的最大值,用于计算标签位置
y_max = max(rmse_scores) * 1.15
plt.ylim(0, y_max) # 设置Y轴范围

# 在柱状图上添加数值标签 - 统一在柱子上方
for i, v in enumerate(rmse_scores):
# 统一在柱子上方显示标签
label_height = v + (y_max * 0.02) # 柱子顶部上方2%的位置
ax1.text(i, label_height, f'{v:.4f}', ha='center', va='bottom', fontsize=10, color='black', fontweight='bold')

plt.grid(axis='y', linestyle='--', alpha=0.7)

# 2. R平方比较
plt.subplot(2, 2, 2)
ax2 = sns.barplot(x='模型', y='R平方', data=results_df, hue='模型', palette=colors, legend=False)
plt.title('各模型R平方比较(越高越好)', fontproperties=font, fontsize=14)
plt.ylabel('R平方', fontsize=12)
plt.xlabel('模型', fontsize=12)

# 设置Y轴范围,从0.95开始以突出差异
y_min_r2 = min(r2_scores) * 0.99
y_max_r2 = 1.001
plt.ylim(y_min_r2, y_max_r2)

# 在柱状图上添加数值标签 - 统一在柱子顶部上方
for i, v in enumerate(r2_scores):
# 统一在柱子上方显示标签
label_height = v + (y_max_r2 - v) * 0.3 # 柱子顶部上方
ax2.text(i, label_height, f'{v:.4f}', ha='center', va='bottom', fontsize=10, color='black', fontweight='bold')

plt.grid(axis='y', linestyle='--', alpha=0.7)

# 3. RMSLE比较(竞赛评估指标)
plt.subplot(2, 2, 3)
ax3 = sns.barplot(x='模型', y='RMSLE (竞赛评估指标)', data=results_df, hue='模型', palette=colors, legend=False)
plt.title('各模型RMSLE比较(竞赛评估指标,越低越好)', fontproperties=font, fontsize=14)
plt.ylabel('RMSLE', fontsize=12)
plt.xlabel('模型', fontsize=12)

# 获取Y轴的最大值,用于计算标签位置
y_max_rmsle = max(rmsle_scores) * 1.15
plt.ylim(0, y_max_rmsle) # 设置Y轴范围

# 在柱状图上添加数值标签 - 统一在柱子上方
for i, v in enumerate(rmsle_scores):
# 统一在柱子上方显示标签
label_height = v + (y_max_rmsle * 0.02) # 柱子顶部上方2%的位置
ax3.text(i, label_height, f'{v:.4f}', ha='center', va='bottom', fontsize=10, color='black', fontweight='bold')

plt.grid(axis='y', linestyle='--', alpha=0.7)

# 4. 综合排名比较
plt.subplot(2, 2, 4)
sorted_df = results_df.sort_values('平均排名', ascending=True)
ax4 = sns.barplot(x='模型', y='平均排名', data=sorted_df, hue='模型', palette=colors, legend=False)
plt.title('各模型综合排名(越低越好)', fontproperties=font, fontsize=14)
plt.ylabel('平均排名', fontsize=12)
plt.xlabel('模型', fontsize=12)

# 设置Y轴范围
y_max_rank = 5
plt.ylim(0, y_max_rank)

# 在柱状图上添加数值标签 - 统一在柱子上方
for i, v in enumerate(sorted_df['平均排名']):
# 统一在柱子上方显示标签
label_height = v + (y_max_rank * 0.05) # 柱子顶部上方5%的位置
ax4.text(i, label_height, f'{v:.2f}', ha='center', va='bottom', fontsize=10, color='black', fontweight='bold')

plt.grid(axis='y', linestyle='--', alpha=0.7)

# 添加总体标题
plt.suptitle('卡路里消耗预测模型性能比较', fontsize=18, fontproperties=font, y=0.98)
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 调整布局,为总标题留出空间

# 保存图表
plt.savefig('模型性能比较.png', dpi=300, bbox_inches='tight')

# 显示图表
plt.show()

# 最终结论
print("\n最终结论:")
best_model = results_df.iloc[0]['模型']
print(f"综合各项指标,{best_model}模型表现最优。")

if best_model == "XGBoost":
print("""
具体优势:
1. XGBoost模型RMSE最低(3.6246),意味着预测误差最小
2. R平方值最高(0.9966),说明模型解释了约99.66%的目标变量方差
3. RMSLE值最低(0.0622),在竞赛评估指标上表现最佳
4. 综合排名第一

推荐在最终提交中使用XGBoost模型预测结果。
""")
elif best_model == "随机森林":
print("""
具体优势:
1. 随机森林模型RMSE仅次于XGBoost(3.7526)
2. R平方值接近XGBoost(0.9964)
3. RMSLE值仅略高于XGBoost(0.0627)
4. 综合排名第二,但与XGBoost非常接近

推荐在最终提交中使用随机森林模型预测结果,或与XGBoost模型结果进行集成。
""")

7.2 未来改进方向

本项目还可在以下方面进行改进:

  1. 收集更多维度的数据,如锻炼类型、强度等
  2. 尝试更多高级算法,如神经网络、集成学习等
  3. 引入时间序列特征,考虑锻炼连续性的影响
  4. 结合领域知识,开发更专业的特征工程方法

上周刚搜到神经网络 今天上课就要求写了 哈哈哈哈…

那来看看神经网络代码:

说一下大概干了什么事情:

  1. 神经网络建模与优化:
  • 模型选择:我选择了Scikit-learn库中的MLPRegressor(多层感知机回归器)作为主要的神经网络模型。

  • 数据标准化:由于神经网络对输入特征的尺度非常敏感,在将数据送入模型前,我们使用了StandardScaler对所有特征进行了标准化处理。

  • 基准建立与调优:首先,我们训练了一个基础配置的神经网络模型,以了解其大致性能。然后,为了找到最优的模型配置,我们采用了网格搜索(GridSearchCV)的方法,对神经网络的关键超参数(如隐藏层结构、激活函数、正则化强度和初始学习率)进行了系统的调优。调优过程中使用了3折交叉验证,并以负均方误差作为评估标准。

  • 性能评估:对于调优后的最佳神经网络模型,我们在独立的验证集上评估了其性能,主要关注的指标包括均方根误差(RMSE)、R²决定系数以及均方根对数误差(RMSLE)。脚本中还计算了这些指标,并将它们与一个预先训练好的XGBoost模型的性能进行了对比。

2.结果可视化与分析:为了更直观地理解模型性能,脚本生成了多种可视化图表。包括:

  • 不同模型(基础神经网络、调优后神经网络、XGBoost)在各项评估指标上的对比条形图。

  • 调优后神经网络的预测值与实际值的对比散点图。

  • 模型预测残差的分布图和残差与预测值的关系图,用于分析模型的偏差和潜在问题。

3.模型应用与输出:最后,利用训练好的最佳神经网络模型和相应的特征缩放器,对测试数据集进行预测,并生成了符合竞赛提交格式要求的CSV文件。

具体分析:

train_neural_network 函数

一开始也是数据分割与标准化,这里可以看下面的完整代码,可以说一下的是:神经网络对输入特征的尺度非常敏感。如果特征尺度差异很大,训练过程可能会变得缓慢且不稳定。StandardScaler将每个特征转换为均值为0,标准差为1的分布。注意:缩放器(scaler)是在训练集上fit_transform的,然后用同样的缩放器在验证集(以及后续的测试集)上transform,以避免数据泄露。

基线模型训练

首先,训练了一个具有基础配置的MLPRegressor,以建立一个性能基准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 # ...
base_nn = MLPRegressor(
hidden_layer_sizes=(100,), # 一个隐藏层,100个神经元
activation='relu', # ReLU激活函数
solver='adam', # Adam优化器
alpha=0.0001, # L2正则化参数
max_iter=500, # 增加最大迭代次数
early_stopping=True, # 启用早停机制
validation_fraction=0.1, # 用于早停的验证集比例
tol=1e-4, # 收敛容忍度
random_state=42,
verbose=True # 显示训练进度
)

start_time = time.time()
base_nn.fit(X_train_scaled, y_train)
base_training_time = time.time() - start_time

y_pred_base = base_nn.predict(X_val_scaled)
base_mse = mean_squared_error(y_val, y_pred_base)
base_rmse = np.sqrt(base_mse)
base_mae = mean_absolute_error(y_val, y_pred_base)
base_r2 = r2_score(y_val, y_pred_base)

print("
基础神经网络模型评估结果:")
print(f" 均方根误差 (RMSE): {base_rmse:.4f}")
print(f" R² 分数: {base_r2:.4f}")
# ...

关键参数解释:

  • hidden_layer_sizes=(100,): 定义了一个包含100个神经元的隐藏层。
  • activation='relu': 使用ReLU作为激活函数,它有助于缓解梯度消失问题。
  • solver='adam': Adam是一种高效的优化算法。
  • alpha=0.0001: L2正则化参数,用于防止过拟合。
  • max_iter=500: 最大迭代次数。
  • early_stopping=True: 早停机制,当验证集性能不再提升时停止训练,防止过拟合。

训练完成后,模型在验证集上进行评估,计算RMSE(均方根误差)和R²(决定系数)等指标。

追求卓越:超参数调优 (GridSearchCV)

为了获得更好的性能,脚本使用GridSearchCV进行超参数调优。这会自动尝试参数网格中的不同组合,并通过交叉验证找到最佳配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ...
param_grid = {
'hidden_layer_sizes': [(50,), (100,), (50, 25)], # 不同的网络结构
'activation': ['relu', 'tanh'], # 不同的激活函数
'alpha': [0.0001, 0.001, 0.01], # 不同的正则化强度
'learning_rate_init': [0.001, 0.01] # 不同的学习率
}

nn_model = MLPRegressor(
solver='adam', max_iter=500, early_stopping=True,
validation_fraction=0.1, tol=1e-4, random_state=42, verbose=False
)

grid_search = GridSearchCV(
estimator=nn_model, param_grid=param_grid, cv=3,
scoring='neg_mean_squared_error', n_jobs=-1, verbose=2
)

grid_search.fit(X_train_scaled, y_train)

best_params = grid_search.best_params_
best_model = grid_search.best_estimator_
print(f"最佳参数: {best_params}")
# ...

GridSearchCV会尝试param_grid中定义的所有超参数组合。这里:

  • cv=3: 表示使用3折交叉验证。
  • scoring='neg_mean_squared_error': 评估指标为负均方误差(因为GridSearchCV试图最大化得分,而我们希望最小化MSE)。
  • n_jobs=-1: 使用所有可用的CPU核心并行计算。

最佳模型评估与比较

找到最佳超参数后,用最佳模型best_model在验证集上进行预测和评估。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ...
y_pred_best = best_model.predict(X_val_scaled)
best_mse = mean_squared_error(y_val, y_pred_best)
best_rmse = np.sqrt(best_mse)
best_mae = mean_absolute_error(y_val, y_pred_best)
best_r2 = r2_score(y_val, y_pred_best)

# 计算RMSLE (Root Mean Squared Logarithmic Error)
# RMSLE = sqrt( (1/n) * sum( (log(pred_i + 1) - log(actual_i + 1))^2 ) )
# 添加1是为了避免log(0)的问题,并且因为卡路里消耗量通常是正数
base_rmsle = np.sqrt(np.mean(np.power(np.log1p(y_val) - np.log1p(y_pred_base), 2))) # 修正:y_val和y_pred_base都应+1
best_rmsle = np.sqrt(np.mean(np.power(np.log1p(y_val) - np.log1p(y_pred_best), 2))) # 修正:y_val和y_pred_best都应+1
# 更正后的RMSLE计算 (如脚本中所示,但y_pred_base也应+1):
# base_rmsle = np.sqrt(np.mean(np.power(np.log1p(y_val + 1) - np.log1p(y_pred_base + 1), 2)))
# best_rmsle = np.sqrt(np.mean(np.power(np.log1p(y_val + 1) - np.log1p(y_pred_best + 1), 2)))


print("
最佳神经网络模型评估结果:")
print(f" 均方根误差 (RMSE): {best_rmse:.4f}")
print(f" R² 分数: {best_r2:.4f}")
# ...

我把前面四个模型最好的模型也就是XGBoost与之对比,引入了XGBOOST_RMSE, XGBOOST_R2, XGBOOST_RMSLE这些常量,它们代表了一个预先训练好的XGBoost模型的性能指标。这允许我将神经网络模型的性能与一个强大的基准模型进行比较。RMSLE(均方根对数误差)是另一个重要的回归评估指标,尤其适用于目标变量数量级跨度较大或我们更关注预测百分比误差的情况。

image-20250528124744490

image-20250528124806904

image-20250528124835120

image-20250528124855657

深入洞察:预测结果可视化

为了更深入地理解模型的行为,我做了以下可视化图表:

  1. 预测值 vs. 实际值:
1
2
3
4
5
6
7
8
9
10
11
12
# ...
# 随机选择100个样本点进行可视化
sample_indices = np.random.choice(len(y_val), min(100, len(y_val)), replace=False)
y_val_sample = y_val.iloc[sample_indices]
y_pred_sample = y_pred_best[sample_indices]

plt.figure(figsize=(10, 6))
plt.scatter(y_val_sample, y_pred_sample, alpha=0.7)
plt.plot([y_val_sample.min(), y_val_sample.max()], [y_val_sample.min(), y_val_sample.max()], 'r--') # 对角线
# ... 设置标题和标签 ...
show_figure(plt.gcf())
# ...

image-20250528125033313

残差分布: 残差是实际值与预测值之差。

1
2
3
4
5
6
7
8
# ...
residuals = y_val - y_pred_best
plt.figure(figsize=(10, 6))
sns.histplot(residuals, kde=True) # 直方图和核密度估计
plt.axvline(x=0, color='r', linestyle='--') # 零点线
# ... 设置标题和标签 ...
show_figure(plt.gcf())
# ...

image-20250528125132720

残差 vs. 预测值:

1
2
3
4
5
6
plt.figure(figsize=(10, 6))
plt.scatter(y_pred_best, residuals, alpha=0.7)
plt.axhline(y=0, color='r', linestyle='--') # 水平零线
# ... 设置标题和标签 ...
show_figure(plt.gcf())
# ...

image-20250528125224921

模型保存

训练和调优完成后,将最佳模型和特征缩放器保存到磁盘,以便将来重用,而无需重新训练。(详细见下面完整代码) 后面还有一个生成竞赛的提交文件的代码

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
卡路里消耗预测 - 神经网络模型实现
本代码使用Scikit-learn的MLPRegressor实现神经网络模型预测锻炼期间燃烧的卡路里
"""

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
from sklearn.neural_network import MLPRegressor
import warnings
import os
import time
from tqdm import tqdm
import joblib
import matplotlib.font_manager as fm
import platform

# XGBoost模型的性能指标(基于之前的训练结果)
XGBOOST_RMSE = 3.6246 # XGBoost模型的均方根误差
XGBOOST_R2 = 0.9966 # XGBoost模型的R²值
XGBOOST_RMSLE = 0.0622 # XGBoost模型的均方根对数误差

# 设置中文显示
try:
# 使用更可靠的方式设置中文字体
import matplotlib.font_manager as fm
import platform

# 常见中文字体列表,按照可用性顺序排列
chinese_fonts = [
'SimHei', 'Microsoft YaHei', 'SimSun', 'STSong', 'WenQuanYi Zen Hei',
'AR PL UMing CN', 'AR PL UKai CN', 'KaiTi', 'FangSong'
]

# 设置rcParams
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题

# 检查已有字体
system_fonts = set([f.name for f in fm.fontManager.ttflist])
print(f"系统内可用字体数量: {len(system_fonts)}")

# 查找可用的中文字体
available_chinese_font = None
for font in chinese_fonts:
if font in system_fonts:
available_chinese_font = font
print(f"找到可用中文字体: {font}")
break

if available_chinese_font:
plt.rcParams['font.sans-serif'] = [available_chinese_font] + plt.rcParams['font.sans-serif']
CHINESE_FONTS_AVAILABLE = True
else:
# 尝试重建字体缓存
print("未找到中文字体,尝试重建字体缓存...")
try:
from matplotlib.font_manager import _rebuild
_rebuild()
# 重新检查字体
system_fonts = set([f.name for f in fm.fontManager.ttflist])
for font in chinese_fonts:
if font in system_fonts:
available_chinese_font = font
print(f"重建缓存后找到可用中文字体: {font}")
plt.rcParams['font.sans-serif'] = [available_chinese_font] + plt.rcParams['font.sans-serif']
CHINESE_FONTS_AVAILABLE = True
break
except:
print("重建字体缓存失败,图表中文将使用FontProperties方式处理")
CHINESE_FONTS_AVAILABLE = False
except Exception as e:
# 如果设置失败,使用默认字体,不显示中文
print(f"字体设置失败,错误信息: {e}")
# 恢复默认设置
plt.rcParams['font.sans-serif'] = ['Arial']
# 修改图表标题和标签为英文
CHINESE_FONTS_AVAILABLE = False

# 设置matplotlib参数,使图表直接显示
plt.rcParams['interactive'] = True
plt.rcParams['figure.figsize'] = (10, 6) # 设置默认图表大小
plt.rcParams['figure.dpi'] = 100 # 设置默认DPI

# 忽略警告
warnings.filterwarnings('ignore')

# 设置随机种子,确保结果可重现
np.random.seed(42)

# 添加一个辅助函数,用于在条形图上显示数值
def add_value_labels(ax, spacing=5):
"""
在条形图上添加数值标签

Args:
ax: matplotlib轴对象
spacing: 标签与条形顶部的距离
"""
# 对于ax中的每个条形
for rect in ax.patches:
# 获取条形的高度
height = rect.get_height()
# 在条形顶部添加文本
ax.annotate(f'{height:.4f}', # 文本内容
xy=(rect.get_x() + rect.get_width() / 2, height), # 文本位置
xytext=(0, spacing), # 文本偏移
textcoords="offset points", # 偏移类型
ha='center', va='bottom') # 对齐方式

# 添加一个函数,用于显示图表
def show_figure(fig, filename=None):
"""
显示图表

Args:
fig: matplotlib图表对象
filename: 可选参数,被忽略
"""
plt.tight_layout()

# 显示图表
plt.show()

# 添加一个辅助函数,用于在绘图时使用中文字体
def use_chinese_font(ax, title=None, xlabel=None, ylabel=None):
"""
为图表设置中文字体

Args:
ax: matplotlib轴对象
title: 标题文本
xlabel: x轴标签文本
ylabel: y轴标签文本
"""
# 如果没有可用的中文字体,尝试使用FontProperties
if not CHINESE_FONTS_AVAILABLE:
# 尝试几个常用中文字体
font_paths = [
# 系统字体路径
'/usr/share/fonts/truetype/SimHei.ttf',
'/usr/share/fonts/chinese/SimHei.ttf',
'/usr/share/fonts/windows/SimHei.ttf',
'/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc',
# Windows常用路径
'C:/Windows/Fonts/SimHei.ttf',
'C:/Windows/Fonts/simhei.ttf',
# Mac常用路径
'/Library/Fonts/Arial Unicode.ttf',
'/System/Library/Fonts/PingFang.ttc'
]

# 尝试找到可用的字体文件
font_prop = None
for font_path in font_paths:
if os.path.exists(font_path):
try:
font_prop = fm.FontProperties(fname=font_path)
print(f"使用字体文件: {font_path}")
break
except:
continue

# 如果仍然没有找到字体,使用系统默认字体
if font_prop is None:
print("无法找到中文字体文件,将尝试使用系统默认字体")
font_prop = fm.FontProperties()

# 设置标题和标签
if title:
ax.set_title(title, fontproperties=font_prop)
if xlabel:
ax.set_xlabel(xlabel, fontproperties=font_prop)
if ylabel:
ax.set_ylabel(ylabel, fontproperties=font_prop)
else:
# 如果有可用的中文字体,直接设置标题和标签
if title:
ax.set_title(title)
if xlabel:
ax.set_xlabel(xlabel)
if ylabel:
ax.set_ylabel(ylabel)

# 主函数
def main(use_sampling=False):
if CHINESE_FONTS_AVAILABLE:
print("开始卡路里消耗预测神经网络模型训练...")
else:
print("Starting calorie consumption prediction neural network model training...")

# 1. 数据获取
train_data, test_data = load_data()

# 2. 数据预处理
train_data = preprocess_data(train_data)
test_data = preprocess_data(test_data, is_train=False)

# 3. 特征工程
train_data = feature_engineering(train_data)
test_data = feature_engineering(test_data)

# 4. 特征与目标变量分离
X_train, y_train = train_data.drop('Calories', axis=1), train_data['Calories']

# 5. 神经网络模型训练和评估
sample_limit = 100000 if use_sampling else None # 如果使用采样,设置为10万条记录
best_nn_model, best_nn_score, best_r2, best_rmsle = train_neural_network(X_train, y_train, sample_limit)

# 6. 使用最佳模型进行预测并生成提交文件
generate_submission(best_nn_model, test_data)

# 7. 输出神经网络与XGBoost模型的比较结果
if CHINESE_FONTS_AVAILABLE:
print("\n=== 神经网络与XGBoost模型性能比较 ===")
print("模型性能指标比较:")
print(f"{'模型名称':<20} {'RMSE':<10} {'R²':<10} {'RMSLE':<10}")
else:
print("\n=== Neural Network vs XGBoost Model Performance Comparison ===")
print("Model Performance Metrics:")
print(f"{'Model Name':<20} {'RMSE':<10} {'R²':<10} {'RMSLE':<10}")
print("-" * 50)
if CHINESE_FONTS_AVAILABLE:
print(f"{'神经网络(调优后)':<20} {best_nn_score:<10.4f} {best_r2:<10.4f} {best_rmsle:<10.4f}")
print(f"{'XGBoost':<20} {XGBOOST_RMSE:<10.4f} {XGBOOST_R2:<10.4f} {XGBOOST_RMSLE:<10.4f}")
else:
print(f"{'Neural Network (Tuned)':<20} {best_nn_score:<10.4f} {best_r2:<10.4f} {best_rmsle:<10.4f}")
print(f"{'XGBoost':<20} {XGBOOST_RMSE:<10.4f} {XGBOOST_R2:<10.4f} {XGBOOST_RMSLE:<10.4f}")
print("-" * 50)

# 比较结果分析
nn_better_rmse = best_nn_score < XGBOOST_RMSE
nn_better_r2 = best_r2 > XGBOOST_R2
nn_better_rmsle = best_rmsle < XGBOOST_RMSLE

print("\n结论:" if CHINESE_FONTS_AVAILABLE else "\nConclusion:")
if nn_better_rmse and nn_better_r2 and nn_better_rmsle:
print("神经网络模型在所有指标上均优于XGBoost模型。" if CHINESE_FONTS_AVAILABLE else "Neural Network model outperforms XGBoost model on all metrics.")
elif not nn_better_rmse and not nn_better_r2 and not nn_better_rmsle:
print("XGBoost模型在所有指标上均优于神经网络模型。" if CHINESE_FONTS_AVAILABLE else "XGBoost model outperforms Neural Network model on all metrics.")
else:
if CHINESE_FONTS_AVAILABLE:
print("模型性能比较:")
print(f"- RMSE: {'神经网络' if nn_better_rmse else 'XGBoost'} 模型表现更好")
print(f"- R²: {'神经网络' if nn_better_r2 else 'XGBoost'} 模型表现更好")
print(f"- RMSLE: {'神经网络' if nn_better_rmsle else 'XGBoost'} 模型表现更好")
else:
print("Model performance comparison:")
print(f"- RMSE: {'Neural Network' if nn_better_rmse else 'XGBoost'} model performs better")
print(f"- R²: {'Neural Network' if nn_better_r2 else 'XGBoost'} model performs better")
print(f"- RMSLE: {'Neural Network' if nn_better_rmsle else 'XGBoost'} model performs better")

if CHINESE_FONTS_AVAILABLE:
print("\n卡路里消耗预测神经网络模型训练完成!")
else:
print("\nCalorie consumption prediction neural network model training completed!")

# 加载数据
def load_data():
"""
加载训练集和测试集数据,支持本地路径和Kaggle路径

Returns:
tuple: (训练数据, 测试数据)
"""
try:
print("正在加载数据...")

# 定义可能的数据路径
possible_train_paths = [
'/kaggle/input/playground-series-s5e5/train.csv', # Kaggle路径
'train.csv' # 本地路径
]

possible_test_paths = [
'/kaggle/input/playground-series-s5e5/test.csv', # Kaggle路径
'test.csv' # 本地路径
]

# 尝试加载训练数据
train_data = None
for path in possible_train_paths:
try:
if os.path.exists(path):
train_data = pd.read_csv(path)
print(f"成功从 {path} 加载训练数据")
break
except Exception as e:
print(f"尝试从 {path} 加载训练数据失败: {e}")

if train_data is None:
raise FileNotFoundError("无法找到训练数据文件,请确保train.csv存在于正确位置")

# 尝试加载测试数据
test_data = None
for path in possible_test_paths:
try:
if os.path.exists(path):
test_data = pd.read_csv(path)
print(f"成功从 {path} 加载测试数据")
break
except Exception as e:
print(f"尝试从 {path} 加载测试数据失败: {e}")

if test_data is None:
raise FileNotFoundError("无法找到测试数据文件,请确保test.csv存在于正确位置")

print(f"训练集大小:{train_data.shape}, 测试集大小:{test_data.shape}")
return train_data, test_data
except Exception as e:
print(f"加载数据时出错:{e}")
raise

# 数据预处理
def preprocess_data(data, is_train=True):
"""
对数据进行预处理

Args:
data (DataFrame): 需要处理的数据
is_train (bool): 是否为训练数据

Returns:
DataFrame: 预处理后的数据
"""
try:
print("正在进行数据预处理...")

# 创建数据副本,避免修改原始数据
df = data.copy()

# 显示数据基本信息
print("\n数据基本信息:")
print(df.info())

# 显示数据统计摘要
print("\n数据统计摘要:")
print(df.describe())

# 检查缺失值
print("\n检查缺失值:")
missing_values = df.isnull().sum()
print(missing_values[missing_values > 0])

# 处理缺失值(如果有)
if df.isnull().sum().sum() > 0:
# 对数值型特征使用均值填充,分类特征使用众数填充
num_features = df.select_dtypes(include=['float64', 'int64']).columns
cat_features = df.select_dtypes(include=['object']).columns

for col in num_features:
if df[col].isnull().sum() > 0:
df[col].fillna(df[col].mean(), inplace=True)

for col in cat_features:
if df[col].isnull().sum() > 0:
df[col].fillna(df[col].mode()[0], inplace=True)

# 性别编码:将性别特征转换为数值
if 'Sex' in df.columns:
df['Sex'] = df['Sex'].map({'male': 1, 'female': 0})

# 删除ID列,因为它不是预测的特征
if 'id' in df.columns:
df = df.drop('id', axis=1)

# 显示处理后的数据信息
print("\n预处理后的数据信息:")
print(df.info())

return df

except Exception as e:
print(f"数据预处理过程中出错:{e}")
raise

# 特征工程
def feature_engineering(data):
"""
创建新特征以提高模型性能

Args:
data (DataFrame): 预处理后的数据

Returns:
DataFrame: 包含新特征的数据
"""
try:
print("正在进行特征工程...")
print(f"特征工程前数据形状: {data.shape}")

# 创建数据副本
df = data.copy()

# 记录原始特征列表
original_features = df.columns.tolist()

# 1. 创建BMI特征(体重指数)
df['BMI'] = df['Weight'] / ((df['Height']/100) ** 2)

# 2. 创建心率与年龄的比率
df['Heart_Rate_Age_Ratio'] = df['Heart_Rate'] / df['Age']

# 3. 创建锻炼强度指标
df['Exercise_Intensity'] = df['Heart_Rate'] * df['Duration'] / 100

# 4. 创建体温与心率的比率
df['Temp_Heart_Ratio'] = df['Body_Temp'] / df['Heart_Rate']

# 5. 体重与身高的比率
df['Weight_Height_Ratio'] = df['Weight'] / (df['Height']/100)

# 获取新创建的特征列表
new_features = [col for col in df.columns if col not in original_features]

print(f"特征工程完成,创建了 {len(new_features)} 个新特征:")
for feature in new_features:
print(f" - {feature}: 均值={df[feature].mean():.4f}, 标准差={df[feature].std():.4f}")

print(f"特征工程后数据形状: {df.shape}")

return df

except Exception as e:
print(f"特征工程过程中出错:{e}")
raise

# 神经网络模型训练和评估
def train_neural_network(X, y, sample_limit=None):
"""
训练神经网络模型并评估性能

Args:
X (DataFrame): 特征数据
y (Series): 目标变量(卡路里消耗量)
sample_limit (int, optional): 可选的数据采样限制,如果指定,将随机采样数据

Returns:
tuple: (最佳模型, 最佳得分, 最佳R², 最佳RMSLE)
"""
try:
print("正在进行神经网络模型训练和评估...")

# 创建保存图形的文件夹
if not os.path.exists('plots'):
os.makedirs('plots')

# 可选的数据采样
if sample_limit is not None and len(X) > sample_limit:
print(f"数据集较大,进行随机采样({sample_limit}/{len(X)}条记录)...")
sample_idx = np.random.choice(len(X), sample_limit, replace=False)
X = X.iloc[sample_idx].copy()
y = y.iloc[sample_idx].copy()
print(f"采样后数据形状: X={X.shape}, y={len(y)}")
else:
print(f"使用完整数据集: X={X.shape}, y={len(y)}")

# 分割数据为训练集和验证集
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)

# 特征缩放(神经网络对特征缩放非常敏感)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_val_scaled = scaler.transform(X_val)

# 保存特征名称,用于后续分析
feature_names = X.columns.tolist()

# 1. 首先创建一个基础神经网络模型,了解性能基线
print("\n训练基础神经网络模型...")

# 创建基础神经网络模型
# hidden_layer_sizes=(100,): 一个隐藏层,包含100个神经元
# activation='relu': 使用ReLU激活函数,这是目前最常用的激活函数,计算效率高且能解决梯度消失问题
# solver='adam': 使用Adam优化器,一种自适应学习率的优化算法,适合大多数问题
# alpha=0.0001: L2正则化参数,用于防止过拟合
# max_iter=500: 最大迭代次数,增加以确保收敛
# early_stopping=True: 启用早停机制,在验证集性能不再提升时停止训练
# validation_fraction=0.1: 用于早停的验证集比例
# tol=1e-4: 收敛容忍度,增加可以放宽收敛条件
# random_state=42: 随机种子,确保结果可重现
base_nn = MLPRegressor(
hidden_layer_sizes=(100,), # 一个隐藏层,100个神经元
activation='relu', # ReLU激活函数
solver='adam', # Adam优化器
alpha=0.0001, # L2正则化参数
max_iter=500, # 增加最大迭代次数
early_stopping=True, # 启用早停机制
validation_fraction=0.1, # 用于早停的验证集比例
tol=1e-4, # 收敛容忍度
random_state=42,
verbose=True # 显示训练进度
)

# 训练基础模型
start_time = time.time()
base_nn.fit(X_train_scaled, y_train)
base_training_time = time.time() - start_time

# 在验证集上进行预测
y_pred_base = base_nn.predict(X_val_scaled)

# 计算评估指标
base_mse = mean_squared_error(y_val, y_pred_base)
base_rmse = np.sqrt(base_mse)
base_mae = mean_absolute_error(y_val, y_pred_base)
base_r2 = r2_score(y_val, y_pred_base)

# 输出基础模型评估结果
print("\n基础神经网络模型评估结果:")
print(f" 均方误差 (MSE): {base_mse:.4f}")
print(f" 均方根误差 (RMSE): {base_rmse:.4f}")
print(f" 平均绝对误差 (MAE): {base_mae:.4f}")
print(f" R² 分数: {base_r2:.4f}")
print(f" 训练时间: {base_training_time:.2f}秒")

# 2. 接下来,通过网格搜索寻找最佳超参数
print("\n正在进行神经网络超参数调优...")
print("这个过程可能需要一些时间,请耐心等待...")

# 定义参数网格
# hidden_layer_sizes: 隐藏层的结构,例如(50,)表示一个隐藏层有50个神经元,(50, 25)表示两个隐藏层,分别有50和25个神经元
# activation: 激活函数,常用的有'relu'、'tanh'和'logistic'
# alpha: L2正则化参数,用于防止过拟合
# learning_rate_init: 初始学习率,控制权重更新的步长
param_grid = {
'hidden_layer_sizes': [(50,), (100,), (50, 25)], # 不同的网络结构
'activation': ['relu', 'tanh'], # 不同的激活函数
'alpha': [0.0001, 0.001, 0.01], # 不同的正则化强度
'learning_rate_init': [0.001, 0.01] # 不同的学习率
}

# 创建一个基础模型用于网格搜索
nn_model = MLPRegressor(
solver='adam', # 使用Adam优化器
max_iter=500, # 增加最大迭代次数
early_stopping=True, # 启用早停机制
validation_fraction=0.1, # 用于早停的验证集比例
tol=1e-4, # 收敛容忍度
random_state=42,
verbose=False # 不显示训练进度,因为GridSearchCV会训练多个模型
)

# 使用网格搜索寻找最佳参数
# 注意:这里使用了较少的参数组合以减少计算时间,实际应用中可以尝试更多组合
grid_search = GridSearchCV(
estimator=nn_model,
param_grid=param_grid,
cv=3, # 3折交叉验证
scoring='neg_mean_squared_error', # 使用负MSE作为评分标准(越高越好)
n_jobs=-1, # 使用所有可用的CPU核心
verbose=2 # 显示详细信息
)

# 执行网格搜索
start_time = time.time()
grid_search.fit(X_train_scaled, y_train)
grid_search_time = time.time() - start_time

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

print("\n网格搜索完成!")
print(f"最佳参数: {best_params}")
print(f"网格搜索耗时: {grid_search_time:.2f}秒")

# 使用最佳模型在验证集上进行预测
y_pred_best = best_model.predict(X_val_scaled)

# 计算最佳模型的评估指标
best_mse = mean_squared_error(y_val, y_pred_best)
best_rmse = np.sqrt(best_mse)
best_mae = mean_absolute_error(y_val, y_pred_best)
best_r2 = r2_score(y_val, y_pred_best)

# 输出最佳模型评估结果
print("\n最佳神经网络模型评估结果:")
print(f" 均方误差 (MSE): {best_mse:.4f}")
print(f" 均方根误差 (RMSE): {best_rmse:.4f}")
print(f" 平均绝对误差 (MAE): {best_mae:.4f}")
print(f" R² 分数: {best_r2:.4f}")

# 3. 可视化基础模型和最佳模型的性能比较
print("\n正在生成模型性能比较图...")

# 准备数据
models = ['Base Neural Network', 'Tuned Neural Network', 'XGBoost']
mse_values = [base_mse, best_mse, XGBOOST_RMSE**2] # 由于有RMSE值,计算MSE = RMSE^2
rmse_values = [base_rmse, best_rmse, XGBOOST_RMSE]
r2_values = [base_r2, best_r2, XGBOOST_R2]

# 计算RMSLE (如果基础模型和最佳模型没有RMSLE,可以简单计算一个近似值)
# 注意:这只是一个简单近似,实际上RMSLE的计算需要对数转换
base_rmsle = np.sqrt(np.mean(np.power(np.log1p(y_val + 1) - np.log1p(y_pred_base + 1), 2)))
best_rmsle = np.sqrt(np.mean(np.power(np.log1p(y_val + 1) - np.log1p(y_pred_best + 1), 2)))
rmsle_values = [base_rmsle, best_rmsle, XGBOOST_RMSLE]

# 绘制MSE比较图
plt.figure(figsize=(12, 6))
bars = plt.bar(models, mse_values, color=['blue', 'green', 'red'])
ax = plt.gca()
use_chinese_font(ax,
title='神经网络与XGBoost模型MSE比较' if CHINESE_FONTS_AVAILABLE else 'MSE Comparison: Neural Network vs XGBoost',
ylabel='MSE (越低越好)' if CHINESE_FONTS_AVAILABLE else 'MSE (Lower is better)')
plt.tight_layout()
# 高亮XGBoost条形
bars[2].set_alpha(0.7)
add_value_labels(plt.gca())
show_figure(plt.gcf(), 'models_mse_comparison.png')

# 绘制RMSE比较图
plt.figure(figsize=(12, 6))
bars = plt.bar(models, rmse_values, color=['blue', 'green', 'red'])
ax = plt.gca()
use_chinese_font(ax,
title='神经网络与XGBoost模型RMSE比较' if CHINESE_FONTS_AVAILABLE else 'RMSE Comparison: Neural Network vs XGBoost',
ylabel='RMSE (越低越好)' if CHINESE_FONTS_AVAILABLE else 'RMSE (Lower is better)')
plt.tight_layout()
# 高亮XGBoost条形
bars[2].set_alpha(0.7)
add_value_labels(plt.gca())
show_figure(plt.gcf(), 'models_rmse_comparison.png')

# 绘制R²比较图
plt.figure(figsize=(12, 6))
bars = plt.bar(models, r2_values, color=['blue', 'green', 'red'])
ax = plt.gca()
use_chinese_font(ax,
title='神经网络与XGBoost模型R²比较' if CHINESE_FONTS_AVAILABLE else 'R² Comparison: Neural Network vs XGBoost',
ylabel='R² (越高越好)' if CHINESE_FONTS_AVAILABLE else 'R² (Higher is better)')
plt.tight_layout()
# 高亮XGBoost条形
bars[2].set_alpha(0.7)
add_value_labels(plt.gca())
show_figure(plt.gcf(), 'models_r2_comparison.png')

# 绘制RMSLE比较图
plt.figure(figsize=(12, 6))
bars = plt.bar(models, rmsle_values, color=['blue', 'green', 'red'])
ax = plt.gca()
use_chinese_font(ax,
title='神经网络与XGBoost模型RMSLE比较' if CHINESE_FONTS_AVAILABLE else 'RMSLE Comparison: Neural Network vs XGBoost',
ylabel='RMSLE (越低越好)' if CHINESE_FONTS_AVAILABLE else 'RMSLE (Lower is better)')
plt.tight_layout()
# 高亮XGBoost条形
bars[2].set_alpha(0.7)
add_value_labels(plt.gca())
show_figure(plt.gcf(), 'models_rmsle_comparison.png')

# 4. 可视化预测结果与实际值的对比
plt.figure(figsize=(10, 6))

# 随机选择100个样本点进行可视化
sample_indices = np.random.choice(len(y_val), min(100, len(y_val)), replace=False)
y_val_sample = y_val.iloc[sample_indices]
y_pred_sample = y_pred_best[sample_indices]

# 绘制散点图
ax = plt.gca()
plt.scatter(y_val_sample, y_pred_sample, alpha=0.7)
plt.plot([y_val_sample.min(), y_val_sample.max()], [y_val_sample.min(), y_val_sample.max()], 'r--')
use_chinese_font(ax,
title='神经网络模型预测值与实际值对比' if CHINESE_FONTS_AVAILABLE else 'Neural Network Prediction vs Actual Values',
xlabel='实际卡路里消耗' if CHINESE_FONTS_AVAILABLE else 'Actual Calories',
ylabel='预测卡路里消耗' if CHINESE_FONTS_AVAILABLE else 'Predicted Calories')
plt.tight_layout()
show_figure(plt.gcf(), 'neural_network_prediction_comparison.png')

# 5. 可视化残差分布
residuals = y_val - y_pred_best

plt.figure(figsize=(10, 6))
sns.histplot(residuals, kde=True)
ax = plt.gca()
use_chinese_font(ax,
title='神经网络模型残差分布' if CHINESE_FONTS_AVAILABLE else 'Neural Network Residual Distribution',
xlabel='残差 (实际值 - 预测值)' if CHINESE_FONTS_AVAILABLE else 'Residual (Actual - Predicted)',
ylabel='频率' if CHINESE_FONTS_AVAILABLE else 'Frequency')
plt.axvline(x=0, color='r', linestyle='--')
plt.tight_layout()
show_figure(plt.gcf(), 'neural_network_residual_distribution.png')

# 6. 可视化残差与预测值的关系
plt.figure(figsize=(10, 6))
plt.scatter(y_pred_best, residuals, alpha=0.7)
plt.axhline(y=0, color='r', linestyle='--')
ax = plt.gca()
use_chinese_font(ax,
title='神经网络模型残差与预测值的关系' if CHINESE_FONTS_AVAILABLE else 'Neural Network Residuals vs Predicted Values',
xlabel='预测卡路里消耗' if CHINESE_FONTS_AVAILABLE else 'Predicted Calories',
ylabel='残差' if CHINESE_FONTS_AVAILABLE else 'Residual')
plt.tight_layout()
show_figure(plt.gcf(), 'neural_network_residual_vs_prediction.png')

# 保存最佳模型
if not os.path.exists('models'):
os.makedirs('models')
joblib.dump(best_model, 'models/best_nn_model.pkl')
print("\n最佳神经网络模型已保存到: models/best_nn_model.pkl")

# 保存特征缩放器,用于后续预测
joblib.dump(scaler, 'models/nn_scaler.pkl')
print("特征缩放器已保存到: models/nn_scaler.pkl")

return best_model, best_rmse, best_r2, best_rmsle

except Exception as e:
print(f"神经网络模型训练和评估过程中出错:{e}")
raise

# 生成提交文件
def generate_submission(model, test_data):
"""
使用训练好的模型生成提交文件

Args:
model: 训练好的模型
test_data (DataFrame): 测试数据
"""
try:
print("正在生成提交文件...")

# 定义可能的测试数据路径
possible_test_paths = [
'/kaggle/input/playground-series-s5e5/test.csv', # Kaggle路径
'test.csv' # 本地路径
]

# 尝试读取测试集ID
test_ids = None
for path in possible_test_paths:
try:
if os.path.exists(path):
test_ids = pd.read_csv(path)['id']
print(f"成功从 {path} 读取测试集ID")
break
except Exception as e:
print(f"尝试从 {path} 读取测试集ID失败: {e}")

if test_ids is None:
raise FileNotFoundError("无法找到测试数据文件,请确保test.csv存在于正确位置")

# 加载保存的特征缩放器
try:
scaler = joblib.load('models/nn_scaler.pkl')
print("已加载保存的特征缩放器")
except FileNotFoundError:
print("未找到保存的特征缩放器,将创建新的缩放器")
scaler = StandardScaler()
X_test_scaled = scaler.fit_transform(test_data)
else:
# 使用加载的scaler转换测试数据
X_test_scaled = scaler.transform(test_data)

# 预测
predictions = model.predict(X_test_scaled)

# 确保预测值为非负数(卡路里不可能为负)
predictions = np.maximum(predictions, 0)

# 创建提交文件
submission = pd.DataFrame({
'id': test_ids,
'Calories': predictions
})

# 保存为CSV文件
submission.to_csv('submission_nn.csv', index=False, encoding='utf-8')

print(f"提交文件已生成: submission_nn.csv,包含 {len(submission)} 个预测结果")

# 显示预测值的基本统计信息
print("\n预测结果统计信息:")
print(f"最小值: {predictions.min():.2f}")
print(f"最大值: {predictions.max():.2f}")
print(f"平均值: {predictions.mean():.2f}")
print(f"中位数: {np.median(predictions):.2f}")
print(f"标准差: {predictions.std():.2f}")

except Exception as e:
print(f"生成提交文件过程中出错:{e}")
raise

# 程序入口
if __name__ == "__main__":
try:
# 直接运行主函数,不进行命令行参数解析
if CHINESE_FONTS_AVAILABLE:
print("开始执行神经网络模型训练,与XGBoost模型性能比较...")
else:
print("Starting neural network model training and comparison with XGBoost model performance...")
main(use_sampling=False)
if CHINESE_FONTS_AVAILABLE:
print("程序执行完成!")
else:
print("Program execution completed!")
except Exception as e:
if CHINESE_FONTS_AVAILABLE:
print(f"程序执行过程中出错:{e}")
else:
print(f"Error during program execution: {e}")

image-20250521154842937

最后还有一个报告:

太长了我截取一部分:

image-20250528125654490

说明了什么呢?
一、训练过程监控
Iteration 9-15
loss = 6.75 → 5.65:模型预测误差逐渐减小
Validation score: 0.996 → 0.997:模型在验证集上表现稳定优化
二、停止训练原因
连续10次迭代验证分提升不足0.0001
这是防止无效训练的自动保护(类似考试连续10次成绩波动小于1分时终止复习)
可能暗示当前模型已达到最佳状态
三、性能评估指标
指标 含义 当前值 评价标准
MSE 平均平方误差 13.23 值越小越好
RMSE 误差的实际量级 3.64 可比对真实数据范围
MAE 平均绝对误差 2.16 忽略误差方向更直观
R² 模型解释数据变化的程度 0.9966 接近1为完美拟合
四、实际意义举例
假设预测卡路里消耗:

当真实消耗是 300千卡 时:
预测值可能在 300±3.64千卡 范围内(RMSE范围)
模型能解释 99.66% 的数据波动(R²接近满分)
总结
该模型已达到极优性能(R²>0.99)。训练耗时23秒属于高效范畴,适合生产环境部署。

附录

代码说明

本项目代码分为以下几个部分:

  1. calories_prediction.py:主程序,包含数据加载、预处理、探索性分析、建模和评估
  2. model_optimization.py:模型优化代码,包含超参数调优和最佳模型选择

算法流程图

决策树流程图

image-20250515150503097

随机森林:

image-20250515150549532

XGBoost:

image-20250515150627021

线性回归:

image-20250515150722380

模型比较流程图

image-20250515150818433

提交后排名

image-20250515123850765