You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何加速spaCy多标签文本分类器的评估计算?

问题:如何加速多标签文本分类的评分计算(log loss、precision/recall/fscore)?

我基于spaCy构建了一个多标签文本分类器,现在评估过程耗时极长。排查后发现,耗时的环节不是模型预测,而是log loss、precision、recall及fscore的计算

以下是我的评估代码:

from sklearn.metrics import precision_recall_fscore_support
from sklearn.metrics import log_loss
import time
import numpy as np

def evaluate( nlp, texts, cats, labels, threshold=0.3, beta=0.5, batch_size=8 ):
    t0 = time.time()
    docs = nlp.pipe(texts, batch_size=batch_size)
    t1 = time.time()
    pred_probs = np.array([list(doc.cats.values()) for doc in docs])
    avg_log_loss = log_loss(cats, pred_probs)
    results = {'log_loss': avg_log_loss}
    y_pred = pred_probs > threshold
    prc, rec, fscore, _ = precision_recall_fscore_support(
        y_true=cats, y_pred=y_pred, beta=beta, average='micro', warn_for=set()
    )
    results[f'f{beta}_{threshold}'] = fscore
    results[f'prc_{threshold}'] = prc
    results[f'rec_{threshold}'] = rec
    t2 = time.time()
    print(f"Used {t1-t0} on predicting; {t2-t1} on scoring")
    return results

运行后输出:

Used 4.76837158203125e-06 on predicting; 377.1225287914276 on scoring

请问有什么方法可以加速这些评分指标的计算?


回答

你的模型预测速度快得离谱,但评分计算直接拖垮了整个流程——这很常见,毕竟scikit-learn的内置指标函数是通用型设计,要兼容各种场景,难免在特定需求下有冗余开销。这里有几个针对性的优化方案,我自己在处理大规模多标签数据时亲测有效:

1. 手动实现micro平均的precision/recall/fscore

precision_recall_fscore_support要处理多类、多标签、不同平均方式等一堆情况,内部分支和检查逻辑很多。而你用的是average='micro',完全可以绕开通用函数,直接用NumPy向量化计算:

# 替换原来的precision_recall_fscore_support调用
y_true = cats
y_pred = pred_probs > threshold

# 用向量化操作快速统计TP、FP、FN
tp = np.sum((y_true == 1) & (y_pred == 1))
fp = np.sum((y_true == 0) & (y_pred == 1))
fn = np.sum((y_true == 1) & (y_pred == 0))

# 计算指标(处理分母为0的边界情况)
prc = tp / (tp + fp) if (tp + fp) > 0 else 0.0
rec = tp / (tp + fn) if (tp + fn) > 0 else 0.0
beta_sq = beta ** 2
fscore = (1 + beta_sq) * prc * rec / (beta_sq * prc + rec) if (beta_sq * prc + rec) > 0 else 0.0

# 赋值到结果字典
results[f'f{beta}_{threshold}'] = fscore
results[f'prc_{threshold}'] = prc
results[f'rec_{threshold}'] = rec

这种方式没有多余的逻辑,完全依赖NumPy的底层优化,数据量越大,速度提升越明显。

2. 手动优化log_loss的计算

sklearn的log_loss同样有很多通用处理(比如类别权重、样本权重),如果你的场景是标准的多标签二分类(每个标签是0/1),可以手动实现更轻量的版本:

epsilon = 1e-15  # 避免log(0)的数值溢出问题
pred_probs_clipped = np.clip(pred_probs, epsilon, 1 - epsilon)

# 向量化计算所有样本的log loss,再取平均
avg_log_loss = -(cats * np.log(pred_probs_clipped) + (1 - cats) * np.log(1 - pred_probs_clipped)).mean()

这个实现跳过了sklearn内部的参数检查、权重处理等步骤,纯NumPy运算的速度会快不少。

3. 用稀疏矩阵处理稀疏多标签数据

如果你的多标签数据大部分标签都是0(稀疏场景),把catsy_pred转换成稀疏矩阵(比如scipy.sparse.csr_matrix),计算时只会处理非零元素,能大幅减少计算量和内存占用:

from scipy.sparse import csr_matrix

# 转换为稀疏矩阵
y_true_sparse = csr_matrix(cats)
y_pred_sparse = csr_matrix(pred_probs > threshold)

# 稀疏矩阵下的TP/FP/FN计算
tp = (y_true_sparse.multiply(y_pred_sparse)).sum()
fp = y_pred_sparse.sum() - tp
fn = y_true_sparse.sum() - tp

稀疏矩阵的运算在处理稀疏数据时,比密集数组快几个数量级,尤其适合标签数量多的场景。

4. 确保数组是连续存储的

NumPy对连续存储的数组(C-contiguous)运算效率更高,如果你的catspred_probs是从列表转换来的非连续数组,先转成连续数组:

pred_probs = np.ascontiguousarray(pred_probs)
cats = np.ascontiguousarray(cats)

这个小操作能让后续的向量化运算跑得更顺畅。

5. 分块处理超大规模数据

如果数据大到内存装不下,可以把数据分成小块,分别计算每个块的TP/FP/FN和log loss,最后汇总结果:

比如计算log loss:

chunk_size = 10000  # 按需调整 chunk 大小
total_loss = 0.0
n_samples = len(cats)
epsilon = 1e-15

for i in range(0, n_samples, chunk_size):
    chunk_cats = cats[i:i+chunk_size]
    chunk_probs = pred_probs[i:i+chunk_size]
    chunk_probs_clipped = np.clip(chunk_probs, epsilon, 1 - epsilon)
    chunk_loss = -(chunk_cats * np.log(chunk_probs_clipped) + (1 - chunk_cats) * np.log(1 - chunk_probs_clipped)).sum()
    total_loss += chunk_loss

avg_log_loss = total_loss / n_samples

TP/FP/FN也可以分块累加,最后再计算指标,既省内存又能避免一次性处理大数组的性能瓶颈。


内容的提问来源于stack exchange,提问作者tsorn

火山引擎 最新活动