Архив метки: sklearn

Реализация алгоритма кластеризации шумных данных DBSCAN на python

Самым известным алгоритмом кластеризации является k-means, с него начинаются все обучающие примеры по кластеризации. Но несмотря на простоту и скорость k-means, у него есть одна очень большая проблема — это наперед заданное количество кластеров. В реальном же мире чаще всего нам не известно на сколько групп следует разбить данные, более того часть данных могут быть вредными выбросами. DBSCAN отлично разбивает множество на оптимальное количество кластеров и не учитывает выбросы, неудивительно что в 2014ом году алгоритм получил метку «проверено временем» на одной из конференций по анализу данных.
Мы будет рассматривать реализация на основе библиотеки sklearn
* В алгоритма два входных параметра min_samples и eps. Если в заданной окрестности eps есть минимальное необходимое количество объектов min_samples, то данная окрестность будет считаться кластером.
* Если в заданной области нет необходимого количества объектов, то инициирующая эту область точка считается выбросом
Кластеры, найденные DBSCAN могут иметь любую форму, в отличии от алгоритма k-means (которые предполагают только выпуклую форму)

Ниже приведём простой пример кластеризации с помощью DBSCAN.

from sklearn.datasets import load_iris
from sklearn.cluster import DBSCAN

iris = load_iris()
dbscan = DBSCAN(eps=0.5, min_samples=5)

dbscan.fit(iris.data)

# Готово! Распечатаем метки принадлежности к кластерам
print(dbscan.labels_)

Теперь давайте визуализируем полученный результат

from sklearn.decomposition import PCA
import matplotlib.pyplot as pl

pca = PCA(n_components=2).fit(iris.data)
pca_2d = pca.transform(iris.data)
for i in range(0, pca_2d.shape[0]):
    if dbscan.labels_[i] == 0:
        c1 = pl.scatter(pca_2d[i, 0], pca_2d[i, 1], c='r', marker='+')
    elif dbscan.labels_[i] == 1:
        c2 = pl.scatter(pca_2d[i, 0], pca_2d[i, 1], c='g', marker='o')
    elif dbscan.labels_[i] == -1:
        c3 = pl.scatter(pca_2d[i, 0], pca_2d[i, 1], c='b', marker='*')

pl.legend([c1, c2, c3], ['Cluster 1', 'Cluster 2', 'Noise'])
pl.title('DBSCAN finds 2 clusters and noise')
pl.show()

В результате получим вот такое разбиение по кластерам и шум
кластеризация dbscan

Простой пример классификации текста на python sklearn

Классификация текста — классическая задача в области обработки естественного языка. Лет 10-15 назад тема классификации бурлила в научных журналах, однако со временем бум утих. Это связоно с тем, что подход на основе TF-IDF показал точность близкую к 95%-99.9%. При такой точности на качество классификации больше уже влияют методы предобработки и особенности текста, чем непосредственно выбор самого алгоритма. Появление ембеддингов в 2013 году сильно повлияло на методы в обработке текстов, открыв новую эпоху. Сейчас практически все «production» решения основываются на ембеддингах, но! Тут надо сделать оговорку — ембеддинги чрезвычайно хороши для анализа коротких текстов, которыми сейчас полон интернет, а вот для средних и больших текстов — TF-IDF по прежнему на высоте!

Ниже я приведу базовый подход классификации на основе библиотеки sklearn в python. Код максимально короткий и понятный

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline


texts = ['текст номер один', 'текст номер два', 'комьютеры в лингвистике', 'компьютеры и обработка текстов']
texts_labels = [1, 1, 0, 0]

text_clf = Pipeline([
                     ('tfidf', TfidfVectorizer()),
                     ('clf', RandomForestClassifier())
                     ])

text_clf.fit(texts, texts_labels)

res = text_clf.predict(['текст номер три'])
print(res)  # [1]

На входе у нас список из текстов и список размеченных классов, далее делаем Pipeline, который включает в себя векторизацию слов на основе TfIdf и классификатор RandomForest . Далее обучаем «пайплайн-классификатор» и пытаемся предсказать новый текст. Как видим классификацию тексто на python сделать очень просто.

Если у Вас получилось запустить первый пример, то наверно вы зададитесь вопросом, как поднять качество существующей модели. Для этого я дам список улучшений, которые следует попробовать.

1. предобработка текста. Попробуйте нормализовать слова, тогда одно смысловое слово в разных склонениях/спряжениях будет интерпретироваться программой одинаково и возможно поднимет качество.
2. У TfIdf есть много разных параметров, наиболее существенные это
2.1 добавить список стоп слов: параметр stop_words
2.2 добавить n-gramm ы слов : параметр ngram_range, например ngram_range=(1,2)
2.3 ограничить список фич, взяв только самые важные и отрезав менее важные: параметр max_features
3. попробовать другой классификатор, например from sklearn.linear_model import SGDClassifier или SVM , XGB итд. соответсвенно подбирая в каждом из них свои гиперпараметры

С помощью перечисленных шагов, Вы достаточно быстро подберёте оптимальный вариант для классификации и получите точность более 90% (основываясь на моём опыте). Если не так, то возможно надо посмотреть внимательнее корректность входных данных.

Примеры использования Random Forest из scikit-learn для классификации

Один из классических алгоритмов классификации является, алгоритм использования ансамбля решающих деревьев Random Forest (далее просто RF). Его выбирают по следующим причинам:

  • Random Forest хорош для не нормализованных данных, многие алгоритмы машинного обучения (ML) дают плохие результаты и данные предварительно надо обрабатывать. Для RF этот шаг можно попробовать опустить и быстро получить работающей прототип
  • Быстрота обучения классификатора, поэтому для обучения можно использовать большое количество данных не беспокоясь в времени (в отличии от SVM). Распараллеливание алгоритма.
  • Можно получить важность / веса параметров из пространства признаков а также вероятность предсказания

Из некоторых минусув можно выделить то, что обучившись на одних и тех-же данных несколько раз — подход будет предсказывать немного разные значения с разными вероятностями. Т.е. детерминированности вы не получите. От этого не уйдешь, т.к. выборка ансамбля решающих деревьев происходит случайным образом. Для заморозки случайности используйте random_seed.

Первый пример c RF: обучаем модель и предсказываем один элемент

from sklearn.ensemble import RandomForestClassifier

x_train = [
    [1, 2],
    [3, 4],
    [-1, 2],
    [-3, 4]
]
y_train = [1, 1, 0, 0]
clf_rf = RandomForestClassifier()
clf_rf.fit(x_train, y_train)

print(clf_rf.predict([[2, 2]]))  # [1] это предсказанный класс
print(clf_rf.predict_proba([[2, 2]]))  # [[0.2 0.8]] вероятности по классам

Пример второй: кросс-валидация или разбиение оценка точности с помощью разбиения на выборки для теста и для тренировки модели RF

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split


x_train = [
    [1, 2], [5, 6],
    [3, 4], [7, 8],
    [-1, 2], [-5, 6],
    [-3, 4], [-7, 8], [0, 0]
]
y_train = [1, 1, 1, 1, 0, 0, 0, 0, 1]
clf_rf = RandomForestClassifier()

# разбиваем на два подмножества / фолда, параметр cv и получаем точность для каждого
scores = cross_val_score(clf_rf, x_train, y_train, cv=2)
print(scores)  # [0.8  0.75]

# кстати рабить на обучающую выборку и тестовую можно функцией
X_train, X_test, Y_train, Y_test = train_test_split(x_train, y_train, test_size=0.2)
print(X_test, Y_test)  # [[5, 6], [-3, 4]] [1, 0]

Возможно вы уже задались вопросом, какие параметры подкрутить, чтобы как-то влиять на точность и адаптировать алгоритм для входных данных и для железа.
n_estimators — параметр, напрямую влияющий на качество, при увеличении пространства признаков, следует рассмотреть увеличение этого параметра
max_depth — поможет если модель получилась сильно переобученной
criterion — entropy или gini . Можно с ними поиграться)
n_jobs — когда можно использовать несколько ядер
Пример третий: использование параметров для настройки RF
Здесь показано как с помощью GridSearchCV найти оптимальные параметры для классификатора.

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV


x_train = [
    [1, 2], [5, 6],
    [3, 4], [7, 8],
    [-1, 2], [-5, 6],
    [-3, 4], [-7, 8], [0, 0]
]
y_train = [1, 1, 1, 1, 0, 0, 0, 0, 1]

parameter_grid = {
            'criterion': ['entropy', 'gini'],
            'max_depth': [10, 20, 100],
            'n_estimators': [10, 20, 100]
        }
clf = RandomForestClassifier()
grid_searcher = GridSearchCV(clf, parameter_grid, verbose=2)
grid_searcher.fit(x_train, y_train)
clf_best = grid_searcher.best_estimator_

print('Best params = ', clf_best.get_params())

Пример четвертый — сохранение и распаковка обученной модели:

from sklearn.externals import joblib
# предполагаем что clf - обучен выше
joblib.dump(clf, path_to_pkl, compress=1)
clf = joblib.load(self.path_to_pkl)

Здесь параметр compress — влияет на то на сколько сильно будет сжат файл классификатора, чем больше тем сильнее, однако не советую увлекаться, т.к. это обратнопропорционально вляет на время распаковки.

Пример пятый — рисуем график важности параметров. Вполне может пригодиться для презентации результатов.

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
import matplotlib.pyplot as plt
import numpy as np



x_train = [
    [1, 2], [5, 6],
    [3, 4], [7, 8],
    [-1, 2], [-5, 6],
    [-3, 4], [-7, 8], [0, 0]
]
y_train = [1, 1, 1, 1, 0, 0, 0, 0, 1]

parameter_grid = {
            'criterion': ['entropy', 'gini'],
            'max_depth': [10, 20, 100],
            'n_estimators': [10, 20, 100]
        }
clf_rf = RandomForestClassifier()
clf_rf.fit(x_train, y_train)

importances = clf_rf.feature_importances_
print(importances)
std = np.std([tree.feature_importances_ for tree in clf_rf.estimators_], axis=0)

indices = np.argsort(importances)[::-1]
names_indices = ['x_coor', 'y_coor']

# Plot the feature importances of the forest
plt.figure()
plt.title("Feature importances")

plt.bar(range(len(importances)), importances[indices], color="r")
plt.xticks(range(len(importances)), names_indices, rotation=90)

plt.tight_layout()
plt.xlim([-1, len(importances)])
plt.show()

Как видно из нарисованного графика, первая координа является более ключевой, что действительно так по выборке. важность параметров в random forest

Пример использования CountVectorizer в sklearn

Одна из первых концепций обрабтки естественных языков Bag-of-Words — это статистический анализ, анализирующий количественное вхождение слов в документах. Не смотря на то, что подход сформирован весьма давно, он отлично подходит для первичной обработки текстов и быстрого прототипирования.

Модуль CountVectorizer в sklearn как раз подзволяет сконвертировать набор текстов в матрицу токенов, находящихся в тексте. Также имеется много полезных настроек, например можно задать минимальное количество необходимое для появления токена в матрице и даже получить статистику по n-граммам. Следует учитывать, что CountVectorizer по умолчанию сам производит токенизацию и выкидывает слова с длиной меньшей чем два.

Пример:

from sklearn.feature_extraction.text import CountVectorizer
import numpy as np

# инициализируем
vectorizer = CountVectorizer()
# составляем корпус документов
corpus = [
  'слово1 слово2 слово3',
  'слово2 слово3',
  'слово1 слово2 слово1',
  'слово4'
]
# подсчитываем
X = vectorizer.fit_transform(corpus)

# таким образом будет подсчитана следующая структура:
#        | слово1 | слово2 | слово3 | слово4
# текст1 |   1    |    1   |   1    |   0
# текст2 |   0    |    1   |   1    |   0
# текст3 |   2    |    1   |   0    |   0
# текст4 |   0    |    0   |   0    |   1

# чтобы получить сгенерированный словарь, из приведенной структуры CountVectorizer, стоит отметить что порядок совпадает с матрицей
vectorizer.get_feature_names()  # ['слово1', 'слово2', 'слово3', 'слово4']

# чтобы узнать индекс токена в словаре
vectorizer.vocabulary_.get('слово3') # вернет 2

# показать матрицу
X.toarray()

# теперь можно быстро подсчитать вектор для нового документа
vectorizer.transform(["слово1 слово4 слово4"])  # результат [[1 0 0 2]]

# чтобы узнать количественное вхождение каждого слова:
matrix_freq = np.asarray(X.sum(axis=0)).ravel()
final_matrix = np.array([np.array(vectorizer.get_feature_names()), matrix_freq])

Один из минусов, что добавлять новые слова в словарь не получится, необходимо сразу указать все документы.
Второй из минусов, отчасти идущий от первого — плохая масштабируемость на большой набор текстов. Есть вероятность натолкнуться на MemoryError уже при десятке тысяч документов. Поэтому либо надо ограничивать количество фич/токенов для попадания в словарь с помощью настройки max_features или же использовать другую библиотеку. Однако для нескольких тысяч можно весьма быстро получить результат и далее использовать фичи например для классификации или кластеризации документов.