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
"ignore", category=ConvergenceWarning) simplefilter(
4 交叉验证
4.1 动机
在上一章 Chapter 3 中, 为了解释交叉验证的 f1分数 远好于 测试集的预测值在竞赛平台上的得分, 我猜测这可能是因为 交叉验证中的验证集包含了 Keywords. 为了验证这个想法. 我们可以自己写一个交叉验证函数.
4.2 Setup
# read train data and test data
= pd.read_csv('./data/train.csv')
train = pd.read_csv('./data/testB.csv')
test
# fillna with empty string
'title'] = train['title'].fillna('')
train['abstract'] = train['abstract'].fillna('')
train[
'title'] = test['title'].fillna('')
test['abstract'] = test['abstract'].fillna('') test[
4.3 手写 k-fold 交叉验证函数
为了保证 每一个 fold 的数据中两种标签的数量平衡. 我使用 StratifiedKFold
.
# combine title, author, abstract, keywords as text
'text'] = train['title'].fillna('') + ' ' + train['author'].fillna('') + ' ' + train['abstract'].fillna('') + ' ' + train['Keywords'].fillna('')
train[
# vectorize text using CountVectorizer (or TfidfVectorizer if you want)
= CountVectorizer().fit(train['text'])
vector
# StratifiedKFold for 5 folds
= StratifiedKFold(n_splits=5)
skf
= CountVectorizer().fit(train['text']) # fit vectorizer to train data
vector
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.iloc[train_index].copy()
train_x = train.iloc[valid_index].copy()
valid_x 'text'] = train_x['title'].fillna('') + ' ' + train_x['author'].fillna('') + ' ' + train_x['abstract'].fillna('') + ' ' + train['Keywords'].fillna('')
train_x['text'] = valid_x['title'].fillna('') + ' ' + valid_x['author'].fillna('') + ' ' + valid_x['abstract'].fillna('')
valid_x[= train_x['label']
train_y = valid_x['label']
valid_y # vectorize text using CountVectorizer (or TfidfVectorizer if you want)
= vector.transform(train_x['text'])
train_x_vector = vector.transform(valid_x['text'])
valid_x_vector
# set model
= LogisticRegression()
model
# Fit to data
model.fit(train_x_vector, train_y)
# Predict
= model.predict(valid_x_vector)
valid_y_pred
# 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
'text'] = train_x['title'].fillna('') + ' ' + train_x['author'].fillna('') + ' ' + train_x['abstract'].fillna('') + ' ' + train['Keywords'].fillna('') train_x[
构建 valid_x
时, 我删除了 + ' ' + train['Keywords'].fillna('')
, 即:
'text'] = valid_x['title'].fillna('') + ' ' + valid_x['author'].fillna('') + ' ' + valid_x['abstract'].fillna('') valid_x[
这种 train_x
和 valid_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.
- 我手写了一个 K-Fold Cross validation 函数.
- 对比了
KFold
和StratifiedKFold
后, 我选择了后者. 目的是: 保持数据中 每个 类别/标签 的数据量之间的平衡.
- 对比了
- 我选择
LogisticRegression()
分类器, 在我在自己写的 K-Fold Cross validation 中做交叉验证 - 交叉验证的 \({F_1}_{\text{score}}\) 仍然过高.
展望
- 在 Task 1 上尝试深度学习算法
- 看看
TfidfVectorizer
是否可以获得更好的效果.