mllib NaiveBayes中的类数量是否有限制?调用model.save()时出错

时间:2018-01-12 21:50:19

标签: python apache-spark pyspark naivebayes apache-spark-ml

我正在尝试训练模型来预测文本输入数据的类别。当类的数量超过一定数量时,我在一个词袋上使用pyspark.ml.classification.NaiveBayes分类器遇到似乎是数值不稳定的问题。

在我的真实世界项目中,我有约10亿条记录和~50类课程。我能够训练我的模型并进行预测但是当我尝试使用model.save()保存它时出现错误。在操作上,这很烦人,因为我每次都要从头开始重新训练我的模型。

在尝试调试时,我将我的数据缩减到大约10k行并且尝试保存时遇到同样的问题。但是,如果减少类标签的数量,保存工作正常。

这让我相信标签数量有限。我无法重现我的确切问题,但下面的代码是相关的。如果我将num_labels设置为大于31的任何内容,则model.fit()会抛出错误。

我的问题:

  1. mllib NaiveBayes实施中的课程数量是否有限制?
  2. 如果我能成功地使用它来进行预测,那么我无法保存模型的原因是什么?
  3. 如果确实存在限制,是否可以将我的数据拆分为较小类的组,训练单独的模型并组合?
  4. 完整工作示例

    创建一些虚拟数据。

    我将使用nltk.corpus.comparitive_sentencesnltk.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东西

    注释

    • 在此示例中,如果我为bigrams添加功能,则num_labels>会发生错误。 15.我想知道这是否也是1,而不是2的幂。
    • 在我的真实项目中,我在尝试拨打model.theta时也遇到错误。 (我不认为错误本身是有意义的 - 它们只是从java / scala方法传回的异常。)

1 个答案:

答案 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.mlpyspark.sql一起使用,因此可以接受,而不会造成重大性能损失。

配置除了我将专注于功能工程组件。使用二进制CountVetorizer(请参阅下面有关HashingTF的说明)和ChiSqSelector可能会提供一种方法来提高可解释性并有效减少功能数量。您还可以考虑更复杂的方法(确定要素重要性,仅在数据子集上应用朴素贝叶斯,更高级的文本处理,如词形还原/词干,或使用自动编码器的某些变体来获得更紧凑的矢量表示)。

备注

  • 请记住,多国Naive Bayes只考虑二元功能。 NaiveBayes会在内部处理此问题,但为了清晰起见,我仍建议使用setBinary
  • 可以说HashingTF在这里毫无用处。抛开哈希碰撞,高度稀疏的特征和基本上没有意义的特征,使其成为NaiveBayes的预处理步骤的不良选择。