二分类问题和混淆矩阵
- 二分类问题:
二分类问题是一种分类问题,涉及将输入样本分为两个类别中的一个。例如,判断一封电子邮件是否为垃圾邮件(垃圾或非垃圾)、诊断患者是否患有某种疾病(患病或未患病)等。 - 混淆矩阵:
混淆矩阵是一种展示分类模型性能的表格。在二分类问题中,混淆矩阵由四个项组成:真正类(True Positive,TP)、真负类(True Negative,TN)、假正类(False Positive,FP)和假负类(False Negative,FN)。这些项表示以下内容:
-
- TP:实际为正类且被预测为正类的数量。
- TN:实际为负类且被预测为负类的数量。
- FP:实际为负类但被预测为正类的数量(误报)。
- FN:实际为正类但被预测为负类的数量(漏报)。
- Precision(精确率):
精确率是预测为正类的样本中实际为正类的比例。计算公式为:Precision = TP / (TP + FP)。精确率衡量了模型的预测准确性,即在所有预测为正类的样本中,有多少样本实际上是正类。 - Recall(召回率):
召回率是实际为正类的样本中被正确预测为正类的比例。计算公式为:Recall = TP / (TP + FN)。召回率也称为灵敏度或真阳性率,它衡量了模型对于实际正类样本的识别能力。
总结:
- 混淆矩阵提供了更详细的分类模型性能信息,可以从 TP、TN、FP 和 FN 的角度了解模型的表现。
- Precision 衡量了模型的预测准确性,即在预测为正类的样本中,有多少是真正的正类。
- Recall 衡量了模型对实际正类样本的识别能力,即有多少实际正类被正确预测为正类。
在二分类问题中,根据任务的不同,可能会更关注 Precision 或 Recall,或在两者之间进行权衡。
解题思路
- 问题分析
- 数据探索:数据类型的不同。
- 数据清洗:缺失/离群的数据,NaN,空值...(异常值)剔除
- 特征工程:寻求办法将冗余数据剔除掉,将有用的数据加以深化
- 模型训练:函数(输入+输出->模型的预测值->f1_score验证->改进输入数据/模型)
- 得到结果
baseline和f1_score
- Baseline(基准线):基准线是一个参考点,用于比较和评估模型的性能。通常情况下,基准线是一种简单、常见或者随机的方法,作为比较模型效果的标准。当开发一个新的模型时,比较模型的表现与基准线可以帮助判断模型是否真正有效。如果模型的性能不能超过基准线,那么这个模型可能不具有实际应用价值。
- F1-score:F1-score 是一个综合考虑 Precision(精确率)和 Recall(召回率)的度量指标,用于衡量二分类模型的性能。F1-score 是 Precision 和 Recall 的调和平均值,公式为:F1-score = 2 (Precision Recall) / (Precision + Recall)。F1-score 的取值范围在 0 到 1 之间,值越高表示模型的性能越好。F1-score 在数据不平衡或模型需在 Precision 和 Recall 之间权衡时特别有用,因为它能综合考虑两者。
代码
baseline
# 1. 导入需要用到的相关库 # 导入 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') # 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,用于将 'udmap' 列进行 One-Hot 编码 def udmap_onethot(d): v = np.zeros(9) # 创建一个长度为 9 的零数组 if d == 'unknown': # 如果 'udmap' 的值是 'unknown' return v # 返回零数组 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 编码后的数组 # 注: 对于不理解的步骤, 可以逐行 print 内容查看 # 使用 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) # 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()) # 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' 列作为模型的目标进行训练 ) # 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)
自己的改进代码
import pandas as pd import numpy as np from sklearn.tree import DecisionTreeClassifier # 读取数据集 train_data = pd.read_csv('用户新增预测挑战赛公开数据/train.csv') test_data = pd.read_csv('用户新增预测挑战赛公开数据/test.csv') # One-Hot 编码 udmap 列 def udmap_onethot(d): v = np.zeros(9) if d == 'unknown': return v d = eval(d) for i in range(1, 10): key = 'key' + str(i) if key in d: v[i-1] = d[key] return v 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))) 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)] train_data = pd.concat([train_data, train_udmap_df], axis=1) test_data = pd.concat([test_data, test_udmap_df], axis=1) # 编码 udmap 是否为空 train_data['udmap_isunknown'] = (train_data['udmap'] == 'unknown').astype(int) test_data['udmap_isunknown'] = (test_data['udmap'] == 'unknown').astype(int) # 提取 eid 的频次特征 eid_freq_mapping = train_data['eid'].value_counts() train_data['eid_freq'] = train_data['eid'].map(eid_freq_mapping) test_data['eid_freq'] = test_data['eid'].map(eid_freq_mapping) # 提取 eid 的标签特征 eid_mean_mapping = train_data.groupby('eid')['target'].mean() train_data['eid_mean'] = train_data['eid'].map(eid_mean_mapping) test_data['eid_mean'] = test_data['eid'].map(eid_mean_mapping) # 提取时间戳 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') train_data['common_ts_hour'] = train_data['common_ts'].dt.hour test_data['common_ts_hour'] = test_data['common_ts'].dt.hour # 训练决策树模型 clf = DecisionTreeClassifier() features = train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1) target = train_data['target'] clf.fit(features, target) # 预测测试集并保存结果 test_features = test_data.drop(['udmap', 'common_ts', 'uuid'], axis=1) result_df = pd.DataFrame({ 'uuid': test_data['uuid'], 'target': clf.predict(test_features) }) # 保存结果文件到本地 result_df.to_csv('submit.csv', index=None)