提示:本次笔记主要从理解赛题以及对于赛题的简易解决方向进行编写,如果想要了解更多本赛题资料,请关注后续文章!
目录
1.赛题介绍
赛题链接https://challenge.xfyun.cn/topic/info?type=subscriber-addition-prediction&ch=ymfk4uU
出题背景:
讯飞开放平台针对不同行业、不同场景提供相应的AI能力和解决方案,赋能开发者的产品和应用,帮助开发者通过AI解决相关实际问题,实现让产品能听会说、能看会认、能理解会思考。用户新增预测是分析用户使用场景以及预测用户增长情况的关键步骤,有助于进行后续产品和应用的迭代升级。
赛题任务:
本次大赛提供了讯飞开放平台海量的应用数据作为训练样本,参赛选手需要基于提供的样本构建模型,预测用户的新增情况。
数据集分析:
赛题数据由约62万条训练集、20万条测试集数据组成,共包含13个字段。
其中uuid为样本唯一标识,eid为访问行为ID,udmap为行为属性,其中的key1到key9表示不同的行为属性,如项目名、项目id等相关字段,common_ts为应用访问记录发生时间(毫秒时间戳),其余字段x1至x8为用户相关的属性,为匿名处理字段。target字段为预测目标,即是否为新增用户。
评价指标:
本次竞赛的评价标准采用f1_score,分数越高,效果越好
什么是f1_score
F1分数(F1 Score),是统计学中用来衡量二分类模型精确度的一种指标。它同时兼顾了分类模型的精确率和召回率。F1分数可以看作是模型精确率和召回率的一种调和平均,它的最大值是1,最小值是0。
为什么使用f1_score作为评价指标
为什么在本赛题里面利用f1_score作为评价指标,而不使用准确率或者ROC呢?对于本赛题的原始数据集其实他的类别分布是不太均匀的, F1 分数可以同时考虑了分类器的精确率和召回率,而准确率和 ROC 曲线只关注了真正例和假正例的比例。F1 分数将精确率和召回率结合起来,通过计算它们的调和平均值,从而提供了一个更全面的性能度量。它更加关注分类器对于少数类别的正确分类能力,并且在不同类别之间取得平衡。因此,在类别分布不均匀的情况下,F1 分数可以更好地反映分类器的实际性能。
计算公式
代码示例
from sklearn.metrics import f1_score
y_true = [0,0,0,1,1,2]
y_pred = [0,0,1,1,2,2]
print(f1_score(y_true, y_pred, average='weighted'))
print(f1_score(y_true, y_pred, average='macro'))
赛题分析
本赛题的任务是基于训练集的样本数据,构建一个模型来预测测试集中用户的新增情况。这是一个二分类任务,其中目标是根据用户的行为、属性以及访问时间等特征,预测该用户是否属于新增用户。具体来说,选手需要利用给定的数据集进行特征工程、模型选择和训练,然后使用训练好的模型对测试集中的用户进行预测,并生成相应的预测结果。
由于这是二分类问题,所以我们可以利用常见的分类模型来去做,比如逻辑回归决策树等。当然在本赛季的任务里面,我们还需要做一些别的任务,我们的原始数据集中存在一些匿名的字段,这就需要我们对于这些匿名字段进行编码,也就是说并不是所有的数据集都是可以用来进行训练的
步骤(思维导图)
思考:
这里从逻辑回归和决策树中选择,哪一个模型更加合适?
-
决策树能够处理非线性关系,并且可以自动捕获特征之间的交互作用。
-
它可以生成可解释的规则,有助于理解模型如何做出决策。
-
决策树能够处理不同类型的特征,包括分类和数值型。
具体解题步骤
导入库以及数据集
首先我们需要导入必要的库以及导入训练测试集
# 导入 pandas 库,用于数据处理和分析
import pandas as pd
# 导入 numpy 库,用于科学计算和多维数组操作
import numpy as np
# 从 sklearn.tree 模块中导入 DecisionTreeClassifier 类
# DecisionTreeClassifier 用于构建决策树分类模型
from sklearn.tree import DecisionTreeClassifier
# 2. 读取训练集和测试集
# 使用 read_csv() 函数从文件中读取训练集数据,文件名为 'train.csv'
train_data = pd.read_csv('用户新增预测挑战赛公开数据/train.csv')
# 使用 read_csv() 函数从文件中读取测试集数据,文件名为 'test.csv'
test_data = pd.read_csv('用户新增预测挑战赛公开数据/test.csv')
处理udmap字段
接着我们就需要对于其中的udmap字段进行处理
# 定义函数 udmap_onethot,用于将 'udmap' 列进行 One-Hot 编码
def udmap_onethot(d):
v = np.zeros(9) # 创建一个长度为 9 的零数组
if d == 'unknown': # 如果 'udmap' 的值是 'unknown'
return v # 返回零数组
# python 中, 形如 {'key1': 3, 'key2': 2} 格式的为字典类型, 通过key-value键值对的方式存储
# 而在本数据集中, udmap是以字符的形式存储, 所以处理时需要先用eval 函数将'udmap' 解析为字典
d = eval(d) # 将 'udmap' 的值解析为一个字典
for i in range(1, 10): # 遍历 'key1' 到 'key9', 注意, 这里不包括10本身
if 'key' + str(i) in d: # 如果当前键存在于字典中
v[i-1] = d['key' + str(i)] # 将字典中的值存储在对应的索引位置上
return v # 返回 One-Hot 编码后的数组
然后我们需要将我们定义的这个函数应用到此数据集中。
# 使用 apply() 方法将 udmap_onethot 函数应用于每个样本的 'udmap' 列
# np.vstack() 用于将结果堆叠成一个数组
train_udmap_df = pd.DataFrame(np.vstack(train_data['udmap'].apply(udmap_onethot)))
test_udmap_df = pd.DataFrame(np.vstack(test_data['udmap'].apply(udmap_onethot)))
# 为新的特征 DataFrame 命名列名
train_udmap_df.columns = ['key' + str(i) for i in range(1, 10)]
test_udmap_df.columns = ['key' + str(i) for i in range(1, 10)]
# 将编码后的 udmap 特征与原始数据进行拼接,沿着列方向拼接
train_data = pd.concat([train_data, train_udmap_df], axis=1)
test_data = pd.concat([test_data, test_udmap_df], axis=1)
# 4. 编码 udmap 是否为空
# 使用比较运算符将每个样本的 'udmap' 列与字符串 'unknown' 进行比较,返回一个布尔值的 Series
# 使用 astype(int) 将布尔值转换为整数(0 或 1),以便进行后续的数值计算和分析
train_data['udmap_isunknown'] = (train_data['udmap'] == 'unknown').astype(int)
test_data['udmap_isunknown'] = (test_data['udmap'] == 'unknown').astype(int)
其中apply
方法会对 'udmap'
列中的每个样本调用 udmap_onethot
函数,并将结果堆叠成一个数组,然后使用 pd.DataFrame
创建一个数据框来存储结果,train_udmap_df
是结果的数据框。
将经过编码的 train_udmap_df
和test_udmap_df
数据框与原始数据框 train_data
和 test_data
进行列方向的拼接(即横向拼接)。pd.concat
函数用于拼接数据框,它接受一个包含要拼接的数据框的列表作为第一个参数,axis=1
表示沿着列方向进行拼接。拼接后的结果重新赋值给 train_data
和 test_data
,即将编码后的 udmap
特征与原始数据拼接在一起,形成新的数据框。
然后将 'udmap'
列是否为空进行编码。首先,将每个样本的 'udmap'
列与字符串 'unknown'
进行比较,得到一个布尔值的 Series,其中对应的元素为 True 表示该样本的 'udmap'
列为空,为 False 表示该样本的 'udmap'
列不为空。然后,使用 .astype(int)
方法将布尔值的 Series 转换为整数类型(0 或 1),以便进行后续的数值计算和分析。True 转换为 1,False 转换为 0。
最后,将编码后的结果存储在新的列 'udmap_isunknown'
中,分别添加到 train_data
和 test_data
数据框中。
处理过后的udmap自传是这样的一种形式:
# 3. 将 'udmap' 列进行 One-Hot 编码
# 数据样例:
# udmap key1 key2 key3 key4 key5 key6 key7 key8 key9
# 0 {'key1': 2} 2 0 0 0 0 0 0 0 0
# 1 {'key2': 1} 0 1 0 0 0 0 0 0 0
# 2 {'key1': 3, 'key2': 2} 3 2 0 0 0 0 0 0 0
处理eid字段
接着我们对eid字段进行处理。
# 5. 提取 eid 的频次特征
# 使用 map() 方法将每个样本的 eid 映射到训练数据中 eid 的频次计数
# train_data['eid'].value_counts() 返回每个 eid 出现的频次计数
train_data['eid_freq'] = train_data['eid'].map(train_data['eid'].value_counts())
test_data['eid_freq'] = test_data['eid'].map(train_data['eid'].value_counts())
# 6. 提取 eid 的标签特征
# 使用 groupby() 方法按照 eid 进行分组,然后计算每个 eid 分组的目标值均值
# train_data.groupby('eid')['target'].mean() 返回每个 eid 分组的目标值均值
train_data['eid_mean'] = train_data['eid'].map(train_data.groupby('eid')['target'].mean())
test_data['eid_mean'] = test_data['eid'].map(train_data.groupby('eid')['target'].mean())
首先我们需要提取 'eid'
的频次特征。使用 train_data['eid'].value_counts()
计算训练数据中每个 'eid'
出现的频次计数。value_counts()
方法返回一个 Series,其中索引是唯一的 'eid'
值,而值是对应 'eid'
值的出现次数。然后,使用 map()
方法将每个样本的 'eid'
映射到训练数据中 'eid'
的频次计数。map()
方法根据给定的映射关系(即每个 'eid'
对应的频次计数)将每个样本的 'eid'
替换为其对应的频次。最后,将提取的 'eid'
频次特征存储在新的列 'eid_freq'
中,分别添加到 train_data
和 test_data
数据框中。
接着提取 'eid'
的标签特征。首先,使用 groupby()
方法按照 'eid'
进行分组,然后计算每个 'eid'
分组的目标值均值。train_data.groupby('eid')['target'].mean()
返回一个 Series,其中索引是唯一的 'eid'
值,而值是对应 'eid'
分组的目标值的均值。然后,使用 map()
方法将每个样本的 'eid'
映射到训练数据中 'eid'
的目标值均值。map()
方法根据给定的映射关系(即每个 'eid'
对应的目标值均值)将每个样本的 'eid'
替换为其对应的目标值均值。最后,将提取的 'eid'
的标签特征存储在新的列 'eid_mean'
中,分别添加到 train_data
和 test_data
数据框中。
处理时间戳字段
将时间戳列转换为 datetime 类型,并使用 dt.hour
属性提取日期时间中的小时信息。然后,将提取的小时信息存储在新的列 'common_ts_hour'
中,分别针对训练数据集和测试数据集进行操作
# 7. 提取时间戳
# 使用 pd.to_datetime() 函数将时间戳列转换为 datetime 类型
# 样例:1678932546000->2023-03-15 15:14:16
# 注: 需要注意时间戳的长度, 如果是13位则unit 为 毫秒, 如果是10位则为 秒, 这是转时间戳时容易踩的坑
# 具体实现代码:
train_data['common_ts'] = pd.to_datetime(train_data['common_ts'], unit='ms')
test_data['common_ts'] = pd.to_datetime(test_data['common_ts'], unit='ms')
# 使用 dt.hour 属性从 datetime 列中提取小时信息,并将提取的小时信息存储在新的列 'common_ts_hour'
train_data['common_ts_hour'] = train_data['common_ts'].dt.hour
test_data['common_ts_hour'] = test_data['common_ts'].dt.hour
加载决策树模型
# 8. 加载决策树模型进行训练(直接使用sklearn中导入的包进行模型建立)
clf = DecisionTreeClassifier()
# 使用 fit 方法训练模型
# train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1) 从训练数据集中移除列 'udmap', 'common_ts', 'uuid', 'target'
# 这些列可能是特征或标签,取决于数据集的设置
# train_data['target'] 是训练数据集中的标签列,它包含了每个样本的目标值
clf.fit(
train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1), # 特征数据:移除指定的列作为特征
train_data['target'] # 目标数据:将 'target' 列作为模型的目标进行训练
)
上面代码创建了一个决策树分类器对象 clf
,然后使用 fit
方法对模型进行训练。训练数据由 train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1)
提供特征数据,即移除了指定的列后的数据;目标数据由 train_data['target']
提供,即训练数据集中的标签列。
# 9. 对测试集进行预测,并保存结果到result_df中
# 创建一个DataFrame来存储预测结果,其中包括两列:'uuid' 和 'target'
# 'uuid' 列来自测试数据集中的 'uuid' 列,'target' 列将用来存储模型的预测结果
result_df = pd.DataFrame({
'uuid': test_data['uuid'], # 使用测试数据集中的 'uuid' 列作为 'uuid' 列的值
'target': clf.predict(test_data.drop(['udmap', 'common_ts', 'uuid'], axis=1)) # 使用模型 clf 对测试数据集进行预测,并将预测结果存储在 'target' 列中
})
# 10. 保存结果文件到本地
# 将结果DataFrame保存为一个CSV文件,文件名为 'submit.csv'
# 参数 index=None 表示不将DataFrame的索引写入文件中
result_df.to_csv('submit.csv', index=None)
最后创建了一个名为 result_df
的 DataFrame,其中包含两列:'uuid' 和 'target'。'uuid' 列的值来自测试数据集中的 'uuid' 列,'target' 列将用来存储模型的预测结果。使用 clf.predict()
方法对测试数据集进行预测,并将预测结果存储在 'target' 列中。将结果文件保存到本地。
最后运行结果:0.62722
上述即为我在对于赛题的理解分析以及对赛题的初步解决,在后面时间我将继续提升F1分数然后对代码进行修改与完善。