Spark的LinearRegressionWithSGD对特征缩放非常敏感

时间:2015-06-22 13:40:38

标签: apache-spark linear-regression apache-spark-mllib

我在Spark的MLlib中遇到了适合LinearRegressionWithSGD的问题。我使用他们的例子从这里拟合https://spark.apache.org/docs/latest/mllib-linear-methods.html(使用Python接口)。

在他们的例子中,所有特征几乎都是缩放,平均值约为0,标准差约为1.现在如果我将其中一个放大10倍,则回归中断(给出nans或非常大的系数):

from pyspark.mllib.regression import LabeledPoint, LinearRegressionWithSGD
from numpy import array

# Load and parse the data
def parsePoint(line):
    values = [float(x) for x in line.replace(',', ' ').split(' ')]
    # UN-SCALE one of the features by a factor of 10
    values[3] *= 10

    return LabeledPoint(values[0], values[1:])

data = sc.textFile(spark_home+"data/mllib/ridge-data/lpsa.data")
parsedData = data.map(parsePoint)

# Build the model
model = LinearRegressionWithSGD.train(parsedData)

# Evaluate the model on training data
valuesAndPreds = parsedData.map(lambda p: (p.label,     model.predict(p.features)))
MSE = valuesAndPreds.map(lambda (v, p): (v - p)**2).reduce(lambda x, y: x + y) / valuesAndPreds.count()
print("Mean Squared Error = " + str(MSE))
print "Model coefficients:", str(model)

所以,我想我需要进行功能扩展。如果我做预缩放它可以工作(因为我回到缩放功能)。但是现在我不知道如何在原始空间中获得系数。

from pyspark.mllib.regression import LabeledPoint, LinearRegressionWithSGD
from numpy import array
from pyspark.mllib.feature import StandardScaler
from pyspark.mllib.feature import StandardScalerModel

# Load and parse the data
def parseToDenseVector(line):
    values = [float(x) for x in line.replace(',', ' ').split(' ')]
    # UN-SCALE one of the features by a factor of 10
    values[3] *= 10
    return Vectors.dense(values[0:])

# Load and parse the data
def parseToLabel(values):
    return LabeledPoint(values[0], values[1:])

data = sc.textFile(spark_home+"data/mllib/ridge-data/lpsa.data")

parsedData = data.map(parseToDenseVector)
scaler = StandardScaler(True, True)
scaler_model = scaler.fit(parsedData)
parsedData_scaled = scaler_model.transform(parsedData)

parsedData_scaled_transformed = parsedData_scaled.map(parseToLabel)

# Build the model
model = LinearRegressionWithSGD.train(parsedData_scaled_transformed)

# Evaluate the model on training data
valuesAndPreds = parsedData_scaled_transformed.map(lambda p: (p.label, model.predict(p.features)))
MSE = valuesAndPreds.map(lambda (v, p): (v - p)**2).reduce(lambda x, y: x + y) / valuesAndPreds.count()
print("Mean Squared Error = " + str(MSE))
print "Model coefficients:", str(model)

所以,这里我有变换空间中的所有系数。现在我如何到达原始空间?我还有 scaler_model ,它是 StandardScalerModel 对象。但我无法从中获得任何手段或差异。这个类唯一的公共方法是 transform ,它可以将点从原始空间转换为变换。但我无法逆转。

3 个答案:

答案 0 :(得分:2)

我刚遇到这个问题。如果培训数据中f(x) = x为高(> 3),则模型甚至无法学习x。太可怕了。

我认为不是缩放数据而是另一种选择是改变步长。这在SPARK-1859中讨论。从那里解释:

  

在Lipschitz常数L上,步长应小于1。   对于二次损失和GD,最佳收敛发生在stepSize = 1/(2L)。 Spark在损失函数上有一个(1/n)乘数。

     

假设您有n = 5个数据点,最大的特征值为1500。所以L = 1500 * 1500 / 5。最佳收敛发生在stepSize = 1/(2L) = 10 / (1500 ^ 2)

最后的平等甚至没有意义(我们怎么在分子中得到2?)但我以前从未听说过Lipschitz常数,所以我没有资格修复它。无论如何,我认为我们可以尝试不同的步长,直到它开始工作。

答案 1 :(得分:1)

要重新解释您的问题,您需要找到解算等式的截距I和系数C_1C_2Y = I + C_1 * x_1 + C_2 * x_2(其中x_1x_2未缩放)。

i成为mllib返回的截距。同样,让c_1c_2成为mllib返回的系数(或权重)。

m_1成为x_1未缩放平均值,m_2x_2的未缩放平均值。

s_1成为x_1s_2未缩放标准偏差,是x_2的未缩放标准差。

然后C_1 = (c_1 / s_1)C_2 = (c_2 / s_2)

I = i - c_1 * m_1 / s_1 - c_2 * m_2 / s_2

这可以很容易地扩展到3个输入变量:

C_3 = (c_3 / s_3)I = i - c_1 * m_1 / s_1 - c_2 * m_2 / s_2 - c_3 * m_3 / s_3

答案 2 :(得分:0)

正如您所指出的那样,pyspark中的StandardScalerModel对象不会暴露std和mean属性。存在问题https://issues.apache.org/jira/browse/SPARK-6523

您可以自己轻松计算出来

import numpy as np
from pyspark.mllib.stat import Statistics

summary = Statistics.colStats(features)
mean = summary.mean()
std = np.sqrt(features.variance())

这些与Scaler使用的平均值和标准相同。您可以使用python magic dict

来验证这一点
print scaler_model.__dict__.get('_java_model').std()
print scaler_model.__dict__.get('_java_model').mean()