我正在尝试在文本训练数据集(Reddit 帖子)上训练一个文本分类器,该数据集具有两个专有类(1 和 0),涉及帖子作者的特征,而不是帖子本身。
类不平衡:大约 75:25,这意味着 75% 的作者是“0”,而 25% 是“1”。
整个数据集由 3 列组成:第一列代表帖子的作者,第二列代表帖子所属的 subreddit,第三列代表实际帖子。
数据
数据集如下所示:
In [1]: train_data_full.head(5)
Out[1]:
author subreddit body
0 author1 subreddit1 post1_1
1 author2 subreddit2 post2_1
2 author3 subreddit2 post3_1
3 author2 subreddit3 post2_2
4 author5 subreddit4 post5_1
其中 postI_J 是第 I 个作者的第 J 个帖子。请注意,在此数据集中,同一作者可能会出现多次,如果她/他发布了多次。
在一个单独的数据集中,我有每个作者所属的类。
我做的第一件事是按作者分组:
def proc_subs(l):
s = set(l)
return " ".join([st.lower() for st in s])
train_data_full_agg = train_data_full.groupby(["author"], as_index = False).agg({'subreddit': proc_subs, "body": " ".join})
train_data_full_agg.head(5)
Out[2]:
author subreddit body
author1 subreddit1 subreddit2 post1_1 post1_2
author2 subreddit3 subreddit2 subreddit3 post2_1 post2_2 post2_3
author3 subreddit1 subreddit5 post3_1 post3_2
author4 subreddit7 post4_1
author5 subreddit1 subreddit2 post5_1 post5_2
总共有 5000 个作者,4000 个用于训练,1000 个用于验证(roc_auc 分数)。这是我正在使用的 spaCy 代码(train_texts
是train_data_full_agg.body.tolist()
我用于训练的子集,而test_texts
我用于验证的代码)。
# Before this line of code there are others (imports and data loading mostly) which i think are irrelevant
train_data = list(zip(train_texts, train_labels))
nlp = spacy.blank("en")
if 'textcat' not in nlp.pipe_names:
textcat = nlp.create_pipe("textcat", config={"exclusive_classes": True, "architecture": "ensemble"})
nlp.add_pipe(textcat, last = True)
else:
textcat = nlp.get_pipe('textcat')
textcat.add_label("1")
textcat.add_label("0")
def evaluate_roc(nlp,textcat):
docs = [nlp.tokenizer(tex) for tex in test_texts]
scores , a = textcat.predict(docs)
y_pred = [b[0] for b in scores]
roc = roc_auc_score(test_labels, y_pred)
return roc
dec = decaying(0.6 , 0.2, 1e-4)
pipe_exceptions = ['textcat']
other_pipes = [pipe for pipe in nlp.pipe_names if pipe not in pipe_exceptions]
with nlp.disable_pipes(*other_pipes):
optimizer = nlp.begin_training()
for epoch in range(10):
random.shuffle(train_data)
batches = minibatch(train_data, size = compounding(4., 32., 1.001) )
for batch in batches:
texts1, labels = zip(*batch)
nlp.update(texts1, labels, sgd=optimizer, losses=losses, drop = next(dec))
with textcat.model.use_params(optimizer.averages):
rocs.append(evaluate_roc(nlp, textcat))
问题
如果与可以使用 scikit learn 编写的更简单算法(如 tfidf、bow、词嵌入等)相比,我的性能也很差(在我没有标签的测试数据集上使用 ROC 测量)
尝试
我试图通过以下过程获得更好的性能:
- 文本的各种预处理/词形还原:最好的方法似乎是删除所有标点符号、数字、停用词、词汇表之外的单词,然后对所有剩余的词进行词形还原
- 尝试过的 textcat 架构:集成、弓(也带有
ngram_size
和attr
参数):最好的似乎是集成,如spaCy 文档。 - 试图包含 subreddit 信息:我通过在相同的 4000 个作者的
subreddit
列上训练一个单独的 textcat 来做到这一点(请参阅第 5 点,以了解如何使用来自此步骤的信息)。 - 试图包含词嵌入信息:使用来自
en_core_web_lg-2.2.5
spaCy 模型的文档向量,我在相同的 4000 个作者的聚合帖子上训练了一个 scikit 多层感知器。(请参阅第 5 点,了解如何使用来自此步骤的信息)。 - 然后,为了混合来自 subreddits、帖子和文档向量的信息,我对三个模型的 1000 个预测进行了逻辑回归训练(我还尝试在最后一步使用adasyn来平衡类
使用这个逻辑回归,我在测试数据集上得到 ROC = 0.89。如果我删除这些步骤中的任何一个并使用中间模型,ROC 就会降低。
我还尝试了以下步骤,这再次降低了 ROC:
- 使用预训练的模型,例如 bert。我使用的代码类似于这个
- 从一开始就尝试平衡类,因此使用较小的训练集。
- 试图留下标点符号并将 a放在管道
sentencizer
的开头nlp
附加信息(主要来自评论)
问:多项式朴素贝叶斯或 SVM 等基线模型的 ROC 是多少?
我可以轻松访问仅在文本上评估的 ROC(没有 subreddits 或向量)。一个 svm 设置如下:
svm= svm.SVC(C=1.0, kernel='poly', degree=2, gamma='scale', coef0=0.0, shrinking=True, probability=True, tol=0.001, cache_size=200, class_weight=None, max_iter=-1)
将给出 roc(使用 CountVectorizer bow)= 0.53(与 rbf 内核相同,但 rbf + class_weight = None 或“平衡”给出 0.63(相同,没有对 cache_size 的约束))。无论如何,一个 XGBregressor使用 gridsearch 设置的参数将给出 roc = 0.88。相同的 XGB,但也带有 CountVectorizer subreddits 和 scikit Doc2Vec 向量(与上面的 lr 结合)给出了大约 93。您在上面看到的集成代码,仅在文本上会给出大约 83。使用 subreddts 和向量(如上处理)给出 89问:你试过不连接吗?
如果我不连接,性能(只是在未连接的文本上,所以再次没有向量/subreddits)类似于我连接的情况,但我不知道如何将同一作者的多个预测组合成一个预言。因为请记住,我有来自每个作者的更多评论,并且我必须预测关于作者的二元特征。
问题
- 你对我正在使用的 spaCy 代码有什么特别的建议吗(例如,使用 subreddits 和/或文档向量信息的任何其他方式)?
- 如何改进整体模型?
任何建议都受到高度赞赏。
由于我是 NLP 新手,请在代码/解释/参考方面尽可能明确。