Datawhale机器学习笔记——2

提示:本系列是基于Datewhale 23年暑期夏令营第三期赛题,赛题具体内容见下方链接。

赛题链接:

https://challenge.xfyun.cn/topic/info?type=subscriber-addition-prediction&ch=ymfk4uU


前言

本文为可视化、交叉验证、特征工程,三部分的笔记


一、数据分析与可视化

1.对原始代码的解读

# 导入库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 读取训练集和测试集文件
train_data = pd.read_csv('用户新增预测挑战赛公开数据/train.csv')
test_data = pd.read_csv('用户新增预测挑战赛公开数据/test.csv')

# 相关性热力图
sns.heatmap(train_data.corr().abs(), cmap='YlOrRd')

# x7分组下标签均值
sns.barplot(x='x7', y='target', data=train_data)

这段代码的作用是可视化,下面逐行解读一下。

前4行导入了四个常用的数据分析和可视化常用的库;紧接着使用pa.read_csv()函数读取了数据集;

详细说明一下sns.heatmap(train_data.corr().abs(), cmap='Yl0rRd'):其中train_data.corr()是用来计算数据集各数字特征之间的皮尔逊相关系数矩阵;abs()取绝对值,变为0到1范围,主要关注相关性强度;heatmap()绘制热力图,矩形颜色表示数值大小;cmap='YlOrRd'设置颜色图谱,深色表示高相关,浅色低相关。因而总结来说就是用来绘制相关性热力图,颜色越深越相关。

sns.barplot(x='x7', y='target', data=train_data):其中sns.barplot()是用来绘制条形图的函数,里面的参数具体理解为x='x7':将x7特征作为x轴;y='target': 将目标变量target作为y轴;data=train_data:使用训练集数据画图。这点刚开始时没有理解清楚,详细点说时x7这个特征中包含数字0~9的数值,这行代码分别计算,x7=0时target的均值,x7=2时target的均值....(这样应该就可以理解了)

但在本地的pycharm中运行中,出现了udmap的数据不能被读取,因而做了一下调整:

构造了一个new_train_data,其中不包括udmap。这样就可以在本地跑通了。结果如下:

图1 皮尔逊相关系数热力图
图2 x7特征中各数值对应target的均值

 至此,可以看到图一中反应出:edi,common_ts,x2,x6,尤其时x7,与target最为相关。图二可知在x7中,当x7=1与target最为相关,可以合理猜测出当x7=1时target一定为1。

作业 

  • (1)字段x1至x8为用户相关的属性,为匿名处理字段。添加代码对这些数据字段的取值分析,那些字段为数值类型?那些字段为类别类型?

  • (2)对于数值类型的字段,考虑绘制在标签分组下的箱线图。

  • (3)从common_ts中提取小时,绘制每小时下标签分布的变化。

  • (4)对udmap进行onehot,统计每个key对应的标签均值,绘制直方图。

(1)字段x1至x8为用户相关的属性,为匿名处理字段。添加代码对这些数据字段的取值分析,那些字段为数值类型?那些字段为类别类型?

       解答过程: 看到这个题目,第一个问题是什么是数值类型,什么又是类别类型,因为从csv文件中来看它们都是一堆数字。通过查阅资料,解答为:a.数值类型,通常为连续的数值,如身高等,可以进行数学运算、排序。b.类别类型,一般是离散的,如性别、种族等,不能直接进行数学运算。区分方法主要是:数值可排序,类别不可排序;数值可计算,类别无法计算;数值表示量或程度,类别表示类型。所以我认为可以通过每个特征下,每个取值的频数来做判断:如果种类单一,仅有几个分类,那么可能是类别类型;若种类较多,且似乎遵循某种规律、或连续,那么大概率是数值类型。因而可以编写一下代码:

import pandas as pd
import matplotlib.pyplot as plt

# 设置绘图风格
plt.figure()
plt.subplots(4, 2)#规定拼接的样式为4行2列
# 读取数据
data = pd.read_csv('data/train.csv')
for i in range(1,9):
    # 提取x1列作为待画数据
    x1_data = data['x'+str(i)]

    # 绘制直方图
    plt.subplot(4, 2, i)#此次绘图在大图中的位置
    plt.hist(x1_data)#绘图
    plt.grid(alpha=0.5, linestyle='-.')#绘图样式
    # 添加标签
    plt.xlabel('x'+str(i))#x轴标签
    plt.ylabel('Fre')#y轴标签,代表频数
    #plt.title('Histogram of x'+str(i))#表名
   
# 显示图形
plt.subplots_adjust(hspace=1.0, wspace=1)
plt.show()

运行后结果如图3:

图3 x1~x7频数直方图

 答案:由图3结合上述规律可发现,x4,x5大概率为数值类型数据;x1,x2,x3,x6,x7,x8大概率为类别类型数据

(2)对于数值类型的字段,考虑绘制在标签分组下的箱线图。

补充知识:箱线图 [1]

箱线图用于可视化数据,可直观看出数据分布。它将数据分成四分位数,并根据从这些四分位数得出的五个数字对其进行汇总:

中位数:数据的中间值。标记为 Q2,描绘了第 50 个百分点。

第一个四分位数:“最小非异常值”和中位数之间的中间值。标记为 Q1,描绘了第 25 个百分点。

第三四分位数:“最大非异常值”和中位数之间的中间值。标记为 Q3,描绘了第 75 个百分点。

“最大非异常值”:按 (Q3 + 1.5*IQR) 计算。高于此值的所有值都被视为异常值。

“最小非异常值”:按 (Q1 – 1.5*IQR) 计算。低于此值的所有值都被视为异常值。

图4 箱线图示例&解析

解答过程:了解了相关知识后,再理解题目——绘制再标签分组下的箱线图。第一个问题是,“标签”具体是什么?回到最初的问题,赛题的目的是判断用户是否为新用户,那么第一个问题的答案可以是以target为标签,这样可以绘制出新用户的x4、x5数据的分布,和老用户的x4、x5数据的分布。可以帮助我们进一步洞察数据内在的规律。那么代码如下:

import pandas as pd
import matplotlib.pyplot as plt

# 读取数据
df = pd.read_csv('data/train.csv')

# 根据Label分组
df_0 = df[df['target'] == 0]
df_1 = df[df['target'] == 1]

# 绘制x4的箱线图
plt.boxplot([df_0['x4'], df_1['x4']], labels=['target 0', 'target 1'])
plt.title('x4 Boxplot by target')
plt.show()

# 绘制x5的箱线图
plt.boxplot([df_0['x5'], df_1['x5']], labels=['target 0', 'target 1'])
plt.title('x5 Boxplot by target')
plt.show()

首先解读df_0 = df[df['target'] == 0]; 其中df['target'] 表示选择DataFrame的'target'这一列,== 0是用来筛选在target这列中等于0的那些值;外面包裹的df[...],是将刚刚筛选过后的数据构成新的DataFrame;所以df_0,是一个只包含target=0的子数据集。同理df_1中只包含target=1。

其次plt.boxplot([df_0['x4'], df_1['x4']], labels=['Label 0', 'Label 1']),这个超长函数我们由外向里解读;plt.boxplot()是plt中的绘制箱线图的方法;其中[df_0['x4'], df_1['x4']]就是给这个方法提供的数据啦(画图总要有数据的),其实就是target=0的那些x4,和target=1的那些x4;labels=...是给上面给的两组数据起个名。同理那下面的plt.boxplot([df_0['x5'], df_1['x5']], labels=['Label 0', 'Label 1'])也是如出一辙。

最后plt.title()是给这个图起一个标题,plt.show()就是字面意思,将我们画好的图像展示出来。图5是画出来的结果

图5 x4及x5的箱线图

(3)从common_ts中提取小时,绘制每小时下标签分布的变化。

 解答过程:第一个问题是common_ts是以12小时制还是24小时制,所以先将它输出出来看一下,代码如下:

# 导入相关的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 使用pd.read_csv()函数读取数据
train_data = pd.read_csv('data/train.csv')
train_data['common_ts'] = pd.to_datetime(train_data['common_ts'], unit='ms')
train_data['common_ts_hour'] = train_data['common_ts'].dt.hour

new_train_data = pd.concat([train_data['common_ts_hour'], train_data['target']], axis=1)

pd.DataFrame(new_train_data).to_csv('datetime.csv', index=None)

这部分代码全部为 Datawhale机器学习笔记——1中的内容(每一句在1里都有解释),不过多说明。结果为:

图6 new_train_data的形式

 可见问题的答案是:24小时制。

那么下一步可以画一个类似图2的直方图即可,将上述代码改为:

# 导入相关的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 使用pd.read_csv()函数读取数据
train_data = pd.read_csv('data/train.csv')
train_data['common_ts'] = pd.to_datetime(train_data['common_ts'], unit='ms')
train_data['common_ts_hour'] = train_data['common_ts'].dt.hour

new_train_data = pd.concat([train_data['common_ts_hour'], train_data['target']], axis=1)
#pd.DataFrame(new_train_data).to_csv('datetime.csv', index=None)

sns.barplot(x='common_ts_hour', y='target', data=new_train_data)
plt.show()

有关sns.barplot()在图二中有详细讲解。绘制出来如图7所示

图7 每小时下标签分布的变化

 (4)对udmap进行onehot,统计每个key对应的标签均值,绘制直方图。

解答过程:第一步对udmap进行onehot。

补充知识onehot:对于一个分类特征,如果它有n个可能的取值,则用n位二进制向量进行编码,其中只有一位为1,其余为0。

代码如下:

import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('train.csv')

# onehot编码udmap列
df = pd.get_dummies(df, columns=['udmap'])

# 对每一列按key分组计算均值 
means = df.groupby('key').mean()

# 绘制每一列的直方图
means.T.plot(kind='bar')
plt.xlabel('Encoded udmap')
plt.ylabel('Mean of Label')
plt.title('Label Mean by Key')

plt.show()

 df = pd.get_dummies(df, columns=['udmap']),使用pandas中的get_dummies()对udmap列进行onehot编码。means = df.groupby('key').mean(),mean()求各组的均值,groupby('key')按key列分组。对求得的包含各列均值的数据,进行转置(.T),kind='bar'表示绘制条形图,x轴是原来的onehot编码列名,y轴是对应的key分组均值。

但是在运行时onehot向量太太太大了,编码后应该超过60万列,运行不了。(不知道是不是我的步骤有问题 T_T)


二、模型交叉检验

1.对原始代码的解读

import pandas as pd
import numpy as np
import lightgbm as lgb
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import MultinomialNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import classification_report

train_data = pd.read_csv('data/train.csv')
test_data = pd.read_csv('data/test.csv')

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')

def udmap_onethot(d):
    v = np.zeros(9)
    if d == 'unknown':
        return v

    d = eval(d)
    for i in range(1, 10):
        if 'key' + str(i) in d:
            v[i - 1] = d['key' + str(i)]

    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)

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())

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())

train_data['udmap_isunknown'] = (train_data['udmap'] == 'unknown').astype(int)
test_data['udmap_isunknown'] = (test_data['udmap'] == 'unknown').astype(int)

train_data['common_ts_hour'] = train_data['common_ts'].dt.hour
test_data['common_ts_hour'] = test_data['common_ts'].dt.hour

######################################################################
# 训练并验证SGDClassifier
pred = cross_val_predict(
    SGDClassifier(max_iter=100),
    train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1),
    train_data['target']
)
print( 'SGDClassifier\n', classification_report(train_data['target'], pred, digits=3))

# 训练并验证DecisionTreeClassifier
pred = cross_val_predict(
    DecisionTreeClassifier(),
    train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1),
    train_data['target']
)
print('DecisionTreeClassifier\n',classification_report(train_data['target'], pred, digits=3))

# 训练并验证MultinomialNB
pred = cross_val_predict(
    MultinomialNB(),
    train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1),
    train_data['target']
)
print( 'MultinomialNB\n', classification_report(train_data['target'], pred, digits=3))

# 训练并验证RandomForestClassifier
pred = cross_val_predict(
    RandomForestClassifier(n_estimators=5),
    train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1),
    train_data['target']
)
print('RandomForestClassifier\n', classification_report(train_data['target'], pred, digits=3))

主要部分是#号分隔线下方, 导入所需的Python库,包括pandas、numpy、sklearn等;读取训练集train.csv和测试集test.csv;对时间戳进行处理,转化为datetime格式;定义udmap_onethot函数,将udmap字符串转换为onehot编码的数组;构造udmap相关特征,并拼接到原始数据中;构造eid的频次和统计特征,加入到数据中;提取时间戳的小时特征;分别使用4种模型(SGDClassifier、DecisionTreeClassifier、MultinomialNB、RandomForestClassifier)进行交叉验证训练和预测;每个模型都打印出其classification_report,用于比较不同模型的精确率、召回率、F1分数等指标;通过结果可以发现效果最好的模型,并用于在测试集上做出预测。

作业

  • 在上面模型中哪一个模型的macro F1效果最好,为什么这个模型效果最好?

  • 使用树模型训练,然后对特征重要性进行可视化;

  • 再加入3个模型训练,对比模型精度;

图8 模型交叉检验结果

 在图8中:

  • support: 每个类别的样本数量。
  • precision: 精确率,对于每个类别的预测结果中有多少是真正正确的。
  • recall: 召回率,分类器找出的正例占所有正例的比例。
  • f1-score: f1值,精确率和召回率的调和平均数。
  • macro avg: 所有类别的平均值,计算时各类别同等对待。
  • weighted avg: 加权平均,计算时会考虑每个类别的样本数量。

(1)在上面模型中哪一个模型的macro F1效果最好,为什么这个模型效果最好?

答案:通过查表易知,DecisionTreeClassifier的macro F1分数最高,也就是决策树模型;它强于其他模型的原因可能是1.决策树对训练数据拟合程度高,它可以通过分支不断拟合训练数据,最大限度地降低训练误差。2.决策树可以通过分支切分更好模拟非线性关系,而在训练集中是存在非线性关系的。3.决策树可以自动学习不同特征间的组合和交叉作用。其他些模型对此没有直接建模。这可能让决策树更好地对数据进行建模,从而取得了更高的 macro F1。

(2)使用树模型训练,然后对特征重要性进行可视化;

import pandas as pd
import numpy as np
import lightgbm as lgb
from sklearn.linear_model import LogisticRegression
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import MultinomialNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt

train_data = pd.read_csv('data/train.csv')

# 定义features
features = train_data.columns.drop(['udmap', 'common_ts', 'uuid', 'target'])

# 训练决策树模型
dt_model = DecisionTreeClassifier()
dt_model.fit(train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1), train_data['target'])

# 计算特征重要性
importances = dt_model.feature_importances_
indices = np.argsort(importances)[::-1]

# 绘制特征重要性柱状图
plt.figure(figsize=(10, 6))
plt.title('Feature Importance')
plt.bar(range(len(indices)), importances[indices], align='center')
plt.xticks(range(len(indices)), [features[i] for i in indices])
plt.xlim([-1, len(importances)])
plt.show()

 这段代码中:

1.定义features,使用drop方法去掉'udmap','common_ts','uuid','target'几个不需要的列。

2.训练决策树模型,初始化DecisionTreeClassifier,使用fit方法,以特征和目标标签训练模型。

3.计算特征重要性:从训练好的模型中直接调用feature_importances_属性,获得每个特征的重要性得分。使用np.argsort将重要性指数排序,从小到大。

4.最终绘制柱状图如下:

图9 特征重要性可视化

 (3)再加入3个模型训练,对比模型精度;

# 省略数据处理过程

# 定义所有的模型
svc_model = SVC()  
mlp_model = MLPClassifier()
xgb_model = XGBClassifier()

# 训练和评估所有的模型
pred = cross_val_predict(SGDClassifier(), train_X, train_y)
print(classification_report(train_y, pred))

pred = cross_val_predict(DecisionTreeClassifier(), train_X, train_y)  
print(classification_report(train_y, pred))

pred = cross_val_predict(MultinomialNB(), train_X, train_y)
print(classification_report(train_y, pred))

pred = cross_val_predict(RandomForestClassifier(), train_X, train_y)
print(classification_report(train_y, pred))

pred = cross_val_predict(svc_model, train_X, train_y)
print(classification_report(train_y, pred))

pred = cross_val_predict(mlp_model, train_X, train_y)
print(classification_report(train_y, pred))  

pred = cross_val_predict(xgb_model, train_X, train_y)
print(classification_report(train_y, pred))

三、特征工程


train_df['hour'] = train_df['time'].apply(lambda x:int(x.split(':')[0]))
test_df['hour'] = test_df['time'].apply(lambda x:int(x.split(':')[0]))

train_df['minute'] = train_df['time'].apply(lambda x:int(x.split(':')[1]))
test_df['minute'] = test_df['time'].apply(lambda x:int(x.split(':')[1]))


# 为了保证时间顺序的一致性,故进行排序
train_df = train_df.sort_values(['file','time'])
test_df = test_df.sort_values(['file','time'])

train_df['wap1'] = (train_df['n_bid1']*train_df['n_bsize1'] + train_df['n_ask1']*train_df['n_asize1'])/(train_df['n_bsize1'] + train_df['n_asize1'])
test_df['wap1'] = (test_df['n_bid1']*test_df['n_bsize1'] + test_df['n_ask1']*test_df['n_asize1'])/(test_df['n_bsize1'] + test_df['n_asize1'])

train_df['wap2'] = (train_df['n_bid2']*train_df['n_bsize2'] + train_df['n_ask2']*train_df['n_asize2'])/(train_df['n_bsize2'] + train_df['n_asize2'])
test_df['wap2'] = (test_df['n_bid2']*test_df['n_bsize2'] + test_df['n_ask2']*test_df['n_asize2'])/(test_df['n_bsize2'] + test_df['n_asize2'])

train_df['wap_balance'] = abs(train_df['wap1'] - train_df['wap2'])
train_df['price_spread'] = (train_df['n_ask1'] - train_df['n_bid1']) / ((train_df['n_ask1'] + train_df['n_bid1'])/2)
train_df['bid_spread'] = train_df['n_bid1'] - train_df['n_bid2']
train_df['ask_spread'] = train_df['n_ask1'] - train_df['n_ask2']
train_df['total_volume'] = (train_df['n_asize1'] + train_df['n_asize2']) + (train_df['n_bsize1'] + train_df['n_bsize2'])
train_df['volume_imbalance'] = abs((train_df['n_asize1'] + train_df['n_asize2']) - (train_df['n_bsize1'] + train_df['n_bsize2']))

test_df['wap_balance'] = abs(test_df['wap1'] - test_df['wap2'])
test_df['price_spread'] = (test_df['n_ask1'] - test_df['n_bid1']) / ((test_df['n_ask1'] + test_df['n_bid1'])/2)
test_df['bid_spread'] = test_df['n_bid1'] - test_df['n_bid2']
test_df['ask_spread'] = test_df['n_ask1'] - test_df['n_ask2']
test_df['total_volume'] = (test_df['n_asize1'] + test_df['n_asize2']) + (test_df['n_bsize1'] + test_df['n_bsize2'])
test_df['volume_imbalance'] = abs((test_df['n_asize1'] + test_df['n_asize2']) - (test_df['n_bsize1'] + test_df['n_bsize2']))

 虽说引入了大量的特征工程,但是在提交后的分数甚至没有之前模型的分数高,是因为特征变多了以后,训练的时间会明显增多,但是现有的分数(0.61)已经是在电脑上跑了几个小时的结果了T_T,在笔记三中想办法改进。

四、总结与感悟

虽然可能成绩上并没有得到很好的结果,但对我个人来说,这确实是一个不小的“小进步”。在大学两年中,我一直在学习基础课程,基本上没有时间,更多的是没有动力去学习专业方面的知识。非常感谢datawhale这次活动,让我真正迈出了这一步,并且期间确实学到了很多东西。万事开头难,能够在这个赛事中正式开始代码方面的实践,算是开了个好头;但“靡不有初鲜克有终”,在之后,我肯定还需要继续努力。

最有感触的是,在助教开会时提到的,说到其实没有必要把自己限制在“我是小白”的死亡圈套里,这样反而会真正一直是小白,给自己找了不去学习的借口(就是我之前的状态)。大胆迈出第一步是每个小白成为大佬的必经之路,也是每个大佬曾经走过的路。就像我的新名字一样“努力成为大佬的小白”哈哈哈哈哈哈,冲冲。


[1]箱线图原文链接:https://blog.csdn.net/lymboy/article/details/122135997

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

End!ess

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值