note_3
Устранение избыточности в данных.
Шокуров Антон В.
shokurov.anton.v@yandex.ru
http://машинноезрение.рф
Версия 0.14

Анотация

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

Это предварительная версия! Любые замечания приветсвуются.

In [1]:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

Метод Главных Компонент

Исходя из предыдущих заметок матрица X признаков не должна содержать избыточные признаки (столбцы матрицы X). Избыточные признаки это те, что могут быть восстановлены по некой формуле из других. В частности, если некий столбец может быть восстановлен как линейная комбинация других столбцов. В последнем случае столбцы будут линейно зависимоми и матрицы будет вырождена, даже в псевдообратном случае. Поэтому, необходимо такие столбцы выявить и устранить.

Синтезированные примеры

Формирование данных на приямой, которая под углом.

Для начала рассмотрим синтезированные данные. Причем сначала вообще идеальные "одномерные", т.е. по сути одномерные, то лежащие в двумерной плоскости.

In [2]:
# Сгенерируем данные
x = np.random.uniform(-10, 10, 30) # Раномерная выборка на отрезке [-10, 10] в количестве 30 элементов.
x.shape
Out[2]:
(30,)
In [3]:
# Две строчки -- к каждому числу добавлена вторая координата (равная 0).
X = np.row_stack( (x, np.zeros_like(x) ) )
X # Выводим матрицу.
Out[3]:
array([[ 8.03327592, -1.80320494, -5.36827133,  5.1921598 ,  0.45230023,
        -2.76423454, -1.29221568, -1.22514082,  6.23261048, -0.26513405,
        -6.14199971, -3.95500788, -1.766722  , -9.15352335, -6.49677412,
        -8.17794397, -0.57422746, -9.83795015, -3.40974571,  2.2891533 ,
        -6.20197413, -7.52331754,  2.73165546,  0.38658454,  5.96382825,
        -7.02887628, -3.10580842,  3.32528621, -1.0904062 ,  5.52274033],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ]])
In [4]:
plt.plot(X[0,:], X[1,:], ".") # Нарисуем их
# Они находятся на прямой.
Out[4]:
[<matplotlib.lines.Line2D at 0x7f8a13fcb588>]
In [5]:
# Теперь подкрутим данные. Разместим их под уголом.
theta = np.radians( 30 ) # Градусы в радианы.
c, s = np.cos(theta), np.sin(theta) # Поэлементвное присвоение,
# Фактически эквивалентно:
# c = np.cos(theta)
# s = np.sin(theta)
In [6]:
R = np.array([[c,-s], [s, c]]) # Формируем матрицу поворота,
T = np.array([[2.5], [-1.3]]) # а также вектор смещения.
In [7]:
XX = R.dot(X) + T # Применяем к нашим данным вращение и трансляцию.
XXt = XX.transpose()
XXt
Out[7]:
array([[ 9.45702102,  2.71663796],
       [ 0.93837872, -2.20160247],
       [-2.14905935, -3.98413567],
       [ 6.99654228,  1.2960799 ],
       [ 2.89170349, -1.07384989],
       [ 0.10610267, -2.68211727],
       [ 1.38090839, -1.94610784],
       [ 1.43899692, -1.91257041],
       [ 7.89759901,  1.81630524],
       [ 2.27038718, -1.43256702],
       [-2.81912778, -4.37099985],
       [-0.92513729, -3.27750394],
       [ 0.96997387, -2.183361  ],
       [-5.42718376, -5.87676168],
       [-3.12637143, -4.54838706],
       [-4.58230723, -5.38897198],
       [ 2.00270444, -1.58711373],
       [-6.01991475, -6.21897508],
       [-0.4529264 , -3.00487285],
       [ 4.48246491, -0.15542335],
       [-2.87106715, -4.40098706],
       [-4.01538411, -5.06165877],
       [ 4.86568302,  0.06582773],
       [ 2.83479203, -1.10670773],
       [ 7.66482677,  1.68191413],
       [-3.58718542, -4.81443814],
       [-0.18970899, -2.85290421],
       [ 5.37978233,  0.36264311],
       [ 1.55568053, -1.8452031 ],
       [ 7.28283342,  1.46137016]])
In [8]:
plt.plot(XXt[:,0], XXt[:,1], "*", 0, 0, "r.")
# Данные теперь под углом. Ордината (красная точка) находится сбоку.
Out[8]:
[<matplotlib.lines.Line2D at 0x7f8a13f5afd0>,
 <matplotlib.lines.Line2D at 0x7f8a13f641d0>]

Метод главных компонент (PCA), данные на прямой.

Как восстановить их? Как устранить факт того, что они под уголом? Для этого есть метод главных компонент.

Он позволяет снизить размерность данных, т.е. найти подпространство, которое содержит данные. Самое пространство и преобразование линейны.

In [9]:
# Загружаем метод главных компонент (PCA -- Principal component analysis):
from sklearn.decomposition import PCA
In [10]:
# PCA это конструктор объектов. Для выполнения вычислений нужно объект создать.
# Самое главное задать требуемое размер пространства.
pca = PCA( n_components = 1 ) # Хотим преобзовать данные к прямой, т.е. сделать их одномерными (как были исходные).
In [11]:
pca.fit( XXt ) # Запускаем метод pca на данных XXt, т.е. ищим подплоскость (смещение и поворот) содержащую данные.
# Выводятся используемые значения параметров метода.
Out[11]:
PCA(copy=True, iterated_power='auto', n_components=1, random_state=None,
    svd_solver='auto', tol=0.0, whiten=False)
In [12]:
XXtd = pca.transform( XXt ) # Применяем найденное (сохраненное в объекте pca) преобразование к данным.
XXtd
Out[12]:
array([[ 9.60170538],
       [-0.23477548],
       [-3.79984188],
       [ 6.76058926],
       [ 2.02072968],
       [-1.19580508],
       [ 0.27621377],
       [ 0.34328864],
       [ 7.80103994],
       [ 1.30329541],
       [-4.57357025],
       [-2.38657842],
       [-0.19829254],
       [-7.58509389],
       [-4.92834466],
       [-6.60951451],
       [ 0.994202  ],
       [-8.26952069],
       [-1.84131625],
       [ 3.85758276],
       [-4.63354467],
       [-5.95488808],
       [ 4.30008492],
       [ 1.955014  ],
       [ 7.53225771],
       [-5.46044682],
       [-1.53737896],
       [ 4.89371567],
       [ 0.47802326],
       [ 7.09116979]])
In [13]:
pca.explained_variance_ratio_ # Хорошо-ли данные коррелируют с вычисленным преобзованием?
# Фактически говорит о том насколько хорошо удалось снизить размерность,
# много ли данных оказалось искаженными (спроецированными с сильным смещением)
# 1 -- 100% по данных по первой оси (она же единственная).
Out[13]:
array([1.])
In [14]:
# Теперь нужно сравнить восстановленные данные с исходными. Для этого их нужно привести к нормальному виду:
from sklearn import preprocessing # preprocessing -- первичные преобразования данных.
# Будем применять preprocessing.scale -- делает выборочное среднее равно нулю, а дисперсию равной 1.
# Иногда, для корретной работы примера, требуется:
XXtd = -XXtd # см ниже.
XXtds = preprocessing.scale( XXtd[:, 0] ) # Нормируем. Используем функию scale из модуля sklearn.preprocessing.
XXtds
Out[14]:
array([-1.98338375,  0.04849658,  0.7849173 , -1.39650638, -0.4174136 ,
        0.24701241, -0.05705631, -0.07091168, -1.61142789, -0.26921623,
        0.9447431 ,  0.49298543,  0.04096045,  1.56682083,  1.01802735,
        1.36529951, -0.20536811,  1.70819998,  0.38035292, -0.79684459,
        0.95713176,  1.23007609, -0.88825039, -0.40383899, -1.55590668,
        1.12794145,  0.31756988, -1.01087419, -0.09874325, -1.46479301])
In [15]:
xs = preprocessing.scale( x ) # x -- это исходные даные. Их тоже нормируем.
xs
Out[15]:
array([ 1.98338375, -0.04849658, -0.7849173 ,  1.39650638,  0.4174136 ,
       -0.24701241,  0.05705631,  0.07091168,  1.61142789,  0.26921623,
       -0.9447431 , -0.49298543, -0.04096045, -1.56682083, -1.01802735,
       -1.36529951,  0.20536811, -1.70819998, -0.38035292,  0.79684459,
       -0.95713176, -1.23007609,  0.88825039,  0.40383899,  1.55590668,
       -1.12794145, -0.31756988,  1.01087419,  0.09874325,  1.46479301])
In [16]:
# Вычислим расстояние между векторами, т.е. отнормированными данными.
np.linalg.norm((xs - XXtds)) # Вызываем функцию из модуля, без from...
# Длина фактически равна нулю. Значит данные были восстановлены полностью.
# Если норма не равна нулю, то требуется массив отразить. см выше: XXtd = -XXtd. Нужно убрать комментарий.
Out[16]:
10.954451150103322
In [17]:
# Что будет если на две оси спроецировать?
pca = PCA( n_components = 2 ) # Настраиваем объект на двумерную плоскость.
XXtd = pca.fit_transform( XXt ) # Настроим параметры модели и сразу преобразуем данные к двум компонентам.
XXtd # Видим, что даные состоят из двумерных точек. Вторая компонента фактически равна нулю (машинное эпсилон).
Out[17]:
array([[ 9.60170538e+00,  1.16765514e-15],
       [-2.34775477e-01,  4.53969079e-16],
       [-3.79984188e+00,  3.24947832e-16],
       [ 6.76058926e+00,  1.53159167e-16],
       [ 2.02072968e+00,  4.69828355e-16],
       [-1.19580508e+00,  3.93997874e-16],
       [ 2.76213774e-01,  4.54039131e-16],
       [ 3.43288636e-01,  5.01011150e-16],
       [ 7.80103994e+00,  5.64277441e-16],
       [ 1.30329541e+00,  2.55100010e-16],
       [-4.57357025e+00,  1.32741900e-16],
       [-2.38657842e+00,  2.62654169e-16],
       [-1.98292543e-01,  3.93035213e-16],
       [-7.58509389e+00,  1.08939798e-15],
       [-4.92834466e+00, -4.68225702e-17],
       [-6.60951451e+00,  1.03670850e-15],
       [ 9.94202002e-01,  3.72070773e-16],
       [-8.26952069e+00,  6.11023658e-16],
       [-1.84131625e+00,  3.23209666e-16],
       [ 3.85758276e+00,  4.76351071e-16],
       [-4.63354467e+00,  5.37025612e-16],
       [-5.95488808e+00,  1.68094093e-16],
       [ 4.30008492e+00,  1.49356870e-16],
       [ 1.95501400e+00,  2.59284710e-16],
       [ 7.53225771e+00,  3.25070552e-16],
       [-5.46044682e+00,  2.25068400e-16],
       [-1.53737896e+00,  2.86167203e-16],
       [ 4.89371567e+00,  3.59848853e-16],
       [ 4.78023260e-01,  4.33359336e-16],
       [ 7.09116979e+00,  3.96792322e-16]])
In [18]:
# Построим преобращованные (с целью снижения размерность) точки.
plt.plot(XXtd[:,0], XXtd[:,1], "*" ) # Но график будет обманчевым. Ось по y отмасштабирована (разные масштабы)!
Out[18]:
[<matplotlib.lines.Line2D at 0x7f89fb102ef0>]
In [19]:
plt.plot(XXtd[:,0], XXtd[:,1], "*" )
plt.axis('equal') # Деламем оси одинакового масштаба.
# Правильнее так. Теперь видно, что данные расположены вдоль оси x.
Out[19]:
(-9.163081996197592,
 10.49526668135322,
 -1.0754645582217078e-16,
 1.2283790287951625e-15)
In [20]:
pca.explained_variance_ratio_ # Теперь тоже проверяем насколько хорошо данные коррелируют с осями.
# Видно, что первая ось все содержит. Для второй оси коеффициент равен 0.
Out[20]:
array([1.00000000e+00, 1.04999627e-32])

Видно, что увеличиение количества осей мы не теряем информацию о том, что данные на самом деле одномерные.

Метод главных компонент (PCA), данные на прямой плюс шум

In [21]:
# Добавим шума (к y, раньше он был раен 0)
X[1,:] = np.random.randn( (30) )/4 # вторая компонента содержит номальный шум, раньше там был 0.
XX = R.dot(X) + T # Как и раньше применяем вращение и трансляцию.
XXt = XX.transpose() # Для удобства. Например, при вызове plot, fit_transform.
In [22]:
plt.plot(XXt[:,0], XXt[:,1], "*", 0, 0, "r.")
Out[22]:
[<matplotlib.lines.Line2D at 0x7f89fb0566d8>,
 <matplotlib.lines.Line2D at 0x7f89fb056828>]
In [23]:
pca = PCA( n_components = 1 ) # Настраиваем объект на одну компоненту.
XXtd = pca.fit_transform( XXt ) # Ищим параметры преобразования и преобразуем.
In [24]:
pca.explained_variance_ratio_ # Теперь важность первой оси немного упала. Но совсем немного!
Out[24]:
array([0.99842037])
In [25]:
# Теперь спроецируем на две оси.
pca = PCA( n_components = 2 )
XXtd = pca.fit_transform( XXt )
XXtd # Видно, что вторая компонента фактически равна шуму.
Out[25]:
array([[ 9.60183687,  0.03071483],
       [-0.23499399, -0.07410886],
       [-3.79974031,  0.04018094],
       [ 6.76069624,  0.0265328 ],
       [ 2.02027461, -0.15802305],
       [-1.19544832,  0.12331323],
       [ 0.27574001, -0.16183158],
       [ 0.34301279, -0.09449354],
       [ 7.80088002, -0.06593779],
       [ 1.30387521,  0.19564357],
       [-4.57195702,  0.55638938],
       [-2.38683768, -0.08483639],
       [-0.19865058, -0.12170463],
       [-7.5865345 , -0.47973019],
       [-4.92861757, -0.08575772],
       [-6.60934194,  0.06849805],
       [ 0.99389238, -0.1069585 ],
       [-8.2696307 , -0.02534785],
       [-1.84060845,  0.24387264],
       [ 3.85705351, -0.18599113],
       [-4.63325604,  0.10514569],
       [-5.95547695, -0.19190798],
       [ 4.30082286,  0.24513095],
       [ 1.95483055, -0.0653757 ],
       [ 7.53176577, -0.17867239],
       [-5.4604106 ,  0.02035368],
       [-1.53716975,  0.07354156],
       [ 4.89447263,  0.25073857],
       [ 0.47878808,  0.25989919],
       [ 7.09073287, -0.15927779]])
In [26]:
plt.plot(XXtd[:,0], XXtd[:,1], "*" ) # Но график как и раньше будет обманный. Ось по y отмасштабирована!
Out[26]:
[<matplotlib.lines.Line2D at 0x7f89fafbbfd0>]
In [27]:
plt.plot(XXtd[:,0], XXtd[:,1], "*" ) # Правильнее так. Теперь видно, что данные расположены вдоль оси x.
plt.axis('equal') # Деламем оси одинакового масштаба. Вдоль y расположен шум.
Out[27]:
(-9.163204077727404,
 10.495410251470753,
 -0.5315361651639028,
 0.6081953615450986)
In [28]:
pca.explained_variance_ratio_ # Теперь тоже проверяем насколько хорошо данные коррелируют с осями.
# Видно, что первая ось содержит почти все. Для второй оси коеффициент почти равен 0, но не 0.
Out[28]:
array([0.99842037, 0.00157963])
In [29]:
XXtd[:,1] # Это шум.
Out[29]:
array([ 0.03071483, -0.07410886,  0.04018094,  0.0265328 , -0.15802305,
        0.12331323, -0.16183158, -0.09449354, -0.06593779,  0.19564357,
        0.55638938, -0.08483639, -0.12170463, -0.47973019, -0.08575772,
        0.06849805, -0.1069585 , -0.02534785,  0.24387264, -0.18599113,
        0.10514569, -0.19190798,  0.24513095, -0.0653757 , -0.17867239,
        0.02035368,  0.07354156,  0.25073857,  0.25989919, -0.15927779])
In [30]:
import scipy.stats as models
models.norm.fit( XXtd[:,1] )[1], models.norm.fit( X[1,:] )[1]
# Почти как исходное 1/4 = 0.25. У исходного и преобразованного оценка почти совпадает.
Out[30]:
(0.19255907259371385, 0.1930816927037976)

Аналогичный пример в 3d

данные находятся в плоскости.

In [31]:
from sklearn.decomposition import PCA

from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
In [32]:
e = np.exp(1)
np.random.seed(4)


def pdf(x):
    return 0.5 * (stats.norm(scale=0.25 / e).pdf(x)
                  + stats.norm(scale=4 / e).pdf(x))

y = np.random.normal(scale=0.5, size=(30000))
x = np.random.normal(scale=0.5, size=(30000))
z = np.random.normal(scale=0.1, size=len(x))

density = pdf(x) * pdf(y)
pdf_z = pdf(5 * z)

density *= pdf_z

a = x + y
b = 2 * y
c = a - b + z

norm = np.sqrt(a.var() + b.var())
a /= norm
b /= norm
In [33]:
density.shape
Out[33]:
(30000,)
In [34]:
def plot_figs(fig_num, elev, azim):
    fig = plt.figure(fig_num, figsize=(8, 6))
    plt.clf()
    ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=elev, azim=azim)

    ax.scatter(a[::10], b[::10], c[::10], c=density[::10], marker='+', alpha=.4)
    Y = np.c_[a, b, c]

    # Using SciPy's SVD, this would be:
    # _, pca_score, V = scipy.linalg.svd(Y, full_matrices=False)

    pca = PCA(n_components=3)
    pca.fit(Y)
    pca_score = pca.explained_variance_ratio_
    V = pca.components_
    
    print( V.shape )

    x_pca_axis, y_pca_axis, z_pca_axis = V.T * pca_score / pca_score.min()

    x_pca_axis, y_pca_axis, z_pca_axis = 3 * V.T
    x_pca_plane = np.r_[x_pca_axis[:2], - x_pca_axis[1::-1]]
    y_pca_plane = np.r_[y_pca_axis[:2], - y_pca_axis[1::-1]]
    z_pca_plane = np.r_[z_pca_axis[:2], - z_pca_axis[1::-1]]
    x_pca_plane.shape = (2, 2)
    y_pca_plane.shape = (2, 2)
    z_pca_plane.shape = (2, 2)
    print(x_pca_plane)
    ax.plot_surface(x_pca_plane, y_pca_plane, z_pca_plane)
    ax.w_xaxis.set_ticklabels([])
    ax.w_yaxis.set_ticklabels([])
    ax.w_zaxis.set_ticklabels([])
In [35]:
elev = -40
azim = -80
plot_figs(1, elev, azim)
(3, 3)
[[-1.01543176 -2.13288239]
 [ 2.13288239  1.01543176]]
In [36]:
elev = 30 # Так можно сменить точки из котрой смотрим на 3д график.
azim = 20
plot_figs(2, elev, azim)
(3, 3)
[[-1.01543176 -2.13288239]
 [ 2.13288239  1.01543176]]

Другие примеры с PCA

In [37]:
X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
X
Out[37]:
array([[-1, -1],
       [-2, -1],
       [-3, -2],
       [ 1,  1],
       [ 2,  1],
       [ 3,  2]])
In [38]:
plt.plot(X[:,0], X[:,1], "*")
plt.axis('equal')
Out[38]:
(-3.3, 3.3, -2.2, 2.2)
In [39]:
pca = PCA(n_components=2)
In [40]:
pca.fit(X)
Out[40]:
PCA(copy=True, iterated_power='auto', n_components=2, random_state=None,
    svd_solver='auto', tol=0.0, whiten=False)
In [41]:
print(pca.explained_variance_ratio_) 
[0.99244289 0.00755711]
In [42]:
print(pca.singular_values_)
[6.30061232 0.54980396]
In [43]:
X2 = pca.transform(X)
X2
Out[43]:
array([[ 1.38340578,  0.2935787 ],
       [ 2.22189802, -0.25133484],
       [ 3.6053038 ,  0.04224385],
       [-1.38340578, -0.2935787 ],
       [-2.22189802,  0.25133484],
       [-3.6053038 , -0.04224385]])
In [44]:
plt.plot(X2[:,0], X2[:,1], "*")
#plt.axis('equal')
Out[44]:
[<matplotlib.lines.Line2D at 0x7f89fa2f3588>]
In [45]:
pca_full = PCA(n_components=2, svd_solver='full')
pca_full.fit( X )
Out[45]:
PCA(copy=True, iterated_power='auto', n_components=2, random_state=None,
    svd_solver='full', tol=0.0, whiten=False)
In [46]:
pca_arpack = PCA(n_components=1, svd_solver='arpack') #долюно быть n_components < n_features
pca_arpack.fit( X )
Out[46]:
PCA(copy=True, iterated_power='auto', n_components=1, random_state=None,
    svd_solver='arpack', tol=0.0, whiten=False)
In [47]:
X_arpack=pca_arpack.transform(X)
X_arpack
Out[47]:
array([[ 1.38340578],
       [ 2.22189802],
       [ 3.6053038 ],
       [-1.38340578],
       [-2.22189802],
       [-3.6053038 ]])
In [48]:
plt.plot(np.sort(X_arpack[:,0]), "*")
Out[48]:
[<matplotlib.lines.Line2D at 0x7f89fa2d5710>]

Ядровый Метод Главных Компонент (Kernel PCA)

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

In [49]:
from sklearn.decomposition import KernelPCA
from sklearn.datasets import make_circles
In [50]:
# Для обеспечения повтаряемости эксперимента
np.random.seed(0)

Синтезированные данные

In [51]:
# Создадим "кривые" данные, две концентрические окружности с центром в точке (0, 0)
X, y = make_circles(n_samples=400, factor=.3, noise=.05)
X.shape, y.shape
Out[51]:
((400, 2), (400,))
In [52]:
np.unique(y)
Out[52]:
array([0, 1])
In [53]:
plt.title("Исходное пространство")
reds = y == 0
blues = y == 1

plt.scatter(X[reds, 0], X[reds, 1], c="red",
            s=20, edgecolor='k')
plt.scatter(X[blues, 0], X[blues, 1], c="blue",
            s=20, edgecolor='k')
plt.xlabel("$x_1$")
plt.ylabel("$x_2$")
Out[53]:
Text(0, 0.5, '$x_2$')
In [54]:
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
In [55]:
plt.scatter(X_pca[reds, 0], X_pca[reds, 1], c="red",
            s=20, edgecolor='k')
plt.scatter(X_pca[blues, 0], X_pca[blues, 1], c="blue",
            s=20, edgecolor='k')
plt.title("После МГК преобразования")
plt.xlabel("1ая главная компонента")
plt.ylabel("2ая главная компонента")
Out[55]:
Text(0, 0.5, '2ая главная компонента')

В общем ничего не поменялось. Теперь пробуем ядровый метод главных компонент.

In [56]:
# Ядро rbf, radial basis function
kpca = KernelPCA(n_components=2, kernel="rbf", fit_inverse_transform=True, gamma=10)
X_kpca = kpca.fit_transform(X)
X_back = kpca.inverse_transform(X_kpca)
In [57]:
plt.scatter(X_kpca[reds, 0], X_kpca[reds, 1], c="red",
            s=20, edgecolor='orange')
plt.scatter(X_kpca[blues, 0], X_kpca[blues, 1], c="blue",
            s=20, edgecolor='c')
plt.title("После Ядровый МГК преобразования")
plt.xlabel("1ая главная компонента")
plt.ylabel("2ая главная компонента")
Out[57]:
Text(0, 0.5, '2ая главная компонента')

Теперь победа. Исходные два множества четко разделились.

Начертим кривые координаты.

In [58]:
l = np.linspace(-1.5, 1.5, 5)
l
Out[58]:
array([-1.5 , -0.75,  0.  ,  0.75,  1.5 ])
In [59]:
grid = np.meshgrid( l, l )
grid
Out[59]:
[array([[-1.5 , -0.75,  0.  ,  0.75,  1.5 ],
        [-1.5 , -0.75,  0.  ,  0.75,  1.5 ],
        [-1.5 , -0.75,  0.  ,  0.75,  1.5 ],
        [-1.5 , -0.75,  0.  ,  0.75,  1.5 ],
        [-1.5 , -0.75,  0.  ,  0.75,  1.5 ]]),
 array([[-1.5 , -1.5 , -1.5 , -1.5 , -1.5 ],
        [-0.75, -0.75, -0.75, -0.75, -0.75],
        [ 0.  ,  0.  ,  0.  ,  0.  ,  0.  ],
        [ 0.75,  0.75,  0.75,  0.75,  0.75],
        [ 1.5 ,  1.5 ,  1.5 ,  1.5 ,  1.5 ]])]
In [60]:
grid[0][1,3],grid[1][1,3]
Out[60]:
(0.75, -0.75)
In [61]:
type(grid), len(grid) #! Это список, а не массив !
Out[61]:
(list, 2)
In [62]:
grid[0]
Out[62]:
array([[-1.5 , -0.75,  0.  ,  0.75,  1.5 ],
       [-1.5 , -0.75,  0.  ,  0.75,  1.5 ],
       [-1.5 , -0.75,  0.  ,  0.75,  1.5 ],
       [-1.5 , -0.75,  0.  ,  0.75,  1.5 ],
       [-1.5 , -0.75,  0.  ,  0.75,  1.5 ]])
In [63]:
grid[1]
Out[63]:
array([[-1.5 , -1.5 , -1.5 , -1.5 , -1.5 ],
       [-0.75, -0.75, -0.75, -0.75, -0.75],
       [ 0.  ,  0.  ,  0.  ,  0.  ,  0.  ],
       [ 0.75,  0.75,  0.75,  0.75,  0.75],
       [ 1.5 ,  1.5 ,  1.5 ,  1.5 ,  1.5 ]])
In [64]:
np.ravel([[1, 2],[3,4]]) # выпрямляет массив в одномерный, схоже с flatten
Out[64]:
array([1, 2, 3, 4])
In [65]:
plt.title("Кривые координаты в исходном пространстве")
reds = y == 0
blues = y == 1

plt.scatter(X[reds, 0], X[reds, 1], c="red",
            s=20, edgecolor='k')
plt.scatter(X[blues, 0], X[blues, 1], c="blue",
            s=20, edgecolor='k')

X1, X2 = np.meshgrid(np.linspace(-1.0, 1.0, 50), np.linspace(-1.0, 1.0, 50))
X_grid = np.array([np.ravel(X1), np.ravel(X2)]).T
# projection on the first principal component (in the phi space)
Z_grid = kpca.transform(X_grid)[:, 0].reshape(X1.shape)
plt.contour(X1, X2, Z_grid, colors='grey', linewidths=1, origin='lower')

plt.xlabel("$x_1$")
plt.ylabel("$x_2$")
Out[65]:
Text(0, 0.5, '$x_2$')
In [66]:
plt.scatter(X_back[reds, 0], X_back[reds, 1], c="red",
            s=20, edgecolor='k')
plt.scatter(X_back[blues, 0], X_back[blues, 1], c="blue",
            s=20, edgecolor='k')
plt.title("Обратно в исходном пространстве")
plt.xlabel("$x_1$")
plt.ylabel("$x_2$")
Out[66]:
Text(0, 0.5, '$x_2$')

Метод главных компонент на примере Iris dataset

In [67]:
# Загрузка данных из самой бибилотеки. 
from sklearn import datasets # В dataset есть нексолько традиционых наборов.
In [68]:
iris = datasets.load_iris()

X = iris.data # Сами данные
y = iris.target # Класс, в данной заметке он нужен нам только для визуализации.
target_names = iris.target_names # Имена классов.
X.shape, y.shape, target_names.shape # Покажем чему раны размеры.
Out[68]:
((150, 4), (150,), (3,))

Инача говоря данные содержат 150 примеров. Для каждого примера заданы 4 признака, а также разметку (чем пример является). Для числовых обозначений классов даны соответствующие текстовые названия.

In [69]:
X # Данные, четыре признака. 150 примера (прецендента).
Out[69]:
array([[5.1, 3.5, 1.4, 0.2],
       [4.9, 3. , 1.4, 0.2],
       [4.7, 3.2, 1.3, 0.2],
       [4.6, 3.1, 1.5, 0.2],
       [5. , 3.6, 1.4, 0.2],
       [5.4, 3.9, 1.7, 0.4],
       [4.6, 3.4, 1.4, 0.3],
       [5. , 3.4, 1.5, 0.2],
       [4.4, 2.9, 1.4, 0.2],
       [4.9, 3.1, 1.5, 0.1],
       [5.4, 3.7, 1.5, 0.2],
       [4.8, 3.4, 1.6, 0.2],
       [4.8, 3. , 1.4, 0.1],
       [4.3, 3. , 1.1, 0.1],
       [5.8, 4. , 1.2, 0.2],
       [5.7, 4.4, 1.5, 0.4],
       [5.4, 3.9, 1.3, 0.4],
       [5.1, 3.5, 1.4, 0.3],
       [5.7, 3.8, 1.7, 0.3],
       [5.1, 3.8, 1.5, 0.3],
       [5.4, 3.4, 1.7, 0.2],
       [5.1, 3.7, 1.5, 0.4],
       [4.6, 3.6, 1. , 0.2],
       [5.1, 3.3, 1.7, 0.5],
       [4.8, 3.4, 1.9, 0.2],
       [5. , 3. , 1.6, 0.2],
       [5. , 3.4, 1.6, 0.4],
       [5.2, 3.5, 1.5, 0.2],
       [5.2, 3.4, 1.4, 0.2],
       [4.7, 3.2, 1.6, 0.2],
       [4.8, 3.1, 1.6, 0.2],
       [5.4, 3.4, 1.5, 0.4],
       [5.2, 4.1, 1.5, 0.1],
       [5.5, 4.2, 1.4, 0.2],
       [4.9, 3.1, 1.5, 0.2],
       [5. , 3.2, 1.2, 0.2],
       [5.5, 3.5, 1.3, 0.2],
       [4.9, 3.6, 1.4, 0.1],
       [4.4, 3. , 1.3, 0.2],
       [5.1, 3.4, 1.5, 0.2],
       [5. , 3.5, 1.3, 0.3],
       [4.5, 2.3, 1.3, 0.3],
       [4.4, 3.2, 1.3, 0.2],
       [5. , 3.5, 1.6, 0.6],
       [5.1, 3.8, 1.9, 0.4],
       [4.8, 3. , 1.4, 0.3],
       [5.1, 3.8, 1.6, 0.2],
       [4.6, 3.2, 1.4, 0.2],
       [5.3, 3.7, 1.5, 0.2],
       [5. , 3.3, 1.4, 0.2],
       [7. , 3.2, 4.7, 1.4],
       [6.4, 3.2, 4.5, 1.5],
       [6.9, 3.1, 4.9, 1.5],
       [5.5, 2.3, 4. , 1.3],
       [6.5, 2.8, 4.6, 1.5],
       [5.7, 2.8, 4.5, 1.3],
       [6.3, 3.3, 4.7, 1.6],
       [4.9, 2.4, 3.3, 1. ],
       [6.6, 2.9, 4.6, 1.3],
       [5.2, 2.7, 3.9, 1.4],
       [5. , 2. , 3.5, 1. ],
       [5.9, 3. , 4.2, 1.5],
       [6. , 2.2, 4. , 1. ],
       [6.1, 2.9, 4.7, 1.4],
       [5.6, 2.9, 3.6, 1.3],
       [6.7, 3.1, 4.4, 1.4],
       [5.6, 3. , 4.5, 1.5],
       [5.8, 2.7, 4.1, 1. ],
       [6.2, 2.2, 4.5, 1.5],
       [5.6, 2.5, 3.9, 1.1],
       [5.9, 3.2, 4.8, 1.8],
       [6.1, 2.8, 4. , 1.3],
       [6.3, 2.5, 4.9, 1.5],
       [6.1, 2.8, 4.7, 1.2],
       [6.4, 2.9, 4.3, 1.3],
       [6.6, 3. , 4.4, 1.4],
       [6.8, 2.8, 4.8, 1.4],
       [6.7, 3. , 5. , 1.7],
       [6. , 2.9, 4.5, 1.5],
       [5.7, 2.6, 3.5, 1. ],
       [5.5, 2.4, 3.8, 1.1],
       [5.5, 2.4, 3.7, 1. ],
       [5.8, 2.7, 3.9, 1.2],
       [6. , 2.7, 5.1, 1.6],
       [5.4, 3. , 4.5, 1.5],
       [6. , 3.4, 4.5, 1.6],
       [6.7, 3.1, 4.7, 1.5],
       [6.3, 2.3, 4.4, 1.3],
       [5.6, 3. , 4.1, 1.3],
       [5.5, 2.5, 4. , 1.3],
       [5.5, 2.6, 4.4, 1.2],
       [6.1, 3. , 4.6, 1.4],
       [5.8, 2.6, 4. , 1.2],
       [5. , 2.3, 3.3, 1. ],
       [5.6, 2.7, 4.2, 1.3],
       [5.7, 3. , 4.2, 1.2],
       [5.7, 2.9, 4.2, 1.3],
       [6.2, 2.9, 4.3, 1.3],
       [5.1, 2.5, 3. , 1.1],
       [5.7, 2.8, 4.1, 1.3],
       [6.3, 3.3, 6. , 2.5],
       [5.8, 2.7, 5.1, 1.9],
       [7.1, 3. , 5.9, 2.1],
       [6.3, 2.9, 5.6, 1.8],
       [6.5, 3. , 5.8, 2.2],
       [7.6, 3. , 6.6, 2.1],
       [4.9, 2.5, 4.5, 1.7],
       [7.3, 2.9, 6.3, 1.8],
       [6.7, 2.5, 5.8, 1.8],
       [7.2, 3.6, 6.1, 2.5],
       [6.5, 3.2, 5.1, 2. ],
       [6.4, 2.7, 5.3, 1.9],
       [6.8, 3. , 5.5, 2.1],
       [5.7, 2.5, 5. , 2. ],
       [5.8, 2.8, 5.1, 2.4],
       [6.4, 3.2, 5.3, 2.3],
       [6.5, 3. , 5.5, 1.8],
       [7.7, 3.8, 6.7, 2.2],
       [7.7, 2.6, 6.9, 2.3],
       [6. , 2.2, 5. , 1.5],
       [6.9, 3.2, 5.7, 2.3],
       [5.6, 2.8, 4.9, 2. ],
       [7.7, 2.8, 6.7, 2. ],
       [6.3, 2.7, 4.9, 1.8],
       [6.7, 3.3, 5.7, 2.1],
       [7.2, 3.2, 6. , 1.8],
       [6.2, 2.8, 4.8, 1.8],
       [6.1, 3. , 4.9, 1.8],
       [6.4, 2.8, 5.6, 2.1],
       [7.2, 3. , 5.8, 1.6],
       [7.4, 2.8, 6.1, 1.9],
       [7.9, 3.8, 6.4, 2. ],
       [6.4, 2.8, 5.6, 2.2],
       [6.3, 2.8, 5.1, 1.5],
       [6.1, 2.6, 5.6, 1.4],
       [7.7, 3. , 6.1, 2.3],
       [6.3, 3.4, 5.6, 2.4],
       [6.4, 3.1, 5.5, 1.8],
       [6. , 3. , 4.8, 1.8],
       [6.9, 3.1, 5.4, 2.1],
       [6.7, 3.1, 5.6, 2.4],
       [6.9, 3.1, 5.1, 2.3],
       [5.8, 2.7, 5.1, 1.9],
       [6.8, 3.2, 5.9, 2.3],
       [6.7, 3.3, 5.7, 2.5],
       [6.7, 3. , 5.2, 2.3],
       [6.3, 2.5, 5. , 1.9],
       [6.5, 3. , 5.2, 2. ],
       [6.2, 3.4, 5.4, 2.3],
       [5.9, 3. , 5.1, 1.8]])
In [70]:
y # Класса. Фактически сначала идут преценденты относящиеся к первому классу, потом второму, а уже потом третьему.
Out[70]:
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])
In [71]:
target_names # Имена классов, т.е. 0 классу соответствует имя setosa, ..., 2 -- virginica
# Да, тип элементов может быть текстом!
Out[71]:
array(['setosa', 'versicolor', 'virginica'], dtype='<U10')
In [72]:
np.unique(y) # Найдем номера различающихся классов. Функция ищет уникальные элементы массива.
Out[72]:
array([0, 1, 2])
In [73]:
# Напомню.
y * 2 + 1 # Выполняет данное вычисление поэлементно. 
Out[73]:
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5])
In [74]:
# По аналогии.
y==1 # Применили проверку на равенство 1 для каждого из элементов массива. Истина -- True, ложь -- False.
Out[74]:
array([False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False, False, False, False,
       False, False, False, False, False, False])
In [75]:
# Напечатам размер каждого из классов, т.е. количество прецендентов в каждом классе.
# Далее идет так называемый цикл, конкретно цикл for. Он выполняет некий код для каждого из элементов по одному.
for i in np.unique(y): # i пробегает список элементов [0,1,2], т.е. принимает каждое из данных значений по отдельности.
    # Тело начинается с отступа. Замечу, в предыдущей строчке в конце был символ :!
    print( "размер класса ", i, sum( y==i ) ) # Для каждого из жлементов выполянется данный код. Идет печать.
    # В арифметических операциях истина (True) равна 1, а лож (False).
# Видим, что классы имеют один и тот же размер, равный 50.
размер класса  0 50
размер класса  1 50
размер класса  2 50
In [76]:
nums = np.unique(y) # Фактически это [0, 1, 2]
list( zip( nums, target_names ) ) # Списки сшиваются по-элементно вместе. Как молния с бегунком у куртки.
# nums[0] и target_names[0] в первый элемент нового списка... (0, 'setosa') ... и так далее
# по аналогии все далается при большем количестве списоков. Рзамеры их должны сопадать!
Out[76]:
[(0, 'setosa'), (1, 'versicolor'), (2, 'virginica')]
In [77]:
X_1 = X[ y == 1 ] # Выбираем только те элементы для которых условие истинно, т.е. относящиеся к классу 1.
X_1
Out[77]:
array([[7. , 3.2, 4.7, 1.4],
       [6.4, 3.2, 4.5, 1.5],
       [6.9, 3.1, 4.9, 1.5],
       [5.5, 2.3, 4. , 1.3],
       [6.5, 2.8, 4.6, 1.5],
       [5.7, 2.8, 4.5, 1.3],
       [6.3, 3.3, 4.7, 1.6],
       [4.9, 2.4, 3.3, 1. ],
       [6.6, 2.9, 4.6, 1.3],
       [5.2, 2.7, 3.9, 1.4],
       [5. , 2. , 3.5, 1. ],
       [5.9, 3. , 4.2, 1.5],
       [6. , 2.2, 4. , 1. ],
       [6.1, 2.9, 4.7, 1.4],
       [5.6, 2.9, 3.6, 1.3],
       [6.7, 3.1, 4.4, 1.4],
       [5.6, 3. , 4.5, 1.5],
       [5.8, 2.7, 4.1, 1. ],
       [6.2, 2.2, 4.5, 1.5],
       [5.6, 2.5, 3.9, 1.1],
       [5.9, 3.2, 4.8, 1.8],
       [6.1, 2.8, 4. , 1.3],
       [6.3, 2.5, 4.9, 1.5],
       [6.1, 2.8, 4.7, 1.2],
       [6.4, 2.9, 4.3, 1.3],
       [6.6, 3. , 4.4, 1.4],
       [6.8, 2.8, 4.8, 1.4],
       [6.7, 3. , 5. , 1.7],
       [6. , 2.9, 4.5, 1.5],
       [5.7, 2.6, 3.5, 1. ],
       [5.5, 2.4, 3.8, 1.1],
       [5.5, 2.4, 3.7, 1. ],
       [5.8, 2.7, 3.9, 1.2],
       [6. , 2.7, 5.1, 1.6],
       [5.4, 3. , 4.5, 1.5],
       [6. , 3.4, 4.5, 1.6],
       [6.7, 3.1, 4.7, 1.5],
       [6.3, 2.3, 4.4, 1.3],
       [5.6, 3. , 4.1, 1.3],
       [5.5, 2.5, 4. , 1.3],
       [5.5, 2.6, 4.4, 1.2],
       [6.1, 3. , 4.6, 1.4],
       [5.8, 2.6, 4. , 1.2],
       [5. , 2.3, 3.3, 1. ],
       [5.6, 2.7, 4.2, 1.3],
       [5.7, 3. , 4.2, 1.2],
       [5.7, 2.9, 4.2, 1.3],
       [6.2, 2.9, 4.3, 1.3],
       [5.1, 2.5, 3. , 1.1],
       [5.7, 2.8, 4.1, 1.3]])
In [78]:
X_1.shape, sum(y==1) # Перепроверяем размеры.
Out[78]:
((50, 4), 50)
In [79]:
X[y == 1, 0] # Берем массив нулевых признаков отсносящихся к классу 1. Фактически 0ой вектор-столбец из X_1.
# Иначе говоря, идет проекция всего признакового пространства (там было 4 признака) на 0 ось.
Out[79]:
array([7. , 6.4, 6.9, 5.5, 6.5, 5.7, 6.3, 4.9, 6.6, 5.2, 5. , 5.9, 6. ,
       6.1, 5.6, 6.7, 5.6, 5.8, 6.2, 5.6, 5.9, 6.1, 6.3, 6.1, 6.4, 6.6,
       6.8, 6.7, 6. , 5.7, 5.5, 5.5, 5.8, 6. , 5.4, 6. , 6.7, 6.3, 5.6,
       5.5, 5.5, 6.1, 5.8, 5. , 5.6, 5.7, 5.7, 6.2, 5.1, 5.7])
In [80]:
colors = ['navy', 'turquoise', 'darkorange']
lw = 2

for color, i, target_name in zip(colors, [0, 1, 2], target_names): # Хитрым образом записаный цикл for.
    plt.scatter(X[y == i, 0], X[y == i, 1], color=color, alpha=.8, lw=lw, # Отрисовываем каждый класс по отдельности.
                label=target_name) # Названия наборов данных.
plt.legend(loc='best', shadow=False, scatterpoints = 1 ) # Отображение легенды.
plt.title('Исходные IRIS данные') # Название графика.
Out[80]:
Text(0.5, 1.0, 'Исходные IRIS данные')

Синий класс можно почти отделить, а вот оранжевый и берюзовый скорее нет. Как быть? Приобразуем систему координат так, что бы было видно. Обращаю внимание: исходные даные 4х мерные, а картинка 2х мерная.

Упр. Изучить другие проекции.

In [81]:
pca = PCA(n_components=2, svd_solver='full')
X_r = pca.fit_transform(X) # Сразу строим модель и преобразуем.
In [82]:
for color, i, target_name in zip(colors, [0, 1, 2], target_names):
    plt.scatter(X_r[y == i, 0], X_r[y == i, 1], color=color, alpha=.8, lw=lw,
                label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('PCA of IRIS dataset')
Out[82]:
Text(0.5, 1.0, 'PCA of IRIS dataset')

Теперь данные гораздо проще отделить данные друг отдруга. По крайней мере можно провести четкую границу/ломанную между классами.

Посмотрим на все это дело в 3d

In [83]:
from mpl_toolkits.mplot3d import Axes3D
from sklearn import decomposition
from sklearn import datasets
np.random.seed(5)
In [84]:
iris = datasets.load_iris()
X = iris.data
y = iris.target
In [85]:
np.choose([0, 2, 1, 2], ['a','b','c']) # Один список используется для индексации элементов из второго списка
Out[85]:
array(['a', 'c', 'b', 'c'], dtype='<U1')
In [86]:
fig = plt.figure(figsize=(8, 6))
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=20, azim=60)

for name, label in [('Setosa', 0), ('Versicolour', 1), ('Virginica', 2)]:
    ax.text3D(X[y == label, 0].mean(),
              X[y == label, 1].mean() + 1.0,
              X[y == label, 2].mean(), name,
              horizontalalignment='center')
              
# Отрисовываем данные.
yy = np.choose(y, [1, 2, 0]).astype(np.float)
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=yy, cmap=plt.cm.spring, edgecolor='k')
Out[86]:
<mpl_toolkits.mplot3d.art3d.Path3DCollection at 0x7f89f89f8630>
In [87]:
pca = decomposition.PCA(n_components=3)
pca.fit(X)
Xpca = pca.transform(X)

fig = plt.figure(figsize=(8, 6))
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=20, azim=90)

for name, label in [('Setosa', 0), ('Versicolour', 1), ('Virginica', 2)]:
    ax.text3D(Xpca[y == label, 0].mean(),
              Xpca[y == label, 1].mean() + 1.0,
              Xpca[y == label, 2].mean(), name,
              horizontalalignment='center')
              
# Отрисовываем данные.
yy = np.choose(y, [1, 2, 0]).astype(np.float)
ax.scatter(Xpca[:, 0], Xpca[:, 1], Xpca[:, 2], c=yy, cmap=plt.cm.spring, edgecolor='k')
Out[87]:
<mpl_toolkits.mplot3d.art3d.Path3DCollection at 0x7f89f819a550>

Ослабим часть условий на метод главных компонент

In [88]:
# В Методе главных компонент считалось, что разброс энергии (шума) постоянен по всем осям.
# В данном методе каждая из ортогональных осей независима.
from sklearn.decomposition import FactorAnalysis
In [89]:
fa = FactorAnalysis( n_components=2 )
X_fa = fa.fit_transform(X)
In [90]:
for color, i, target_name in zip(colors, [0, 1, 2], target_names):
    plt.scatter(X_fa[y == i, 0], X_fa[y == i, 1], color=color, alpha=.8, lw=lw,
                label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('FA of IRIS dataset')
Out[90]:
Text(0.5, 1.0, 'FA of IRIS dataset')

Когда будет разница видна?

В методе главных компонентов оси ортонормированы. Это жесткое требование. от него можно отказатся применив следующий подход.

Еще более общий случай метода главных компонент

In [91]:
# Теперь оси необязательно ортогональны
from sklearn.decomposition import FastICA, FactorAnalysis
In [92]:
# ?rng
In [93]:
# Создадим данные
rng = np.random.RandomState(42)
S = rng.standard_t(1.5,  size=(20000, 2))  #standard_t 1.5,  size=(20000, 2) randn
S[:, 0] *= 2.
S, S.shape
Out[93]:
(array([[  0.84805472,  -0.41316453],
        [ 36.41730631,   0.53773481],
        [ -1.76643788,  -0.67843317],
        ...,
        [ -1.35307067,   1.32228283],
        [ -3.47661776, -29.51830934],
        [ -5.41834779,   0.1860394 ]]), (20000, 2))
In [94]:
SS = S / S.std()
plt.scatter(SS[:, 0], SS[:, 1], s=2, marker='o', zorder=10,
                color='steelblue', alpha=0.5)
plt.xlim(-3, 3)
plt.ylim(-3, 3)
Out[94]:
(-3, 3)
In [95]:
# Смешиваем данные распределенные по разным осям
A = np.array([[1, 1], [0, 2]])  # Матрица смешивания
X = np.dot(S, A.T)  # Формируем наблюдения
XX = X / X.std()
plt.scatter(XX[:, 0], XX[:, 1], s=2, marker='o', zorder=10,
                color='steelblue', alpha=0.5)
plt.xlim(-3, 3)
plt.ylim(-3, 3)
Out[95]:
(-3, 3)
In [96]:
pca = FactorAnalysis() #PCA()
S_pca_ = pca.fit(X).transform(X)

ica = FastICA(random_state=rng)
S_ica_ = ica.fit(X).transform(X)  # Estimate the sources

S_ica_ /= S_ica_.std(axis=0)
In [97]:
def plot_samples(S, axis_list=None):
    plt.scatter(S[:, 0], S[:, 1], s=2, marker='o', zorder=10,
                color='steelblue', alpha=0.5)
    if axis_list is not None:
        colors = ['orange', 'red']
        for color, axis in zip(colors, axis_list):
            axis /= axis.std()
            x_axis, y_axis = axis
            # Trick to get legend to work
            plt.plot(0.1 * x_axis, 0.1 * y_axis, linewidth=2, color=color)
            plt.quiver(0, 0, x_axis, y_axis, zorder=11, width=0.01, scale=6,
                       color=color)

    plt.hlines(0, -3, 3)
    plt.vlines(0, -3, 3)
    plt.xlim(-3, 3)
    plt.ylim(-3, 3)
    plt.xlabel('x')
    plt.ylabel('y')

plt.figure(figsize=(8, 6))
plt.subplot(2, 2, 1)
plot_samples(S / S.std())
plt.title('True Independent Sources')

axis_list = [pca.components_.T, ica.mixing_]
plt.subplot(2, 2, 2)
plot_samples(X / np.std(X), axis_list=axis_list)
legend = plt.legend(['PCA', 'ICA'], loc='upper right')
legend.set_zorder(100)

plt.title('Observations')

plt.subplot(2, 2, 3)
plot_samples(S_pca_ / np.std(S_pca_, axis=0))
plt.title('PCA recovered signals')

plt.subplot(2, 2, 4)
plot_samples(S_ica_ / np.std(S_ica_))
plt.title('ICA recovered signals')
Out[97]:
Text(0.5, 1.0, 'ICA recovered signals')