Рубрики
Без рубрики

scikit-learn: Сохранение и восстановление моделей

Автор оригинала: Mihajlo Pavloski.

Во многих случаях, работая с библиотекой scikit-learn , вам нужно будет сохранить ваши модели прогнозирования в файл, а затем восстановить их, чтобы повторно использовать вашу предыдущую работу: протестировать вашу модель на новых данных, сравнить несколько моделей или что-то еще. Эта процедура сохранения также известна как сериализация объекта – представление объекта с потоком байтов, чтобы сохранить его на диске, отправить по сети или сохранить в базе данных, в то время как процедура восстановления известна как десериализация. В этой статье мы рассмотрим три возможных способа сделать это в Python и scikit-learn, каждый из которых представлен со своими плюсами и минусами.

Инструменты для сохранения и восстановления моделей

Первый инструмент , который мы описываем, – это Pickle , стандартный инструмент Python для сериализации объектов (de). Затем мы рассмотрим библиотеку Joblib , которая предлагает легкую (de)сериализацию объектов, содержащих большие массивы данных, и, наконец, представим ручной подход для сохранения и восстановления объектов в/из JSON (JavaScript Object Notation). Ни один из этих подходов не является оптимальным решением, но правильный подход должен быть выбран в соответствии с потребностями вашего проекта.

Инициализация модели

Сначала давайте создадим одну модель scikit-learn. В нашем примере мы будем использовать модель Логистической регрессии и набор данных Iris . Давайте импортируем необходимые библиотеки, загрузим данные и разделим их на обучающие и тестовые наборы.

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# Load and split data
data = load_iris()
Xtrain, Xtest, Ytrain, Ytest = train_test_split(data.data, data.target, test_size=0.3, random_state=4)

Теперь давайте создадим модель с некоторыми нестандартными параметрами и поместим ее в обучающие данные. Мы предполагаем, что вы заранее нашли оптимальные параметры модели, то есть те, которые дают наибольшую оценочную точность.

# Create a model
model = LogisticRegression(C=0.1, 
                           max_iter=20, 
                           fit_intercept=True, 
                           n_jobs=3, 
                           solver='liblinear')
model.fit(Xtrain, Ytrain)

И наша результирующая модель:

LogisticRegression(C=0.1, class_weight=None, dual=False, fit_intercept=True,
    intercept_scaling=1, max_iter=20, multi_class='ovr', n_jobs=3,
    penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
    verbose=0, warm_start=False)

Используя метод fit , модель узнала свои коэффициенты, которые хранятся в файле model.coef_ . Цель состоит в том, чтобы сохранить параметры и коэффициенты модели в файл, чтобы вам не нужно было повторять шаги обучения модели и оптимизации параметров снова на новых данных.

Модуль Рассола

В следующих нескольких строках кода модель, которую мы создали на предыдущем шаге, сохраняется в файл, а затем загружается как новый объект с именем pickled_model . Затем загруженная модель используется для расчета показателя точности и прогнозирования результатов на новых невидимых (тестовых) данных.

import pickle

#
# Create your model here (same as above)
#

# Save to file in the current working directory
pkl_filename = "pickle_model.pkl"
with open(pkl_filename, 'wb') as file:
    pickle.dump(model, file)

# Load from file
with open(pkl_filename, 'rb') as file:
    pickle_model = pickle.load(file)
    
# Calculate the accuracy score and predict target values
score = pickle_model.score(Xtest, Ytest)
print("Test score: {0:.2f} %".format(100 * score))
Ypredict = pickle_model.predict(Xtest)

Запуск этого кода должен дать ваш счет и сохранить модель с помощью Pickle:

$ python save_model_pickle.py
Test score: 91.11 %

Самое замечательное в использовании Pickle для сохранения и восстановления наших моделей обучения заключается в том, что это быстро – вы можете сделать это в двух строках кода. Это полезно, если вы оптимизировали параметры модели на обучающих данных, поэтому вам не нужно повторять этот шаг снова. Во всяком случае, он не сохраняет результаты тестов или какие-либо данные. Тем не менее, вы можете сделать это, сохранив кортеж или список из нескольких объектов (и запомнив, какой объект куда идет), следующим образом:

tuple_objects = (model, Xtrain, Ytrain, score)

# Save tuple
pickle.dump(tuple_objects, open("tuple_model.pkl", 'wb'))

# Restore tuple
pickled_model, pickled_Xtrain, pickled_Ytrain, pickled_score = pickle.load(open("tuple_model.pkl", 'rb'))

Модуль Joblib

Библиотека Joblib предназначена для замены Pickle для объектов, содержащих большие данные. Мы повторим процедуру сохранения и восстановления, как и в случае с Pickle.

from sklearn.externals import joblib

# Save to file in the current working directory
joblib_file = "joblib_model.pkl"
joblib.dump(model, joblib_file)

# Load from file
joblib_model = joblib.load(joblib_file)

# Calculate the accuracy and predictions
score = joblib_model.score(Xtest, Ytest)
print("Test score: {0:.2f} %".format(100 * score))
Ypredict = pickle_model.predict(Xtest)
$ python save_model_joblib.py
Test score: 91.11 %

Как видно из примера, библиотека Joblib предлагает немного более простой рабочий процесс по сравнению с Pickle. В то время как Pickle требует передачи объекта file в качестве аргумента, Joblib работает как с объектами file, так и со строковыми именами файлов. Если ваша модель содержит большие массивы данных, каждый массив будет храниться в отдельном файле, но процедура сохранения и восстановления останется прежней. Joblib также допускает различные методы сжатия, такие как “zlib”, “gzip”, ” bz2 ” и различные уровни сжатия.

Ручное сохранение и восстановление в JSON

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

Ниже приведен пример сохранения и восстановления объектов вручную с помощью JSON. Этот подход позволяет нам выбрать данные, которые необходимо сохранить, такие как параметры модели, коэффициенты, обучающие данные и все остальное, что нам нужно.

Поскольку мы хотим сохранить все эти данные в одном объекте, один из возможных способов сделать это-создать новый класс, который наследуется от класса модели, который в нашем примере является LogisticRegression . Новый класс, называемый Bylogreg , затем реализует методы save_json и load_json для сохранения и восстановления в/из JSON-файла соответственно.

Для простоты мы сохраним только три параметра модели и обучающие данные. Некоторые дополнительные данные, которые мы могли бы хранить при таком подходе,-это, например, оценка перекрестной проверки обучающего набора, тестовые данные, оценка точности тестовых данных и т. Д.

import json
import numpy as np

class MyLogReg(LogisticRegression):
    
    # Override the class constructor
    def __init__(self, C=1.0, solver='liblinear', max_iter=100, X_train=None, Y_train=None):
        LogisticRegression.__init__(self, C=C, solver=solver, max_iter=max_iter)
        self.X_train = X_train
        self.Y_train = Y_train
        
    # A method for saving object data to JSON file
    def save_json(self, filepath):
        dict_ = {}
        dict_['C'] = self.C
        dict_['max_iter'] = self.max_iter
        dict_['solver'] = self.solver
        dict_['X_train'] = self.X_train.tolist() if self.X_train is not None else 'None'
        dict_['Y_train'] = self.Y_train.tolist() if self.Y_train is not None else 'None'
        
        # Creat json and save to file
        json_txt = json.dumps(dict_, indent=4)
        with open(filepath, 'w') as file:
            file.write(json_txt)
    
    # A method for loading data from JSON file
    def load_json(self, filepath):
        with open(filepath, 'r') as file:
            dict_ = json.load(file)
            
        self.C = dict_['C']
        self.max_iter = dict_['max_iter']
        self.solver = dict_['solver']
        self.X_train = np.asarray(dict_['X_train']) if dict_['X_train'] != 'None' else None
        self.Y_train = np.asarray(dict_['Y_train']) if dict_['Y_train'] != 'None' else None
        

Теперь давайте попробуем класс Mylogger . Сначала мы создаем объект bylogreg , передаем ему обучающие данные и сохраняем их в файл. Затем мы создаем новый объект json_bylogreg и вызываем метод load_json для загрузки данных из файла.

filepath = "mylogreg.json"

# Create a model and train it
mylogreg = MyLogReg(X_train=Xtrain, Y_train=Ytrain)
mylogreg.save_json(filepath)

# Create a new object and load its data from JSON file
json_mylogreg = MyLogReg()
json_mylogreg.load_json(filepath)
json_mylogreg

Распечатав новый объект, мы можем видеть наши параметры и обучающие данные по мере необходимости.

MyLogReg(C=1.0,
     X_train=array([[ 4.3,  3. ,  1.1,  0.1],
       [ 5.7,  4.4,  1.5,  0.4],
       ...,
       [ 7.2,  3. ,  5.8,  1.6],
       [ 7.7,  2.8,  6.7,  2. ]]),
     Y_train=array([0, 0, ..., 2, 2]), class_weight=None, dual=False,
     fit_intercept=True, intercept_scaling=1, max_iter=100,
     multi_class='ovr', n_jobs=1, penalty='l2', random_state=None,
     solver='liblinear', tol=0.0001, verbose=0, warm_start=False)

Поскольку сериализация данных с помощью JSON фактически сохраняет объект в строковом формате, а не в байтовом потоке, файл ‘mylogreg.json’ можно открыть и изменить с помощью текстового редактора. Хотя такой подход был бы удобен для разработчика, он менее безопасен, поскольку злоумышленник может просматривать и изменять содержимое файла JSON. Более того, этот подход больше подходит для объектов с небольшим количеством переменных экземпляра, таких как модели scikit-learn, поскольку любое добавление новых переменных требует изменений в методах сохранения и восстановления.

Проблемы совместимости

Хотя некоторые плюсы и минусы каждого инструмента были рассмотрены в тексте до сих пор, вероятно, самым большим недостатком инструментов Pickle и Joblib является их совместимость с различными моделями и версиями Python.

Совместимость версий Python – В документации обоих инструментов говорится, что не рекомендуется (де)сериализовывать объекты в разных версиях Python, хотя это может работать при незначительных изменениях версий.

Совместимость моделей – Одной из наиболее частых ошибок является сохранение модели с помощью Pickle или Joblib, а затем изменение модели перед попыткой восстановления из файла. Внутренняя структура модели должна оставаться неизменной между сохранением и перезагрузкой.

Последняя проблема как с Pickle, так и с Joblib связана с безопасностью. Оба инструмента могут содержать вредоносный код, поэтому не рекомендуется восстанавливать данные из ненадежных или неаутентифицированных источников.

Выводы

В этом посте мы описали три инструмента для сохранения и восстановления моделей scikit-learn. Библиотеки Pickle и Joblib быстры и просты в использовании, но имеют проблемы совместимости в разных версиях Python и изменения в модели обучения. С другой стороны, ручной подход более сложен в реализации и нуждается в модификации с любым изменением структуры модели, но с положительной стороны он может быть легко адаптирован к различным потребностям и не имеет никаких проблем совместимости.