import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import mglearn
from IPython.display import display
%matplotlib inline
Plot twist: deadline 27 ноември (понеделник)
Формат: repository в GitHub с три notebook-а.
Този dataset съдържа снимки на разни хора, label-нати с имената им.
Има го в sklearn.datasets
.
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
fix, axes = plt.subplots(2, 5, figsize=(15, 8), subplot_kw={'xticks': (), 'yticks': ()})
for target, image, ax in zip(people.target, people.images, axes.ravel()):
ax.imshow(image, cmap='gray')
ax.set_title(people.target_names[target])
people.images
съдържа тензор с картинки, докато в people.data
картинката е представена като ред в матрица.
print("people.images.shape: {}".format(people.images.shape))
print("people.data.shape: {}".format(people.data.shape))
print("people.target.shape: {}".format(people.target.shape))
print("People: {}".format(len(people.target_names)))
people.target
съдържа класовете, докато people.target_names
съпоставя класовете на истински имена.
people.target
people.target_names
Как ли изглеждат данните?
pd.Series(people.target_names[people.target]).value_counts().head(10)
George Bush и Colin Powell са доста overrepresented. Ще се опитаме да намалим снимките до най-много 50 на човек.
Този код може да свърши тази работа:
mask = np.zeros(people.target.shape, dtype=np.bool)
for target in np.unique(people.target):
mask[np.where(people.target == target)[0][:50]] = 1
Нека го разпакетираме.
np.zeros
създава матрица от нули. Ако типа е np.bool
, ще е матрица от False
:
np.zeros((10,), dtype=np.bool)
==
на np масив със скалар прави векторизирано сравнение.
np.array([1, 2, 1, 2]) == 2
np.where
приема булев масив и връща индексите на True
-тата.
np.where(np.array([1, 2, 1, 2]) == 2)
Забележете, че резултате е tuple.
Присвояването на индекс с масив работи както очаквате:
things = np.zeros((10,), dtype=np.bool)
things[np.array([1, 3, 5])] = 1
things
mask = np.zeros(people.target.shape, dtype=np.bool)
for target in np.unique(people.target):
mask[np.where(people.target == target)[0][:50]] = 1
Свеждаме резултата само до картинките в маската:
X = people.data[mask]
y = people.target[mask]
Също, ще направим poor man's scaling:
print(np.min(X), np.max(X))
X = people.data[mask] / 255.
print(np.min(X), np.max(X))
Да погледнем дали резултатите са както очакваме:
pd.Series(people.target_names[y]).value_counts().head(15)
print("X.shape: {}".format(X.shape))
print("y.shape: {}".format(y.shape))
Да се пробваме да направим много прост класификатор.
Нека си разделим тренировъчни и тестови данни:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)
Първо, нека видим как ще се справи един наивен kNN:
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train, y_train)
knn.score(X_test, y_test)
Нека да пробваме да преработим данните му през PCA
:
from sklearn.decomposition import PCA
from sklearn.pipeline import make_pipeline
pipeline = make_pipeline(
PCA(n_components=100, whiten=True, random_state=0),
KNeighborsClassifier(n_neighbors=1)
)
pipeline.fit(X_train, y_train)
pipeline.score(X_test, y_test)
Имаме малко подобрение! Това може да се очаква с kNN, защото PCA практически намалява шума.
Образно казано.
Как изглеждат данните, които се подават на kNN?
print("X.shape: {}".format(X_test.shape))
print("Transformed X.shape: {}".format(pipeline.steps[0][1].transform(X_test).shape))
Всяка картинка е сведена до вектор от 100 числа.
Какво ли е научил PCA
?:
pca = pipeline.steps[0][1]
pca.components_
pca.components_.shape
Изглежда алгоритъма е научил 100 вектора, всеки от които с 5655 компонента – броя пиксели във всяка картинка. Какво ли ще стане ако визуализираме тези вектори?
fix, axes = plt.subplots(4, 5, figsize=(15, 16), subplot_kw={'xticks': (), 'yticks': ()})
for i, (component, ax) in enumerate(zip(pca.components_, axes.ravel())):
ax.imshow(component.reshape(image_shape), cmap='gray')
ax.set_title("component {}".format((i + 1)))
Тези картинки са компонентите, открити от PCA. Когато прекарате изображение през pca.transform
, той го свежда до линейна комбинация на тези 100 компонента. 100те числа в резултата са коефициентите пред всеки от компонентите.
В такива случаи, компонентите често биват наричани eigenfaces.
X_test_pca = pca.transform(X_test)
print("X_test.shape: {}".format(X_test.shape))
print("X_test_pca.shape: {}".format(X_test_pca.shape))
PCA
(и някои други) имат inverse_transform
, която проектира трансформирания вектор в оригиналното пространство.
X_reformed = pca.inverse_transform(X_test_pca)
print("X_test.shape: {}".format(X_test.shape))
print("X_test_pca.shape: {}".format(X_test_pca.shape))
print("X_reformed.shape: {}".format(X_reformed.shape))
Бихме могли да визуализираме резултата от inverse_transform
, така че да го интерпретираме:
import itertools
fig, axes = plt.subplots(3, 4, figsize=(15, 14), subplot_kw={'xticks': (), 'yticks': ()})
for i, (picture, ax) in enumerate(zip(itertools.chain(*zip(X_reformed, X_test)), axes.ravel())):
ax.imshow(picture.reshape(image_shape), cmap='gray')
Какво ли ще стане, ако ползваме повече компоненти? Нека си напишем функция, с която да пробваме.
def compression_with_pca(train, test, n_components):
pca = PCA(n_components=n_components, whiten=True, random_state=0)
pca.fit(train)
reformed = pca.inverse_transform(pca.transform(test))
fig, axes = plt.subplots(3, 4, figsize=(15, 14), subplot_kw={'xticks': (), 'yticks': ()})
for i, (picture, ax) in enumerate(zip(itertools.chain(*zip(reformed, test)), axes.ravel())):
ax.imshow(picture.reshape(image_shape), cmap='gray')
compression_with_pca(X_train, X_test, 500)
compression_with_pca(X_train, X_test, 1000)
compression_with_pca(X_train, X_test, 2000)
Да видим Какво ще научи NMF
в този dataset.
from sklearn.decomposition import NMF
nmf = NMF(n_components=15, random_state=1)
nmf.fit(X_train)
X_train_nmf = nmf.transform(X_train)
X_test_nmf = nmf.transform(X_test)
fix, axes = plt.subplots(3, 5, figsize=(15, 12), subplot_kw={'xticks': (), 'yticks': ()})
for i, (component, ax) in enumerate(zip(nmf.components_, axes.ravel())):
ax.imshow(component.reshape(image_shape), cmap='gray')
ax.set_title("component {}".format(i))
NMF търси "адитивни" компоненти, чиято сума може да изрази добре оригиналния dataset. Резултатите са по-лесни за интерпретация, понеже няма отрицателни стойности (за разлика от при PCA). Любопитно е да разгледаме какво са научили някои от компонентите.
За целта може да си напишем функция, която намира картинките, за които този компонент има най-висок коефициент.
def draw_closest(component, components, data):
indices = np.argsort(data[:, component])[::-1]
fig, axes = plt.subplots(2, 5, figsize=(15, 8), subplot_kw={'xticks': (), 'yticks': ()})
axes[0][0].imshow(components[component].reshape(image_shape), cmap='gray')
for i, (ind, ax) in enumerate(zip(indices, axes.ravel()[1:])):
ax.imshow(X_train[ind].reshape(image_shape), cmap='gray')
Компонент 14 всякаш хваща лица, завъртяни наляво.
draw_closest(14, nmf.components_, X_train_nmf)
Компонент 12 е аналогичен, но в другата посока.
draw_closest(12, nmf.components_, X_train_nmf)
Компонент 2 хваща бели шапки или други светли региони около главата.
draw_closest(2, nmf.components_, X_train_nmf)
Компонент 4 изглежда да хваща яки отдясно.
draw_closest(4, nmf.components_, X_train_nmf)
Компонент 13 е много сходен.
draw_closest(13, nmf.components_, X_train_nmf)
Бихме могли да видим и как изглежда компресията при NMF.
def compression_with_nmf(train, test, n_components):
nmf = NMF(n_components=n_components, random_state=0)
nmf.fit(train)
reformed = nmf.inverse_transform(nmf.transform(test))
fig, axes = plt.subplots(3, 4, figsize=(15, 14), subplot_kw={'xticks': (), 'yticks': ()})
for i, (picture, ax) in enumerate(zip(itertools.chain(*zip(reformed, test)), axes.ravel())):
ax.imshow(picture.reshape(image_shape), cmap='gray')
compression_with_nmf(X_train, X_test, 100)
Базирана на този пример.
Ще ограничим dataset-а само до хора, които имат повече от 70 снимки.
people = fetch_lfw_people(min_faces_per_person=70, resize=0.4)
Данните изглеждат наред:
print("people.images.shape: {}".format(people.images.shape))
print("Number of people: {}".format(len(people.target_names)))
Кои ли са хората?
pd.Series(people.target_names[people.target]).value_counts()
Да си извлечем нужните данни:
X = people.data
y = people.target
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, test_size=0.25, random_state=42)
Нека тренираме модел (с предварително grid-search-нати хиперпараметри):
from sklearn.preprocessing import MinMaxScaler
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
pipeline = Pipeline([
('scaler', MinMaxScaler()),
('pca', PCA(n_components=150, whiten=True, random_state=42)),
('svc', SVC(C=10, gamma=0.0025, kernel='rbf', class_weight='balanced')),
])
pipeline.fit(X_train, y_train)
pipeline.score(X_test, y_test)
Изглежда доста добре!
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
y_pred = pipeline.predict(X_test)
print(classification_report(y_test, y_pred, target_names=people.target_names))
print(confusion_matrix(y_test, y_pred, labels=range(len(people.target_names))))
Резултатите без PCA изглеждат много аналогично, стига да нацелите параметрите:
from sklearn.svm import SVC
pipeline = make_pipeline(
MinMaxScaler(),
SVC(kernel='rbf', class_weight='balanced', C=1000, gamma=0.00007)
)
pipeline.fit(X_train, y_train)
pipeline.score(X_test, y_test)
y_pred = pipeline.predict(X_test)
print(classification_report(y_test, y_pred, target_names=people.target_names))
print(confusion_matrix(y_test, y_pred, labels=range(len(people.target_names))))
Неща пробваме DBSCAN
с различни стойности на eps
:
from sklearn.cluster import DBSCAN
X_norm = X / 255.
for eps in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]:
pipeline = make_pipeline(
PCA(n_components=100, whiten=True, random_state=0),
DBSCAN(eps=eps, min_samples=3)
)
labels = pipeline.fit_predict(X_norm)
print("{:>3} | {}".format(eps, np.unique(labels)))
print("{:>3} | {}".format(eps, np.bincount(labels + 1)))
print("----------------------------------------------------------")
При eps=7
има интересни резултати, които си струва да погледнем:
def show_clusters(labels, limit=100):
rows, h, w = people.images.shape
image_shape = (h, w)
for cluster in range(max(labels) + 1):
mask = labels == cluster
n_images = min(limit, np.sum(mask))
fig, axes = plt.subplots(1, n_images, figsize=(n_images * 1.5, 4), subplot_kw={'xticks': (), 'yticks': ()})
for image, label, ax in zip(X[mask], y[mask], axes):
ax.imshow(image.reshape(image_shape), cmap='gray')
ax.set_title(people.target_names[label].split()[-1])
pipeline = make_pipeline(
PCA(n_components=100, whiten=True, random_state=0),
DBSCAN(eps=7, min_samples=3)
)
labels = pipeline.fit_predict(X_norm)
show_clusters(labels)
Намерило е някакви клъстери, ама не и такива, каквито ние искаме.
Как ли ще се справи KMeans?
from sklearn.cluster import KMeans
pipeline = make_pipeline(
PCA(n_components=100, whiten=True, random_state=0),
KMeans(n_clusters=200),
)
pipeline.fit(X_norm)
labels = pipeline.predict(X_norm)
np.bincount(labels)
Това едва ли са правилните резултати – имаше клъстер с 530 картинки.
print("Expected: {}".format(sorted(np.bincount(y))))
print("Actual: {}".format(sorted(np.bincount(labels))))
Изглежда наистина да има голямо разминаване. Какво ли ще каже adjusted_rand_score?
.
from sklearn.metrics import adjusted_rand_score
print(adjusted_rand_score(labels, y))
show_clusters(labels, limit=10)
from sklearn.cluster import AgglomerativeClustering
agglo = make_pipeline(
PCA(n_components=100, whiten=True),
AgglomerativeClustering(n_clusters=7),
)
labels = agglo.fit_predict(X_norm)
adjusted_rand_score(labels, y)
show_clusters(labels, limit=10)
LDA е алгоритъм, който търси общи теми в корпуси от документи.
Ще пробваме да го приложим върху dataset от IMDB ревюта.
Dataset-а може да бъде изтеглен от тук:
Ето така може да го заредим:
from sklearn.datasets import load_files
reviews_train = load_files("/path/to/aclImdb/train/")
text_train, y_train = reviews_train.data, reviews_train.target
Ето така може да го прекараме през LDA
:
from sklearn.decomposition import LatentDirichletAllocation
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer(max_df=.15, max_features=10000)
X = vectorizer.fit_transform(text_train)
lda = LatentDirichletAllocation(n_components=10, learning_method="batch", max_iter=15, random_state=0)
topics = lda.fit_transform(X)
И ето още малко код, който ще визуализира най-популярните думи от темите:
sorting = np.argsort(lda.components_, axis=1)[:, ::-1]
feature_names = np.array(vect.get_feature_names())
mglearn.tools.print_topics(topics=range(10), feature_names=feature_names, sorting=sorting, topics_per_chunk=5, n_words=10)
Ето го и резултата:
topic 0 topic 1 topic 2 topic 3 topic 4
-------- -------- -------- -------- --------
action guy war show didn
game re world series thought
effects around american tv actors
special nothing us episode nothing
fight sex our years am
fi looks documentary old 10
sci pretty history now worst
10 look real kids going
star worst black dvd got
alien stupid america shows actually
topic 5 topic 6 topic 7 topic 8 topic 9
-------- -------- -------- -------- --------
director horror funny father role
work gore comedy wife cast
between killer girl woman john
us original doesn young performance
real dead humor family actor
may blood school mother plays
without scary fun son played
both quite guy police star
audience budget laugh gets screen
yet genre jokes home james
Тема 2 изглежда като ревюта по документални/исторически филми. Тема 4 изглежда като негативни ревюта. Тема 7 вероятно е комедии. Може да интерпретираме темите по много начини.