4  交叉验证

4.1 动机

在上一章 Chapter 3 中, 为了解释交叉验证的 f1分数 远好于 测试集的预测值在竞赛平台上的得分, 我猜测这可能是因为 交叉验证中的验证集包含了 Keywords. 为了验证这个想法. 我们可以自己写一个交叉验证函数.

4.2 Setup

import numpy as np
import pandas as pd
import os
from sklearn.model_selection import StratifiedKFold

# Import common score metrics
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score

# Import BOW (Bag of Words) vectorizer
# we can also use TF-IDF vectorizer instead. import TfidfVectorizer
from sklearn.feature_extraction.text import CountVectorizer

# Import logistic regression model
from sklearn.linear_model import LogisticRegression


# filter out warnings
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter("ignore", category=ConvergenceWarning)
# read train data and test data
train = pd.read_csv('./data/train.csv')
test = pd.read_csv('./data/testB.csv')

# fillna with empty string
train['title'] = train['title'].fillna('')
train['abstract'] = train['abstract'].fillna('')

test['title'] = test['title'].fillna('')
test['abstract'] = test['abstract'].fillna('')

4.3 手写 k-fold 交叉验证函数

为了保证 每一个 fold 的数据中两种标签的数量平衡. 我使用 StratifiedKFold.

# combine title, author, abstract, keywords as text
train['text'] = train['title'].fillna('') + ' ' +  train['author'].fillna('') + ' ' + train['abstract'].fillna('') + ' ' + train['Keywords'].fillna('')

# vectorize text using CountVectorizer (or TfidfVectorizer if you want) 
vector = CountVectorizer().fit(train['text'])

# StratifiedKFold for 5 folds
skf = StratifiedKFold(n_splits=5)

vector = CountVectorizer().fit(train['text']) # fit vectorizer to train data

for i, (train_index, valid_index) in enumerate(skf.split(train, train['label'])):
    # for train select rows whose index is in train_index, store a copy in train_x
    # same for validation set

    train_x = train.iloc[train_index].copy()
    valid_x = train.iloc[valid_index].copy()
    train_x['text'] = train_x['title'].fillna('') + ' ' +  train_x['author'].fillna('') + ' ' + train_x['abstract'].fillna('') + ' ' + train['Keywords'].fillna('')
    valid_x['text'] = valid_x['title'].fillna('') + ' ' +  valid_x['author'].fillna('') + ' ' + valid_x['abstract'].fillna('')
    train_y = train_x['label']
    valid_y = valid_x['label']
    # vectorize text using CountVectorizer (or TfidfVectorizer if you want) 

    train_x_vector = vector.transform(train_x['text'])
    valid_x_vector = vector.transform(valid_x['text'])

    # set model
    model = LogisticRegression()

    # Fit to data
    model.fit(train_x_vector, train_y)

    # Predict
    valid_y_pred = model.predict(valid_x_vector)

    # print fold number, accuracy, f1_score
    print(f"Fold {i}:")
    print(f"Accuracy={accuracy_score(valid_y, valid_y_pred)}")
    print(f"F1_score={f1_score(valid_y, valid_y_pred)}")
Fold 0:
Accuracy=0.9716666666666667
F1_score=0.9709897610921501
Fold 1:
Accuracy=0.9675
F1_score=0.9669211195928754
Fold 2:
Accuracy=0.9716666666666667
F1_score=0.970790378006873
Fold 3:
Accuracy=0.9783333333333334
F1_score=0.977815699658703
Fold 4:
Accuracy=0.9775
F1_score=0.9767841788478073

为了模拟验证集不包含 keywords 的情况, 在上面代码中,

相对于 train_x

train_x['text'] = train_x['title'].fillna('') + ' ' +  train_x['author'].fillna('') + ' ' + train_x['abstract'].fillna('') + ' ' + train['Keywords'].fillna('')

构建 valid_x 时, 我删除了 + ' ' + train['Keywords'].fillna(''), 即:

valid_x['text'] = valid_x['title'].fillna('') + ' ' +  valid_x['author'].fillna('') + ' ' + valid_x['abstract'].fillna('')

这种 train_xvalid_x 的差异化, 在 sklearn 内置的 cross_validate 函数中无法简单实现.

4.4 讨论

面对

  • 验证集的预测得到的 \({F_1}_{\text{score}}\) 较高
  • 测试集的预测在竞赛平台得到的 \({F_1}_{\text{score}}\) 较低

这个情况在 LogisticRegression() 尤为明显: 上一章 (Chapter 3) 中, 逻辑回归在交叉验证中的 \({F_1}_{\text{score}}\approx 98\%\), 然而竞赛平台上的 \({F_1}_{\text{score}}\approx 67.116\%\).

鉴于这种显著差异, 本节以 LogisticRegression() 为例研究 导致这一差异的因素.

经过实践, 我自己写的交叉验证仍然得到了很高的 \({F_1}_{\text{score}}\approx 0.96\). 因此, 导致 LogisticRegression() 得分较高的原因并非 “验证集中包含 论文keywords”.

4.5 结论

导致 LogisticRegression() 得分较高的原因并非 验证集中包含 论文keywords.

真实原因位置. 有待在群中讨论.

4.6 总结

本节中, 我验证 (Chapter 3) 中提出的猜想 : 基于验证集的预测得分高, 基于测试集的预测得分低, 这个现象的原因是验证集包含 原论文的keywords.

  1. 我手写了一个 K-Fold Cross validation 函数.
    1. 对比了 KFoldStratifiedKFold 后, 我选择了后者. 目的是: 保持数据中 每个 类别/标签 的数据量之间的平衡.
  2. 我选择 LogisticRegression() 分类器, 在我在自己写的 K-Fold Cross validation 中做交叉验证
  3. 交叉验证的 \({F_1}_{\text{score}}\) 仍然过高.

展望

  • 在 Task 1 上尝试深度学习算法
  • 看看 TfidfVectorizer 是否可以获得更好的效果.