背景:
我正在使用pyspark.ml中的RandomForestClassifier做一个简单的二进制分类。在将数据提供给训练之前,我设法通过提供参数maxCategories来使用VectorIndexer来确定要素是数字的还是分类的。
问题:
即使我将VectorIndexer的maxCategories设置设置为30,在训练过程中仍然出现错误:
An error occurred while calling o15371.fit.
: java.lang.IllegalArgumentException: requirement failed: DecisionTree requires maxBins (= 32) to be at least as large as the number of values in each categorical feature, but categorical feature 0 has 10765 values. Considering remove this and other categorical features with a large number of values, or add more training examples.
我的代码很简单,col_idx是我生成的列字符串列表,它将传递给stringindexer,col_all是列字符串列表,它将传递给stringindexer和onehotencoder,col_num是数字列名称。
from pyspark.ml.feature import OneHotEncoderEstimator, StringIndexer, VectorAssembler, IndexToString, VectorIndexer
from pyspark.ml import Pipeline
from pyspark.ml.classification import RandomForestClassifier
my_data.cache()
# stringindexers and encoders
stIndexers = [StringIndexer(inputCol = Col, outputCol = Col + 'Index').setHandleInvalid('keep') for Col in col_idx]
encoder = OneHotEncoderEstimator(inputCols = [Col + 'Index' for Col in col_all], outputCols = [Col + 'ClassVec' for Col in col_all]).setHandleInvalid('keep')
# vector assemblor
col_into_assembler = [cols + 'Index' for cols in col_idx] + [cols + 'ClassVec' for cols in col_all] + col_num
assembler = VectorAssembler(inputCols = col_into_assembler, outputCol = "features")
# featureIndexer, labelIndexer, rf classifier and labelConverter
featureIndexer = VectorIndexer(inputCol = "features", outputCol = "indexedFeatures", maxCategories = 30)
# columns smaller than maxCategories => categorical features, columns larger than maxCategories => numerical / continuous features, smaller value => less categorical features, larger value => more categorical features.
labelIndexer = StringIndexer(inputCol = "label", outputCol = "indexedLabel").fit(my_data)
rf = RandomForestClassifier(featuresCol = "indexedFeatures", labelCol = "indexedLabel")
labelConverter = IndexToString(inputCol = "prediction", outputCol = "predictedLabel", labels=labelIndexer.labels)
# chain all the estimators and transformers stages into a Pipeline estimator
rfPipeline = Pipeline(stages = stIndexers + [encoder, assembler, featureIndexer, labelIndexer, rf, labelConverter])
# split data, cache them
training, test = my_data.randomSplit([0.7, 0.3], seed = 100)
training.cache()
test.cache()
# fit the estimator with training dataset to get a compiled pipeline with transformers and fitted models.
ModelRF = rfPipeline.fit(training)
# make predictions
predictions = ModelRF.transform(test)
predictions.printSchema()
predictions.show(5)
所以我的问题是:即使我在VectorIndexer中将maxCategories设置为30,我的数据中仍然会有一个高级分类功能。我可以将rf分类器中的maxBins设置为更高的值,但是我很好奇:为什么VectorIndexer不能按预期工作(好,正如我所期望的那样):将小于maxCategories的特征强制转换为分类特征,将其转换为数值特征。
最佳答案
看起来与文档相反,该文档列出了:
在TODO中,元数据已被保留。
from pyspark.sql.functions import col
from pyspark.ml import Pipeline
from pyspark.ml.feature import *
df = spark.range(10)
stages = [StringIndexer(inputCol="id", outputCol="idx"), VectorAssembler(inputCols=["idx"], outputCol="features"), VectorIndexer(inputCol="features", outputCol="features_indexed", maxCategories=5)]
Pipeline(stages=stages).fit(df).transform(df).schema["features"].metadata
# {'ml_attr': {'attrs': {'nominal': [{'vals': ['8',
# '4',
# '9',
# '5',
# '6',
# '1',
# '0',
# '2',
# '7',
# '3'],
# 'idx': 0,
# 'name': 'idx'}]},
# 'num_attrs': 1}}
Pipeline(stages=stages).fit(df).transform(df).schema["features_indexed"].metadata
# {'ml_attr': {'attrs': {'nominal': [{'ord': False,
# 'vals': ['0.0',
# '1.0',
# '2.0',
# '3.0',
# '4.0',
# '5.0',
# '6.0',
# '7.0',
# '8.0',
# '9.0'],
# 'idx': 0,
# 'name': 'idx'}]},
# 'num_attrs': 1}}
在正常情况下,所需行为是。您不应该将索引分类特征用作连续变量
但是,如果仍然想避免这种行为,则必须重置元数据,例如:
pipeline1 = Pipeline(stages=stages[:1])
pipeline2 = Pipeline(stages=stages[1:])
dft1 = pipeline1.fit(df).transform(df).withColumn("idx", col("idx").alias("idx", metadata={}))
dft2 = pipeline2.fit(dft1).transform(dft1)
dft2.schema["features_indexed"].metadata
# {'ml_attr': {'attrs': {'numeric': [{'idx': 0, 'name': 'idx'}]},
# 'num_attrs': 1}}