Избавляться от размерности можно методами отбора признаков (Feature Selection) и методами уменьшения размерности (Feature Reduction)
Методы деляться на три группы:
Сколько информации $x$ сообщает об $y$. $$NormalizedMI(y,x) = \frac{MI(y,x)}{H(y)}$$
Загрузим довольно известный набор данных о выживаемости после катастрофы титаника.
df_titanic = pd.read_csv('titanic.csv')
df_titanic.head()
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S |
3 | 4 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35.0 | 1 | 0 | 113803 | 53.1000 | C123 | S |
4 | 5 | 0 | 3 | Allen, Mr. William Henry | male | 35.0 | 0 | 0 | 373450 | 8.0500 | NaN | S |
pd.crosstab(df_titanic.Survived, df_titanic.Sex, normalize=True, )
Sex | female | male |
---|---|---|
Survived | ||
0 | 0.090909 | 0.525253 |
1 | 0.261504 | 0.122334 |
Найдем MI между выживаемостью и остальными признаками
def calc_mutual_information(y, x):
P = pd.crosstab(x, y, normalize=True).values
logP = np.log(((P/P.sum(axis=0)).T/P.sum(axis=1)).T)
return (P*logP).sum()
calc_mutual_information(df_titanic.Survived, df_titanic.Sex)
0.15087048925218172
from sklearn.metrics import mutual_info_score
from sklearn.metrics import normalized_mutual_info_score
mutual_info_score(df_titanic.Survived, df_titanic.Sex)
0.66591197352676512
При данном подходе из (линейной) модели последовательно удаляются признаки с наименьшим коэффициентом
Используйте реализацию RFE в sklean c кросс-валидацией.
from sklearn.linear_model import LogisticRegression
from sklearn.feature_selection import RFECV
from sklearn.model_selection import StratifiedKFold
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=123)
def titanic_preproc(df_input):
df = df_input.copy()
# Удаляем пропуски
df = df.dropna()
# Создаем такой признак
df.loc[:, 'has_cabin'] = df.loc[:, 'Cabin'].isnull().astype(int)
# Удаляем колонки
cols2drop = ['PassengerId', 'Name', 'Ticket', 'Cabin']
df = df.drop(cols2drop, axis=1)
# Нормализуем Age Fare и SibSp (Так делать не оч хорошо)
df.loc[:, 'Age'] = (df.loc[:, 'Age'] - df.loc[:, 'Age'].mean())/df.loc[:, 'Age'].std()
df.loc[:, 'Fare'] = (df.loc[:, 'Fare'] - df.loc[:, 'Fare'].mean())/df.loc[:, 'Fare'].std()
df.loc[:, 'SibSp'] = (df.loc[:, 'SibSp'] - df.loc[:, 'SibSp'].mean())/df.loc[:, 'SibSp'].std()
# Закодируем поле Sex
df.loc[:, 'Sex'] = df.loc[:, 'Sex'].replace({'male': 0, 'female':1})
# Pclass и Embarked можно рассматривать как категориальный признак
df = pd.get_dummies(df, prefix_sep='=', columns=['Pclass', 'Embarked'], drop_first=True)
return df
df_prep.head()
Survived | Sex | Age | SibSp | Parch | Fare | has_cabin | Pclass=2 | Pclass=3 | Embarked=Q | Embarked=S | |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 0.148657 | 0.831347 | 0 | -0.096914 | 0 | 0 | 0 | 0 | 0 |
3 | 1 | 1 | -0.043111 | 0.831347 | 0 | -0.335078 | 0 | 0 | 0 | 0 | 1 |
6 | 0 | 0 | 1.171422 | -0.721066 | 0 | -0.351287 | 0 | 0 | 0 | 0 | 1 |
10 | 1 | 1 | -2.024719 | 0.831347 | 1 | -0.811843 | 0 | 0 | 1 | 0 | 1 |
11 | 1 | 1 | 1.427114 | -0.721066 | 0 | -0.682828 | 0 | 0 | 0 | 0 | 1 |
df_prep = df_titanic.pipe(titanic_preproc)
X, y = df_prep.iloc[:, 1:].values, df_prep.iloc[:, 0].values
model = LogisticRegression(random_state=123)
rfe = RFECV(model, step=1, cv=cv, scoring='roc_auc', verbose=1, n_jobs=-1)
rfe.fit(X, y)
Fitting estimator with 10 features. Fitting estimator with 10 features. Fitting estimator with 10 features. Fitting estimator with 10 features. Fitting estimator with 9 features. Fitting estimator with 9 features. Fitting estimator with 9 features. Fitting estimator with 9 features. Fitting estimator with 8 features. Fitting estimator with 8 features. Fitting estimator with 8 features. Fitting estimator with 8 features. Fitting estimator with 7 features. Fitting estimator with 7 features. Fitting estimator with 7 features. Fitting estimator with 7 features. Fitting estimator with 6 features. Fitting estimator with 6 features. Fitting estimator with 6 features. Fitting estimator with 6 features. Fitting estimator with 5 features. Fitting estimator with 5 features. Fitting estimator with 5 features. Fitting estimator with 5 features. Fitting estimator with 4 features. Fitting estimator with 4 features. Fitting estimator with 4 features. Fitting estimator with 4 features. Fitting estimator with 3 features. Fitting estimator with 3 features. Fitting estimator with 3 features. Fitting estimator with 3 features. Fitting estimator with 2 features. Fitting estimator with 2 features. Fitting estimator with 10 features. Fitting estimator with 2 features. Fitting estimator with 2 features. Fitting estimator with 9 features. Fitting estimator with 8 features. Fitting estimator with 7 features. Fitting estimator with 6 features. Fitting estimator with 5 features. Fitting estimator with 4 features. Fitting estimator with 3 features. Fitting estimator with 2 features.
RFECV(cv=StratifiedKFold(n_splits=5, random_state=123, shuffle=True), estimator=LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1, penalty='l2', random_state=123, solver='liblinear', tol=0.0001, verbose=0, warm_start=False), n_jobs=-1, scoring='roc_auc', step=1, verbose=1)
rfe.grid_scores_
array([ 0.78383333, 0.780375 , 0.80856944, 0.82218056, 0.82697222, 0.82897222, 0.82772222, 0.82772222, 0.82838889, 0.82838889])
d = rfe.grid_scores_.shape[0]
plt.plot(range(1, d+1), rfe.grid_scores_)
plt.xlabel('number of features')
plt.ylabel('ROC-AUC cv')
<matplotlib.text.Text at 0x1151d4b10>
print 'Выбранные признаки'
fnames = df_prep.columns[1:].values
fnames[rfe.support_]
Выбранные признаки
array(['Sex', 'Age', 'Parch', 'Pclass=3', 'Embarked=Q', 'Embarked=S'], dtype=object)
pd.Series(index=fnames[rfe.support_], data=rfe.estimator_.coef_[0])
Sex 2.372815 Age -0.554682 Parch -0.243279 Pclass=3 -0.931482 Embarked=Q -0.329946 Embarked=S -0.338861 dtype: float64
## Your code here
Аналогичными выкладками приходим к тому, что $a_2$ - собственный вектор $\Sigma$ при $\lambda_2$
Для любой матрицы $X$ размера $n \times m$ можно найти разложение вида: $$ X = U S V^\top ,$$ где
Матрицы $U$ и $V$ ортогональны и могут быть использованы для перехода к ортогональному базису: $$ XV = US$$
Сокращение размерности заключается в том, что вместо того, чтобы умножать $X$ на всю матрицу $V$, а лишь на первые $k<m$ её столбцов - матрицу $V'$
Квадраты сингулярных чисел в $S$ содержат дисперсию, объясненную в главных компонентах
from sklearn.decomposition import PCA
from numpy.linalg import svd
from sklearn.datasets import load_digits
C = np.array([[0., -0.7], [1.5, 0.7]])
X = np.dot(np.random.randn(200, 2) + np.array([4, 2]), C)
plt.scatter(X[:, 0], X[:, 1])
plt.axis('equal')
pca = PCA(n_components=2)
PC = pca.fit_transform(X)
coef = pca.components_
coef
m = np.mean(X,axis=0)
fig, ax = plt.subplots(1,2)
ax[0].plot([0, coef[0,0]*2]+m[0], [0, coef[0,1]*2]+m[1],'--k')
ax[0].plot([0, coef[1,0]*2]+m[0], [0, coef[1,1]*2]+m[1],'--k')
ax[0].scatter(X[:,0], X[:,1])
ax[0].set_xlabel('$x_1$')
ax[0].set_ylabel('$x_2$')
ax[1].scatter(PC[:,0], PC[:,1])
ax[1].set_xlabel('$pc_1$')
ax[1].set_ylabel('$pc_2$')
ax[0].axis('equal')
ax[1].axis('equal')
## Your Code Here
digits = load_digits()
X = digits.images
y = digits.target
plt.imshow(X[2,:], cmap='Greys', interpolation='none')
y
X
на нужную матрицу и убедитесь, что у вас получается тот же результат### Your Code Here
diet.csv
df = pd.read_csv('diet.csv', sep=';')
df = df.dropna(axis=1)
df = df.drop('Energy (kcal/day)', axis=1)
df = df.set_index('Countries')
df.head()
X = df.values
X = (X - X.mean(axis=0))/X.std(axis=0)
## Your Code Here
## Your Code Here