交叉验证&模型集成 | datawhale夏令营NLP赛道第三阶段打卡笔记(二)

前言

根据净好大佬的两份笔记, 这里记录一下交叉验证和模型集成. 交叉验证是在kaggle教程里学的, 模型集成是抱佛脚学的.

A榜笔记:tvq27xqm30o.feishu.cn/docx/V2gfdv...

B榜笔记:tvq27xqm30o.feishu.cn/docx/VUh0dY...

交叉验证

交叉验证可以帮助你更准确地评估模型的表现,因为它可以减少由于数据划分方式的偶然性导致的误差。

机器学习是一个迭代的过程。我们将面临关于使用哪些预测变量,使用哪种类型的模型,向这些模型提供什么参数等选择。

数据验证方法有一些缺点: 数据集验证集的划分可能会因为数据集本身的位置安放问题而并未分布平均. 在极端情况下,可以想象在验证集中只有1行数据。如果你比较替代模型,哪一个在单个数据点上做出最好的预测将大部分是运气的问题~

一般来说,验证集越大,我们衡量模型质量的随机性(也称为"噪声")就越小,它就越可靠。不幸的是,我们只能通过从训练数据中移除行来获得大的验证集,而较小的训练数据集意味着更差的模型!

什么是交叉验证?

定义:交叉验证是一种统计学方法,用于评估机器学习模型的性能。它通过将数据集划分为训练集和验证集,并在这些子集上多次运行模型,来减少模型性能评估的偶然性。

过程: 在交叉验证中,数据首先被划分为k个子集,每个子集被称为一个"折叠"。然后,对每个折叠进行一次实验,其中一个折叠被用作验证集,其余折叠被用作训练集。这个过程重复k次,每个折叠都被用作验证集一次。最后,这k次实验的结果被平均,得到最终的模型性能评估。

我们应该何时使用交叉验证?

交叉验证给出了更准确的模型质量度量,这在你做很多建模决策时尤其重要。然而,它可能需要更长的时间来运行,因为它估计多个模型(每个折叠一个)。

所以,考虑到这些权衡,你应该何时使用每种方法?

  • 对于小数据集,其中额外的计算负担不是大问题,你应该运行交叉验证。
  • 对于较大的数据集,单个验证集就足够了。你的代码将运行得更快,你可能有足够的数据,以至于没有必要重新使用一些数据作为保留。
  • 没有什么简单的阈值可以构成大数据集与小数据集。但是如果你的模型需要几分钟或更短的时间来运行,那么可能值得切换到交叉验证

示例(来自kaggle教程翻译)

我们在X中加载输入数据,在y中加载输出数据。

python 复制代码
import pandas as pd

# Read the data
data = pd.read_csv('../input/melbourne-housing-snapshot/melb_data.csv')

# Select subset of predictors
cols_to_use = ['Rooms', 'Distance', 'Landsize', 'BuildingArea', 'YearBuilt']
X = data[cols_to_use]

# Select target
y = data.Price

然后,我们定义一个管道,该管道使用插补器填充缺失值,使用随机森林模型进行预测。

虽然不使用管道也可以进行交叉验证,但难度很大!使用管道将使代码变得非常简单。

python 复制代码
from sklearn.ensemble import RandomForestRegressor
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer

my_pipeline = Pipeline(steps=[('preprocessor', SimpleImputer()),
                              ('model', RandomForestRegressor(n_estimators=50,
                                                              random_state=0))
                             ])

我们使用scikit-learn的cross_val_score()函数获得交叉验证分数。我们通过cv参数设置折叠的数量。

python 复制代码
from sklearn.model_selection import cross_val_score

# Multiply by -1 since sklearn calculates *negative* MAE
scores = -1 * cross_val_score(my_pipeline, X, y,
                              cv=5,
                              scoring='neg_mean_absolute_error')

print("MAE scores:\n", scores)

# MAE scores:
# [301628.7893587  303164.4782723  287298.331666   236061.84754543  260383.45111427]

scoring参数选择一个报告模型质量的度量:在这种情况下,我们选择了负平均绝对误差(MAE)。scikit-learn的文档显示了一系列的选项。

我们通常想要一个模型质量的单一度量来比较替代模型。所以我们取实验的平均值。

python 复制代码
print("Average MAE score (across experiments):")
print(scores.mean())

# Average MAE score (across experiments):
# 277707.3795913405

模型集成

模型集成是一种机器学习策略,它结合了多个模型的预测结果,以产生一个最终的预测结果。这种方法的主要优点是,通过结合多个模型,我们可以减少模型的偏差和方差,从而提高模型的预测性能。

模型集成的主要方法

  1. Bagging :Bagging 是 Bootstrap Aggregating 的缩写,它通过从训练数据中随机抽样(有放回)生成多个训练集,然后训练多个模型并结合它们的预测结果。随机森林就是一种基于 Bagging 的模型集成方法。
  2. Boosting :Boosting 是一种迭代的方法,每一次迭代都会训练一个新的模型 ,这个模型试图纠正前一次迭代中模型的错误。然后,所有模型的预测结果会按照一定的权重进行结合。XGBoost 和 AdaBoost 都是基于 Boosting 的模型集成方法。Boosting方法的一个关键特点是,它的新模型是在前一个模型训练完成后,再进行训练的。这种顺序性质使得Boosting方法无法进行并行化,这是它与Bagging方法的一个主要区别。
  3. Stacking :Stacking 是一种将多个不同的模型结合在一起的方法。首先,我们会训练多个不同的模型,然后我们会训练一个新的模型,这个模型会使用前面模型的预测结果作为输入,以产生最终的预测结果。
  4. 投票(Voting) :投票是一种简单的模型集成方法,它结合了多个模型的预测结果,通过多数投票的方式来决定最终的预测结果。投票可以分为硬投票和软投票两种。硬投票是指每个模型对预测结果的投票权重相同 ,而软投票是指每个模型的投票权重与其预测的概率成正比
  5. 混合(Blending) :混合是一种类似于Stacking的模型集成方法,但是它不需要使用交叉验证。在混合中,我们将数据集分为两部分,一部分用于训练多个基模型 ,另一部分用于创建一个新的数据集 ,这个新的数据集的特征是基模型的预测结果 ,标签仍然是原始标签。然后,我们在新的数据集上训练一个元模型,用于产生最终的预测结果。

净好大佬的模型集成方法

将数据集中每条样本的文本内容转为 预训练模型 [CLS]768维的embedding表示

具体来说,大佬首先使用了多个预训练模型(一共15个 'bert-base-uncased', 'bert-large-uncased', 'deberta-v3-base', 'deberta-v3-large', 'roberta-base', 'roberta-large', 'longformer-base-4096', 'albert-xxlarge-v2', 'longformer-large-4096-answering-race', 'multilingual-e5-large', 'paraphrase-multilingual-mpnet-base-v2', 'nuclear_medicine_DARoBERTa', 'multilingual-e5-base', 'multilingual-e5-small', 'e5-large-v2')对输入文本进行转化,得到各自的embedding表示。

训练各个模型

然后,这些预训练模型的输出被作为下一级模型(这里是LSTM)的输入,进行进一步的训练。这样,预训练模型的输出就被堆叠起来,作为下一级模型的输入,形成了Stacking的模型集成结构。

python 复制代码
# 加载预训练词向量
if 'longformer' in model_name:
    vectors = SentenceModel(model_name_or_path=f"../code/premodel/{model_name}", max_seq_length=648, device='cuda' if torch.cuda.is_available() else 'cpu') # 256 使用shibing624/text2vec-base-chinese
else:
    vectors = SentenceModel(model_name_or_path=f"../code/premodel/{model_name}", max_seq_length=512, device='cuda' if torch.cuda.is_available() else 'cpu') # 256 使用shibing624/text2vec-base-chinese

# 模型定义
class LSTM(nn.Module):
    def __init__(self):
        super(LSTM, self).__init__()
        # LSTM
        hidden_size = 256
        self.lstm1   = nn.LSTM(input_size=768, hidden_size=hidden_size, bidirectional=False)
        self.lstm2   = nn.LSTM(input_size=1024, hidden_size=hidden_size, bidirectional=False)
        self.lstm3   = nn.LSTM(input_size=4096, hidden_size=hidden_size, bidirectional=False)
        self.lstm4   = nn.LSTM(input_size=384, hidden_size=hidden_size, bidirectional=False)
        self.act    = nn.Tanh() #nn.Tanh()
        self.classifier1 = nn.Linear(hidden_size, 2)
        self.classifier2 = nn.Linear(hidden_size, 1)

    def forward(self, x):
        try:
            x, _ = self.lstm1(x)
        except:
            try:
                x, _ = self.lstm2(x)
            except:
                try:
                    x, _ = self.lstm3(x)
                except:
                    x, _ = self.lstm4(x)
        e = x
        x = self.act(x)
        if not opt.use_BCE:
            x = self.classifier1(x)
        else:
            x = self.classifier2(x)
        return x, e

对各个模型进行不同的组合并评估

然后大佬开始了模型集成,这里他是将多个模型的预测结果进行平均,然后基于这个平均结果进行最终的预测,这应该属于软投票(soft voting)策略.

python 复制代码
import torch
import pandas as pd
from sklearn.metrics import f1_score
import warnings
warnings.filterwarnings('ignore')

def run(model_indexs, use_BCE):
    model_num = len(model_indexs)
    # 读取所有的model_prob.pth,并全加在一起
    avg_pred = torch.zeros([1,1])
    for i in model_indexs:
        pred = torch.load(f'./pred_prob/{i}_prob.pth').data
        if i == model_indexs[0]:
            avg_pred = pred
        else:
            avg_pred += pred
        
    # 求平均
    avg_pred /= model_num

    if use_BCE:
        # 选取大于0.5的作为预测结果
        pred = (avg_pred > 0.5).int()
        pred = pred.reshape(-1)
    else:
        # 选取最大的概率作为预测结果
        pred = torch.argmax(avg_pred, dim=1)
    pred = pred.numpy()

    # 读取结果文件
    df = pd.read_csv(f'./embedding/val_0.5.csv', delimiter=',', header=None, names=['label'])
    # df = df.dropna() # 这一步的作用是删除空行
    # df = df['label']
    df = df.to_numpy()

    # 计算F1
    f1 = f1_score(df, pred, average='binary')
    result = f'{model_indexs}的F1: {f1}'
    return result
    
if __name__ == '__main__':
    model_indexs_list = []
    # 遍历多种可能
    model_num = 8
    import itertools
    for i in range(1, model_num+1):
        model_indexs_list += list(itertools.combinations(range(1, model_num+1), i))
    
    results = []
    for model_indexs in model_indexs_list:
        result = run(model_indexs, use_BCE=False)
        results.append(result)
    results.sort(key=lambda x: float(x.split(': ')[1]), reverse=True)
    results = '\n'.join(results)
    # 写入文件
    temp = open('./ensemble_results.txt', 'w', encoding='utf8')
    temp.write(results)
    temp.close()

    # 'bert-large-uncased', 'deberta-v3-base', 'deberta-v3-large', 'roberta-base' [2,3,4,5]
  1. run函数接受一个包含所有要进行集成的模型索引列表model_indexs和一个决定了是否使用二元交叉熵(Binary Cross Entropy)作为损失函数的Bool值use_BCE
  2. run函数中,首先初始化一个全零的张量avg_pred,然后遍历model_indexs中的每一个索引,对应的模型预测结果被加载并加到avg_pred上。这样,avg_pred就变成了所有模型预测结果的和。
  3. 接下来,avg_pred被除以模型的数量,得到了所有模型预测结果的平均值。
  4. 如果use_BCE为真,那么将avg_pred中大于0.5的值作为预测结果;否则,选择avg_pred中最大的概率作为预测结果
  5. 最后,这个函数会计算预测结果和真实标签的F1分数,并返回一个包含模型索引和F1分数的字符串。

碎碎念的其他无关紧要事情

其实就是管道啦~感觉模型集成应该有对应的pipeline才对的,毕竟代码块真的好长鸭 不过还没找到,找到再放上来

最后附一个有可视化的jupyterbook代码(不过不是nlp这个比赛)

XGBoost&one-hot&gridSearch&pipeline www.kaggle.com/code/valeri...

参考资料

www.kaggle.com/learn/inter...

净好大佬的A榜笔记:tvq27xqm30o.feishu.cn/docx/V2gfdv...

净好大佬的B榜笔记:tvq27xqm30o.feishu.cn/docx/VUh0dY...

相关推荐
AI街潜水的八角9 分钟前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
白榆maple34 分钟前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少38 分钟前
数据结构——线性表与链表
数据结构·c++·算法
此生只爱蛋2 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
独行soc2 小时前
#渗透测试#SRC漏洞挖掘#深入挖掘XSS漏洞02之测试流程
web安全·面试·渗透测试·xss·漏洞挖掘·1024程序员节
咕咕吖2 小时前
对称二叉树(力扣101)
算法·leetcode·职场和发展
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
九圣残炎3 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
lulu_gh_yu3 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
丫头,冲鸭!!!3 小时前
B树(B-Tree)和B+树(B+ Tree)
笔记·算法