{% raw %}
Автор: Trent Hauck
Переводчик: muxuezi
Лицензия: CC BY-NC-SA 4.0
Эта глава включает следующие темы:1. Получение образцов данных из внешних источников
В этой главе рассматриваются вопросы получения данных (получение данных), подготовки данных (подготовка данных) и уменьшения размерности перед обучением модели (уменьшение размерности до обучения модели). Эти аспекты не являются центральными в машинном обучении (machine learning, ML), но они часто определяют успех модели.{% endraw %}Эта глава состоит из трёх основных частей. Во-первых, мы рассмотрим, как создавать синтетические данные (ложные данные). Это может показаться незначительной задачей, но создание синтетических данных и их использование для обучения моделей является важным шагом в тестировании моделей. Более того, когда вы реализуете алгоритм от начала до конца, строка за строкой, важно знать, работает ли он так, как ожидалось, особенно если доступных данных недостаточно. В этом случае можно создать синтетические данные для проверки. Затем мы поговорим о некоторых методах предварительной обработки данных, таких как заполнение пропущенных значений (импутация) и кодирование категориальных переменных. Наконец, мы представим несколько методов понижения размерности, включая анализ главных компонент, факторный анализ и нормальные случайные процессы.
Если это возможно, используйте знакомый вам набор данных при прохождении материала книги; для удобства мы будем использовать встроенные базы данных scikit-learn. Эти встроенные базы данных могут быть использованы для тестирования различных моделей, таких как регрессия и классификация. Кроме того, эти базы данных являются очень известными. Это полезно для авторов академических работ в разных областях, так как они могут сравнивать свои модели с другими моделями, используя эти встроенные базы данных.
Рекомендуется использовать IPython для выполнения команд, приведённых в книге. Большой объём памяти важен, чтобы обеспечивать нормальное выполнение команд. Если вы используете IPython Notebook, то это ещё лучше. Если вы работаете с Notebook, не забудьте использовать команду
%matplotlib inline
, чтобы графики отображались внутри Notebook, а не открывались в новом окне.
Встроенные базы данных scikit-learn находятся в модуле datasets
. Импортировать можно следующим образом:
from sklearn import datasets
import numpy as np
```Запустив `datasets.*?` в IPython, вы сможете просмотреть список команд модуля `datasets`.
## Как это сделать...
Модуль `datasets` содержит два основных типа данных. Маленькие тестовые наборы данных доступны через `sklearn.datasets.load_*?`. Большие наборы данных можно скачать по мере необходимости. Последние по умолчанию не включены в пакет `sklearn`; однако, иногда большие наборы данных могут быть более подходящими для тестирования моделей и алгоритмов, поскольку они достаточно сложны для моделирования реальных ситуаций.
Наборы данных, доступные по умолчанию в пакете `sklearn`, можно получить с помощью `datasets.load_*?`. Другие наборы данных требуют загрузки через `datasets.fetch_*?`, они больше и не были автоматически установлены. Часто используются для тестирования алгоритмов, решающих практические задачи.
Сначала загрузим набор данных Boston:```python
boston = datasets.load_boston()
print(boston.DESCR)
Характеристики набора данных:
Количество экземпляров: 506
Количество атрибутов: 13 числовых/категориальных предикторов
Средняя стоимость (атрибут 14) обычно является целевой переменной
Информация об атрибутах (в порядке следования):
Отсутствующие значения атрибутов: Нет
Автор: Harrison, D. и Rubinfeld, D. L.
Это копия датасета UCI ML жилищных данных.
http://archive.ics.uci.edu/ml/datasets/Housing
Этот датасет был взят из библиотеки StatLib, которая поддерживается Университетом Карнеги-Меллона.
Данные о ценах на жилье в Бостоне были использованы в многочисленных научных работах по машинному обучению, которые рассматривают задачи регрессии.
Ссылки
- Belsley, Kuh & Welsch, "Регрессионные диагностики: Определение влиятельных данных и источников коллинеарности", Wiley, 1980. 244-261.
- Quinlan, R. (1993). Объединение методов обучения на основе образцов и моделирования. В Трудах десятой Международной конференции по машинному обучению, 236-243, Университет штата Массачусетс, Амхерст. Morgan Kaufmann.
- и многое другое! (см. http://archive.ics.uci.edu/ml/datasets/Housing)
DESCR
будет содержать некоторые общие сведения о наборе данных. Давайте теперь скачаем один из наборов данных:```python
housing = datasets.fetch_california_housing()
print(housing.DESCR)
Скачивание Cal. housing с http://lib.stat.cmu.edu/modules.php?op=modload&name=Downloads&file=index&req=getit&lid=83 в C:\Users\tj2\scikit_learn_data
Набор данных о недвижимости Калифорнии.
Оригинальная база данных доступна на сайте StatLib
http://lib.stat.cmu.edu/
В наборе данных содержится 20 640 наблюдений на 9 переменных.
Этот набор данных содержит среднюю стоимость дома как целевую переменную
и следующие входные переменные (признаки): средний доход, средний возраст жилья, среднее количество комнат, среднее количество спален, население, средняя занятость, широта и долгота в указанном порядке.
Ссылки
------
Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,
Statistics and Probability Letters, 33 (1997) 291-297.
## Как это работает...
Когда эти наборы данных загружаются, они не сразу преобразуются в массивы NumPy. Они являются типом `Bunch`. **Bunch** — это часто используемая структура данных Python. Можно считать её словарём, где ключи используются как атрибуты экземпляров.
Свойство `data` используется для связи NumPy-массива, содержащего независимые переменные, и свойства `target` для связи зависимых переменных.
```python
X, y = boston.data, boston.target
На интернете есть различные реализации объектов типа Bunch
; написать свою реализацию тоже несложно. Scikit-learn использует основной модуль для определения Bunch
.
При получении набора данных из внешнего источника он по умолчанию сохраняется в папке `scikit_learn_data/` текущей директории. Это поведение можно настроить двумя способами:
- Установка переменной окружения `SCIKIT_LEARN_DATA`, чтобы указать местоположение загрузки
- Первый параметр метода `fetch_*` является `data_home`, который позволяет узнать местоположение загрузки
Местоположение по умолчанию для загрузки можно легко проверить через `datasets.get_data_home()`.
## Смотрите также
База данных машинного обучения UCI (UCI Machine Learning Repository) — отличное место для поиска простых наборов данных. Многие наборы данных scikit-learn находятся там, а также много других. Другие источники данных включают известные KDD и Kaggle.
# 1.2 Создание пробных данных
Надеюсь, что вы будете использовать свои собственные данные при прохождении этой книги, но если у вас нет данных, ниже приведено описание того, как создать несколько пробных наборов данных (toy data) с помощью scikit-learn.<!-- TEASER_END -->
## Подготовка
Аналогично получению встроенных наборов данных, создание образцовых наборов данных осуществляется с помощью функции `make_название_датасета`. Все эти данные являются искусственными:```
datasets.make_biclusters
datasets.make_blobs
datasets.make_checkerboard
datasets.make_circles
datasets.make_classification
datasets.make_friedman1
datasets.make_friedman2
datasets.make_friedman3
datasets.make_gaussian_quantiles
datasets.make_hastie_10_2
datasets.make_low_rank_matrix
datasets.make_moons
datasets.make_multilabel_classification
datasets.make_regression
datasets.make_s_curve
datasets.make_sparse_coded_signal
datasets.make_sparse_spd_matrix
datasets.make_sparse_uncorrelated
datasets.make_spd_matrix
datasets.make_swiss_roll
```Для удобства далее будем использовать `d` вместо `datasets`, а `np` вместо `numpy`:
```python
import sklearn.datasets as d
import numpy as np
Эта секция приведёт вас через создание нескольких наборов данных; в следующей секции Как это работает..., мы проверим характеристики этих наборов данных. Кроме образцов, мы также создадим некоторые специфичные наборы данных для демонстрации особенностей алгоритмов.
Сначала создадим набор данных для регрессии:
reg_data = d.make_regression()
reg_data[0].shape, reg_data[1].shape
reg_data
по умолчанию представляет собой кортеж, где первый элемент — это матрица размером (100 \times 10), то есть 100 объектов, каждый из которых имеет 10 признаков (независимых переменных), второй элемент — это одномерный массив размером ((100,)), представляющий одну зависимую переменную, которая соответствует количеству объектов, то есть 100. Однако, по умолчанию только 10 признаков связаны с зависимой переменной (значение параметра n_informative
равно 10), остальные 90 признаков не имеют отношения к ней.
Можно создать более сложные наборы данных. Например, создадим матрицу размером (1000 \times 10), где 5 признаков связаны с зависимой переменной, коэффициент ошибки равен 0.2, а количество зависимых переменных равно двум. Код представлен ниже:
complex_reg_data = d.make_regression(n_samples=1000, n_features=10, n_informative=5, n_targets=2, effective_rank=1.0)
complex_reg_data[0].shape, complex_reg_data[1].shape
```Наборы данных для классификации также легко создаются. Создание балансированного набора данных для классификации довольно просто, но в реальности такое случается крайне редко — большинство пользователей не меняют свои покупательские привычки, большинство транзакций не являются мошенническими, и так далее. Поэтому создание несбалансированного набора данных имеет больше смысла:
Классификационные данные также можно создать. Есть несколько функций, которые могут создать соответствующие наборы данных для различных алгоритмов кластеризации. Например, функция `blobs` легко создаёт набор данных для метода K-Means:
```python
%matplotlib inline
import sklearn.datasets as d
from matplotlib import pyplot as plt
import numpy as np
blobs = d.make_blobs(200)
f = plt.figure(figsize=(8, 4))
ax = f.add_subplot(111)
ax.set_title("A blob with 3 centers")
colors = np.array(['r', 'g', 'b'])
ax.scatter(blobs[0][:, 0], blobs[0][:, 1], color=colors[blobs[1].astype(int)], alpha=0.75)
Давайте рассмотрим исходный код библиотеки scikit-learn для генерации набора данных для регрессии. Любые параметры, которые не переопределены, используются с их значениями по умолчанию в функции make_regression
.
На самом деле всё очень просто. В первую очередь, при вызове функции создаётся случайный массив заданной размерности.
X = np.random.randn(n_samples, n_features)
Для базового случая равновесия целевые данные создаются следующим образом:```python ground_truth = np.zeros((n_samples, n_targets)) ground_truth[:n_informative, :] = 100 * np.random.rand(n_informative, n_targets)
Затем `X` и `ground_truth` перемножаются, а затем к полученному результату прибавляется значение `bias`, чтобы получить `y`:
```python
y = np.dot(X, ground_truth) + bias
Произведение — это основная операция над матрицами ( A_{m \times n} \cdot B_{n \times s} = C_{m \times s} ). Таким образом, количество примеров в наборе данных
y
равноn_samples
, то есть количеству строк в наборе данных, а количество целевых значений равноn_targets
.
Из-за механизма распространения NumPy (broadcasting
) значение bias
, являющееся скалярным, будет добавлено ко всем элементам матрицы. Добавление шума и перемешивание данных выполняются довольно просто. Таким образом, экспериментальный набор данных для регрессии готов.
Часто требуется привести данные к стандартному нормальному распределению (стандартизировать). Стандартное нормальное распределение является одним из самых важных распределений в статистике. Если вы знакомы со статистикой, вам должно быть известно понятие z-оценки (z-score). На самом деле, z-оценка используется для преобразования характеристик, имеющих какое-либо распределение, в значения, соответствующие стандартному нормальному распределению.
Стандартизация данных очень полезна. Многие алгоритмы машинного обучения демонстрируют различное качество обучения при работе с данными, имеющими различные диапазоны признаков. Например, SVM (машина опорных векторов) плохо работает с нестандартизованными данными, так как один признак может иметь диапазон значений от 0 до 10000, а другой — от 0 до 1. Модуль preprocessing
предоставляет несколько функций для приведения признаков к стандартному виду:
from sklearn import preprocessing
import numpy as np
Используйте набор данных boston
для выполнения следующего кода:
from sklearn import datasets
boston = datasets.load_boston()
X, y = boston.data, boston.target
X[:, :3].mean(axis=0) # среднее значение первых трех признаков
X[:, :3].std(axis=0) # стандартное отклонение первых трех признаков
Здесь можно получить много информации. Во-первых, среднее значение первого признака является наименьшим среди всех трёх признаков, но его стандартное отклонение больше, чем у третьего признака. Второй признак имеет наибольшее среднее значение и стандартное отклонение — что указывает на большую вариабельность значений этого признака. Мы можем стандартизировать эти значения с помощью модуля preprocessing
:
X_2 = preprocessing.scale(X[:, :3])
``````python
X_2.mean(axis=0)
X_2.std(axis=0)
Функции центрирования и нормализации довольно просты: они вычитают среднее значение и делят на стандартное отклонение. Формула выглядит следующим образом:
$$x=\frac{x-\bar{x}}{\sigma}$$
Кроме этой функции, существует также класс центрирования и нормализации, который полезен при использовании вместе с командами конвейера (Pipeline) для работы с большими наборами данных. Отдельное использование экземпляра класса центрирования и нормализации тоже весьма полезно:
my_scaler = preprocessing.StandardScaler()
my_scaler.fit(X[:, :3])
my_scaler.transform(X[:, :3]).mean(axis=0)
Приводит средние значения образцов каждого признака к нулю, а стандартное отклонение к единице. Эта форма стандартизации не единственная. Модуль preprocessing
также предлагает класс MinMaxScaler
, который масштабирует данные таким образом, чтобы они находились в диапазоне между минимальным и максимальным значением:
my_minmax_scaler = preprocessing.MinMaxScaler()
my_minmax_scaler.fit(X[:, :3])
my_minmax_scaler.transform(X[:, :3]).max(axis=0)
массив([1., 1., 1.])
Используя класс MinMaxScaler
, можно легко изменить дефолтный диапазон от 0
до 1
на нужный:
my_odd_scaler = preprocessing.MinMaxScaler(feature_range=(-3.14, 3.14))
my_odd_scaler.fit(X[:, :3])
my_odd_scaler.transform(X[:, :3]).max(axis=0)
массив([3.14, 3.14, 3.14])Ещё одним методом является нормализация (normalisation). Она стандартизирует каждый образец до единицы. Этот метод отличается от предыдущих тем, что его значения являются скалярными. Код для нормализации представлен ниже:```python normalized_X = preprocessing.normalize(X[:, :3])
На первый взгляд может показаться бесполезным, но при вычислении евклидова расстояния (как метрики схожести) это становится необходимым. Например, три образца представляют собой векторы `(1,1,0)`, `(3,3,0)` и `(1,-1,0)`. Расстояние между первым и третьим образцами меньше, чем между первым и вторым, хотя образцы 1 и 3 являются осевыми симметриями, а образцы 1 и 2 имеют лишь различия в масштабе. Поскольку расстояние часто используется как метрика схожести, то без предварительной нормализации данных перед моделированием могут возникнуть ошибки.
## Дополнительно...
Обработка пропущенных значений (data imputation) — это обширная тема, и при использовании функционала импутации scikit-learn стоит обратить внимание на следующие моменты.
### Создание инволютивного объекта нормализатора (idempotent scaler object)
При необходимости стандартизировать среднее значение и / или дисперсию экземпляра `StandardScaler`. Например, возможно (хотя это и бессмысленно), создать `StandardScaler`, который будет полностью идентичен оригинальному:
```python
my_useless_scaler = preprocessing.StandardScaler(with_mean=False, with_std=False)
transformed_sd = my_useless_scaler.fit_transform(X[:, :3]).std(axis=0)
original_sd = X[:, :3].std(axis=0)
np.array_equal(transformed_sd, original_sd)
True
При стандартизации данные в виде редко встречающихся матриц обрабатываются аналогично обычным матрицам. Это связано с тем, что после центрирования нулевые значения становятся ненулевыми, поэтому редкие матрицы после обработки уже не будут такими редкими.```python import scipy матрица = scipy.sparse.eye(1000) preprocessing.scale(матрица)
```plaintext
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-45-466df6030461> in <module>()
----> 1 preprocessing.scale(матрица)
d:\programfiles\Miniconda3\lib\site-packages\sklearn\preprocessing\data.py в функции scale(X, axis, with_mean, with_std, copy)
120 if with_mean:
121 raise ValueError(
--> bk 122 "Невозможно центрировать разряженные матрицы: передайте `with_mean=False` вместо этого"
123 "См. описание функции для мотивации и альтернатив."
124 if axis != 0:
ValueError: Невозможно центрировать разряженные матрицы: передайте `with_mean=False` вместо этого См. описание функции для мотивации и альтернатив.
Этот ошибочный вывод указывает на то, что при стандартизации разряженной матрицы нельзя использовать параметр with_mean
, но можно использовать with_std
:
preprocessing.scale(matrix, with_mean=False)
<1000x1000 разряженная матрица типа '<class 'numpy.float64'>'
c с 1000 хранящимися элементами в формате сжатых строк>
Другой подход заключается в прямом преобразовании matrix.todense()
. Однако этот метод может быть опасным, так как матрица уже является разряженной, и это может вызвать исключение по причине недостатка памяти.
import scipy
матрица = scipy.sparse.eye(1000)
preprocessing.scale(матрица)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-45-466df6030461> in <module>()
----> 1 preprocessing.scale(матрица)
d:\\programfiles\\Miniconda3\\lib\\site-packages\\sklearn\\preprocessing\\data.py в функции scale(X, axis, with_mean, with_std, copy)
120 if with_mean:
121 raise ValueError(
--> 122 "Невозможно центрировать разряженные матрицы: передайте `with_mean=False` вместо этого"
123 "См. описание функции для мотивации и альтернатив."
124 if axis != 0:
ValueError: Невозможно центрировать разряженные матрицы: передайте `with_mean=False` вместо этого См. описание функции для мотивации и альтернатив.
Этот ошибочный вывод указывает на то, что при стандартизации разряженной матрицы нельзя использовать параметр with_mean
, но можно использовать with_std
:
preprocessing.scale(matrix, with_mean=False)
<1000x1000 разряженная матрица типа '<class 'numpy.float64'>'
c с 1000 хранящимися элементами в формате сжатых строк>
Другой подход заключается в прямом преобразовании matrix.todense()
. Однако этот метод может быть опасным, так как матрица уже является разряженной, и это может вызвать исключение по причине недостатка памяти.
Создание бинарных признаков обычно полезно, но следует проявлять осторожность. Мы продолжим использовать набор данных boston
для демонстрации создания бинарных признаков.
Сначала загрузим набор данных boston
:
from sklearn import datasets
boston = datasets.load_boston()
import numpy as np
Подобно стандартизации, scikit-learn предлагает два способа создания бинарных признаков:
preprocessing.binarize
(функция)preprocessing.Binarizer
(класс)Целевая переменная набора данных boston
представляет собой медианную цену дома (в тысячах долларов США). Этот набор данных хорошо подходит для тестирования регрессий и других алгоритмов прогнозирования для непрерывных значений, но если нам нужно предсказать, стоит ли цена дома выше средней цены всех домов. Для решения этой задачи нам нужно установить пороговое значение среднего. Если значение больше среднего, то оно будет равно 1; в противном случае — 0:
(boston.target[:5] > boston.target.mean()).astype(int)
```Массив:
array([1, 0, 1, 1, 1])
Зачем использовать функцию `binarize` из библиотеки scikit-learn, если можно сделать то же самое с помощью NumPy? Ответ будет дан в разделе "Как это работает..." с объяснением использования пайплайновых команд, которые будут рассмотрены в следующей главе.
## Как это работает...
Метод выглядит очень простым; на самом деле, scikit-learn создает слой мониторинга на уровне реализации, который возвращает `True`, если значение больше порогового значения. Затем все значения, превышающие пороговое значение, обновляются до `1`, а остальные — до `0`.
## Дополнительно...
Давайте рассмотрим ещё немного информации о разреженных матрицах и методе `fit`.
### Разреженные матрицы
Разреженная матрица хранит только ненулевые значения, что позволяет значительно экономить место. Это вызывает проблемы при использовании `binarizer`, так как требуется указывать параметр `threshold` не меньше `0`. В противном случае возникает ошибка:
```python
from scipy.sparse import coo
spar = coo.coo_matrix(np.random.binomial(1, .25, 100))
preprocessing.binarize(spar, threshold=-1)
Ошибка:
ValueError: Cannot binarize a sparse matrix with threshold < 0
fit
Класс binarizer
содержит метод fit
, но этот метод является лишь общим интерфейсом и не выполняет реальной операции обучения, а просто возвращает объект.
Поэтому при моделировании часто требуется преобразование этих переменных в числовое значение, однако простое использование id
или оригинальной формы недостаточно. Также следует избегать проблем, возникающих при создании двоичных признаков путем установки пороговых значений, как было показано в предыдущем разделе. Если мы рассматриваем данные как непрерывные, то должны объяснять их как непрерывные.
Датасет boston
здесь не подходит для демонстрации. Хотя он хорошо подходит для демонстрации двоичных признаков, его использование для создания категориальных переменных не очень подходящее. Поэтому здесь используется датасет iris
.
Проблему стоит сначала точно описать. Предположим, что задача заключается в прогнозировании ширины лепестков цветка; тогда вид цветка может быть полезным признаком.
Сначала импортируем данные:
from sklearn import datasets
iris = datasets.load_ Yöris()
X = iris.data
y = iris.target
Теперь X
и y
содержат соответствующие значения, объединяем их вместе:
data = np.c_[X, y]
``````python
import numpy as np
d = np.column_stack((X, y))
Теперь превратим колонку y
, представляющую вид цветка, в категориальный признак:
from sklearn import preprocessing
text_encoder = preprocessing.OneHotEncoder()
text_encoder.fit_transform(d[:, -1:]).toarray()[:5]
array([[1., 0., 0.],
[1., 0., 0.],
[1., 0., 0.],
[1., 0., 0.],
[1., 0., 0.]])
Здесь кодировщик создаёт дополнительные признаки для каждого категориального признака, преобразуя его в редкий массив. Массив определяется следующим образом: каждая строка состоит из нулей и единиц, где соответствующий категориальному признаку столбец равен единице, а остальные — нулю. Хранение данных в виде редкого массива вполне обоснованно.
text_encoder
является стандартной моделью scikit-learn и может использоваться повторно:
text_encoder.transform(np.ones((3, 1))).toarray()
array([[0., 1., 0.],
[0., 1., 0.],
[0., 1., 0.]])
В библиотеках scikit-learn и Python существуют различные способы создания категориальных переменных. Если вы предпочитаете использовать scikit-learn и принципы категориальной кодировки просты, попробуйте DictVectorizer
. Если вам нужно работать с более сложными принципами категориальной кодировки, patsy
будет отличным выбором.### DictVectorizer
DictVectorizer
может преобразовать строки в категориальные признаки:```python
from sklearn.feature_extraction import DictVectorizer
dv = DictVectorizer()
my_dict = [{'species': iris.target_names[i]} for i in y]
dv.fit_transform(my_dict).toarray()[:5]
массив([[ 1., 0., 0.],
[ 1., 0., 0.],
[ 1., 0., 0.],
[ 1., 0., 0.],
[ 1., 0., 0.]])
> В Python словарь можно рассматривать как сжатую матрицу, содержащую только ненулевые значения.
### Pasty
`patsy` — это ещё один пакет для кодирования категориальных переменных. Часто используется вместе с `StatsModels`, и `patsy` может преобразовать набор строк в матрицу.
> Эта часть материала имеет мало отношения к scikit-learn, поэтому пропустить её можно.
Например, если `x` и `y` являются строками, то `dm = patsy.design_matrix("x + y")` создаёт соответствующие столбцы. Если нет, то `C(x)` создаёт категориальную переменную.
Например, при первом взгляде на `iris.target`, его можно рассматривать как непрерывную переменную. Поэтому используйте следующий команд:
---
Текст переведён и исправлен согласно правилам.```python
import patsy
patsy.dmatrix("0 + C(species)", {'species': iris.target})
Матрица дизайна с размерностью (150, 3)
C(вид)[0] | C(вид)[1] | C(вид)[2] |
---|---|---|
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1 | 0 | |
1В этом разделе мы продемонстрируем другой способ представления категориальных переменных. Иногда важны лишь один или два категориальных признака, поэтому следует избегать излишних размерностей, особенно если имеется множество категориальных переменных. |
Есть ещё одно методическое решение для работы с категориальными переменными, которое не требует использования OneHotEncoder
. Вместо этого можно использовать LabelBinarizer
, который представляет собой метод комбинирования пороговых значений с категориальными переменными. Прежде чем показать его применение, давайте загрузим набор данных iris
:
from sklearn import datasets as d
iris = d.load_iris()
target = iris.target
Импортируйте LabelBinarizer()
для создания объекта:
from sklearn.preprocessing import LabelBinarizer
label_binarizer = LabelBinarizer()
Теперь преобразуйте значения зависимой переменной в новый вектор признаков:
new_target = label_binarizer.fit_transform(target)
Давайте посмотрим на результаты new_target
и объекта label_binarizer
:
new_target.shape
new_target[:5]
new_target[-5:]
label_binarizer.classes_
iris
имеют базовое количество равное 3
, то есть три различных значения. Когда LabelBinarizer
преобразует $N \times 1$ вектор в $N \times C$ матрицу, $C$ равно количеству уникальных значений в $N \times 1$ векторе. Обратите внимание, что после того как label_binarizer
обработал зависимую переменную, все значения за пределами этих уникальных значений будут представляться как [0, 0, 0]
.```pythonlabel_binarizer.transform([4])
## Дополнительно...
Нули и единицы не обязательно должны представлять положительные и отрицательные примеры целевой переменной. Например, если нам нужно использовать значение `1000` для положительных примеров и `-1000` для отрицательных, мы можем использовать `label_binarizer` следующим образом:
```python
label_binarizer = LabelBinarizer(neg_label=-1000, pos_label=1000)
label_binarizer.fit_transform(target)[:5]
Единственным ограничением для положительных и отрицательных значений является то, что они должны быть целыми числами.
Практика работы с числовыми данными незаменима, но существует множество методов, которые могут помочь в этом. В этой главе мы рассмотрим некоторые из них. Однако эти методы могут не решить вашу конкретную проблему.
Библиотека scikit-learn предлагает несколько общих подходов к заполнению пропущенных значений в существующих данных. Однако если пропущенные значения являются намеренно внесенными — например, время ответа сервера больше 100 миллисекунд — более подходящим будет использование других пакетов, таких как PyMC для решения байесовских задач, lifelines для моделирования рисков или создание своего собственного подхода.
Первым шагом при работе с пропущенными значениями является создание этих пропущенных значений. Это можно легко сделать с помощью Numpy:```python from sklearn import datasets import numpy as np iris = datasets.load_iris() iris_X = iris.data masking_array = np.random.binomial(1, .25, iris_X.shape).astype(bool) iris_X[masking_array] = np.nan
Рассмотрим эти строки кода. В отличие от обычного использования, здесь используется массив как индекс другого массива. Для создания случайных пропущенных значений сначала создаётся случайный массив булевых значений, имеющий ту же форму, что и данные `iris_X`. Затем, используя этот массив булевых значений, пропущенные значения распределяются. Поскольку каждый запуск генерирует случайные данные, массив маски `masking_array` будет уникален для каждого запуска.
```python
masking_array[:5]
array([[[False, True, False, False],
[False, True, False, False],
[False, False, False, False],
[True, False, False, True]],
[[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
[False, False, False, False]]], dtype=bool)
iris_X[:5]
array([[[5.1, nan, 1.4, 0.2],
[4.9, nan, 1.4, 0.2],
[4.7, 3.2, 1.3, 0.2],
[nan, 3.1, 1.5, nan]],
[[5., 3.6, nan, 0.2],
[5.4, 3.9, 1.7, 0.4],
[4.6, 3.4, 1.4, 0.3],
[5., 3.5, 1.5, 0.2]]])
Основной принцип, который протягивается через весь пример - использование массива булевых значений для создания пропущенных значений (NaN
) в данных iris_X
. Обратите внимание на различие в позиции [3,0]
:
iris_X_prime[3,0]
iris_X[3,0]
Вышеуказанные вычисления могут быть выполнены с использованием различных методов. По умолчанию используется среднее значение mean
, всего три:
```- Среднее значение mean
(по умолчанию)
median
most_frequent
Scikit-learn будет использовать указанный метод для расчета каждого пропущенного значения в наборе данных, а затем заполнит его.
Например, используя метод median
для пересчета iris_X
, можно переинициализировать impute
следующим образом:
impute = preprocessing.Imputer(strategy='median')
iris_X_prime = impute.fit_transform(iris_X)
iris_X_prime[:5]
Если данные содержат пропущенные значения, они могут вызывать проблемы во время дальнейших вычислений. Например, в разделе Как это сделать..., np.nan
используется как значение пропущенного значения по умолчанию, но формы представления пропущенных значений могут различаться. Иногда -1
используется для обозначения пропущенного значения. Для обработки этих пропущенных значений можно указать, какие значения являются пропущенными, в методах. По умолчанию форма представления пропущенных значений — это NaN
, то есть значение np.nan
.
Предположим, что все пропущенные значения в iris_X
представлены значением -1
. Это может показаться странным, но метрики в наборе данных iris
не могут быть отрицательными, поэтому использование -1
для обозначения пропущенных значений вполне логично.
iris_X[np.isnan(iris_X)] = -1
iris_X[:5]
Заполнение этих пропущенных значений также довольно просто:```python impute = preprocessing.Imputer(missing_values=-1) iris_X_prime = impute.fit_transform(iris_X) iris_X_prime[:5]
## Дополнительно...
Библиотека pandas также может обрабатывать пропущенные значения, причём более гибко, однако она менее универсальна:
```python
import pandas as pd
iris_X[masking_array] = np.nan
iris_df = pd.DataFrame(iris_X, columns=iris.feature_names)
iris_df.fillna(iris_df.mean())['sepal length (см)'].head(5)
0 1.054196
1 1.422873
2 0.023683
3 -0.850524
4 5.097046
Name: sepal length (см), dtype: float64
Его гибкость заключается в том, что fillna
может заполнить любое значение статистического параметра:
iris_df.fillna(iris_df.max())['sepal length (см)'].head(5)
0 5.1
1 4.9
2 4.7
3 7.9
4 5.0
Name: sepal length (см), dtype: float64
Пайплайн команды не часто используется, но она очень полезна. Они позволяют объединять несколько шагов в один объект для выполнения. Это делает более удобной и гибкой регулировку и контроль всего конфигурационного модели, а не одного шага за раз.
Это первая часть нашей работы по объединению нескольких шагов обработки данных в один объект. В scikit-learn это называется "пайплайн". Здесь мы сначала вычисляем и заполняем пропущенные значения; затем нормируем данные так, чтобы среднее значение было равно нулю, а стандартное отклонение — единице.
Давайте создадим набор данных с пропущенными значениями, а затем продемонстрируем использование пайплайна:```python from sklearn import datasets import numpy as np мат = datasets.make_spd_matrix(10) маска = np.random.binomial(1, 0.1, мат.shape).astype(bool) мат[маска] = np.nan мат[:4, :4]
```python
from sklearn import preprocessing
импьютер = preprocessing.Imputer()
шкалер = preprocessing.StandardScaler()
мат_импьютрованный = импьютер.fit_transform(мат)
мат_импьютрованный[:4, :4]
мат_импьютрованный_и_нормированный = шкалер.fit_transform(мат_импьютрованный)
мат_импьютрованный_и_нормированный[:4, :4]
``````markdown
array([[ 1.09907483e+00, 2.62635324e-01, -3.88958755e-01,
-4.80451718e-01],
[ 1.63825210e+00, 2.01707858e+00, -7.50508486e-17,
-1.80311396e+00],
[ -4.08014393e-01, -3.99538476e-01, 2.90716556e+00,
2.97005140e-01],
[ -1.68651124e+00, -1.59341549e+00, 1.25317595e-02,
1.88986410e+00]])
Теперь мы продемонстрируем это с помощью pipeline
:
from sklearn import pipeline
pipe = pipeline.Pipeline([('импьютер', импьютер), ('шкалер', шкалер)])
Давайте посмотрим на содержимое pipe
. Как и было описано ранее, конвейер команд определяет шаги обработки данных:
pipe
Затем вызываем метод fit_transform
объекта pipe
, чтобы объединить несколько шагов в один объект:
новая_мат = pipe.fit_transform(мат)
новая_мат[:4, :4]
Можно использовать Numpy для проверки результата:```python np.array_equal(new_mat, mat_imp_and_scaled)
True
Всё верно! В последующих разделах книги мы будем демонстрировать мощь конвейера команд. Он может использоваться не только для предварительной обработки данных, но также для понижения размерности пространства и обучения алгоритмов.
## Как это работает...
Как уже упоминалось ранее, каждый алгоритм scikit-learn имеет похожий интерфейс. Основные функции `pipeline` включают следующие три:
- `fit`
- `transform`
- `fit_transform`
Конкретнее говоря, если конвейер состоит из `N` объектов, то первые `N-1` объект должны реализовать как минимум методы `fit` и `transform`, а последний объект должен реализовать хотя бы метод `fit`. В противном случае возникнет ошибка.
Если эти условия выполнены, конвейер будет работать, однако не все методы будут доступны. Например, у объекта `pipe` есть метод `inverse_transform`, который не всегда работает. Поскольку некоторые шаги вычислений не имеют обратного преобразования, при попытке его использования возникает ошибка:
```python
pipe.inverse_transform(new_mat)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In [12], line 1
----> 1 pipe.inverse_transform(new_mat)
2
3 # Обратное преобразование не существует для всех шагов конвейера, поэтому эта операция приведёт к ошибке.
d:\programfiles\Miniconda3\lib\site-packages\sklearn\utils\metaestimators.py в <lambda>(*args, **kwargs)
```35 self.get_attribute(obj)
36 # лямбда, но не partial, позволяет функции help() работать с update_wrapper
---> 37 out = lambda *args, **kwargs: self.fn(obj, *args, **kwargs)
38 # обновляем строку документации возвращаемой функции
39 update_wrapper(out, self.fn)
``` d:\programfiles\Miniconda3\lib\site-packages\sklearn\pipeline.py в inverse_transform(self, X)
265 Xt = X
266 for name, step in self.steps[::-1]:
--> 267 Xt = step.inverse_transform(Xt)
268 return Xt
269
AttributeError: объект 'Imputer' не имеет атрибута 'inverse_transform'
Однако объект `scaler` работает корректно:
```python
scaler.inverse_transform(new_mat)[:4, :4]
array([[ 1.05419595, 1.42287309, 0.02368264, -0.8505244 ], [ 1.42287309, 5.09704588, 0.09560571, -2.46408728], [ 0.02368264, 0.03614203, 0.63317494, 0.09792298], [-0.8505244 , -2.46408728, 0.09792298, 2.04110849]])
При правильной настройке команды пайплайна он будет работать как следует. Это всего лишь цикл for
, который выполняет fit
и transform
для каждого шага, передавая результат следующему преобразованию.
Основные причины использования команды пайплайна:- Во-первых, это удобство. Код становится более компактным, так как нет необходимости повторно вызывать fit
и transform
.
Теперь пришло время углубиться! Анализ главных компонент (Principal Component Analysis, PCA) — это первый продвинутый метод, представленный в этой книге. До этого момента мы рассматривали простые статистические концепции, а PCA объединяет статистику и линейную алгебру для уменьшения размерности, что делает его мощным инструментом для работы со сложными моделями.
PCA является частью модуля декомпозиции scikit-learn. Есть еще несколько модулей декомпозиции, которые будут рассмотрены далее. Давайте воспользуемся набором данных iris
для демонстрации, вы также можете использовать свой собственный набор данных:
from sklearn import datasets
iris = datasets.load_ Iris()
iris_X = iris.data
Сначала импортируйте модуль декомпозиции:
from sklearn import decomposition
Затем инициализируйте объект PCA:
pca = decomposition.PCA()
pca
Выход:
PCA(copy=True, n_components=None, whiten=False)
У PCA гораздо меньше параметров по сравнению с другими объектами scikit-learn. Таким образом, объект PCA создан, теперь применим fit_transform
к данным iris_X
. После выполнения PCA давайте посмотрим, какова эффективность снижения размерности:
pca.explained_variance_ratio_
PCA имеет общую математическую формулировку в анализе данных и конкретные применения. PCA представляет исходный набор данных с помощью множества взаимно ортогональных векторов.Обычно PCA преобразует исходный набор данных в новый пространственный контекст, где каждый столбец является ортогональным другому. Из точки зрения анализа данных, PCA преобразует ковариационную матрицу данных в несколько колонок, каждая из которых "объясняет" определённый процент вариаций. Например, в наборе данных iris
, 92,5% вариаций можно объяснить первым главным компонентом.
Анализ данных часто сталкивается с проблемой проклятия размерности, поэтому снижение размерности крайне важно. Обычно алгоритмы, работающие с высокомерной тренировочной выборкой, склонны переобучаться (overfitting), что затрудняет обобщение характеристик тестовой выборки. Если реальная структура данных может быть представлена меньшим количеством измерений, то обычно стоит попробовать это сделать.
Для демонстрации этого, мы используем PCA для преобразования набора данных iris
в двумерное пространство. Набор данных iris
обычно хорошо классифицируется при использовании всех измерений:
pca = decomposition.PCA(n_components=2)
iris_X_prime = pca.fit_transform(iris_X)
iris_X_prime.shape
Матрица теперь имеет размер (150 \times 2), а не (150 \times 4). Два измерения легче визуализировать:
%matplotlib inline
from matplotlib import pyplot as plt
f = plt.figure(figsize=(5, 5))
ax = f.add_subplot(111)
ax.scatter(iris_X_prime[:, 0], iris_X_prime[:, 1], c=iris.target)
ax.set_title("PCA 2 Components")
```После снижения до двух измерений, отличительные характеристики данных все ещё сохранены. Мы можем проверить, какой процент вариаций сохранён этими двумя измерениями:
```python
pca.explained_variance_ratio_.sum()
Результат:
0.97763177502480336
Объект PCA также может быть настроен для объяснения определённого процента вариационной информации с самого начала. Например, если мы хотим объяснить 98% вариационной информации, объект PCA можно создать следующим образом:
pca = decomposition.PCA(n_components=0.98)
iris_X_prime = pca.fit_transform(iris_X)
pca.explained_variance_ratio_.sum()
Результат:
0.99481691454981014
iris_X_prime.shape
Результат:
(150, 3)
Поскольку мы хотим объяснить больше вариационной информации, чем в двухмерном пространстве главных компонент, нам требуется третья размерность.
Факторный анализ — это другой методический подход к уменьшению размерности. В отличие от PCA, факторный анализ имеет определённые допущения, тогда как PCA их не имеет. Основное допущение факторного анализа заключается в том, что есть скрытые характеристики, связанные с характеристиками набора данных.
Эта тема сводится к тому, чтобы концентрироваться на явных характеристиках выборочной совокупности данных, пытаясь понять скрытые связи между ними так же, как мы понимаем зависимые переменные.## Подготовка
Давайте снова воспользуемся набором данных iris
, чтобы сравнить PCA и факторный анализ. Сначала загрузим класс факторного анализа:
from sklearn import datasets
iris = datasets.load_iris()
from sklearn.decomposition import FactorAnalysis
С точки зрения программирования, эти два метода практически не различаются:
fa = FactorAnalysis(n_components=2)
iris_two_dim = fa.fit_transform(iris.data)
iris_two_dim[:5]
%matplotlib inline
from matplotlib import pyplot as plt
f = plt.figure(figsize=(5, 5))
ax = f.add_subplot(111)
ax.scatter(iris_two_dim[:,0], iris_two_dim[:, 1], c=iris.target)
ax.set_title("Факторный анализ 2 компоненты")
Поскольку факторный анализ является вероятностным преобразованием, его можно рассматривать с разных углов, например, через логарифмическую вероятность модели, которая лучше всего сравнивает модели.
У факторного анализа есть недостатки. Поскольку вы не прогнозируете результат напрямую путём обучения модели, обученная модель представляет собой промежуточный шаг. Это само по себе не является проблемой, но при обучении реальной модели могут возникнуть ошибки.## Как это работает...Факторный анализ схож с ранее рассмотренным методом главных компонент (PCA). Однако между ними есть одна существенная разница. PCA получает пространство главных компонент путём линейного преобразования данных таким образом, чтобы каждая главная компонента была ортогональной друг другу. Можно представить PCA как уменьшение размерности (N)-мерного набора данных до (M)-мерного, где (M < N).В то время как основное предположение факторного анализа заключается в том, что $M$ важных признаков и их линейные комбинации (с шумом) могут составить исходный $N$-мерный набор данных. Другими словами, вам не требуется указывать конечные переменные (то есть $N$-мерный результат), а вместо этого следует указывать количество факторов модели данных ($M$).
Поскольку большинство статистических методов начинаются как линейные, для решения нелинейных задач требуются некоторые изменения. PCA также является линейным преобразованием. В этом разделе мы сначала рассмотрим его нелинейную форму, а затем покажем, как можно уменьшить размерность.
Как было бы замечательно, если все данные были бы линейными! К сожалению, реальность таковой не является. Ядерный анализ главных компонент (Kernel PCA) позволяет решать нелинейные проблемы. Данные преобразуются через ядерную функцию (kernel function) в новое пространство, после чего применяется PCA.
Для лучшего понимания ядерной функции рекомендуется сначала попробовать создать набор данных, который может быть линейно разделён с использованием ядерной функции в Kernel PCA. Мы будем использовать косинусное ядро (cosine kernel) для демонстрации. Этот раздел будет содержать больше теории по сравнению с предыдущими разделами.## Как это сделать...
Косинусное ядро используется для пропорционального отображения углов между двумя образцами в пространстве данных. Это полезно тогда, когда величина (модуль) вектора не подходит для традиционной меры расстояния.
Формула для вычисления косинуса угла между двумя векторами (A) и (B):
$$\cos(\theta)=\frac{A \cdot B}{{|A|}|B|}$$
Косинус угла между векторами (A) и (B) равен скалярному произведению этих двух векторов, деленному на произведение их L2 норм. Размер векторов (A) и (B) не влияет на значение косинуса.
Давайте сгенерируем некоторый набор данных для демонстрации. Предположим, что у нас есть два различных процесса данных (процесса), называемых (A) и (B):
import numpy as np
A1_mean = [1, 1]
A1_cov = [[2, .99], [1, 1]]
A1 = np.random.multivariate_normal(A1_mean, A1_cov, 50)
A2_mean = [5, 5]
A2_cov = [[2, .99], [1, 1]]
A2 = np.random.multivariate_normal(A2_mean, A2_cov, 50)
A = np.vstack((A1, A2))
B_mean = [5, 0]
B_cov = [[.5, -1], [.9, -.5]]
B = np.random.multivariate_normal(B_mean, B_cov, 100)
Приведенная выше диаграмма явно показывает данные двух различных процессов, но разделить их одной гиперплоскостью затруднительно. Поэтому мы используем ядерный метод главной компоненты с косинусным ядром, который был рассмотрен ранее:
from sklearn.decomposition import KernelPCA
kpca = KernelPCA(kernel='cosine', n_components=1)
AB = np.vstack((A, B))
AB_transformed = kpca.fit_transform(AB)
``````python
A_color = np.array(['r'] * len(B))
B_color = np.array(['b'] * len(B))
colors = np.hstack((A_color, B_color))
f = plt.figure(figsize=(10, 4))
ax = f.add_subplot(111)
ax.set_title("Cosine kernel KPCA in one dimension")
ax.scatter(AB_transformed, np.zeros_like(AB_transformed), color=colors);
```После применения ядерного метода главной компоненты с косинусным ядром данные становятся одномерными. В случае использования обычного PCA это будет выглядеть так:
```python
from sklearn.decomposition import PCA
pca = PCA(1)
AB_transformed_Reg = pca.fit_transform(AB)
f = plt.figure(figsize=(10, 4))
ax = f.add_subplot(111)
ax.set_title("PCA в одном измерении")
ax.scatter(AB_transformed_Reg, np.zeros_like(AB_transformed_Reg), color=colors)
Очевидно, что ядерный метод главной компоненты лучше справляется с задачей снижения размерности данных.
Scikit-learn предоставляет несколько типов ядер, таких как линейное, полиномиальное, радиальный базисный функционал (RBF), сигмоидальное и косинусное ядра. Также можно использовать пользовательское ядро (precomputed
). По умолчанию используется линейное ядро.
Есть также некоторые факторы, влияющие на выбор ядра. Например, параметр degree
может использоваться для установки степени полиномиального, RBF и сигмоидального ядер. Параметр gamma
влияет на RBF и полиномиальное ядра. Подробнее см. документацию по KernelPCA
.
Дальнейшее описание ядра RBF будет дано в теме, посвященной машинному обучению с помощью SVM. Обратите внимание, что ядра хорошо справляются с нелинейной разделением, но при невнимательности могут привести к переобучению.
Также стоит отметить, что ядерные методы эффективно решают задачи снижения размерности данных, особенно когда требуется учет нелинейных зависимостей.Усечённое разложение по сингулярным значениям (Truncated Singular Value Decomposition, TSVD) представляет собой метод факторизации матрицы, который позволяет разложить матрицу ( M ) на ( U ), ( \Sigma ) и ( V ). Этот метод очень похож на PCA, за исключением того, что SVD выполняется на самой матрице данных, а PCA — на матрице ковариаций данных. Обычно TSVD используется для выявления основных компонент матрицы.
Разница между общим SVD и TSVD заключается в том, что последний может генерировать разложение матрицы указанной размерности. Например, имеется матрица размером ( n \times n ), после применения обычного SVD она остаётся матрицей размером ( n \times n ), тогда как TSVD может создать матрицу указанной размерности. Это позволяет осуществлять уменьшение размерности.
Для демонстрации TSVD мы будем использовать набор данных iris
:
from sklearn.datasets import load_iris
iris = load_iris()
iris_data = iris.data
Использование объекта TSVD аналогично другим объектам. Сначала импортируйте необходимый класс, затем инициализируйте его и выполните обучение:
from sklearn.decomposition import TruncatedSVD
svd = TruncatedSVD(2)
iris_transformed = svd.fit_transform(iris_data)
iris_data[:5]
iris_transformed[:5]
Полученные данные можно визуализировать следующим образом:
%matplotlib inline
import matplotlib.pyplot as plt
f = plt.figure(figsize=(5, 5))
ax = f.add_subplot(111)
```ax.scatter(iris_transformed[:, 0], iris_transformed[:, 1], c=iris.target)
ax.set_title("Усечённое SVD, 2 Компонента")
---
### Как это работает...
Теперь, когда мы продемонстрировали модуль `TruncatedSVD` из scikit-learn, давайте рассмотрим некоторые детали с помощью библиотеки `scipy`.
Сначала используем `scipy` для обработки SVD:
```python
import numpy as np
from scipy.linalg import svd
D = np.array([[1, 2], [1, 3], [1, 4]])
D
Мы можем восстановить матрицу D с помощью определения SVD, используя U, S и V:
np.diag(S)
Выход:
array([[ 5.64015854, 0. ],
[ 0. , 0.43429448]])
np.dot(U.dot(np.diag(S)), V)
Выход:
array([[ 1., 2.],
[ 1., 3.],
[ 1., 4.]])
TruncatedSVD
возвращает произведение матриц U и S. Если мы хотим имитировать TSVD, то нам следует удалить последнее сингулярное значение и соответствующий столбец матрицы U. Например, если мы хотим один главный компонент, можно сделать следующее:
новое_S = S[0]
новый_U = U[:, 0]
новый_U.dot(новое_С)
Выход:
array([-2.20719466, -3.16170819, -4.11622173])
Общая ситуация такова, что если мы хотим уменьшить размерность до t, то нам следует удалить N-t сингулярных значений.
TruncatedSVD
имеет несколько деталей, которые стоит учесть.
Знаковый сдвиг может привести к тому, что сингулярные значения будут иметь противоположные знаки, но это не повлияет на качество факторизации.TruncatedSVD
имеет "ловушку". Изменение состояния генератора случайных чисел приведёт к тому, что последовательное применение TruncatedSVD
будет менять знак выходных данных. Чтобы избежать этой проблемы, рекомендуется использовать TruncatedSVD
только один раз для обучения, а затем использовать другие преобразования. Это ещё одна причина использования пайплайна команд.Чтобы избежать этого, вы можете сделать следующее:
tsvd = TruncatedSVD(2)
tsvd.fit(iris_data)
tsvd.transform(iris_data)[:5]
Выход:
array([[ 5.91220352, -2.30344211],
[ 5.57207573, -1.97383104],
[ 5.4464847 , -2.09653267],
[ 5.43601924, -1.87168085],
[ 5.87506555, -2.32934799]])
Одним из преимуществ TruncatedSVD
перед PCA является его способность работать с матрицами, которые PCA не может обрабатывать. Это связано с тем, что PCA требует вычисления ковариационной матрицы, которая должна быть вычислена на всем массиве, и если этот массив слишком велик, вычислительные ресурсы могут оказаться недостаточными.
В этом разделе мы рассмотрим метод декомпозиции, который можно использовать для классификации — метод словаря (Dictionary Learning), который позволяет представить данные в виде редкого представления.
Идея метода DictionaryLearning
заключается в том, чтобы рассматривать признаки как основу для набора данных. Сначала мы импортируем набор данных iris
:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import load_iris
iris = load_iris()
iris_data = iris.data
iris_target = iris.target
Сначала импортируем DictionaryLearning
:
from sklearn.decomposition import DictionaryLearning
``````python
dl = DictionaryLearning(3)
Далее мы применяем fit_transform
, чтобы преобразовать другую часть данных, что позволит нам сравнивать данные до и после обучения:
transformed = dl.fit_transform(iris_data[::2])
transformed[:5]
Мы можем визуализировать этот результат. Обратите внимание, что значение каждого компонента параллельно оси ( x ), ( y ) и ( z ), а все остальные координаты равны нулю; именно это и называется сжатостью.
from mpl_toolkits.mplot3d import Axes3D
colors = np.array(list('rgb'))
f = plt.figure()
ax = f.add_subplot(111, projection='3d')
ax.set_title("Обучающий набор")
ax.scatter(transformed[:, 0], transformed[:, 1], transformed[:, 2], color=colors[iris.target[::2]]);
Если вы внимательно посмотрите, вы заметите некоторые ошибки. Один образец был отнесен к неверному типу, хотя одна ошибка не является серьезной.
Теперь давайте обучим наш набор данных с помощью fit
, но не будем использовать fit_transform
:
transformed = dl.transform(iris_data[1::2])
colors = np.array(list('rgb'))
f = plt.figure()
ax = f.add_subplot(111, projection='3d')
ax.set_title("Обучающий набор")
ax.scatter(transformed[:, 0], transformed[:, 1], transformed[:, 2], color=colors[iris.target[1::2]]);
Опять же, есть несколько образцов, которые были неправильно классифицированы. Если вы посмотрите на диаграммы из темы о снижении размерности, вы заметите, что данные зелёного и голубого классов имеют пересечение.
dl = DictionaryLearning(3)
````DictionaryLearning` имеет знание из области обработки сигналов и нейрофизиологии. Его идея заключается в том, что в любой момент времени только несколько признаков могут быть активированы. Поэтому `DictionaryLearning` пытается найти подходящее представление данных, предполагая, что большинство признаков равно нулю.<!-- TEASER_END -->## Подготовка
В этом разделе мы снова продемонстрируем мощь пайплайн команд. Ранее мы использовали их для обработки пропущенных значений, но это было лишь начало. В этот раз мы соединим несколько шагов предварительной обработки с помощью пайплайн команд, что значительно упрощает процесс.
Сначала загрузим набор данных `iris`, содержащий пропущенные значения:
```python
from sklearn.datasets import load_сomma_iris
import numpy as np
iris = load_iris()
iris_data = iris.data
mask = np.random.binomial(1, .25, iris_data.shape).astype(bool)
iris_data[mask] = np.nan
iris_data[:5]
[4.9, 3.0, 1.4, 0.2],
[4.7, 3.2, 1.3, 0.2],
[4.6, nan, 1.5, nan],
[5.0, 3.6, 1.4, 0.2]])
Цель этого раздела — заполнить пропущенные значения в iris_data
, а затем применить PCA к заполненному набору данных. Можно заметить, что этот процесс требует тренировочного набора данных и контрольного набора (holdout set); использование пайплайн команд делает задачу намного проще, хотя перед этим потребуется выполнить некоторые подготовительные действия.
Сначала загрузим необходимые модули:
from sklearn import pipeline, preprocessing, decomposition
Затем создадим объекты классов Imputer
и PCA
:
pca = decomposition.PCA()
imputer = preprocessing.Imputer()
Имея эти два объекта, можно использовать пайплайн команд для обработки:
pipe = pipeline.Pipeline([('imputer', imputer), ('pca', pca)])
np.set_printoptions(2)
iris_data_transformed = pipe.fit_transform(iris_data)
iris_data_transformed[:5]
```Если бы каждый шаг выполнялся отдельно, то каждому шагу пришлось бы вызывать метод `fit_transform`. Здесь же требуется всего один вызов, и используется всего один объект.
## Как это работает...
Каждый шаг пайплайн команд представлен как кортеж, где первый элемент — имя объекта, а второй — сам объект.
Основная идея заключается в том, что все шаги выполняются последовательно при вызове метода `fit_transform` пайплайн командой. Также существует более быстрый, но менее чистый способ создания пайплайн команд, аналогично тому, как мы быстро создаем модель стандартизации. Однако вместо использования `StandardScaler` здесь используются дополнительные возможности. Функция `make_pipeline` автоматически создаёт названия для каждого шага пайплайн команды. Этот раздел представляет собой перевод текстового описания и пояснений из исходного текста на русский язык, при этом сохраняются все синтаксические конструкции и спецификации кода.
```python
pipe2 = make_pipeline(imputer, pca)
pipe2.steps
Результат такой же как и в предыдущей модели:
iris_data_transformed2 = pipe2.fit_transform(iris_data)
iris_data_transformed2[:5]
Команды внутри пайплайна соединены через атрибуты объектов методом set_params
, где параметры указываются в виде <объект_название>__<объект_параметр>
. Например, установка количества компонент PCA равной двум:```python
pipe2.set_params(pca__n_components=2)
> `__` метка читается как **dunder** в сообществе Python.
Здесь `n_components=2` является параметром самого `pca`. Пример использования:
```python
iris_data_transformed3 = pipe2.fit_transform(iris_data)
iris_data_transformed3[:5]
В этом разделе будет рассмотрено использование нормального случайного процесса (Gaussian Process, GP) для решения задач регрессии. В части о линейных моделях мы уже рассматривали, как можно использовать байесовскую регрессию с岭回归(Bayesian Ridge Regression)来表示先验概率分布(prior)的信息,在变量之间存在相关性的情况下。
正常随机过程关注的是方程式而非平均值。然而,如果假设一个正态分布的平均值为零,则需要确定协方差。
这种处理方式类似于在线性回归问题中使用相关系数表示先验概率分布的方式。用于GP处理的先验可以通过数据以及样本数据之间的协方差函数来表示,因此这些函数必须从数据中推导出来。具体细节请参阅 The Gaussian Processes Web Site.
Сначала мы воспользуемся некоторыми данными для демонстрации работы с GP в scikit-learn:
import numpy as np
from sklearn.datasets import load_boston
boston = load_boston()
boston_X = boston.data
boston_y = boston.target
train_set = np.random.choice([True, False], len(boston_y), p=[.75, .25])
Получив данные, создаем объект GaussianProcess
из библиотеки scikit-learn. По умолчанию он использует константную регрессионную функцию и квадратичную экспоненциальную корреляционную функцию, что является одной из наиболее распространенных конфигураций:
from sklearn.gaussian_process import GaussianProcess
gp = GaussianProcess()
gp.fit(boston_X[train_set], boston_y[train_set])
GaussianProcess(beta0=None,
corr=<function squared_exponential at 0x0000000006F1C950>,
normalize=True, nugget=array(2.220446049250313e-15),
optimizer='fmin_cobyla', random_start=1,
random_state=<mtrand.RandomState object at 0x00000000052A4BA8>,
regr=<function constant at 0x0000000006F15950>,
storage_mode='full', theta0=array([[ 0.1]]), thetaL=None,
thetaU=None, verbose=False)
где:
beta0
: веса регрессии. По умолчанию они оцениваются методом максимального правдоподобия (МПР).corr
: корреляционная функция. Представлено несколько вариантов, которые будут рассмотрены позже.normalize
: значение по умолчанию True
, которое позволяет нормализовать значения выборки для удобства применения МПР.nugget
: параметр регуляризации, который может быть указан как число или массив чисел одинаковой длины со значением каждого образца. По умолчанию используется очень маленькое значение.regr
: константная регрессионная функция по умолчанию.Теперь давайте обучим наш объект и проверим его работу:
test_preds = gp.predict(boston_X[~train_set])
```Давайте сравним прогнозируемые значения с реальными значениями. Поскольку мы выполняем регрессию, также можно посмотреть на график распределения остатков и гистограмму остатков.
```python
%matplotlib inline
from matplotlib import pyplot as plt
f, ax = plt.subplots(figsize=(10, 7), nrows=3)
f.tight_layout()
ax[0].plot(range(len(test_preds)), test_preds, label='Прогнозируемые значения');
ax[0].plot(range(len(test_preds)), boston_y[~train_set], label='Реальные значения');
ax[0].set_title("Прогнозируемые значения против реальных")
ax[0].legend(loc='best')
ax[1].plot(range(len(test_preds)),
test_preds - boston_y[~train_set]);
ax[1].set_title("График остатков")
ax[2].hist(test_preds - boston_y[~train_set]);
ax[2].set_title("Гистограмма остатков");
## Как это работает...
Прежде чем мы углубимся в детали, давайте рассмотрим эти параметры подробнее и узнаем, как их можно оптимизировать. В первую очередь обратим внимание на тип функции corr
. Эта функция описывает корреляцию между различными группами X
. Scikit-learn предлагает пять различных типов функций:
Например, формула для квадратичного экспоненциального выглядит следующим образом:
$$ K=\exp{\left(-\frac{|d|^2}{2l^2}\right)} $$
Линейная функция представляет собой скалярное произведение двух точек:
$$ K=x^T x' $$
Ещё один важный параметр — это theta0
, который указывает начальную точку для оценки параметров.
Как только у нас есть $K$ и оценка среднего значения, процесс становится полностью определённым, так как это Гауссовские процессы; нормальное распределение используется потому что оно широко применяется в машинном обучении.
Давайте поменяем тип функции regr
и значение параметра theta0
и посмотрим, как это повлияет на результат:
gp = GaussianProcess(regr='linear', theta0=5e-1)
gp.fit(boston_X[train_set], boston_y[train_set])
linear_preds = gp.predict(boston_X[~train_set])
``````python
f, ax = plt.subplots(figsize=(7, 5))
f.tight_layout()
ax.hist(test_preds - boston_y[~train_set], label='Residuals Original model', color='b', alpha=.5)
ax.hist(linear_preds - boston_y[~train_set], label='Residuals Linear model', color='r', alpha=.5)
ax.set_title("Residuals")
ax.legend(loc='best')
```
Конечно, вторая модель демонстрирует лучшие прогнозы в большинстве областях. Если мы суммируем остатки, то можем получить среднеквадратическую ошибку (MSE) прогноза:
```python
np.power(test_preds - boston_y[~train_set], 2).mean()
np.power(linear_preds - boston_y[~train_set], 2).mean()
Мы также можем захотеть контролировать оценку неопределенности. Когда мы делаем прогнозы, если мы установим eval_MSE
равным True
, то получим значение MSE, а прогноз вернёт пару значений прогноза и оценки MSE.
test_preds, MSE = gp.predict(boston_X[~train_set], eval_MSE=True)
MSE[:5]
Таким образом, мы можем рассчитать оценку ошибки. Давайте построим график, чтобы лучше понять точность: Вы увидите, что многие точки имеют некоторое изменение в своих оценках. Однако, как показывают данные выше, общая погрешность не является особенно большой.
До сих пор мы лишь касались поверхности нормальных случайных процессов. В этом разделе мы рассмотрим прямое создание нормального случайного процесса с указанным корреляционным функционалом.
gaussian_process
позволяет непосредственно связывать различные корреляционные функции с регрессионными уравнениями. Это позволяет создавать необходимые объекты через функции без создания объекта GaussianProcess
. Если вы более знакомы с объектно-ориентированным подходом программирования, это можно рассматривать как модульный метод класса.В этом разделе мы будем использовать большинство функций и демонстрировать их результаты на нескольких примерах. Чтобы действительно понять особенности этих корреляционных функций, не ограничивайтесь этими примерами. Здесь больше нет новых математических теорий; давайте сразу перейдем к практике.Сначала импортируем данные для регрессии:
from sklearn.datasets import make_regression
X, y = make_regression(n_samples=1000, n_features=1, noise=1)
from sklearn.gaussian_process import kernels
Первая корреляционная функция — это константная корреляционная функция. Она состоит из нескольких констант:
kernels.ConstantKernel()(X)[:5]
Есть также линейная корреляционная функция и квадратичная экспоненциальная корреляционная функция, которые являются значениями по умолчанию для класса GaussianProcess
:
kernels.LinearKernel()(X)[:1]
kernels.RBFKernel()(X)[:1]
Таким образом, мы можем получить регрессионные функции, которые могут быть использованы напрямую объектом GaussianProcess
. По умолчанию используется константная корреляционная функция, но мы можем легко передать линейную модель и квадратичную экспоненциальную модель.
SGD является незаслуженно героическим алгоритмом в области машинного обучения. Многие алгоритмы используют его как основу благодаря своей простоте и скорости — эти качества особенно ценятся при работе с большими объемами данных.
Еще одним важным аспектом использования SGD в качестве ядра многих алгоритмов машинного обучения является то, что он легко описывается. В конце этой главы мы выполним преобразование данных и применим функцию потерь модели для их аппроксимации.
Если SGD хорошо работает с большими наборами данных, давайте воспользуемся большим набором данных для демонстрации:
from sklearn import datasets
X, y = datasets.make_regression(int(1e6))
print("{:,}".format(int(1e6)))
1 000 000
Зная состав и размер данных также полезно. Мы используем NumPy массивы, поэтому можем получить nbytes
. Python сам по себе не предоставляет метод для получения размера NumPy массива. Выходные данные могут отличаться в зависимости от системы:
print("{:,}".format(X.nbytes))
800 000 000
Мы конвертируем байты nbytes
в мегабайты (MB) для более удобного представления:
X.nbytes / 1e6
800.0
Следовательно, количество байт на каждый объект данных составляет:
X.nbytes / (X.shape[0] * X.shape[1])
8.0
Эта информация не имеет прямого отношения к нашей цели, но знание состава и размера данных может быть полезным.Теперь, когда у нас есть данные, мы будем использовать SGDRegressor
для их аппроксимации:
import numpy as np
from sklearn import linear_model
sgd = linear_model.SGDRegressor()
train = np.random.choice([True, False], size=len(y), p=[.75, .25])
sgd.fit(X[train], y[train])
SGDRegressor(alpha=0.0001, average=False, epsilon=0.1, eta0=0.01,
fit_intercept=True, l1_ratio=0.15, learning_rate='invscaling',
loss='squared_loss', n_iter=5, penalty='l2', power_t=0.25,
random_state=None, shuffle=True, verbose=0, warm_start=False)
Здесь снова встречается "тяжелый" объект. Основное внимание следует уделить тому, что наша функция потерь squared_loss
эквивалентна сумме квадратов остатков в линейной регрессии. Также важно отметить, что параметр shuffle
случайным образом перемешивает данные, что полезно при решении проблем псевдорелевантности. Scikit-learn автоматически добавляет столбец единиц через параметр fit_intercept
. Чтобы видеть больше вывода во время обучения, установите verbose
равным 1. Используя API scikit-learn для прогнозирования, можно проанализировать распределение остатков.
linear_preds = sgd.predict(X[~train])
%matplotlib inline
from matplotlib import pyplot as plt
f, ax = plt.subplots(figsize=(7, 5))
f.tight_layout()
ax.hist(linear_preds - y[~train], label='Residuals Linear', color='b', alpha=.5);
ax.set_title("Residuals")
ax.legend(loc='best');
Модель хорошо справилась. Аномалий немного, а гистограмма представляет собой идеальное нормальное распределение.
while not_converged:
w = w - learning_rate * gradient(cost(w))
Здесь используются следующие переменные:
w
: корреляционный векторlearning_rate
: длина шага на каждом итерационном цикле. Важно регулировать этот параметр, если сходимость плоха.gradient
: матрица производныхcost
: сумма квадратов ошибок регрессии. Мы рассмотрим различные функции потерь в различных методах классификации позже, и эта вариабельность является одной из причин широкого применения SGD.Хотя функция градиента сложна, сам метод вполне работоспособен. По мере увеличения размера корреляционного вектора вычисление градиента становится всё медленнее. Перед каждым обновлением нам нужно вычислить новые веса для каждого точки данных.
Метод SGD работает несколько иначе; вместо того чтобы обновлять градиент в батче на каждой итерации, он обновляет параметры только для новых случайно выбранных точек данных. Именно поэтому этот метод называется стохастическим градиентным спуском.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )