首页 文章

如何准备mllib中的训练数据

提问于
浏览
5

TL;DR; 如何使用 mllib 训练我的维基数据(文本和类别)以预测推文?

我无法弄清楚如何转换我的标记化维基数据,以便可以通过 NaiveBayesLogisticRegression 进行训练 . 我的目标是使用经过训练的模型与推文进行比较* . 我已经尝试使用带有LR的管道和带有 IDFHashingTF 用于 NaiveBayes ,但我一直得到错误的预测 . 在这里_1184927已尝试:

*请注意,我想在我的标签中使用维基数据中的许多类别...我只看到二元分类(它是一个或另一个类别)....是否可以做我想要的?

管道w LR

import org.apache.spark.rdd.RDD
import org.apache.spark.SparkContext
import org.apache.spark.ml.feature.HashingTF
import org.apache.spark.mllib.linalg.Vector
import org.apache.spark.ml.feature.RegexTokenizer

case class WikiData(category: String, text: String)
case class LabeledData(category: String, text: String, label: Double)

val wikiData = sc.parallelize(List(WikiData("Spark", "this is about spark"), WikiData("Hadoop","then there is hadoop")))

val categoryMap = wikiData.map(x=>x.category).distinct.zipWithIndex.mapValues(x=>x.toDouble/1000).collectAsMap

val labeledData = wikiData.map(x=>LabeledData(x.category, x.text, categoryMap.get(x.category).getOrElse(0.0))).toDF

val tokenizer = new RegexTokenizer()
  .setInputCol("text")
  .setOutputCol("words")
  .setPattern("/W+")
val hashingTF = new HashingTF()
  .setNumFeatures(1000)
  .setInputCol(tokenizer.getOutputCol)
  .setOutputCol("features")
val lr = new LogisticRegression()
  .setMaxIter(10)
  .setRegParam(0.01)
val pipeline = new Pipeline()
  .setStages(Array(tokenizer, hashingTF, lr))

val model = pipeline.fit(labeledData)

model.transform(labeledData).show

朴素贝叶斯

val hashingTF = new HashingTF()
val tf: RDD[Vector] = hashingTF.transform(documentsAsWordSequenceAlready)

import org.apache.spark.mllib.feature.IDF

tf.cache()
val idf = new IDF().fit(tf)
val tfidf: RDD[Vector] = idf.transform(tf)

tf.cache()
val idf = new IDF(minDocFreq = 2).fit(tf)
val tfidf: RDD[Vector] = idf.transform(tf)

//to create tfidfLabeled (below) I ran a map set the labels...but again it seems to have to be 1.0 or 0.0?

NaiveBayes.train(tfidfLabeled)
  .predict(hashingTF.transform(tweet))
  .collect

1 回答

  • 3

    ML LogisticRegression 尚不支持多项分类,但MLLib NaiveBayesLogisticRegressionWithLBFGS 都支持它 . 在第一种情况下,它应该默认工作:

    import org.apache.spark.mllib.classification.NaiveBayes
    
    val nbModel = new NaiveBayes()
      .setModelType("multinomial") // This is default value
      .run(train)
    

    但是对于逻辑回归,你应该提供一些类:

    import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS
    
    val model = new LogisticRegressionWithLBFGS()
      .setNumClasses(n) // Set number of classes
      .run(trainingData)
    

    关于预处理步骤,这是一个相当广泛的主题,如果没有访问您的数据,很难给您一个有意义的建议,所以您在下面找到的所有内容只是一个疯狂的猜测:

    • 据我所知,您使用维基数据进行培训和推文进行测试 . 如果这是真的,一般来说这是一个坏主意 . 您可以预期两组使用显着不同的词汇,语法和拼写

    • 简单的正则表达式标记符可以在标准化文本上很好地执行,但根据我的经验,它不会像推文那样在非正式文本上运行良好

    • HashingTF 可以是获得基线模型的好方法,但它是极其简化的方法,特别是如果您不应用任何过滤步骤 . 如果您决定使用它,至少应增加功能数量或使用默认值(2 ^ 20)

    EDIT (用IDF为朴素贝叶斯准备数据)

    • 使用ML管道:
    import org.apache.spark.mllib.regression.LabeledPoint
    import org.apache.spark.mllib.linalg.Vector
    import org.apache.spark.ml.feature.IDF
    import org.apache.spark.sql.Row
    
    val tokenizer = ???
    
    val hashingTF = new HashingTF()
      .setNumFeatures(1000)
      .setInputCol(tokenizer.getOutputCol)
      .setOutputCol("rawFeatures")
    
    val idf = new IDF()
      .setInputCol(hashingTF.getOutputCol)
      .setOutputCol("features")
    
    val pipeline = new Pipeline().setStages(Array(tokenizer, hashingTF, idf))
    val model = pipeline.fit(labeledData)
    
    model
     .transform(labeledData)
     .select($"label", $"features")
     .map{case Row(label: Double, features: Vector) => LabeledPoint(label, features)}
    
    • 使用MLlib变换器:
    import org.apache.spark.mllib.feature.HashingTF
    import org.apache.spark.mllib.linalg.Vector
    import org.apache.spark.mllib.feature.{IDF, IDFModel}
    
    val labeledData = wikiData.map(x => 
      LabeledData(x.category, x.text, categoryMap.get(x.category).getOrElse(0.0)))
    
    val p = "\\W+".r
    val raw = labeledData.map{
        case LabeledData(_, text, label) => (label, p.split(text))}
    
    val hashingTF: org.apache.spark.mllib.feature.HashingTF = new HashingTF(1000)
    val tf = raw.map{case (label, text) => (label, hashingTF.transform(text))}
    
    val idf: org.apache.spark.mllib.feature.IDFModel = new IDF().fit(tf.map(_._2))
    tf.map{
      case (label, rawFeatures) => LabeledPoint(label, idf.transform(rawFeatures))}
    

    注意:由于变换器需要JVM访问,因此MLlib版本在PySpark中不起作用 . 如果你更喜欢Python,你必须split data transform and zip .

    EDIT (为ML算法准备数据):

    虽然下面的代码看起来有点乍一看

    val categoryMap = wikiData
      .map(x=>x.category)
      .distinct
      .zipWithIndex
      .mapValues(x=>x.toDouble/1000)
      .collectAsMap
    
    val labeledData = wikiData.map(x=>LabeledData(
        x.category, x.text, categoryMap.get(x.category).getOrElse(0.0))).toDF
    

    它不会为 ML 算法生成有效标签 .

    首先 ML 期望标签在(0.0,1.0,...,n.0)中,其中n是类的数量 . 如果你的示例管道中有一个类得到标签0.001,你将得到如下错误:

    ERROR LogisticRegression:分类标签应在{0到0中找到1个无效标签 .

    显而易见的解决方案是在生成映射时避免除法

    .mapValues(x=>x.toDouble)
    

    虽然它适用于 LogisticRegression 其他 ML 算法仍然会失败 . 例如 RandomForestClassifier 你会得到

    给RandomForestClassifier输入了无效的标签列标签,没有指定的类数 . 请参见StringIndexer .

    有趣的是, RandomForestClassifier 的ML版本与 MLlib 版本不同,它没有提供设置多个类的方法 . 事实证明,它希望在 DataFrame 列上设置特殊属性 . 最简单的方法是使用错误消息中提到的 StringIndexer

    import org.apache.spark.ml.feature.StringIndexer
    
    val indexer = new StringIndexer()
      .setInputCol("category")
      .setOutputCol("label")
    
    val pipeline = new Pipeline()
      .setStages(Array(indexer, tokenizer, hashingTF, idf, lr))
    
    val model = pipeline.fit(wikiData.toDF)
    

相关问题