我正在尝试训练模型来预测文本输入数据的类别。当类的数量超过一定数量时,我在一个词袋上使用pyspark.ml.classification.NaiveBayes
分类器遇到似乎是数值不稳定的问题。
在我的真实世界项目中,我有约10亿条记录和~50类课程。我能够训练我的模型并进行预测但是当我尝试使用model.save()
保存它时出现错误。在操作上,这很烦人,因为我每次都要从头开始重新训练我的模型。
在尝试调试时,我将我的数据缩减到大约10k行并且尝试保存时遇到同样的问题。但是,如果减少类标签的数量,保存工作正常。
这让我相信标签数量有限。我无法重现我的确切问题,但下面的代码是相关的。如果我将num_labels
设置为大于31的任何内容,则model.fit()
会抛出错误。
我的问题:
mllib
NaiveBayes
实施中的课程数量是否有限制?创建一些虚拟数据。
我将使用nltk.corpus.comparitive_sentences
和nltk.corpus.sentence_polarity
。请记住,这只是一个无意义数据的说明性示例 - 我并不关心拟合模型的性能。
import pandas as pd
from pyspark.sql.types import StringType
# create some dummy data
from nltk.corpus import comparative_sentences, sentence_polarity
df = pd.DataFrame(
{
'sentence': [" ".join(s) for s in cs.sents() + sp.sents()]
}
)
# assign a 'category' to each row
num_labels = 31 # seems to be the upper limit
df['category'] = (df.index%num_labels).astype(str)
# make it into a spark dataframe
spark_df = sqlCtx.createDataFrame(df)
数据准备管道
from pyspark.ml.feature import NGram, Tokenizer, StopWordsRemover
from pyspark.ml.feature import HashingTF, IDF, StringIndexer, VectorAssembler
from pyspark.ml import Pipeline
from pyspark.ml.linalg import Vector
indexer = StringIndexer(inputCol='category', outputCol='label')
tokenizer = Tokenizer(inputCol="sentence", outputCol="sentence_tokens")
remove_stop_words = StopWordsRemover(inputCol="sentence_tokens", outputCol="filtered")
unigrammer = NGram(n=1, inputCol="filtered", outputCol="tokens")
hashingTF = HashingTF(inputCol="tokens", outputCol="hashed_tokens")
idf = IDF(inputCol="hashed_tokens", outputCol="tf_idf_tokens")
clean_up = VectorAssembler(inputCols=['tf_idf_tokens'], outputCol='features')
data_prep_pipe = Pipeline(
stages=[indexer, tokenizer, remove_stop_words, unigrammer, hashingTF, idf, clean_up]
)
transformed = data_prep_pipe.fit(spark_df).transform(spark_df)
clean_data = transformed.select(['label','features'])
训练模型
from pyspark.ml.classification import NaiveBayes
nb = NaiveBayes()
(training,testing) = clean_data.randomSplit([0.7,0.3], seed=12345)
model = nb.fit(training)
test_results = model.transform(testing)
评估模型
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
acc_eval = MulticlassClassificationEvaluator()
acc = acc_eval.evaluate(test_results)
print("Accuracy of model at predicting label was: {}".format(acc))
在我的机器上打印:
Accuracy of model at predicting label was: 0.0305764788269
错误消息
如果我将num_labels
更改为32或更高,这是我拨打model.fit()
时出现的错误:
Py4JJavaError:调用o1336.fit时发生错误。 : org.apache.spark.SparkException:作业因阶段失败而中止: 阶段86.0中的任务0失败4次,最近失败:丢失任务 阶段86.0中的0.3(TID 1984,someserver.somecompany.net,executor 22):org.apache.spark.SparkException:Kryo序列化失败:缓冲区 溢出。可用:7,必需:8序列化跟踪:值 (org.apache.spark.ml.linalg.DenseVector)。为了避免这种情况,增加 spark.kryoserializer.buffer.max值。 ... ... 等等等等等等永远存在的java东西
num_labels
>会发生错误。 15.我想知道这是否也是1,而不是2的幂。model.theta
时也遇到错误。 (我不认为错误本身是有意义的 - 它们只是从java / scala方法传回的异常。)答案 0 :(得分:3)
硬限制:
要素数量*类别数量必须更低Integer.MAX_VALUE
(2 31 - 1)。你远不及这些价值。
软限制:
Theta矩阵(条件概率)具有大小特征数*类的数量。 Theta本地存储在驱动程序中(作为模型的一部分)并序列化并发送给工作人员。这意味着所有机器至少需要足够的内存来序列化或反序列化并存储结果。
由于您使用HashingTF.numFeatures
(2 20 )的默认设置,因此每个附加类添加262144 - 它不是那么多,但很快就会加起来。根据您发布的部分回溯,看起来失败的组件是Kryo序列化程序。同样的追溯也提出了解决方案,该问题正在增加spark.kryoserializer.buffer.max
。
您还可以通过设置:
尝试使用标准Java序列化 spark.serializer org.apache.spark.serializer.JavaSerializer
由于您将PySpark与pyspark.ml
和pyspark.sql
一起使用,因此可以接受,而不会造成重大性能损失。
配置除了我将专注于功能工程组件。使用二进制CountVetorizer
(请参阅下面有关HashingTF
的说明)和ChiSqSelector
可能会提供一种方法来提高可解释性并有效减少功能数量。您还可以考虑更复杂的方法(确定要素重要性,仅在数据子集上应用朴素贝叶斯,更高级的文本处理,如词形还原/词干,或使用自动编码器的某些变体来获得更紧凑的矢量表示)。
备注强>:
NaiveBayes
会在内部处理此问题,但为了清晰起见,我仍建议使用setBinary
。HashingTF
在这里毫无用处。抛开哈希碰撞,高度稀疏的特征和基本上没有意义的特征,使其成为NaiveBayes
的预处理步骤的不良选择。