Узнайте, как использовать PCA для уменьшения размерности набора данных.
Очень часто в машинном обучении используются наборы данных высокой размерности с большим количеством признаков. Наборы данных высокой размерности создают ряд проблем, наиболее распространенной из которых является переоснащение, что снижает возможность обобщения за пределы того, что есть в обучающем наборе. Таким образом, вы должны использовать методы уменьшения размерности, чтобы уменьшить количество объектов в вашем наборе данных. Анализ основных компонентов (PCA) – один из таких методов.
В этой статье я расскажу о PCA и о том, как вы можете использовать его для машинного обучения. В частности, я покажу вам, как применить PCA к образцу набора данных.
Что такое анализ главных компонентов (PCA)?
Короче говоря, PCA — это метод уменьшения размерности, который преобразует набор признаков в наборе данных в меньшее количество признаков, называемых главными компонентами, в то время как в то же время пытаясь сохранить как можно больше информации в исходном наборе данных:
Основная цель PCA – уменьшить количество переменных в наборе данных, сохранив при этом как можно больше информации.
Вместо того, чтобы объяснять теорию работы PCA в этой статье, я оставлю объяснение для следующего видео, в котором представлено отличное пошаговое руководство по работе PCA.
Хорошая аналогия понимания PCA — представить себя фотографирующим. Представьте, что вы на концерте и хотите запечатлеть атмосферу, сделав снимок. Вместо того, чтобы запечатлеть атмосферу в 3-х измерениях, фотография может запечатлеть только в 2-х измерениях. Хотя это уменьшение размера приводит к потере некоторых деталей, вы все же можете зафиксировать большую часть информации. Например, относительный размер человека на фотографии говорит нам, кто стоит впереди, а кто сзади. Таким образом, 2D-изображение по-прежнему позволит нам кодировать большую часть информации, которая в противном случае была бы доступна только в 3D:
Аналогичным образом, после того как вы применили метод PCA к своему набору данных, уменьшенные функции (известные как основные компоненты) по-прежнему смогут адекватно представлять информацию в исходном наборе данных.
Итак, каковы преимущества использования PCA в вашем наборе данных? Вот несколько причин, по которым вы хотите использовать PCA:
- Удаляет связанные функции. PCA поможет вам удалить все коррелирующие признаки, явление, известное как мультиколлинеарность. Поиск коррелирующих признаков занимает много времени, особенно если число признаков велико.
- Повышает производительность алгоритма машинного обучения. Благодаря сокращению количества функций с помощью PCA время, необходимое для обучения вашей модели, теперь значительно сокращается.
- Уменьшить переобучение. Удаляя ненужные функции в вашем наборе данных, PCA помогает преодолеть переоснащение.
С другой стороны, у PCA есть свои недостатки:
- Независимые переменные теперь менее интерпретируемы. PCA сокращает ваши функции до меньшего количества компонентов. Каждый компонент теперь представляет собой линейную комбинацию ваших исходных функций, что делает его менее читабельным и интерпретируемым.
- Потеря информации. Потеря данных может произойти, если вы не проявите осторожность при выборе правильного количества компонентов.
- Масштабирование функций. Поскольку PCA — это максимальное отклонение, перед обработкой PCA требует масштабирования объектов.
PCA полезен в тех случаях, когда в наборе данных имеется большое количество объектов.
В машинном обучении PCA — это неконтролируемый алгоритм машинного обучения.
Использование образца набора данных
В этой статье я собираюсь продемонстрировать PCA, используя классический набор данных о раке молочной железы, доступный на sklearn:
from sklearn.datasets import load_breast_cancer breast_cancer = load_breast_cancer()
Набор данных рака молочной железы является хорошим примером для иллюстрации PCA, поскольку он имеет большое количество функций и все типы данных функций представляют собой числа с плавающей запятой. Давайте напечатаем имена функций и количество функций в этом наборе данных:
print(breast_cancer.feature_names) print(len(breast_cancer.feature_names))
Вы должны увидеть следующее:
['mean radius' 'mean texture' 'mean perimeter' 'mean area' 'mean smoothness' 'mean compactness' 'mean concavity' 'mean concave points' 'mean symmetry' 'mean fractal dimension' 'radius error' 'texture error' 'perimeter error' 'area error' 'smoothness error' 'compactness error' 'concavity error' 'concave points error' 'symmetry error' 'fractal dimension error' 'worst radius' 'worst texture' 'worst perimeter' 'worst area' 'worst smoothness' 'worst compactness' 'worst concavity' 'worst concave points' 'worst symmetry' 'worst fractal dimension'] 30
Давайте также распечатаем цель и изучим значение цели и распределение цели:
import numpy as np print(breast_cancer.target) print(breast_cancer.target_names) print(np.array(np.unique(breast_cancer.target, return_counts=True)))
Вы должны увидеть что-то вроде этого:
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 1 1 1 1 1 1 1 0 0 0 0 0 0 1] ['malignant' 'benign'] [[ 0 1] [212 357]]
Целевое значение 0 означает, что опухоль злокачественная, а 1 означает, что она доброкачественная. Цель несбалансирована, но не до серьезной степени.
Изучение взаимосвязи между функциями и целью
На этом этапе полезно иметь возможность визуализировать, как каждый признак влияет на диагноз — является ли опухоль злокачественной или доброкачественной. Итак, давайте построим гистограмму для каждого признака, а затем по цвету различаем злокачественные и доброкачественные опухоли:
import numpy as np import matplotlib.pyplot as plt _, axes = plt.subplots(6,5, figsize=(15, 15)) malignant = breast_cancer.data[breast_cancer.target==0] benign = breast_cancer.data[breast_cancer.target==1] ax = axes.ravel() # flatten the 2D array for i in range(30): # for each of the 30 features bins = 40 #---plot histogram for each feature--- ax[i].hist(malignant[:,i], bins=bins, color='r', alpha=.5) ax[i].hist(benign[:,i], bins=bins, color='b', alpha=0.3) #---set the title--- ax[i].set_title(breast_cancer.feature_names[i], fontsize=12) #---display the legend--- ax[i].legend(['malignant','benign'], loc='best', fontsize=8) plt.tight_layout() plt.show()
Вы должны увидеть следующий вывод:
Для каждого признака, если две гистограммы отдельные, это означает, что признак важен и напрямую влияет на цель (диагноз). Например, если вы посмотрите на гистограмму функции средний радиус, вы заметите, что чем больше опухоль, тем больше вероятность того, что она злокачественная (красный):
С другой стороны, функция ошибка сглаженности на самом деле не говорит вам, является ли опухоль злокачественной или доброкачественной:
Загрузка данных в DataFrame
Следующим шагом будет загрузка данных о раке груди в Pandas DataFrame:
import pandas as pd df = pd.DataFrame(breast_cancer.data, columns = breast_cancer.feature_names) df['diagnosis'] = breast_cancer.target df
Вы должны увидеть следующий вывод:
Метод 1 — Обучение модели с использованием всех функций
Прежде чем мы выполним PCA для набора данных, давайте воспользуемся логистической регрессией для обучения модели с использованием всех 30 функций в наборе данных и посмотрим, насколько хорошо она работает:
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split X = df.iloc[:,:-1] y = df.iloc[:,-1] #---perform a split--- random_state = 12 X_train, X_test, y_train, y_test = \ train_test_split(X, y, test_size = 0.3, shuffle = True, random_state=random_state) #---train the model using Logistic Regression--- log_reg = LogisticRegression(max_iter = 5000) log_reg.fit(X_train, y_train) #---evaluate the model--- log_reg.score(X_test,y_test)
Точность этой модели:
0.9239766081871345
Метод 2 — обучение модели с использованием сокращенных функций
Для следующего метода давайте рассмотрим различные функции и попытаемся исключить те функции, которые наименее коррелируют с целью. В то же время мы также хотим удалить те признаки, которые демонстрируют мультиколлинеарность. Цель состоит в том, чтобы уменьшить количество признаков и посмотреть, можно ли повысить точность модели.
Получение коэффициентов корреляции
Давайте сначала получим корреляцию каждой функции по отношению к цели (диагнозу):
df_corr = df.corr()['diagnosis'].abs().sort_values(ascending=False) df_corr
Вы должны увидеть следующее:
diagnosis 1.000000 worst concave points 0.793566 worst perimeter 0.782914 mean concave points 0.776614 worst radius 0.776454 mean perimeter 0.742636 worst area 0.733825 mean radius 0.730029 mean area 0.708984 mean concavity 0.696360 worst concavity 0.659610 mean compactness 0.596534 worst compactness 0.590998 radius error 0.567134 perimeter error 0.556141 area error 0.548236 worst texture 0.456903 worst smoothness 0.421465 worst symmetry 0.416294 mean texture 0.415185 concave points error 0.408042 mean smoothness 0.358560 mean symmetry 0.330499 worst fractal dimension 0.323872 compactness error 0.292999 concavity error 0.253730 fractal dimension error 0.077972 smoothness error 0.067016 mean fractal dimension 0.012838 texture error 0.008303 symmetry error 0.006522 Name: diagnosis, dtype: float64
Затем мы извлекаем все те функции, которые имеют относительно высокую корреляцию с целью (мы произвольно устанавливаем порог 0,6):
# get all the features that has at least 0.6 in correlation to the # target features = df_corr[df_corr > 0.6].index.to_list()[1:] features # without the 'diagnosis' column
И теперь у вас есть следующие функции:
['worst concave points', 'worst perimeter', 'mean concave points', 'worst radius', 'mean perimeter', 'worst area', 'mean radius', 'mean area', 'mean concavity', 'worst concavity']
Но ясно, что несколько признаков коррелированы — например, радиус, периметр и площадь — все коррелированы. Некоторые из этих функций должны быть удалены.
Проверка мультиколлинеарности
Давайте удалим те функции, которые демонстрируют мультиколлинеарность:
import pandas as pd from sklearn.linear_model import LinearRegression def calculate_vif(df, features): vif, tolerance = {}, {} # all the features that you want to examine for feature in features: # extract all the other features you will regress against X = [f for f in features if f != feature] X, y = df[X], df[feature] # extract r-squared from the fit r2 = LinearRegression().fit(X, y).score(X, y) # calculate tolerance tolerance[feature] = 1 - r2 # calculate VIF vif[feature] = 1/(tolerance[feature]) # return VIF DataFrame return pd.DataFrame({'VIF': vif, 'Tolerance': tolerance}) calculate_vif(df,features)
Вы должны увидеть следующий вывод:
Ваша цель состоит в том, чтобы удалить те функции, которые имеют VIF больше 5. Вы можете итеративно вызывать функцию calculate_vif()
с различными функциями, пока у вас не будет набора функций, у которого все значения VIF меньше 5.
С некоторыми попытками я сузил до 3 функций:
# try to reduce those feature that has high VIF until each feature # has VIF less than 5 features = [ 'worst concave points', 'mean radius', 'mean concavity', ] calculate_vif(df,features)
И их значения VIF выглядят великолепно:
Обучение модели
С 30 функциями, уменьшенными до 3, давайте теперь обучим модель с помощью логистической регрессии:
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split X = df.loc[:,features] # get the reduced features in the # dataframe y = df.loc[:,'diagnosis'] # perform a split X_train, X_test, y_train, y_test = \ train_test_split(X, y, test_size = 0.3, shuffle = True, random_state=random_state) log_reg = LogisticRegression() log_reg.fit(X_train, y_train) log_reg.score(X_test,y_test)
На этот раз точность упала до:
0.847953216374269
Метод 3 — Обучение модели с использованием сокращенных функций (PCA)
Наконец, давайте применим PCA к набору данных и посмотрим, можно ли обучить лучшую модель.
Выполнение стандартного масштабирования
Помните, что PCA чувствителен к масштабированию? Итак, первый шаг — выполнить стандартное масштабирование по 30 функциям:
from sklearn.preprocessing import StandardScaler # get the features and label from the original dataframe X = df.iloc[:,:-1] y = df.iloc[:,-1] # performing standardization sc = StandardScaler() X_scaled = sc.fit_transform(X)
Применение анализа главных компонентов (PCA)
Теперь вы можете применить PCA к функциям, используя класс PCA
в модуле sklearn.decomposition:
from sklearn.decomposition import PCA components = None pca = PCA(n_components = components) # perform PCA on the scaled data pca.fit(X_scaled)
Инициализатор класса PCA имеет параметр с именем n_components
. Вы можете указать одно из следующих значений:
- целое число, чтобы указать, до скольких основных компонентов вы хотите сократить функции.
- число с плавающей запятой от 0‹n‹1, и он вернет количество компонентов, необходимых для захвата указанного процента изменчивости данных. Например, если вы хотите найти количество компонентов, необходимых для захвата 85% изменчивости данных, передайте
0.85
параметруn_components
. None
. В этом случае количество возвращенных компонентов будет таким же, как количество исходных объектов в наборе данных.
Как только компоненты определены с помощью метода fit()
, вы можете распечатать объясненные отклонения:
# print the explained variances print("Variances (Percentage):") print(pca.explained_variance_ratio_ * 100) print()
Вы должны увидеть следующий вывод:
Variances (Percentage): [4.42720256e+01 1.89711820e+01 9.39316326e+00 6.60213492e+00 5.49576849e+00 4.02452204e+00 2.25073371e+00 1.58872380e+00 1.38964937e+00 1.16897819e+00 9.79718988e-01 8.70537901e-01 8.04524987e-01 5.23365745e-01 3.13783217e-01 2.66209337e-01 1.97996793e-01 1.75395945e-01 1.64925306e-01 1.03864675e-01 9.99096464e-02 9.14646751e-02 8.11361259e-02 6.01833567e-02 5.16042379e-02 2.72587995e-02 2.30015463e-02 5.29779290e-03 2.49601032e-03 4.43482743e-04]
Итак, как вы интерпретируете приведенный выше вывод? Вы можете интерпретировать это следующим образом:
- Один только первый компонент фиксирует около 44% изменчивости данных.
- Второй фиксирует около 19% изменчивости данных и так далее.
- Все 30 компонентов охватывают 100% изменчивость данных.
Гораздо более простой способ понять приведенный выше результат - распечатать совокупные отклонения:
print("Cumulative Variances (Percentage):") print(pca.explained_variance_ratio_.cumsum() * 100) print()
Теперь вы должны увидеть следующий вывод:
Cumulative Variances (Percentage): [ 44.27202561 63.24320765 72.63637091 79.23850582 84.73427432 88.75879636 91.00953007 92.59825387 93.98790324 95.15688143 96.13660042 97.00713832 97.81166331 98.33502905 98.64881227 98.91502161 99.1130184 99.28841435 99.45333965 99.55720433 99.65711397 99.74857865 99.82971477 99.88989813 99.94150237 99.96876117 99.99176271 99.99706051 99.99955652 100. ]
Теперь вы можете интерпретировать кумулятивные отклонения следующим образом:
- Один только первый компонент фиксирует около 44% изменчивости данных.
- Первые два компонента охватывают около 63% изменчивости данных и так далее.
- Первые 8 компонентов вместе охватывают около 92,6% изменчивости данных.
Наглядным способом просмотра совокупных отклонений является построение графика осыпи.
График осыпи — это линейный график основных компонентов.
# plot a scree plot components = len(pca.explained_variance_ratio_) \ if components is None else components plt.plot(range(1,components+1), np.cumsum(pca.explained_variance_ratio_ * 100)) plt.xlabel("Number of components") plt.ylabel("Explained variance (%)")
График осыпи позволяет легко визуализировать количество компонентов, необходимых для захвата различной степени изменчивости данных:
Давайте теперь применим PCA, чтобы найти желаемое количество компонентов на основе желаемой объясненной дисперсии, скажем, 85%:
from sklearn.decomposition import PCA pca = PCA(n_components = 0.85) pca.fit(X_scaled) print("Cumulative Variances (Percentage):") print(np.cumsum(pca.explained_variance_ratio_ * 100)) components = len(pca.explained_variance_ratio_) print(f'Number of components: {components}') # Make the scree plot plt.plot(range(1, components + 1), np.cumsum(pca.explained_variance_ratio_ * 100)) plt.xlabel("Number of components") plt.ylabel("Explained variance (%)")
Вы получите следующий вывод:
Cumulative Variances (Percentage): [44.27202561 63.24320765 72.63637091 79.23850582 84.73427432 88.75879636] Number of components: 6
И, как видно из диаграммы, необходимо 6 компонентов, чтобы покрыть 85% изменчивости данных:
Вы также можете узнать важность каждой функции, которая влияет на каждый из компонентов, используя атрибут components_
объекта pca
:
pca_components = abs(pca.components_) print(pca_components)
Вы увидите такой вывод:
[[2.18902444e-01 1.03724578e-01 2.27537293e-01 2.20994985e-01 1.42589694e-01 2.39285354e-01 2.58400481e-01 2.60853758e-01 1.38166959e-01 6.43633464e-02 2.05978776e-01 1.74280281e-02 2.11325916e-01 2.02869635e-01 1.45314521e-02 1.70393451e-01 1.53589790e-01 1.83417397e-01 4.24984216e-02 1.02568322e-01 2.27996634e-01 1.04469325e-01 2.36639681e-01 2.24870533e-01 1.27952561e-01 2.10095880e-01 2.28767533e-01 2.50885971e-01 1.22904556e-01 1.31783943e-01] [2.33857132e-01 5.97060883e-02 2.15181361e-01 2.31076711e-01 1.86113023e-01 1.51891610e-01 6.01653628e-02 3.47675005e-02 1.90348770e-01 3.66575471e-01 1.05552152e-01 8.99796818e-02 8.94572342e-02 1.52292628e-01 2.04430453e-01 2.32715896e-01 1.97207283e-01 1.30321560e-01 1.83848000e-01 2.80092027e-01 2.19866379e-01 4.54672983e-02 1.99878428e-01 2.19351858e-01 1.72304352e-01 1.43593173e-01 9.79641143e-02 8.25723507e-03 1.41883349e-01 2.75339469e-01] [8.53124284e-03 6.45499033e-02 9.31421972e-03 2.86995259e-02 1.04291904e-01 7.40915709e-02 2.73383798e-03 2.55635406e-02 4.02399363e-02 2.25740897e-02 2.68481387e-01 3.74633665e-01 2.66645367e-01 2.16006528e-01 3.08838979e-01 1.54779718e-01 1.76463743e-01 2.24657567e-01 2.88584292e-01 2.11503764e-01 4.75069900e-02 4.22978228e-02 4.85465083e-02 1.19023182e-02 2.59797613e-01 2.36075625e-01 1.73057335e-01 1.70344076e-01 2.71312642e-01 2.32791313e-01] [4.14089623e-02 6.03050001e-01 4.19830991e-02 5.34337955e-02 1.59382765e-01 3.17945811e-02 1.91227535e-02 6.53359443e-02 6.71249840e-02 4.85867649e-02 9.79412418e-02 3.59855528e-01 8.89924146e-02 1.08205039e-01 4.46641797e-02 2.74693632e-02 1.31687997e-03 7.40673350e-02 4.40733510e-02 1.53047496e-02 1.54172396e-02 6.32807885e-01 1.38027944e-02 2.58947492e-02 1.76522161e-02 9.13284153e-02 7.39511797e-02 6.00699571e-03 3.62506947e-02 7.70534703e-02] [3.77863538e-02 4.94688505e-02 3.73746632e-02 1.03312514e-02 3.65088528e-01 1.17039713e-02 8.63754118e-02 4.38610252e-02 3.05941428e-01 4.44243602e-02 1.54456496e-01 1.91650506e-01 1.20990220e-01 1.27574432e-01 2.32065676e-01 2.79968156e-01 3.53982091e-01 1.95548089e-01 2.52868765e-01 2.63297438e-01 4.40659209e-03 9.28834001e-02 7.45415100e-03 2.73909030e-02 3.24435445e-01 1.21804107e-01 1.88518727e-01 4.33320687e-02 2.44558663e-01 9.44233510e-02] [1.87407904e-02 3.21788366e-02 1.73084449e-02 1.88774796e-03 2.86374497e-01 1.41309489e-02 9.34418089e-03 5.20499505e-02 3.56458461e-01 1.19430668e-01 2.56032561e-02 2.87473145e-02 1.81071500e-03 4.28639079e-02 3.42917393e-01 6.91975186e-02 5.63432386e-02 3.12244482e-02 4.90245643e-01 5.31952674e-02 2.90684919e-04 5.00080613e-02 8.50098715e-03 2.51643821e-02 3.69255370e-01 4.77057929e-02 2.83792555e-02 3.08734498e-02 4.98926784e-01 8.02235245e-02]]
Важность каждой функции отражается величиной соответствующих значений в выходных данных — чем выше величина, тем выше важность. На следующем рисунке показано, как вы можете интерпретировать приведенные выше результаты:
Для любопытства давайте распечатаем 4 основные функции, которые больше всего влияют на каждый из 6 компонентов:
print('Top 4 most important features in each component') print('===============================================') for row in range(pca_components.shape[0]): # get the indices of the top 4 values in each row temp = np.argpartition(-(pca_components[row]), 4) # sort the indices in descending order indices = temp[np.argsort((-pca_components[row])[temp])][:4] # print the top 4 feature names print(f'Component {row}: {df.columns[indices].to_list()}')
Вы увидите следующий вывод:
Top 4 most important features in each component =============================================== Component 0: ['mean concave points', 'mean concavity', 'worst concave points', 'mean compactness'] Component 1: ['mean fractal dimension', 'fractal dimension error', 'worst fractal dimension', 'mean radius'] Component 2: ['texture error', 'smoothness error', 'symmetry error', 'worst symmetry'] Component 3: ['worst texture', 'mean texture', 'texture error', 'mean smoothness'] Component 4: ['mean smoothness', 'concavity error', 'worst smoothness', 'mean symmetry'] Component 5: ['worst symmetry', 'symmetry error', 'worst smoothness', 'mean symmetry']
Преобразование всех 30 столбцов в 6 основных компонентов
Теперь вы можете преобразовать стандартизированные данные 30 столбцов в наборе данных в 6 основных компонентов:
X_pca = pca.transform(X_scaled) print(X_pca.shape) print(X_pca)
Вы должны получить следующий результат:
(569, 6) [[ 9.19283683 1.94858307 -1.12316616 3.6337309 -1.19511012 1.41142445] [ 2.3878018 -3.76817174 -0.52929269 1.11826386 0.62177498 0.02865635] [ 5.73389628 -1.0751738 -0.55174759 0.91208267 -0.1770859 0.54145215] ... [ 1.25617928 -1.90229671 0.56273053 -2.08922702 1.80999133 -0.53444719] [10.37479406 1.67201011 -1.87702933 -2.35603113 -0.03374193 0.56793647] [-5.4752433 -0.67063679 1.49044308 -2.29915714 -0.18470331 1.61783736]]
Создание конвейера машинного обучения
Давайте теперь создадим конвейер машинного обучения, чтобы мы могли формализовать весь процесс:
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.linear_model import LogisticRegression _sc = StandardScaler() _pca = PCA(n_components = components) _model = LogisticRegression() log_regress_model = Pipeline([ ('std_scaler', _sc), ('pca', _pca), ('regressor', _model) ])
Затем мы разделяем набор данных на наборы для обучения и тестирования и обучаем модель, используя набор для обучения:
# perform a split X_train, X_test, y_train, y_test = \ train_test_split(X, y, test_size=0.3, shuffle=True, random_state=random_state) # train the model using the PCA components log_regress_model.fit(X_train,y_train)
И давайте оценим модель, чтобы увидеть, как она работает:
log_regress_model.score(X_test,y_test)
И теперь мы имеем следующую точность:
0.9824561403508771
Краткое содержание
В этой статье мы обсудили идею PCA, а также плюсы и минусы ее использования. В частности, мы обучили три модели:
- Использование всех 30 функций в наборе данных о раке молочной железы
- Использование только 3 функций в наборе данных
- Применение PCA к набору данных, а затем использование 6 компонентов для обучения
Для вашего собственного набора данных было бы полезно опробовать различные методы, которые я продемонстрировал в этой статье, и посмотреть, какой из них работает лучше.