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)4 交叉验证
4.1 动机
在上一章 Chapter 3 中, 为了解释交叉验证的 f1分数 远好于 测试集的预测值在竞赛平台上的得分, 我猜测这可能是因为 交叉验证中的验证集包含了 Keywords. 为了验证这个想法. 我们可以自己写一个交叉验证函数.
4.2 Setup
# 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_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是否可以获得更好的效果.