用户新增预测挑战赛N1

364 阅读9分钟

🐋任务一:跑通Baseline

tip:baseline的意思是基线,这个概念是作为算法提升的参照物而存在的,相当于一个基础模型,可以以此为基准来比较对模型的改进是否有效。

🐋用户新增预测挑战赛注意事项

  • 大赛网址 用户新增预测挑战赛注意事项
  • 二分类问题——预测用户是否为新用户
  • 比赛要求:根据讯飞开放平台海量的应用数据作为训练样本,基于提供的样本构建模型,预测用户的新增情况
  • 评价标准为f1_score

F1 Score是评估分类模型性能的一项指标,它综合了模型的精确率和召回率。在一个二分类问题中,我们可以将数据分为Positive和Negative两类,而我们分类模型的任务就是将测试数据正确地分类到这两类之中。

假设你参加了一个考试,考试的题目分为正例和负例两种类型。在评估你的成绩时,可以把你的答对正例题目的个数看作是模型的召回率,也就是你所回答正确的正例题目占所有正例题目总数的比例。而你回答正确的正例题目个数与你自己总共回答的正例题目个数的比例,可以看作是模型的精确率。

以一个医疗诊断的实例为例,我们希望检测某种疾病是否存在。如果模型将所有样本判断为Negative,那么这样的模型精确率会很高,但召回率会很差;反之,如果模型将所有样本判断为Positive,那么这样的模型召回率会很高,但精确率会很差。因此,我们需要找到一个平衡点,同时考虑到精确率和召回率。

F1 Score就是基于这个想法计算出来的一个指标,它是精确率和召回率的调和平均数,公式如下:

F1 Score = 2 * (precision * recall) / (precision + recall)

其中,precision为模型的精确率,即被正确预测为Positive的样本所占的比例;recall为模型的召回率,即所有真实Positive样本被正确预测为Positive的比例。F1 Score的取值范围在0到1之间,分数越高表示模型性能越好,同时考虑到了精确率和召回率。

在实际机器学习应用中,F1 Score是评估模型性能的一个重要指标之一。它表现了分类器的整体效果,能够在平衡精确率和召回率之间找到一个合适的点,并考虑到了Positive和Negative两种数据的分布情况,因此得到了广泛的应用。

  • 赛题数据集 赛题数据由约62万条训练集、20万条测试集数据组成,共包含13个字段。

其中uuid为样本唯一标识,eid为访问行为ID,udmap为行为属性,其中的key1到key9表示不同的行为属性,如项目名、项目id等相关字段,common_ts为应用访问记录发生时间(毫秒时间戳),其余字段x1至x8为用户相关的属性,为匿名处理字段。target字段为预测目标,即是否为新增用户。

uuid样本唯一标识字段
eid访问行为字段
udmap行为属性
common_ts访问记录发生时间
x1-x8用户相关属性
target预测字段,该用户是否为新增用户

🐋实践

大佬提供了可以直接用的baseline,可以在百度AI Studio上运行

本人在代码中另加入了一些学习到的知识,对一些代码做了更详细的解释

# 1. 导入需要用到的相关库
# 导入库,import<库名>as<别名>
# 导入 pandas 库,用于数据处理和分析
import pandas as pd
# 导入 numpy 库,用于科学计算和多维数组操作
import numpy as np
# 从 sklearn.tree 模块中导入 DecisionTreeClassifier 类
# DecisionTreeClassifier 用于构建决策树分类模型
from sklearn.tree import DecisionTreeClassifier
#from sklearn.metrics import precision_score,recall_score,f1_score


# 2. 读取训练集和测试集
# pd.read_csv()是一个函数,字符串两侧单引号或双引号 划分区域,作为传入函数的参数
# 使用 read_csv() 函数从文件中读取训练集数据,文件名为 'train.csv'
train_data = pd.read_csv('用户新增预测挑战赛公开数据/train.csv')
# 使用 read_csv() 函数从文件中读取测试集数据,文件名为 'test.csv'
test_data = pd.read_csv('用户新增预测挑战赛公开数据/test.csv')


# 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

# 在 python 中, 形如 {'key1': 3, 'key2': 2} 格式的为字典类型对象, 通过key-value键值对的方式存储
# 而在本数据集中, udmap实际是以字符的形式存储, 所以处理时需要先用eval 函数将'udmap' 解析为字典

# 具体实现代码:
# 定义函数 udmap_onethot,接受一个字符串d,返回一个numpy数组,用于将 'udmap' 列进行 One-Hot 编码
def udmap_onethot(d):
    v = np.zeros(9)  # 创建一个长度为 9 的零数组
    if d == 'unknown':  # 如果 'udmap' 的值是 'unknown',则运行缩进的语句块
        return v  # 返回零数组,这是缩进的语句块
    d = eval(d)  # 如果d不是"umknown",那么它是一个转换成字符串的字典,将 'udmap' 的值解析为一个字典;eval()函数去掉引号
    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 编码后的数组

# 注: 对于不理解的步骤, 可以逐行 print 内容查看
# 第一步:将udmap_onehot函数运用于dataframe中"udmap"那一列的所有行(df[column_name.apply(...)])
# 第二步:将这些行纵向(vertical)叠起来(np.vstack(...)),得到一个矩阵
# 第三步:将矩阵变成一个dataframe对象
# 使用 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 特征与原始数据进行拼接,沿着列方向拼接
# 使用pd.concat 函数将原来的dataframe 与新生成的dataframe 连接
train_data = pd.concat([train_data, train_udmap_df], axis=1)
test_data = pd.concat([test_data, test_udmap_df], axis=1)

# 缺失值处理
train_data = train_data.fillna(0)
test_data = test_data.fillna(0)

# 4. 编码 udmap 是否为空
#判断每一行的udmap 字段是否为unknown,并将结果转换为int
# 使用比较运算符将每个样本的 '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)


# 5. 提取 eid 的频次特征
# 使用 map() 方法将每个样本的 eid 映射到训练数据中 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 的标签特征
# 计算每种eid对应target的平均值
# 使用 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())


# 7. 提取时间戳
# 提取每条信息common_ts字段对应的小时数
# 使用 pd.to_datetime() 函数将时间戳列转换为 datetime 类型
# 样例:1678932546000->2023-03-15 15:14:16
# 注: 需要注意时间戳的长度, 如果是13位则unit 为 毫秒, 如果是10位则为 秒, 这是转时间戳时容易踩的坑
# 函数中的 unit 为关键字参数,在传入参数时需要指定参数名称
# train_data.head(5),head就是头几行,显示数据的头几行,head(n:int+5),tail是尾部几行数据
# 查看数据的整体属性,train_data.info()
# 具体实现代码:
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

train_data['common_ts_day'] = train_data['common_ts'].dt.day.astype(np.float32)
test_data['common_ts_day'] = test_data['common_ts'].dt.day.astype(np.float32)

train_data['common_ts_week'] = train_data['common_ts'].dt.isocalendar().week.astype(np.float32)
test_data['common_ts_week'] = test_data['common_ts'].dt.isocalendar().week.astype(np.float32)

train_data.drop(['udmap','common_ts','uuid','target'],axis=1).astype(np.float32).isna().sum(axis=0)#=1去掉列不去掉行,isna判定,


# 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' 列作为模型的目标进行训练,拟合目标是target字段
)
#pred = clf.predict(train_data.drop(['udmap', 'common_ts', 'uuid', 'target'],axis=1))
#precision = precision_score(train_data["target"],pred)
#recall = recall(train_data["target"],pred)
#fi_score = f1_score(train_data["target"],pred)
#print(f"precision:\t{precision},\nrecall:\t\t{recall},\nfi_score:\t{fi_score}")

# 9. 对测试集进行预测,并保存结果到result_df中
# 创建一个DataFrame来存储预测结果,其中包括两列:'uuid' 和 'target'
# 'uuid' 列来自测试数据集中的 'uuid' 列,'target' 列将用来存储模型的预测结果
# 使用模型 clf 对测试数据集进行预测,并将预测结果存储在 'target' 列中})
# 10. 保存结果文件到本地
# 将结果DataFrame保存为一个CSV文件,文件名为 'submit.csv'
# 参数 index=None 表示不将DataFrame的索引写入文件中
pd.DataFrame({
    'uuid': test_data['uuid'],
    'target': clf.predict(test_data.drop(['udmap', 'common_ts', 'uuid'], axis=1))
}).to_csv('submit.csv', index=None)
  • 最后返回分数为0.72969
  • 代码中如何对udmp进行了人工的onehot:
    对于udmap列的值分为两种情况进行处理: 如果udmap的值是unknown,那么直接返回一个长度为9的零数组,表示该样本无法确定对应的one-hot编码 如果udmap的值不是unknown,将其解析为一个字典,然后使用循环遍历索引1到9,遍历key1到key9,对于每个键值对,判断当前键是否存在于字典中,如果存在,则将字典中对应键的值赋值给one-hot编码数组v,存储在对应的索引位置 最终返回one-hot编码后的数组,表示udmap列的one-hot编码结果。

🐋在看代码的过程,回顾了之前python字典的相关知识,以链接形式打开 python字典

🐋同时回顾了map()函数相关知识点 python零碎知识点

🐋关于在机器学习中处理缺失值的方法 机器学习中处理缺失值的方法

🐋关于机器学习中的特征选取相关知识 特征选取