Apache Spark стал одним из наиболее часто используемых и поддерживаемых инструментов с открытым исходным кодом для машинного обучения и анализа данных.

В этом посте я помогу вам начать использовать Apache Spark’s spark.ml Linear Regression для прогнозирования цен на жилье в Бостоне. Наши данные взяты из Конкурса Kaggle: стоимость жилья в пригородах Бостона. Для каждого наблюдения за домом у нас есть следующая информация:

ПРЕСТУПНОСТЬ - уровень преступности на душу населения по городам.

ZN - доля земли под жилую застройку, зонированная на участки площадью более 25 000 кв. футов.

INDUS - доля акров, не относящихся к розничной торговле, на город.

CHAS - фиктивная переменная реки Чарльз (= 1, если участок ограничивает реку; 0 в противном случае).

NOX - концентрация оксидов азота (частей на 10 миллионов).

RM - среднее количество комнат в доме.

ВОЗРАСТ - доля занимаемых владельцами единиц жилья, построенных до 1940 года.

DIS - средневзвешенное расстояние до пяти бостонских центров занятости.

RAD - индекс доступности радиальных магистралей.

НАЛОГ - полная ставка налога на имущество из расчета 10 000 долларов США.

PTRATIO - соотношение учеников и учителей по городам.

ЧЕРНЫЙ - 1000 (Bk - 0,63) ², где Bk - доля черных по городам.

LSTAT - более низкий статус населения (в процентах).

MV - средняя стоимость домов, занимаемых владельцами, в 1000 долларов США. Это целевая переменная.

Набор исходных данных содержит данные о деталях различных домов. На основе предоставленной информации цель состоит в том, чтобы разработать модель для прогнозирования средней стоимости данного дома в этом районе.

Загрузите данные

from pyspark import SparkConf, SparkContext
from pyspark.sql import SQLContext
sc= SparkContext()
sqlContext = SQLContext(sc)
house_df = sqlContext.read.format('com.databricks.spark.csv').options(header='true', inferschema='true').load('boston.csv')
house_df.take(1)

[Строка (CRIM = 0,00632, ZN = 18,0, INDUS = 2,309999943, CHAS = 0, NOX = 0,537999988, RM = 6,574999809, ВОЗРАСТ = 65,19999695, DIS = 4,090000153, RAD = 1, TAX = 296, PT = 15.30000019, B = 396.8999939, LSTAT = 4.980000019, MV = 24.0)]

Исследование данных

Распечатать схему в виде дерева.

house_df.cache()
house_df.printSchema()
root
 |-- CRIM: double (nullable = true)
 |-- ZN: double (nullable = true)
 |-- INDUS: double (nullable = true)
 |-- CHAS: integer (nullable = true)
 |-- NOX: double (nullable = true)
 |-- RM: double (nullable = true)
 |-- AGE: double (nullable = true)
 |-- DIS: double (nullable = true)
 |-- RAD: integer (nullable = true)
 |-- TAX: integer (nullable = true)
 |-- PT: double (nullable = true)
 |-- B: double (nullable = true)
 |-- LSTAT: double (nullable = true)
 |-- MV: double (nullable = true)

Проведите описательную аналитику

house_df.describe().toPandas().transpose()

Матрица разброса - отличный способ примерно определить, есть ли у нас линейная корреляция между несколькими независимыми переменными.

import pandas as pd
numeric_features = [t[0] for t in house_df.dtypes if t[1] == 'int' or t[1] == 'double']
sampled_data = house_df.select(numeric_features).sample(False, 0.8).toPandas()
axs = pd.scatter_matrix(sampled_data, figsize=(10, 10))
n = len(sampled_data.columns)
for i in range(n):
    v = axs[i, 0]
    v.yaxis.label.set_rotation(0)
    v.yaxis.label.set_ha('right')
    v.set_yticks(())
    h = axs[n-1, i]
    h.xaxis.label.set_rotation(90)
    h.set_xticks(())

Трудно увидеть. Найдем корреляцию между независимыми переменными и целевой переменной.

import six
for i in house_df.columns:
    if not( isinstance(house_df.select(i).take(1)[0][0], six.string_types)):
        print( "Correlation to MV for ", i, house_df.stat.corr('MV',i))
Correlation to MV for  CRIM -0.3883046116575088
Correlation to MV for  ZN 0.36044534463752903
Correlation to MV for  INDUS -0.48372517128143383
Correlation to MV for  CHAS 0.17526017775291847
Correlation to MV for  NOX -0.4273207763683772
Correlation to MV for  RM 0.695359937127267
Correlation to MV for  AGE -0.37695456714288667
Correlation to MV for  DIS 0.24992873873512172
Correlation to MV for  RAD -0.3816262315669168
Correlation to MV for  TAX -0.46853593528654536
Correlation to MV for  PT -0.5077867038116085
Correlation to MV for  B 0.3334608226834164
Correlation to MV for  LSTAT -0.7376627294671615
Correlation to MV for  MV 1.0

Коэффициент корреляции колеблется от –1 до 1. Когда он близок к 1, это означает, что существует сильная положительная корреляция; например, среднее значение имеет тенденцию повышаться при увеличении количества комнат. Когда коэффициент близок к –1, это означает, что имеется сильная отрицательная корреляция; среднее значение имеет тенденцию снижаться, когда увеличивается процент населения с более низким статусом. Наконец, коэффициенты, близкие к нулю, означают отсутствие линейной корреляции.

Пока мы сохраним все переменные.

Подготовьте данные для машинного обучения. А нам нужно всего два столбца - характеристики и метка («MV»):

from pyspark.ml.feature import VectorAssembler
vectorAssembler = VectorAssembler(inputCols = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PT', 'B', 'LSTAT'], outputCol = 'features')
vhouse_df = vectorAssembler.transform(house_df)
vhouse_df = vhouse_df.select(['features', 'MV'])
vhouse_df.show(3)

splits = vhouse_df.randomSplit([0.7, 0.3])
train_df = splits[0]
test_df = splits[1]

Линейная регрессия

from pyspark.ml.regression import LinearRegression
lr = LinearRegression(featuresCol = 'features', labelCol='MV', maxIter=10, regParam=0.3, elasticNetParam=0.8)
lr_model = lr.fit(train_df)
print("Coefficients: " + str(lr_model.coefficients))
print("Intercept: " + str(lr_model.intercept))

Коэффициенты: [0.0,0.007302310571175137, -0.03286303124593804,1.4134773328268, -7.91932366863737,5.341921692409693,0.0, -0.5791187396097941,0.0, -0,0010503197747184644, -0.7748333592630333,0.01126108224671488, -0,3932170620689197]
Intercept: +11,327590788070061

Обобщите модель по обучающей выборке и распечатайте некоторые показатели:

trainingSummary = lr_model.summary
print("RMSE: %f" % trainingSummary.rootMeanSquaredError)
print("r2: %f" % trainingSummary.r2)

RMSE: 4,675914
r2: 0,743627

RMSE измеряет различия между значениями, прогнозируемыми моделью, и фактическими значениями. Однако само по себе среднеквадратичное значение не имеет смысла, пока мы не сравним его с фактическим значением «MV», таким как среднее, минимальное и максимальное. После такого сравнения наша RMSE выглядит неплохо.

train_df.describe().show()

R в квадрате 0,74 указывает на то, что в нашей модели около 74% изменчивости MV можно объяснить с помощью модели. Это согласуется с результатом Scikit-Learn. Это не плохо. Однако мы должны быть осторожны, так как производительность на обучающем наборе не может быть хорошим приближением к производительности на тестовом наборе.

lr_predictions = lr_model.transform(test_df)
lr_predictions.select("prediction","MV","features").show(5)
from pyspark.ml.evaluation import RegressionEvaluator
lr_evaluator = RegressionEvaluator(predictionCol="prediction", \
                 labelCol="MV",metricName="r2")
print("R Squared (R2) on test data = %g" % lr_evaluator.evaluate(lr_predictions))

test_result = lr_model.evaluate(test_df)
print("Root Mean Squared Error (RMSE) on test data = %g" % test_result.rootMeanSquaredError)

Среднеквадратичная ошибка (RMSE) для тестовых данных = 5,52048

Конечно, на тестовой выборке мы достигли худших значений RMSE и R в квадрате.

print("numIterations: %d" % trainingSummary.totalIterations)
print("objectiveHistory: %s" % str(trainingSummary.objectiveHistory))
trainingSummary.residuals.show()

numIterations: 11
objectiveHistory: [0,49999999999999956, +0,4281126976069304, +0,22539203628598917, +0,20185326295592582, +0,1686847843494657, +0,16588096079648484, +0,16543041085178495, +0,16508485781434112, 0,16484472289473545, 0,16454785266359198, +0,16447743850144508]

Используя нашу модель линейной регрессии, чтобы сделать некоторые прогнозы:

predictions = lr_model.transform(test_df)
predictions.select("prediction","MV","features").show()

Регрессия дерева решений

from pyspark.ml.regression import DecisionTreeRegressor
dt = DecisionTreeRegressor(featuresCol ='features', labelCol = 'MV')
dt_model = dt.fit(train_df)
dt_predictions = dt_model.transform(test_df)
dt_evaluator = RegressionEvaluator(
    labelCol="MV", predictionCol="prediction", metricName="rmse")
rmse = dt_evaluator.evaluate(dt_predictions)
print("Root Mean Squared Error (RMSE) on test data = %g" % rmse)

Среднеквадратичная ошибка (RMSE) для тестовых данных = 4,39053

Важность функции

dt_model.featureImportances

SparseVector (13, {0: 0,0496, 1: 0,0, 4: 0,0118, 5: 0,624, 6: 0,0005, 7: 0,1167, 8: 0,0044, 10: 0,013, 12: 0,1799})

house_df.take(1)

[Строка (CRIM = 0,00632, ZN = 18,0, INDUS = 2,309999943, CHAS = 0, NOX = 0,537999988, RM = 6,574999809, ВОЗРАСТ = 65,19999695, DIS = 4,090000153, RAD = 1, TAX = 296, PT = 15.30000019, B = 396.8999939, LSTAT = 4.980000019, MV = 24.0)]

По-видимому, количество комнат является наиболее важной характеристикой для прогнозирования средней цены дома в наших данных.

Регрессия дерева с усилением градиента

from pyspark.ml.regression import GBTRegressor
gbt = GBTRegressor(featuresCol = 'features', labelCol = 'MV', maxIter=10)
gbt_model = gbt.fit(train_df)
gbt_predictions = gbt_model.transform(test_df)
gbt_predictions.select('prediction', 'MV', 'features').show(5)

gbt_evaluator = RegressionEvaluator(
    labelCol="MV", predictionCol="prediction", metricName="rmse")
rmse = gbt_evaluator.evaluate(gbt_predictions)
print("Root Mean Squared Error (RMSE) on test data = %g" % rmse)

Среднеквадратичная ошибка (RMSE) для тестовых данных = 4,19795

Древовидная регрессия с градиентным усилением показала наилучшие результаты на наших данных.

Исходный код можно найти на Github. Буду рад услышать любые отзывы или вопросы.