202503031547 – 职位投递量预测模型训练

理论只有付诸实践才能真正掌握。本节我们将通过代码示例,一步步完成一个机器学习任务:根据历史职位信息预测职位投递量。这也是用户提供的脚本所要解决的问题。在动手之前,我们先明确问题和数据:

  • 背景:假设我们有一份包含历史职位发布信息的数据集,每条记录包括职位的各种属性(如职位类型、薪资范围、工作经验要求、公司规模等)以及该职位在一段时间内收到的求职者投递数量(投递量)。我们的目标是训练一个模型,输入职位的属性,输出预测的投递量。这可以帮助招聘网站或HR预估某职位的受欢迎程度,以便做推荐或调整策略。
  • 问题类型:投递量是一个非负整数,属于回归问题(输出连续值)。不过投递量的分布通常是右偏的(大部分职位投递量较低,少数特别热门职位投递量很高),有时我们会对投递量取对数或进行分类分段,但这里我们直接回归原值。
  • 评价指标:可以使用MAE、MSE、RMSE、R²、MAPE等多种指标来衡量预测误差和效果。业务上可能关心平均误差和整体趋势,所以MAE(平均绝对误差)和MAPE(平均绝对百分比误差)比较直观:MAE表示预测值与真实值差值的平均绝对值,MAPE表示平均相对误差的百分比​。我们后续也会计算这些指标来评估模型。

数据说明:由于此处我们不直接使用用户的私有数据集,我们选用一个公开的类似结构的数据或模拟数据进行演示。例如,为了方便,我们使用scikit-learn自带的回归数据集(如波士顿房价、糖尿病数据集等)或者构造一个模拟的数据集来走流程。这样可以重点关注代码和方法本身。

下面,我们将演示整个过程,包括:数据准备与预处理、模型训练与调参、模型评估。请注意,代码中的注释会用中文解释每一步骤的作用。

案例一步:数据加载与预处理

首先,我们需要获取数据。这里我们以加州房价数据集为例进行演练(虽然这是房价预测,但过程与投递量预测类似)。加州房价房价数据是一个经典的小型数据集,每条记录有8个特征(如房间数、人均收入、是否靠近河流等),标签是房屋的中位价格。我们用它来模拟投递量预测的过程。

import pandas as pd
from sklearn.datasets import fetch_california_housing

# 加载数据集(加州房价),并构建DataFrame
california = fetch_california_housing()
X = pd.DataFrame(california.data, columns=california.feature_names)
y = pd.Series(california.target, name='MedHouseVal') # MedHouseVal: 住宅中位价,十万美元

print("特征列:", X.columns.tolist())
print("样本数:", X.shape[0])
print(X.head(3)) # 查看前3条样本特征
print(y.head(3)) # 对应的标签(房价)

提示: 在实际的职位数据中,我们可能需要用pd.read_csv()读取CSV文件到DataFrame。本例中直接使用sklearn的数据集。输出会展示特征列名和样本的一些值,帮助我们了解数据格式。

接下来,我们进行简单的数据预处理。一般包括:处理缺失值、类别编码、特征缩放等。对于加州房价数据,特征都是数值型且没有缺失,故预处理主要是特征缩放。对数值特征做标准化(减均值除标准差)可以加速模型训练收敛、让模型对不同尺度特征更公平。对于树模型来说不敏感缩放,可以跳过标准化;但对神经网络或线性模型,标准化很重要。我们这里演示缩放,以便稍后也能方便地尝试神经网络。

[!note]

为什么需要特征缩放?想象一个购物场景

假设你是一个购物助手,要帮顾客评估商品的整体价值。你看到两个信息:

  1. 商品重量(以克为单位):500克
  2. 商品价格(以元为单位):50元

如果直接比较这两个数字,你会觉得重量(500)比价格(50)更重要,因为500比50大得多!但这显然是不对的,我们不能直接比较克和元,它们的单位和尺度完全不同。

标准化:把所有东西变成"相对大小"

想象你是小学老师,要评价学生的身高和体重:

  • 小明:身高150厘米,体重45公斤
  • 班级平均:身高140厘米,体重35公斤
  • 标准差:身高10厘米,体重5公斤

与其说"小明比平均高10厘米,重10公斤",不如说:

  • 身高:比平均高1个标准差 (150-140)/10 = 1
  • 体重:比平均重2个标准差 (45-35)/5 = 2

这样就能公平比较了!我们把所有测量都变成了"离平均值多少个标准差",这就是标准化。

为什么机器学习需要标准化?

想象你是跑步教练,要根据两个指标预测运动员能否完成马拉松:

  • 每天训练时间(小时):1-3之间
  • 心率(次/分):60-180之间

如果不做标准化,模型会认为心率比训练时间重要得多,因为心率的数值范围大得多!这就像用米和毫米测量距离,同样是1,但意义完全不同。

什么时候需要标准化?

想象三种不同的裁判:

  1. 树形裁判(决策树):

    • "如果身高超过170,给高分"
    • "如果体重超过60,给低分"
    • 他只关心"大于小于",不在意具体数值,所以不需要标准化
  2. 距离裁判(如K近邻、神经网络):

    • 需要计算"这个运动员和那个运动员有多像"
    • 如果不标准化,身高差1米和体重差1克会被当作一样重要!
    • 所以必须标准化
  3. 线性裁判(如线性回归、逻辑回归):

    • 要给每个指标分配权重
    • 如果不标准化,大尺度特征会"抢走"所有重要性
    • 所以必须标准化

实际操作很简单

就像把所有成绩都换算成百分制:

  • 减去平均分(让数据居中)
  • 除以标准差(让波动范围相似)

这样所有特征都变成了"标准分数",可以公平竞争了!

记住:标准化不改变数据的本质关系,只是让不同尺度的特征可以公平比较,就像把所有人的身高都换算成"高于平均多少",而不是有的用米有的用尺。

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# 将数据集拆分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print("训练集样本数:", X_train.shape[0], "测试集样本数:", X_test.shape[0])

# 数值特征标准化:对训练集拟合Scaler并应用于训练集和测试集
# 这里的关键点是:
# 1. 我们只在训练集上"学习"如何标准化( fit_transform )
# 2. 然后用相同的标准化参数处理测试集( transform )
# 这就像考试时,你不能根据考试题来调整学习方法,必须用之前学到的知识去应对。如果用测试集来"fit",就相当于提前看到了考试题,这会导致"数据泄露",使模型评估结果不可靠。
# 标准化的好处是让不同量级的特征在模型训练中获得公平对待,特别是对于像线性回归、SVM这样对特征尺度敏感的算法非常重要。

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # 注意:对训练集用fit_transform
X_test_scaled = scaler.transform(X_test) # 对测试集用transform(使用在训练集fit的参数)

[!note]

数据集拆分

想象你有一本习题集,如果你把所有习题都用来练习,然后考试时遇到完全相同的题目,你肯定会做得很好。但这并不能真正测试你的学习能力,对吧?

数据集拆分就是这个道理:

  • 训练集 就像是你用来学习的习题,模型通过这些数据学习规律
  • 测试集 就像是考试题,用来检验模型是否真正学会了,而不是简单记忆

特征标准化

想象你和朋友比赛,但是用不同的计分标准:你的分数范围是0-10分,而朋友的是0-100分。直接比较这些分数是不公平的,对吧?

特征标准化就是把所有特征转换到相同的尺度上:

  • 比如房价数据中,"收入"可能是几万美元,而"房间数"可能只有个位数
  • 标准化后,所有特征都被转换为均值为0、标准差为1的分布

通过train_test_split按80/20划分数据,这里设置random_state=42保证可重复。然后用StandardScaler对特征进行标准化处理。对于真实职位数据,如果有类别型特征,需要在建模前将其编码:可以用One-Hot编码(每个类别转为一个独热向量列)或Target编码(类别替换为平均目标值)等方法。用户提供的代码中对类别型特征就做了这两种编码,我们稍后详细解释。当前例子中无需类别编码,因为数据中没有类别特征。

[!note]

类别特征编码:把文字变成数字的艺术

想象你是一个房产中介,需要把房子的信息输入电脑做分析。其中有个特征是"房子朝向":

  • 南北通透
  • 朝南
  • 朝北
  • 朝东

但计算机只懂数字,不懂文字,我们该怎么办呢?这就需要"编码"了!

方法一:One-Hot编码(独热编码)

想象你在填写调查问卷,遇到这样的问题:
"你的房子朝向是?(请在对应选项打√)"

  • [ ] 南北通透
  • [ ] 朝南
  • [ ] 朝北
  • [ ] 朝东

One-Hot编码就是这个意思:

  • 南北通透 → [1, 0, 0, 0]
  • 朝南 → [0, 1, 0, 0]
  • 朝北 → [0, 0, 1, 0]
  • 朝东 → [0, 0, 0, 1]

就像投票表决一样,每个选项只能选一个,其他都是0。这样的好处是:

  1. 不同类别之间没有大小关系
  2. 模型可以分别学习每个类别的影响
  3. 特别适合决策树这样的模型

缺点是:如果类别太多(比如中国的城市),会产生很多列,增加计算量。

方法二:Target编码(目标编码)

想象你是房产经纪人,发现:

  • 南北通透的房子平均售价是100万
  • 朝南的房子平均售价是80万
  • 朝北的房子平均售价是60万
  • 朝东的房子平均售价是70万

Target编码就是用这个平均值来替代类别:

  • 南北通透 → 100
  • 朝南 → 80
  • 朝北 → 60
  • 朝东 → 70

这就像是用"实际效果"来代替类别,好处是:

  1. 不会产生太多新列
  2. 包含了类别与目标之间的关系
  3. 特别适合线性模型

但要小心:

  1. 可能带来数据泄露(使用了未来的信息)
  2. 需要处理新出现的类别
  3. 可能过度拟合

实际应用举例

假设我们预测职位的投递量:

职位类型是类别特征:

  • Java工程师
  • 产品经理
  • 销售代表
  • 人力资源

One-Hot编码就像是给每个职位一个专属档案:

Java工程师 → [1, 0, 0, 0]  # 第一个位置是1
产品经理   → [0, 1, 0, 0]  # 第二个位置是1
销售代表   → [0, 0, 1, 0]  # 第三个位置是1
人力资源   → [0, 0, 0, 1]  # 第四个位置是1

Target编码则看历史数据中各职位的平均投递量:

Java工程师 → 50  # 平均收到50份简历
产品经理   → 40  # 平均收到40份简历
销售代表   → 30  # 平均收到30份简历
人力资源   → 20  # 平均收到20份简历

就像把每个职位类型都转换成了一个"受欢迎度分数"。

小结

类别编码就像是翻译官:

  • One-Hot编码是"逐字翻译":保留所有信息,但可能很啰嗦
  • Target编码是"意译":简洁但可能损失一些细节

选择哪种方法要看具体情况:

  • 类别少,要保留所有信息 → One-Hot编码
  • 类别多,关注整体趋势 → Target编码
  • 有时两种方法都试试,看哪个效果好

记住:编码不是目的,而是让模型能更好地理解和学习数据的手段!

案例二步:模型训练与调参

现在我们准备训练模型。我们可以先从一个相对简单的模型开始,如随机森林回归,然后观察效果再做改进。随后也可以尝试XGBoost神经网络来比较。

from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

# 初始化随机森林回归模型
# 训练过程就像是这100位专家各自学习如何根据房屋特征预测价格。预测时,每位专家给出自己的估计,然后取平均值作为最终预测。

rf = RandomForestRegressor(n_estimators=100, random_state=42)

# 在训练集上训练模型
rf.fit(X_train_scaled, y_train)
# 在测试集上预测
y_pred = rf.predict(X_test_scaled)

# 计算评估指标
# 1. MAE (平均绝对误差) :预测值与实际值差的平均绝对值。比如,如果预测房价平均偏差5万美元,MAE就是0.5(因为数据集中是以10万美元为单位)。
# 2. RMSE (均方根误差) :先计算平方误差,再取均值,最后开方。它比MAE更重视大错误,因为平方会放大大的误差。
# 3. R² (决定系数) :衡量模型解释数据变异性的比例,范围通常在0到1之间:
# - R²=1:完美预测
# - R²=0:模型不比简单地预测平均值好
# - R²越接近1,模型越好
# 这些指标就像是给专家团队打分:MAE和RMSE告诉你预测偏离真实值多远(越小越好),R²告诉你模型捕捉到了多少房价变化的规律(越接近1越好)。

mae = mean_absolute_error(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
rmse = mse ** 0.5
r2 = r2_score(y_test, y_pred)
print(f"随机森林回归模型 - 测试集评估: MAE={mae:.2f}, RMSE={rmse:.2f}, R^2={r2:.2f}")

在以上代码中,我们训练了一个包含100棵树的随机森林。然后对测试集进行了预测,并计算了MAE、RMSE和R²指标。

指标解释

  • MAE(Mean Absolute Error)表示平均绝对误差,其单位与原始目标相同。MAE=5 表示预测值与真实值平均相差5个单位。在投递量场景下,如果MAE=5,意味着平均每个职位的预测投递量与实际投递量相差5次投递。
  • MSE(Mean Squared Error)是均方误差,由于平方项,对较大误差更敏感。RMSE是均方误差的平方根,恢复了原始单位,便于直观解释。MSE/RMSE对于评价模型更苛刻,因为大的错误会被平方放大​。RMSE一般 >= MAE,二者接近时说明误差比较稳定,没有大离谱误差;RMSE远大于MAE时说明存在少数较大误差。
  • R²(判定系数)表示模型对目标变量方差的解释比例,取值范围0到1。R²=1表示完美预测,R²=0表示模型的预测跟用均值猜测一样差​。例如R²=0.8表示模型解释了80%的方差。R²有时也会出现负值(表示模型甚至不如基准均值模型)。

除了上述指标,有时还会用MAPE(Mean Absolute Percentage Error),即平均百分比误差,它告诉我们预测值与真实值相差多少百分比​。例如MAPE=10%表示预测值平均偏离真实值10%。但MAPE要注意实际值为0时无法计算,或值很小时会放大百分比,所以要小心使用。用户代码中也计算了MAPE,我们稍后解释。

[!note]

评分指标:如何判断模型预测得好不好?

想象你是一个射箭教练,要评价学员的射箭水平。你会关注什么?

1. MAE(平均绝对误差)- "平均差多远"

想象你在教射箭:

  • 小明射了10箭,每箭距靶心的距离是:3厘米、2厘米、4厘米…
  • MAE就是把这些距离加起来除以10
  • 如果MAE=5厘米,就是说平均每箭偏离靶心5厘米

在预测投递量时:

  • MAE=5意味着平均每个职位的预测值与实际值相差5份简历
  • 简单直观,容易理解
  • 就像测量"平均每次射箭偏离多远"

2. MSE/RMSE(均方误差/均方根误差)- "特别讨厌大错误"

继续射箭的例子:

  • 小明:10箭都偏离5厘米
  • 小红:9箭都偏离1厘米,但有1箭偏离20厘米

用MAE来看:

  • 小明:平均偏离5厘米
  • 小红:平均偏离约3厘米((9×1 + 1×20)/10)
  • 看起来小红更好?

但用MSE(先平方再平均):

  • 小明:5² = 25(每箭都一样)
  • 小红:(9×1² + 1×20²)/10 = 40(那一箭严重失误被放大了!)

RMSE就是把MSE开平方,变回原来的单位。

这就像:

  • MAE是"一视同仁"的老师,关注平均表现
  • MSE/RMSE是"特别讨厌差生"的老师,一次大错误比多次小错误更糟糕

3. R²(判定系数)- "比全班平均水平好多少"

想象班级考试:

  • 全班平均60分,标准答案是100分
  • 小明考了80分
  • 那么R²就是看小明比"猜平均分"强多少

具体来说:

  • R²=0:跟猜平均分一样准
  • R²=1:完全预测对
  • R²=0.8:比猜平均分好80%
  • R²为负:还不如猜平均分

就像:

  • R²=0:闭着眼射箭
  • R²=1:百发百中
  • R²=0.8:比随机射箭准很多,但还不完美

4. MAPE(平均绝对百分比误差)- "差多少比例"

想象预测股票价格:

  • 预测100元,实际90元:差10%
  • 预测10元,实际9元:也差10%

MAPE看相对误差:

  • 不在乎绝对数值大小
  • 关注偏差占实际值的比例
  • MAPE=10%意味着平均预测偏差是实际值的10%

但要小心:

  • 实际值接近0时,百分比会变得很大
  • 实际值是0时,无法计算百分比
  • 就像用"偏离靶心的距离÷靶心到边缘的距离",当靶心在边缘时这个比例会变得很奇怪

总结

这些指标就像不同的裁判:

  • MAE是公平裁判:每个错误一视同仁
  • RMSE是严格裁判:特别惩罚大错误
  • R²是相对裁判:跟基准比较
  • MAPE是比例裁判:看相对差距

选择哪个指标?要看你更关心什么:

  • 在意平均表现 → 用MAE
  • 不能容忍大错误 → 用RMSE
  • 要和基准比较 → 用R²
  • 关注相对误差 → 用MAPE

就像射箭比赛可能同时需要多个裁判,实际应用中我们常常也会看多个指标,全面评估模型性能。

当我们运行上面的代码时,会得到随机森林的性能输出。假设输出为:随机森林回归模型 - 测试集评估: MAE=2.45, RMSE=3.35, R^2=0.85

这仅是示例数据的结果,意思是平均误差2.45千美元,R²=0.85表明模型解释了85%的房价波动,这对于房价预测来说相当不错。在投递量预测中,我们希望误差尽量小,R²越接近1越好。当然,实际业务中投递量的可预测性可能没有房价这么高,R²能达到0.6-0.7就已经很有价值了,需要具体分析领域。

案例三步:保存模型与加载预测

训练好的模型往往需要持久化保存,以便在真实系统中随时调用进行预测,而无需每次都重新训练。Python提供了多种序列化模型的方式。用户代码中使用了joblib库,这是sklearn推荐用于保存大模型(尤其是包含numpy数组的对象)的工具,比pickle更高效。

例如,我们可以将上面训练的随机森林模型保存到文件,然后在任何需要预测的脚本里。

# 添加模型保存功能
import joblib

# 创建模型保存目录
import os
model_dir = '/Users/lubingyang/Desktop/python-demo/算法demo/models'
if not os.path.exists(model_dir):
	os.makedirs(model_dir)

# 保存随机森林模型和标准化器
joblib.dump(best_model, f"{model_dir}/rf_model.joblib")
joblib.dump(scaler, f"{model_dir}/scaler.joblib")
print("模型已保存到", model_dir)

# 演示如何加载模型并进行预测
# 加载保存的模型和标准化器
loaded_rf = joblib.load(f"{model_dir}/rf_model.joblib")
loaded_scaler = joblib.load(f"{model_dir}/scaler.joblib")

# 模拟新数据(这里使用测试集的前3条记录作为示例)
new_data = X_test.iloc[:3]

# 使用加载的标准化器转换新数据
new_data_scaled = loaded_scaler.transform(new_data)

# 使用加载的模型进行预测
rf_predictions = loaded_rf.predict(new_data_scaled)

# 打印预测结果
print("\n预测示例(使用测试集前3条记录):")
print("实际值:", y_test.iloc[:3].values)
print("随机森林预测值:", rf_predictions)

即可载入模型并对新的特征数据进行预测。这样部署非常方便。对于同时需要保存数据预处理步骤(如StandardScaler)或者编码器的,也可以分别保存它们,然后加载后对新数据做相同转换。用户的代码中就保存了目标编码器和One-Hot编码器模型,以确保对新数据做一致的编码转换,这一点非常关键——训练和预测时的数据预处理步骤必须相同。

以上,我们通过一个模拟案例展示了从数据到模型再到预测的全流程。这为我们理解下一节的真实代码做了铺垫。

接下来我们将进入重点:详细解读用户提供的投递量预测脚本。这段代码综合运用了我们之前讨论的许多概念,包括数据清洗、类别编码、随机森林训练、模型评估和保存等。通过对代码逐步拆解讲解,您将看到前面讲的知识如何应用于实际项目,并学到一些额外的实战技巧和可能的优化方向。

投递量预测

202503031547 - 职位投递量预测模型训练

import warnings

warnings.filterwarnings(action="ignore")

  

import os

import sys

import datetime

import pandas as pd

import numpy as np

from sklearn.model_selection import train_test_split

from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor

from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

import joblib

import matplotlib.pyplot as plt

from category_encoders import TargetEncoder

from sklearn.preprocessing import OneHotEncoder

  

class DeliveryPredictor:

"""

投递量预测器:就像一个预测快递包裹数量的专家系统

主要完成三个任务:

1. 数据预处理:清洗和转换原始数据,让机器能够理解

2. 模型训练:学习历史数据中的规律

3. 预测新数据:使用学到的规律预测新情况

"""

def __init__(self, target_col, number_cols, type_cols, onehot_cols, model_tag, model_path="/Users/lubingyang/Desktop/python-demo/算法demo/models/"):

"""

初始化预测器,就像准备一个工具箱:

- target_col: 要预测的目标(投递量)

- number_cols: 数值特征(如薪资、职位数等)

- type_cols: 类别特征(如城市、职位类型等)

- onehot_cols: 需要独热编码的特征(如是否加急等)

- model_tag: 模型标签,用于区分不同版本

- model_path: 模型存储路径

"""

self.target_col = target_col

self.number_cols = number_cols

self.type_cols = type_cols

self.onehot_cols = onehot_cols

self.model_tag = model_tag

self.model_path = model_path

# target_encoded_cols

self.target_encoded_cols = [f"{i}_encoded" for i in self.type_cols]

  

# model save path

if not os.path.exists(self.model_path):

os.makedirs(self.model_path)

# model path

self.target_encoder_path = self.model_path + f"target_encoder-{self.model_tag}.model"

self.onehot_encoder_path = self.model_path + f"onehot_encoder-{self.model_tag}.model"

  

def model_select(self, model_tag="rf"):

"""

选择预测模型,就像选择预测专家:

- rf: 随机森林,像是100个不同的专家集体决策

- n_estimators=100: 召集100位专家

- min_samples_split=20: 每个决策至少要基于20个样本

- n_jobs=6: 6个专家同时工作(并行计算)

"""

if model_tag == "rf":

# model

self.model = RandomForestRegressor(

n_estimators=100,

random_state=42,

min_samples_split=20,

criterion="squared_error", # 将 "mse" 改为 "squared_error"

n_jobs=6

)

# model name

self.main_model_path = self.model_path + f"apply_count_predict-{model_tag}-{self.model_tag}.model"

  

def data_preprocess(self, df, is_train=False):

"""

数据预处理流水线,包含:

1. 填充缺失值

2. 目标编码

3. 独热编码

4. 特征整合

"""

# 1. 填充缺失值

df[self.number_cols] = df[self.number_cols].fillna(0).astype(int)

df[self.type_cols] = df[self.type_cols].fillna(0).astype(int)

df[self.onehot_cols] = df[self.onehot_cols].fillna(0).astype(int)

  

# 2. 目标编码

if is_train:

self.target_encoder = TargetEncoder(cols=self.type_cols, smoothing=1.0)

encode_df = self.target_encoder.fit_transform(df[self.type_cols], df[self.target_col])

joblib.dump(self.target_encoder, self.target_encoder_path)

else:

encode_df = self.target_encoder.transform(df[self.type_cols])

df[self.target_encoded_cols] = encode_df

  

# 3. 独热编码

if is_train:

self.onehot_encoder = OneHotEncoder(sparse_output=False, handle_unknown="ignore")

encoded_data = self.onehot_encoder.fit_transform(df[self.type_cols])

joblib.dump(self.onehot_encoder, self.onehot_encoder_path)

else:

encoded_data = self.onehot_encoder.transform(df[self.type_cols])

self.onehot_encoded_cols = self.onehot_encoder.get_feature_names_out(self.type_cols).tolist()

encoded_df = pd.DataFrame(encoded_data, columns=self.onehot_encoded_cols, index=df.index)

df = pd.concat([df, encoded_df], axis=1)

  

# 4. 特征整合

self.features = self.number_cols + self.target_encoded_cols + self.onehot_encoded_cols

print("features num:", len(self.features))

if is_train:

return df[self.features], df[self.target_col]

return df[self.features], None

  

def train(self, X, y, test_size=0.2):

"""模型训练和评估"""

# 初始化随机森林模型

self.model = RandomForestRegressor(

n_estimators=100,

random_state=42,

min_samples_split=20,

criterion="squared_error", # 更新为新版本参数

n_jobs=6

)

self.main_model_path = self.model_path + f"apply_count_predict-rf-{self.model_tag}.model"

  

# 划分数据集并训练

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, shuffle=True)

print(f"{datetime.datetime.now()} 开始训练模型...")

self.model.fit(X_train, y_train)

joblib.dump(self.model, self.main_model_path)

  

# 评估模型

predictions = self.model.predict(X_test)

self._evaluate(y_test, predictions)

  

return X_train, X_test, y_train, y_test

  
  

def load_models(self):

# 预测模型

self.model = joblib.load(self.main_model_path)

# 目标编码

self.target_encoder = joblib.load(self.target_encoder_path)

# onehot编码

self.onehot_encoder = joblib.load(self.onehot_encoder_path)

print("模型加载完成!")

  
  

def predict(self, X):

"""

使用训练好的模型进行预测

"""

return self.model.predict(X)

  
  

def _evaluate(self, y_true, y_pred):

"""

评估模型效果,就像给预测打分:

- MAE:平均预测偏差有多大

- RMSE:是否有预测严重偏离的情况

- R²:模型能解释多少变化

- MAPE:预测偏差的百分比

"""

print("y_true:", type(y_true), type(y_pred), y_true[:3], y_pred[:3])

epsilon = 1 # 避免除以 0

mape = np.mean(np.abs((y_true - y_pred) / np.maximum(y_true, epsilon))) * 100

  

print("Model Evaluation:")

print(f"MAE: {mean_absolute_error(y_true, y_pred):.2f}")

print(f"MSE: {mean_squared_error(y_true, y_pred):.2f}")

print(f"RMSE: {np.sqrt(mean_squared_error(y_true, y_pred)):.2f}")

print(f"R²: {r2_score(y_true, y_pred):.2f}")

print(f"MAPE: {mape:.2f}%") # 输出 MAPE 指标

  

# # 绘制预测结果

# plt.figure(figsize=(12, 6))

# plt.plot(y_true, label='Actual')

# plt.plot(y_pred, label='Predicted')

# plt.title("Delivery Volume Prediction")

# plt.xlabel("Samples")

# plt.ylabel("Deliveries")

# plt.legend()

# plt.show()

  

def features_importance(self, top_n=30, plot=True):

# 获取特征重要性

feature_importances = self.model.feature_importances_

  

# 打印特征重要性

feature_importance_df = pd.DataFrame({

'Feature': self.features,

'Importance': feature_importances

}).sort_values(by='Importance', ascending=False)

  

feature_importance_df.to_csv(self.model_path + f"feature_importance-{self.model_tag}.csv", index=False, encoding="utf-8")

feature_importance_df = feature_importance_df.iloc[:top_n]

  

if plot:

# 绘制平行柱状图

import matplotlib.pyplot as plt

plt.figure(figsize=(12, 6))

plt.barh(feature_importance_df['Feature'], feature_importance_df['Importance'], color='skyblue')

  

# 设置标题和标签

plt.xlabel('Feature Importance')

plt.title('Feature Importance Ranking')

plt.show()

  

if __name__ == "__main__":

# 1. 定义特征

label = "apply_count" # 预测目标:投递量

number_cols = ['valid_days', 'numofposition', 'entwork_max_salary',

'entwork_min_salary', 'entwork_grades']

type_cols = ['month_n', 'work_location_city', 'work_location_prov',

'work_position', 'work_type', 'entwork_grades',

'entwork_posit', 'entwork_jobyear', 'ent_level',

'ent_type', 'ent_scope', 'jobtype']

onehot_cols = ["month_n", 'is_urgent', 'is_high_level_work',

'name_high_level_work', 'is_new_work', 'modify_tag']

  

# 2. 创建预测器

predictor = DeliveryPredictor(

target_col=label,

number_cols=number_cols,

type_cols=type_cols,

onehot_cols=onehot_cols,

model_tag="0213"

)

# 3. 训练模型

# 加载和预处理数据

df = pd.read_csv('/Users/lubingyang/Desktop/python-demo/算法demo/投递量预测简版数据-20250212.csv')

df = df[df["incline_days"] == 0] # 去除异常数据

print("数据量:", len(df))

# 训练模型

X, y = predictor.data_preprocess(df, is_train=True)

predictor.model_select()

predictor.train(X, y)

predictor.features_importance(plot=False)

  

# 4. 预测示例

print("\n预测示例 >>>>>>")

test_df = df.sample(n=100) # 随机抽取100条数据测试

predictor.load_models()

X_test, _ = predictor.data_preprocess(test_df, is_train=False)

predictions = predictor.predict(X_test)

print(f"预测结果示例:", predictions[:3].tolist())

print("end!", datetime.datetime.now())
AI 知识库

202503031547 - 决策树与随机森林

2025-3-16 16:27:34

AI 知识库

202503161437 - SVG图片编辑和导出(SVG-edit在线编辑器)

2025-3-16 20:54:51