0.1基础DataWhale夏令营第二期学习笔记(1)baseline 代码逐行分析

0.1基础DataWhale夏令营第二期学习笔记(1)baseline 代码逐行分析

1 写在前面

习 Python 几个月有余,参加过数学建模,写过爬虫,做过简单的数据分析……曾尝试数次学习 ai 相关知识,却都在庞大的知识内容面前屡屡放弃。这次 DataWhale 夏令营良心地提供了 baseline ,我想就让我从代码逐行解读开始,再一次尝试拥抱机器学习。

2 赛题分析

这是一道来自讯飞 AI 开发者大赛的量化金融模型预测题。简单概括,提供的训练集中包含买卖双方的数据,每条数据包含五个标签,分别代表5、10、20、40、60个 tick 之后价格移动的方向;而我们要做的就是在测试集给定前面买卖的数据之后,预测后五个标签。

容易发现,这其实是一道披着“量化”外衣的典型机器学习类数据分析题目,即通过数据集的数据提取特征形成自变量,建立与标签(因变量)之间的关系(即模型),再运用这一模型在测试集上进行预测。

2.1 机器学习解题基本流程

0.1基础DataWhale夏令营第二期学习笔记(1)baseline 代码逐行分析_第1张图片

其中特征工程十分关键,“在数据挖掘比赛中,特征 ​总是最终制胜法宝”。我们要去思考什么信息可以帮助我们提高预测精准度,然后将其转化为特征输入到模型。

3 baseline 代码逐行解析

本文写于 8.2 晚,不知后期官方是否会发布他们的逐行解析,但我相信我的版本只会更加细致,争取做到行行有解析。

3. 1 导入模块

import numpy as np
# numpy是一个常用的科学计算库,常用来处理多维数组
import pandas as pd
# pandas是一个常用的数据分析库,常用来处理结构化数据
from catboost import CatBoostClassifier
# catboost是一个基于梯度提升树的机器学习算法,常用来处理结构化数据
from sklearn.model_selection import StratifiedKFold, KFold, GroupKFold
# sklearn是一个常用的机器学习库,常用来处理结构化数据
# StratifiedKFold是分层采样的交叉验证, KFold是k折交叉验证, GroupKFold是分组交叉验证
# 这份代码里只用到了KFold,后文详细说明了含义
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss, mean_squared_log_error
# sklearn.metrics是一个常用的机器学习库,常用来评估模型的性能
# accuracy_score是准确率, f1_score是f1分数, roc_auc_score是auc分数, log_loss是对数损失, mean_squared_log_error是均方对数误差
# 这份代码里只用到了f1分数,后面使用时会详细说明含义
import tqdm, sys, os, gc, argparse, warnings
# 一些常用的库,用于读取数据、显示进度条、读参数等
warnings.filterwarnings('ignore')
# 忽略输出的警告

3. 2 数据探索

# 读取数据
path = 'AI量化模型预测挑战赛公开数据/'
# 数据路径

train_files = os.listdir(path+'train')
# 获取训练集文件夹下所有文件名
train_df = pd.DataFrame()
# 创建空的训练集
for filename in tqdm.tqdm(train_files):
    # tqdm.tqdm()是一个进度条,返回一个可迭代对象
    tmp = pd.read_csv(path+'train/'+filename)
    # 读取文件
    tmp['file'] = filename
    # 添加以 file 为表头的一列,内容为filename,即数据的来源表格
    train_df = pd.concat([train_df, tmp], axis=0, ignore_index=True)
    # 将读取的文件添加到训练集中

# 以下同理,只是把训练集换成测试集
test_files = os.listdir(path+'test')
test_df = pd.DataFrame()
for filename in tqdm.tqdm(test_files):
    tmp = pd.read_csv(path+'test/'+filename)
    tmp['file'] = filename
    test_df = pd.concat([test_df, tmp], axis=0, ignore_index=True)

3.2.1 需要补充的知识

listdir

os.listdir()​ 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表,如下图所示。

0.1基础DataWhale夏令营第二期学习笔记(1)baseline 代码逐行分析_第2张图片

tqdm.tqdm

tqdm.tqdm(files)​ 展示一个进度条,返回一个可迭代对象,即上述获取到的文件名。

请添加图片描述

pandas相关

0.1基础DataWhale夏令营第二期学习笔记(1)baseline 代码逐行分析_第3张图片

pd.dataFrame() ​​生成一个二维表格结构,类似于Excel表格,包含行标签与列标签。

pd.read_csv()​ 读取csv文件。

pd.concat()​ 用于将多个数据结构按照行或列的方向进行合 并。它可以将数据连接在一起,形成一个新的 DataFrame。如图所示,axis=0 表示按行拼接,即接在表格的下面,ignore_index=True​则表示重新生成索引。

​​

3. 3 特征工程

这里的特征工程处理的比较简单,先把时间按小时、分钟分开,之后将小时、分钟与买卖数据等全部提取为特征。

# 时间相关特征, 时间的格式是09:40:03 小时:分钟:秒,可以按照:分割,取小时和分钟作为特征
train_df['hour'] = train_df['time'].apply(lambda x:int(x.split(':')[0]))
# apply函数的作用是将一个函数应用到一个数据框的行或列上,这里是列
# lambda函数的作用是创建一个匿名函数;这里的作用是将time列的每个元素按照冒号分割,取第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]))
# 同上,这里取第1个元素,即分钟
test_df['minute'] = test_df['time'].apply(lambda x:int(x.split(':')[1]))

# 入模特征
cols = [f for f in test_df.columns if f not in ['uuid','time','file']]
# 列表生成式,这里的意思是取test_df中除了uuid,time,file三列之外的所有列,即特征列

如果对 cols 所含列不清晰,可以看下图,其实就是除了 uuid, time, file 三列之外的所有列。

print(cols)

[‘date’, ‘sym’, ‘n_close’, ‘amount_delta’, ‘n_midprice’, ‘n_bid1’, ‘n_bsize1’, ‘n_bid2’, ‘n_bsize2’, ‘n_bid3’, ‘n_bsize3’, ‘n_bid4’, ‘n_bsize4’, ‘n_bid5’, ‘n_bsize5’, ‘n_ask1’, ‘n_asize1’, ‘n_ask2’, ‘n_asize2’, ‘n_ask3’, ‘n_asize3’, ‘n_ask4’, ‘n_asize4’, ‘n_ask5’, ‘n_asize5’, ‘hour’, ‘minute’]

3. 4 模型的训练与调用

3. 4. 1 需要补充的知识

K 折交叉验证

K折交叉验证,就是将数据集等比例划分成K份,以其中的一份作为测试数据,其他的K-1份数据作为训练数据。这样以来只有实验K次才算完成完整的一次,也就是说交叉验证实际是把实验重复做了K次。​

0.1基础DataWhale夏令营第二期学习笔记(1)baseline 代码逐行分析_第4张图片

  1. 如果训练数据集相对较小,则增大k值。

增大k值,在每次迭代过程中将会有更多的数据用于模型训练,能够得到最小偏差,同时算法时间延长。且训练块间高度相似,导致评价结果方差较高。

  1. 如果训练集相对较大,则减小k值。

减小k值,降低模型在不同的数据块上进行重复拟合的性能评估的计算成本,在平均性能的基础上获得模型的准确评估。

def cv_model(clf, train_x, train_y, test_x, clf_name, seed = 2023):
    '''
    模型建立部分
    :param clf: 分类器
    :param train_x: 训练集特征
    :param train_y: 训练集标签
    :param test_x: 测试集特征
    :param clf_name: 分类器名称
    :param seed: 随机种子
    :return: oof(交叉验证结果), test_predict(test_x的预测结果)
    '''
    folds = 5
    kf = KFold(n_splits=folds, shuffle=True, random_state=seed)
    # 指定每次交叉验证划分的折数、是否打乱顺序、随机种子
    oof = np.zeros([train_x.shape[0], 3])
    # 生成一个和训练集样本数相同的全零数组, 列数为标签类别数
    test_predict = np.zeros([test_x.shape[0], 3])

    cv_scores = []
    # 交叉验证结果分数

    for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):
        # enumerate()函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列, 同时列出数据和数据下标, 一般用在 for 循环当中
        # kf.split(train_x, train_y)返回的是train_x, train_y中划分为训练集和验证集的下标
        # i为第几次交叉验证, (train_index, valid_index)为第i次交叉验证中训练集和验证集的下标
        print('************************************ {} ************************************'.format(str(i+1)))
        trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
        # iloc()函数用于对数据进行筛选, train_index为训练集下标, valid_index为验证集下标
     
        if clf_name == "cat":
            params = {'learning_rate': 0.2, 'depth': 6, 'bootstrap_type':'Bernoulli','random_seed':2023,
                      'od_type': 'Iter', 'od_wait': 100, 'random_seed': 11, 'allow_writing_files': False,
                      'loss_function': 'MultiClass'}
            # 设置模型参数
            # learning_rate: 学习率, depth: 树的最大深度, bootstrap_type: 采样方法, random_seed: 随机种子
            # od_type: 过拟合检查类型, od_wait: 早停轮数, allow_writing_files: 是否允许写文件, loss_function: 损失函数
            # Bernoulli: 伯努利采样, Iter: 迭代次数, MultiClass: 多分类            
          
            model = clf(iterations=100, **params)
            # iterations: 迭代次数, **params: 模型参数
            # **是Python的一种语法,表示将字典扩展为关键字参数, 例如: dict = {'a': 1, 'b': 2}, func(**dict)等价于func(a=1, b=2)
            model.fit(trn_x, trn_y, eval_set=(val_x, val_y),
                      metric_period=20,
                      use_best_model=True, 
                      cat_features=[],
                      verbose=1)
            # eval_set: 验证集, metric_period: 每隔多少轮输出一次评估结果, use_best_model: 是否使用最佳模型
            # cat_features: 类别特征, verbose: 是否输出日志信息
            # cat_features为空, 因为catboost会自动识别类别特征;verbose=1, 因为要输出日志信息
          
            val_pred  = model.predict_proba(val_x)
            # predict_proba()函数用于预测概率, 返回的是一个n行k列的数组, n为样本数, k为标签类别数
            # 第i行第j列的值为第i个样本预测为第j类的概率
            test_pred = model.predict_proba(test_x)
      
        oof[valid_index] = val_pred
        # 将第i次交叉验证中验证集的预测结果赋值给oof中对应的行
        test_predict += test_pred / kf.n_splits
        # 将第i次交叉验证中测试集的预测结果除以折数后累加到test_predict中
      
        F1_score = f1_score(val_y, np.argmax(val_pred, axis=1), average='macro')
        # f1_score()函数用于计算F1分数, average='macro'表示计算宏平均值
        # F1分数是精确率和召回率的调和平均数, 用于评估分类模型的好坏, 越接近1越好
        # val_y为验证集标签, np.argmax(val_pred, axis=1)为验证集预测结果
        # 精确率: TP/(TP+FP), 召回率: TP/(TP+FN), TP: 预测为正样本且实际为正样本的数量, FP: 预测为正样本但实际为负样本的数量, FN: 预测为负样本但实际为正样本的数量
        cv_scores.append(F1_score)
        print(cv_scores)
      
    return oof, test_predict
  
for label in ['label_5','label_10','label_20','label_40','label_60']:
    print(f'=================== {label} ===================')
    cat_oof, cat_test = cv_model(CatBoostClassifier, train_df[cols], train_df[label], test_df[cols], 'cat')
    train_df[label] = np.argmax(cat_oof, axis=1)
    # np.argmax()函数用于返回数组中最大值的下标, axis=1表示按行查找
    test_df[label] = np.argmax(cat_test, axis=1)
  

注释中已经涵盖了绝大部分知识点,这里不再赘述。

补充几点:Catboost 训练参数详细说明请见文档: Overview - Training parameters | CatBoost

流程如下:

0.1基础DataWhale夏令营第二期学习笔记(1)baseline 代码逐行分析_第5张图片

3. 5 文件输出

# 指定输出文件夹路径
output_dir = './submit'

# 如果文件夹不存在则创建
if not os.path.exists(output_dir):
    os.makedirs(output_dir)

# 首先按照'file'字段对 dataframe 进行分组
grouped = test_df.groupby('file')

# 对于每一个group进行处理
for file_name, group in grouped:
    # 选择你所需要的列
    selected_cols = group[['uuid', 'label_5', 'label_10', 'label_20', 'label_40', 'label_60']]
  
    # 将其保存为csv文件,file_name作为文件名
    selected_cols.to_csv(os.path.join(output_dir, f'{file_name}'), index=False)

总结

Baseline 的代码是十分清晰的,思路上比较简单,无脑将所有特征不加太多处理直接开跑 Catboost 模型便能得到一个基本的结果。

因此也能看出特征工程与模型调优的重要性,机器学习“简单”却又不简单。

你可能感兴趣的:(学习,笔记)