在使用朴素贝叶斯对非常长的文本进行分类时,避免获得无意义的值(例如 -0.0)
Avoid getting nonsensical values (eg. -0.0) when classifying very long texts with Naive Bayes
我已经实现了多项式朴素贝叶斯分类器的相当有效的实现,它的效果非常好。直到分类器遇到非常长的消息(大约 10k 个词),其中预测结果是无意义的(例如 -0.0)并且我在使用 Python 时得到 math domain error
math.log
] 功能。我使用对数的原因是,当处理非常小的浮点数时,如果将它们相乘得到的是非常小的浮点数,对数有助于避免无限小的数字导致预测失败。
一些上下文
我使用的是没有任何向量化的词袋方法(比如 TF-IDF,因为我不知道如何正确地实现它并平衡出现 0 的词。一个片段是也很感激 ;) ) 并且我正在使用频率计数和拉普拉斯加法平滑(基本上是为每个频率计数加 1,因此它永远不会为 0)。
我可以只删除日志,但这意味着在如此长的消息的情况下,引擎无论如何都无法正确检测到它们,所以这不是重点。
如果应用 log-sum-exp
,朴素贝叶斯中没有乘法,只有加法,因此不太可能出现下溢。如果你使用平滑(就像你说的那样),你永远不会得到 log
.
的未定义行为
This stats stackexchange answer describes the underlying math. For reference implementation I have a snippet of mine lying around for MultinomialNaiveBayes
(analogous to sklearn's sklearn.naive_bayes.MultinomialNB
和类似 API):
import numpy as np
import scipy
class MultinomialNaiveBayes:
def __init__(self, alpha: float = 1.0):
self.alpha = alpha
def fit(self, X, y):
# Calculate priors from data
self.log_priors_ = np.log(np.bincount(y) / y.shape[0])
# Get indices where data belongs to separate class, creating a slicing mask.
class_indices = np.array(
np.ma.make_mask([y == current for current in range(len(self.log_priors_))])
)
# Divide dataset based on class indices
class_datasets = np.array([X[indices] for indices in class_indices])
# Calculate how often each class occurs and add alpha smoothing.
# Reshape into [n_classes, features]
classes_metrics = (
np.array([dataset.sum(axis=0) for dataset in class_datasets]).reshape(
len(self.log_priors_), -1
)
+ self.alpha
)
# Calculate log likelihoods
self.log_likelihoods_ = np.log(
classes_metrics / classes_metrics.sum(axis=1)[:, np.newaxis]
)
return self
def predict(self, X):
# Return most likely class
return np.argmax(
scipy.sparse.csr_matrix.dot(X, self.log_likelihoods_.T) + self.log_priors_,
axis=1,
)
顺便说一句。 -0.0
与 0.0
完全相同,是有意义的值。
我已经实现了多项式朴素贝叶斯分类器的相当有效的实现,它的效果非常好。直到分类器遇到非常长的消息(大约 10k 个词),其中预测结果是无意义的(例如 -0.0)并且我在使用 Python 时得到 math domain error
math.log
] 功能。我使用对数的原因是,当处理非常小的浮点数时,如果将它们相乘得到的是非常小的浮点数,对数有助于避免无限小的数字导致预测失败。
一些上下文
我使用的是没有任何向量化的词袋方法(比如 TF-IDF,因为我不知道如何正确地实现它并平衡出现 0 的词。一个片段是也很感激 ;) ) 并且我正在使用频率计数和拉普拉斯加法平滑(基本上是为每个频率计数加 1,因此它永远不会为 0)。
我可以只删除日志,但这意味着在如此长的消息的情况下,引擎无论如何都无法正确检测到它们,所以这不是重点。
如果应用 log-sum-exp
,朴素贝叶斯中没有乘法,只有加法,因此不太可能出现下溢。如果你使用平滑(就像你说的那样),你永远不会得到 log
.
This stats stackexchange answer describes the underlying math. For reference implementation I have a snippet of mine lying around for MultinomialNaiveBayes
(analogous to sklearn's sklearn.naive_bayes.MultinomialNB
和类似 API):
import numpy as np
import scipy
class MultinomialNaiveBayes:
def __init__(self, alpha: float = 1.0):
self.alpha = alpha
def fit(self, X, y):
# Calculate priors from data
self.log_priors_ = np.log(np.bincount(y) / y.shape[0])
# Get indices where data belongs to separate class, creating a slicing mask.
class_indices = np.array(
np.ma.make_mask([y == current for current in range(len(self.log_priors_))])
)
# Divide dataset based on class indices
class_datasets = np.array([X[indices] for indices in class_indices])
# Calculate how often each class occurs and add alpha smoothing.
# Reshape into [n_classes, features]
classes_metrics = (
np.array([dataset.sum(axis=0) for dataset in class_datasets]).reshape(
len(self.log_priors_), -1
)
+ self.alpha
)
# Calculate log likelihoods
self.log_likelihoods_ = np.log(
classes_metrics / classes_metrics.sum(axis=1)[:, np.newaxis]
)
return self
def predict(self, X):
# Return most likely class
return np.argmax(
scipy.sparse.csr_matrix.dot(X, self.log_likelihoods_.T) + self.log_priors_,
axis=1,
)
顺便说一句。 -0.0
与 0.0
完全相同,是有意义的值。