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

Пример нахождения косинусного расстония между текстами в sklearn

Косинусное расстояние — это распространенная метрика поиска схожих объектов, часто в разряженном пространстве. Поэтому косинусное расстоние часто применяют для сравнения текстов. Ниже приведу код для поиска наиболее похожего текста с помощью встроенных функций sklearn.
Пусть у нас есть большое количество текстов, предобработаем тексты с помощью tf-idf, после этого возьмем новый текст и найдем 3 самых похожих.

import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel

# зададим массив текстов
some_texts = [
   'текст номер один',
   'текст следующий под номером два',
   'третий набор слов',
   'что-то ещё для полноценного дата-сета',
   'пример текстового документа',
]
df = pd.DataFrame({'texts': some_texts})

# А к данному тексту будем искать наиболее похожий из заданного выше набора
find_nearest_to = "текст номер три"

# формирование весов tf-idf
tfidf = TfidfVectorizer()
mx_tf = tfidf.fit_transform(some_texts)
new_entry = tfidf.transform([find_nearest_to])

# расчет косинусного расстояния
cosine_similarities = linear_kernel(new_entry, mx_tf).flatten()

# запишем все попарные результаты сравнений
df['cos_similarities'] = cosine_similarities
# и отсортируем по убыванию (т.к. cos(0)=1)
df = df.sort_values(by=['cos_similarities'], ascending=[0])

# Выведем 3 самых близких результата
for index, row in df[0:3].iterrows():
    print(row['texts'], row['cos_similarities'])
# output:
# текст номер один 0.7891589913464455
# текст следующий под номером два 0.23490553492076713
# третий набор слов 0.0

Заметим, что в данном примере мы не использовали cosine_similarity, а почему-то взяли linear_kernel. Дело в том, что когда вектора нормализованы (а они нормализованы после TfidfVectorizer) результат будет одинаковый, однако linear_kernel требует меньше вычислительной мощности и вычислится быстрее. Особенно это будет хорошо заметно на больших данных. Если же вектора не нормализованы, то правильно использовать sklearn.metrics.pairwise.cosine_similarity .

Пример также можно рассматривать как рекомендательную систему на большом корпусе каких-то объектов, работает быстро и хорошо подходит для бейзлайна. В случае с объектами не текстовыми, рекомендуется также попробовать использовать CountVectorizer, вместо TfidfVectorizer.

Пример KFold из библиотеки sklearn (кроссвалидации по фолдам)

Практически всегда от моделей машинного обучения требуется указать их точность. Для расчета точности необходимо
1) обучить модель на тренировочном датасете
2) предсказать результаты на тестовом датасете
3) сравнить правильные данные с предсказанными
Данная процедура проста, но в зависимости от разбиения на обучающий датасет и тестовый датасет мы получим немного разные значения. И какой же результат более правильный?
Правильного нет, ведь точность классификатора само по себе понятие относительное. Но интуитивно понятно, что для лучшей оценки необходимо провести процедуру 1-2-3 как можно большее количество раз.

И вот как раз для оценки точности классификатора и придумали кросс-валидацию. Или K-fold cross-validation.
Идея проста, весь датасет делится на N частей. На каждой итерации N-1 часть идёт на train, и одна на test.
В sklearn для этого есть специальный метод cross_val_score:

import numpy as np
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier

# воспользуемся известным датасетом для примера
iris = load_iris()

clf = RandomForestClassifier(n_estimators=30, random_state=42)
# передаем классификатор, X, y и кол-во фолдов=5
res = cross_val_score(clf, iris['data'], iris['target'], cv=5)

print(res)
# [0.96666667 0.96666667 0.93333333 0.96666667 1.        ]
print(np.mean(res))
# 0.966

Как видим всё очень просто, по сравнению с train_test_split мы получим более точную оценку качества классификатора, т.к. провели эксперимент N раз (в нашем случае 5 раз).
Да, пришлось потратить в 5 раз больше времени по сравнению с train_test_split, но когда данных не очень много — это оправдано.

Однако не всегда удаётся воспользоваться методом cross_val_score, например если хотим что-то ещё подсчитать в это время.
Для этого есть более гибкий метод KFold.
Kfold часто используют не только для оценки точности классификатора, но и например для контроля переобучения для классификатора.
Для многих моделей очень важно знать, в какой момент начинается переобучение. Таким образом можно обучить 10 классификаторов с контролем переобучения и потом усреднить их предсказания. Это может дать дать лучий результат, чем если обучить одну модель сразу на всех данных, без контроля переобучения.
Для примера возьмем библиотеку catboost и будем валидироваться оставшемся фолде

from sklearn.model_selection import KFold
from sklearn.datasets import load_iris
from catboost import CatBoostClassifier

iris = load_iris()

# инициализация KFold с 5тью фолдами
cv = KFold(n_splits=5, random_state=42, shuffle=True)

classifiers = []
# метод split возвращает индексы для объектов train и test
for train_index, test_index in cv.split(iris['target']):
    clf = CatBoostClassifier(random_state=42, iterations=200, verbose=50, learning_rate=0.1)
    X_train, X_test = iris['data'][train_index], iris['data'][test_index]
    y_train, y_test = iris['target'][train_index], iris['target'][test_index]
    clf.fit(X_train, y_train, eval_set=(X_test, y_test), use_best_model=True)

    # получим 5 оптимальных классификаторов
    classifiers.append(clf)

Реализация алгоритма кластеризации шумных данных 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 или же использовать другую библиотеку. Однако для нескольких тысяч можно весьма быстро получить результат и далее использовать фичи например для классификации или кластеризации документов.