Недавно я прочитал статью под названием Обучайте sklearn в 100 раз быстрее, в которой рассказывается о модуле Python с открытым исходным кодом под названием sk-dist. Модуль реализует распределенный« scikit-learn », расширяя его встроенное распараллеливание мета-оценки, например, pipeline.Pipeline, model_selection.GridSearchCV, feature_selection.SelectFromModel и ensemble.BaggingClassifier, и т. Д., Используя spark.

Был час ночи. Мудрецы и женщины посоветовали мне не ложиться спать допоздна и пользоваться компьютерами. Однако у меня слишком малоподвижный образ жизни, чтобы рано спать, мне слишком скучно netflix and chill, и я слишком трезв, чтобы мечтать о следующем большом событии после tiktok. Итак, я сделал следующее лучшее. Чтение статей о программировании и машинном обучении.

В статье был представлен образец кода, который в основном реализует уменьшенную задачу распознавания цифр MNIST (классификации изображений) с использованием sk-dist. Никаких упоминаний о требованиях к библиотеке не было, но импорт выглядел следующим образом:

from sklearn import datasets, svm
from skdist.distribute.search import DistGridSearchCV
from pyspark.sql import SparkSession

Я работаю как с Spark, так и с Scikit - довольно много учусь. У меня был Dockerfile, который создает образ контейнера с помощью Spark 3.0.1, Python 3.7.9 и scikit-learn 0.24.0. Я расширил файл требований с помощью sk-dist 0.1.9 и построил образ. Затем я записал образец кода и запустил команду:

docker run -it -v ${PWD}:/opt/app spark3-dev python digit.py

После некоторых отладочных сообщений программа резко провалилась. Я был немного в зоне. Очевидно, призрак Агаты Кристи сказал мне:

Все надо учитывать. Если факт не укладывается в теорию - отпустите теорию .

Итак, я начал свое расследование. Могу ли я действительно ускорить обучение scikit-learn, как предлагает статья?

Работает ли код?

Статья написана осенью 2019 года. Должно быть, тогда она сработала. Посмотрел репо. Последний коммит с успешной сборкой был сделан где-то в ноябре 2020 года. В нем упоминаются как Spark 2.4, так и Spark 3.0. Итак, я подумал, что если я создам образ докера с немного более старой версией Spark, Python и scikit-learn, это может сработать. После небольшого количества проб и ошибок код работал со следующим Dockerfile.

FROM python:3.7.4-stretch

RUN apt-get update && apt-get install -yq openjdk-8-jdk && apt-get clean
RUN apt-get update && apt-get install ca-certificates-java && apt-get clean && update-ca-certificates -f
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64/
RUN export JAVA_HOME

ENV HADOOP_VERSION 2.7
ENV APACHE_SPARK_VERSION 2.4.4
ENV APACHE_SPARK_HASH 2E3A5C853B9F28C7D4525C0ADCB0D971B73AD47D5CCE138C85335B9F53A6519540D3923CB0B5CEE41E386E49AE8A409A51AB7194BA11A254E037A848D0C4A9E5
ENV SPARK_HOME /usr/local/spark-${APACHE_SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}


RUN cd /tmp && \
        wget -q --show-progress https://archive.apache.org/dist/spark/spark-${APACHE_SPARK_VERSION}/spark-${APACHE_SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}.tgz && \
        echo "${APACHE_SPARK_HASH} *spark-${APACHE_SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}.tgz" | sha512sum -c - && \
        tar xzf spark-${APACHE_SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}.tgz -C /usr/local && \
        rm spark-${APACHE_SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}.tgz

RUN python -m pip install --upgrade pip
RUN pip install --no-cache-dir --upgrade setuptools
COPY requirements.txt /tmp/requirements.txt
RUN pip install -r /tmp/requirements.txt
WORKDIR /opt/app

Файл requirements.txt включает следующие модули:

pyspark==2.4.4
sk-dist==0.1.9
scikit-learn==0.23.0

На этот раз контейнер успешно выполнил код. Мне удалось создать аналогичную модель исполнения, используя следующее:

  • sklearn.model_selection.GridSearchCV
  • skdist.distribute.search.DistGridSearchCV

Конечно, поскольку я выполнил код на одной машине, я не заметил реальной разницы в скорости, когда GridSearchCV вызывается с использованием 10 заданий.

Может ли sk-dist обучать модели быстрее для небольших объемов данных?

В статье Обучите sklearn в 100 раз быстрее предполагается, что sk-dist применимо к данным малого и среднего размера (менее 1 миллиона записей), и утверждает, что обеспечивает лучшую производительность, чем параллельные scikit-learn и spark.ml. Я решил сравнить разницу во времени выполнения между scikit-learn, sk-dist и spark.ml при классификации изображений MNIST. Однако я выбрал больший набор данных и другой алгоритм, чтобы сравнение было более справедливым.

Я решил использовать Databricks для запуска теста, который является моим переходом на платформу, чтобы опробовать правильные искровые коды. Схема эксперимента описана ниже:

Кластер

Кластер содержит 1 драйвер с памятью 14,0 ГБ, 4 ядрами ЦП и 1–8 рабочих с памятью 14,0–112,0 ГБ, 4–32 ядра. Кластер оснащен Spark 2.4.5, Python 3.7.4, scikit-learn 0.23.0 и sk-dist 0.1.9.

Данные

Я использовал полный набор MNIST, который включает 60000 представлений изображений для обучения и 10000 для тестирования. Его можно легко загрузить из файловой системы databricks, используя следующий код:

def get_mnist_data_databricks(is_train_data, cache):    
    data_path="/databricks-datasets/mnist-digits/data-001/"  
    if is_train_data:
        filename = "mnist-digits-train.txt" 
    else: 
        filename = "mnist-digits-test.txt"
    data = spark.read.format("libsvm")\
        .option("numFeatures", "784")\
        .load(os.path.join(data_path, filename))
    if cache:
        data.cache()
    return data

Этот код возвращает фрейм данных PySpark с двумя столбцами: label и feature, где label указывает конкретный код изображения, а компонент представляет 784-мерное представление изображения в кодировке SparseVector. Чтобы преобразовать эти данные в массив numpy, я создал следующую вспомогательную функцию.

def get_numpy_array_from_sparse_vector(data):
    dataset = data\
        .apply(lambda _ : np.array(_.toArray()))\
        .values.reshape(-1,1)
    ser_data = np.apply_along_axis(lambda _ : _[0], 1, dataset)
    return ser_data

Модель обучения scikit-learn

Я обучил модель scikit-learn, используя следующий код.

sk_estimator = sklearn.tree.DecisionTreeClassifier(random_state=0)
sklearn_model = GridSearchCV(
    estimator=sk_estimator,
    param_grid={"max_depth": [2, 3, 4, 5, 6, 7, 8, 9, 10]},
    cv=10,
    scoring="f1_weighted",
    n_jobs=10
)

Эта модель дала результат f1 0,8511 по наилучшей кратности обучения и 0,8655 по тестовым данным.

Тренировочная модель sk-dist

Я обучил модель scikit-learn, используя следующий код.

sd_estimator = sklearn.tree.DecisionTreeClassifier(random_state=0)
skdist_model = DistGridSearchCV(
    estimator=sd_estimator,
    param_grid={"max_depth": [2, 3, 4, 5, 6, 7, 8, 9, 10]},
    cv=10,
    scoring="f1_weighted",
    sc=spark.sparkContext
)

Модель дала ту же оценку, что и sklearn_model.

Обучающая модель spark.ml

Для обучения модели spark.ml я использовал следующий код. Модель дала оценку f1 0,8554 на лучшем тренировочном сгибе и 0,8677 на тестовых данных. Это означает, что модель дала такую ​​же точность, как и две другие модели.

from pyspark.ml.classification import DecisionTreeClassifier
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
def get_pyspark_validator():
    idxr = StringIndexer(inputCol="label", outputCol="indexedLabel")
    clfr = DecisionTreeClassifier(labelCol="indexedLabel")
    estimator = Pipeline(stages=[idxr, clfr])
    mce = MulticlassClassificationEvaluator(labelCol="indexedLabel")
    grid = ParamGridBuilder()\
        .addGrid(classifier.maxDepth, [2, 3, 4, 5, 6, 7, 8, 9, 10])\
        .build()
    validator = CrossValidator(estimator=estimator,
                               evaluator=mce, 
                               estimatorParamMaps=grid, 
                               numFolds=10)
    return validator

Сравнение времени выполнения

Чтобы сравнить время выполнения, я сгенерировал все три модели, используя приведенные выше коды, и 50 раз применил их к набору обучающих данных. Я использовал модуль Python timeit для расчета времени выполнения.

Модель scikit-learn заняла около 261 секунды на испытание, модель spark.ml - 391 секунду на испытание, а модель sk-dist - 78 секунд. Ясно, что sk-dist дал лучшую производительность. Однако это точно не в 100 раз быстрее.

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

После мыслей

  • Значительно ли ускоряет sk-dist обучение scikit-learn? да.
  • Обеспечивает ли sk-dist ускорение, как говорится в статье Обучайте sklearn в 100 раз быстрее. Очень сложно сказать. Автор не предоставил достаточно подробностей своих результатов, чтобы повторить тот же эксперимент. В следующий раз расскажите подробнее!
  • Интересно ли заглянуть в ск-дист? Определенно.
  • Пригоден ли такой несовершенный эксперимент? да. В реальном мире проводить тщательные испытания сложно, а зачастую и нецелесообразно. Достаточно хороший тест намного лучше, чем отсутствие тестов. Однако к результатам такого теста следует относиться с недоверием. Они должны открывать больше возможностей для дальнейших испытаний, а не давать однозначных выводов.
  • Было ли это исследование поздней ночью / ранним утром, подкрепленное апельсиновым соком и сэндвичем с балони, на фоне того, что происходило во 2 сезоне Марко Поло Netflix, стоящим? Черт возьми. Я увеличил свои знания в Spark и scikit-learn частично. Какой выброс адреналина!

Заявление об ограничении ответственности

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