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

Две функции повышения точности классификатора с помощью target encoding / mean encoding

Раз Вы смогли нагуглить эту статью, значит скорее всего знаете что такое target encoding, а тажке ознакомлены со всеми прелестями этого мощного метода и знаете все опасности переобучения, которые могут возникнуть при неправильном использовании подхода.
Поэтому здесь я приведу лишь две функции, которые можно быстро использовать для Ваших задач.
Если же не знакомы c mean encoding, то вкратце так:
На основе знаний о target, и его распределения по какой-нибудь категориальной фичи можно добавить новую переменную, показывающую характеристику встречаемости чисто внутри какой-то категории. Для примера пусть есть датасет с разными характеристиками автомобиля, необходимо предсказать город, где был куплен автомобиль. Можно подсчитать среднее кол-во каждой марки в каждом городе и добавить это как дополнительную фичу. Так скажем для тойоты это число будет больше во Владивостоке, чем для Москвы и наоборот, какой-нибудь мерседес явно будет более встречаемый в Москве, нежели рядом с Японией. Как видим, разделяемость по новой фиче будет очень хорошая. Но в этом и есть большой риск переобучения (например какая-нибудь редкая марка, которая в датасете встретится всего пару раз и в одном городе) поэтому напрямую считать среднее не рекомендуется, а правильнее будет использовать регуляризации. Ниже приведены две функции для корректного использования mean-encoding:

Функция 1: CV loop регуляризация
Достаточно интуитивный и устойчивый метод для mean encoding. Данные разбиваются на фолды и значение для каждого фолда вычисляется на основе оставшихся фолдов. Таким образом не будет переобучения на текущем куске данных, т.к. вычисляемое среднее значение будет браться на остальном подмножестве данных. Обычно 4-5 фолдов достаточно.

# Для начала создадим дата фрейм с категориальной фичей
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold


cities = ['C1', 'C1', 'C1', 'C2', 'C2', 'C3', 'C3', 'C3', 'C3', 'C3']
target = [1, 1, 0, 1, 0, 1, 1, 0, 0, 0]

df = pd.DataFrame()
df['cities'] = cities
df['target'] = target

def make_mean_encoding(df_tr, target_col_name, feature_col_name):
    y_tr = df_tr[target_col_name].values
    skf = StratifiedKFold(n_splits=2, shuffle=True, random_state=123)
    train_new = df_tr.copy()

    global_mean = df_tr[target_col_name].mean()
    train_new[feature_col_name + '_mean_target'] = global_mean

    for tr_ind, val_ind in skf.split(y_tr, y_tr):
        X_tr, X_val = df_tr.iloc[tr_ind], df_tr.iloc[val_ind]
        for col in [feature_col_name]:
            means = X_val[col].map(X_tr.groupby(col)[target_col_name].mean())
            X_val[col + '_mean_target'] = means
        train_new.iloc[val_ind] = X_val

    # fill nan
    train_new.fillna(global_mean, inplace=True)

    return train_new

df_encoded = make_mean_encoding(df, 'target', 'cities')
print(df_encoded.head(10))

Функция 2: Регуляризация на основе размытия
Суть данной регуляризации в том, что мы доверяем большим категориям, с большим кол-вом значений и устанавливаем коэффициент недоверия к небольшим, слабопредставленным категориям. По сути говоря коэффициент alpha это размер группы, начиная с которой мы доверяем среднему значению

def make_mean_encoding_smooth(df_tr, target_col_name, feature_col_name):
    train_new = df_tr.copy()
    global_mean = df_tr['target'].mean()
    encod_type = df_tr.groupby(by=[feature_col_name], as_index=False)[target_col_name].transform(np.mean).values
    nrows = df_tr.groupby(by=[feature_col_name], as_index=False)[target_col_name].transform(len).values
    alpha = 4
    train_new[feature_col_name + '_mean_target'] = (encod_type * nrows + global_mean * alpha) / (nrows + alpha)
    return train_new

df_encoded = make_mean_encoding_smooth(df, 'target', 'cities')
print(df_encoded.head(10))

Некоторые библиотеки градиентного бустинга из коробки умеют делать mean-encoding, например catboost. Делает котяра эту вещь отменно, достаточно просто указать категориальные фичи, и мне даже когда-то не хотелось лезть в эту тему, но в один момент надо было работать с очень большим датасетом, и оказалась что эта фича библиотеки ужасно прожорлива на используемую память, а если использовать ещё и обучение на видеокарте, то терабайтов оперативной памяти не хватит. Поэтому иногда полезно самим прикрутить фичи на основе mean-енкодинга, кроме того, за основу не обязательно брать таргет, можно поэкспериментировать и с другими величинами из датасета.

Классификация аудио / звуков с помощью python

В данном посте приведу относительно простой способ классифицировать звукозаписи с помощью python. Как и для любой задачи классификации, в первую очередь понадобится из аудиозаписи извлечь фичи, для этого воспользуемся библиотекой librosa. Звук — весьма сложная структура, не спроста задача распознавания голоса решена по сути только гигантами вроде гугла, яндекса, да и то не идеально. Хоти они явно не ограничены в вычислительных ресурсах.
В общем примерная суть всех фич заключается в том, что звук — это спектрограммы, т.е. если все данные закидывать как фичи, то классификатору будет очень тяжело. Поэтому мы из различных спектрограмм будем брать только некоторые характеристики в виде среднего, дисперсии, минимальных, максимальных значений итд.

Подробнее о каждом спектре можно прочитать здесь: github.com/subho406/Audio-Feature-Extraction-using-Librosa/blob/master/Song%20Analysis.ipynb
Здесь же представлю упрощённую версию:

import librosa
import numpy as np
import librosa.display
import scipy
from scipy import stats

# функция вернет 72 фичи для аудиозаписи по пути wav_path
def get_base_features(wav_path):
    ff_list = []

    y, sr = librosa.load(wav_path, sr=None)

    y_harmonic, y_percussive = librosa.effects.hpss(y)

    tempo, beat_frames = librosa.beat.beat_track(y=y_harmonic, sr=sr)
    chroma = librosa.feature.chroma_cens(y=y_harmonic, sr=sr)
    mfccs = librosa.feature.mfcc(y=y_harmonic, sr=sr, n_mfcc=13)
    cent = librosa.feature.spectral_centroid(y=y, sr=sr)
    contrast = librosa.feature.spectral_contrast(y=y_harmonic, sr=sr)
    rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)
    zrate = librosa.feature.zero_crossing_rate(y_harmonic)

    chroma_mean = np.mean(chroma, axis=1)
    chroma_std = np.std(chroma, axis=1)

    for i in range(0, 12):
        ff_list.append(chroma_mean[i])
    for i in range(0, 12):
        ff_list.append(chroma_std[i])

    mfccs_mean = np.mean(mfccs, axis=1)
    mfccs_std = np.std(mfccs, axis=1)

    for i in range(0, 13):
        ff_list.append(mfccs_mean[i])
    for i in range(0, 13):
        ff_list.append(mfccs_std[i])

    cent_mean = np.mean(cent)
    cent_std = np.std(cent)
    cent_skew = scipy.stats.skew(cent, axis=1)[0]

    contrast_mean = np.mean(contrast,axis=1)
    contrast_std = np.std(contrast,axis=1)

    rolloff_mean=np.mean(rolloff)
    rolloff_std=np.std(rolloff)

    data = np.concatenate(([cent_mean, cent_std, cent_skew], 
                           contrast_mean, contrast_std, 
                           [rolloff_mean, rolloff_std, rolloff_std]), axis=0)
    ff_list += list(data)

    zrate_mean = np.mean(zrate)
    zrate_std = np.std(zrate)
    zrate_skew = scipy.stats.skew(zrate,axis=1)[0]

    ff_list += [zrate_mean, zrate_std, zrate_skew]

    ff_list.append(tempo)

    return ff_list

Далее для классификации можно использовать любой алгоритм градиентного бустинга, буть то lightgbm, xgboost или catboost.
Предположим, что в одной папке лежат все аудиозаписи из одного класса, а в другой папке аудиозаписи из второго класса. Прочитаем их, вычислим фичи и запустим обучение.

import glob
from catboost import CatBoostClassifier
from sklearn.model_selection import train_test_split

h_features = []
s_features = []
ii, jj = 0, 0

for f_path in glob.glob('../Training_Data/human/*.wav'):
    h_features.append(get_base_features(f_path))
    ii += 1
    if ii > 50:
        break
for f_path in glob.glob('../Training_Data/spoof/*.wav'):
    s_features.append(get_base_features(f_path))
    jj += 1
    if jj > 50:
        break


X_train, X_test, y_train, y_test = train_test_split(
                                        h_features+s_features,
                                        [1]*len(h_features) + [0]*len(s_features),
                                        test_size=0.25,
                                        random_state=42,
                                    )

clf = CatBoostClassifier(iterations=200)
clf.fit(X_train, y_train, eval_set=(X_test, y_test))

Стоит отметить, что почему-то библиотека librosa медленно читает файл. Поэтому предложу альтернативный метод из библиотеки scipy:

import scipy.io.wavfile
sr, y = wavfile.read("audio/some_file.wav")

Пример 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)

Определение угла наклона текста на изображении

В данном посте поделюсь итеративным методом определения угла наклона сканированного текста.
Суть метода заключается в следующем: постепенно меняя угол наклона — считаем некоторую характеристику-функцию от изображения (о ней чуть дальше). При каком угле наклона получим максимум — значит это и есть оптимальный угол наклона.
Теперь о том, что считать, для этого внимательно взглянем на то как выглядит текст на сканированном документе. Если представим изображение только в виде черных и белых пикселей и потом спроецируем их все на ось X, то в местах где есть строчки гистограмма будет выдавать пики по черным пикселям, а где междустрочный интервал — по белым пикселям. Пики будут максимально выраженными при правильном угле. Но мы хотим получить наиболее простой алгоритм, поэтому будет считать только кол-во черных и белых пикселей при проекции на ось X. Т.к. строки текста парралельны друг другу, то при лучшем угле кол-во белых пикселей будет максимальным, а кол-во черных минимальным. Этим и воспользуемся:

import numpy as np
import cv2


def get_angle(img):
    # сперва переведём изображение из RGB в чёрно серый
    # значения пикселей будут от 0 до 255
    img_gray = cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY)

    # а теперь из серых тонов, сделаем изображение бинарным
    th_box = int(img_gray.shape[0] * 0.007) * 2 + 1
    img_bin_ = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, th_box, th_box)

    img_bin = img_bin_.copy()
    num_rows, num_cols = img_bin.shape[:2]

    best_zero, best_angle = None, 0
    # итеративно поворачиваем изображение на пол градуса
    for my_angle in range(-20, 21, 1):
        rotation_matrix = cv2.getRotationMatrix2D((num_cols/2, num_rows /2 ), my_angle/2, 1)
        img_rotation = cv2.warpAffine(img_bin, rotation_matrix, (num_cols*2, num_rows*2),
                                      borderMode=cv2.BORDER_CONSTANT,
                                      borderValue=255)

        img_01 = np.where(img_rotation > 127, 0, 1)
        sum_y = np.sum(img_01, axis=1)
        th_ = int(img_bin_.shape[0]*0.005)
        sum_y = np.where(sum_y < th_, 0, sum_y)

        num_zeros = sum_y.shape[0] - np.count_nonzero(sum_y)

        if best_zero is None:
            best_zero = num_zeros
            best_angle = my_angle

        # лучший поворот запоминаем
        if num_zeros > best_zero:
            best_zero = num_zeros
            best_angle = my_angle

    return best_angle * 0.5


img = cv2.imread('singapore_01.jpg')
best_angle = get_angle(img)
print(best_angle)

cv2.imshow('Result', img)
cv2.waitKey()

Наложение текста на изображение с помощью python

В основном, для базовых операций с изображениями в питоне, используются две библиотеки OpenCV и PIL. Одна из операций — это добавление произвольного текста в определенную область на фото. В данном посте рассмотрим оба варианта добавления текста и с помощью OpenCV и через PIL. Однако, забегая вперёд скажу, что в OpenCV весьма урезанная функция добавления текста.

1. Пример наложения текста с использованием библиотеки python-OpenCV

import numpy as np
import cv2

# создадим белое изображение
# или можно считать изобрежние с помощью cv2.imread("path_to_file")
img = np.zeros((256, 512, 3), np.uint8)
img[:, :, :] = 255

font = cv2.FONT_HERSHEY_COMPLEX
# вставка текста красного цвета
cv2.putText(img, 'наш произвольный текст', (10, 150), font, 1, color=(0, 0, 255), thickness=2)

cv2.imshow('Result', img)
cv2.waitKey()

# есть ограниченное кол-во вариантов выбора шрифта
# FONT_HERSHEY_COMPLEX
# FONT_HERSHEY_COMPLEX_SMALL
# FONT_HERSHEY_DUPLEX
# FONT_HERSHEY_PLAIN
# FONT_HERSHEY_SCRIPT_COMPLEX
# FONT_HERSHEY_SCRIPT_SIMPLEX
# FONT_HERSHEY_SIMPLEX
# FONT_HERSHEY_TRIPLEX
# FONT_ITALIC

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

Для более продвинутого добавления надписей (можно сказать даже художественного) давайте сформулируем требования, предъявляемые к скрипту.
1. Использование произвольного шрифта из файла ttf
2. Возможность задать угол наклона текста
3. Возможность задать прозрачность текста
4. Выравнивание текста по центру
Четвёртый пункт очень важен, ведь используя произвольный шрифт мы не можем расчитать ширину надписи, и было бы здорово передать эту предобработку библиотеке.
Теперь перейдём к коду:

2. Пример наложение текста с использованием библиотеки PIL

import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFont

# создадим белое изображение
# или можно считать изобрежние с помощью cv2.imread("path_to_file")
img = np.zeros((256, 512, 3), np.uint8)
img[:, :, :] = 255


# для простоты и совместимости возьмем пустое изображение из первого примера
# Чтобы не использовать opencv, а только PIL используйте функцию Image.open()
def put_text_pil(img: np.array, txt: str):
    im = Image.fromarray(img)

    font_size = 24
    font = ImageFont.truetype('LibreFranklin-ExtraBold.ttf', size=font_size)

    draw = ImageDraw.Draw(im)
    # здесь узнаем размеры сгенерированного блока текста
    w, h = draw.textsize(txt, font=font)

    y_pos = 50
    im = Image.fromarray(img)
    draw = ImageDraw.Draw(im)

    # теперь можно центрировать текст
    draw.text((int((img.shape[1] - w)/2), y_pos), txt, fill='rgb(0, 0, 0)', font=font)

    img = np.asarray(im)

    return img


img = put_text_pil(img, 'Some Styled Black Text Here')
cv2.imshow('Result', img)
cv2.waitKey()

Пример кода с альфа каналом и наклоном будет чуть позже

Сохранение обученной ML модели (объекта python) в базе данных (BLOB)

Стандартная архитектура при создании сервисов с использованием машинного обучения подразумеват обучение модели на одном сервере и её использование на другом сервере, который непосредственно работает на предикт. Однако, на предикт может работать много серверов, и возникает вопрос: как доставить обученную модель на все сервера? Обычно модель сохраняют в виде файла. Поэтому можно сделать mount какого-то общего сетевого ресурса, и на нём хранить модель. Но более гибко будет сохранить модель в базе данных, тем более что все инстансы уже скорее всего имеют соединение с общей базой.
Ниже приведу пример того, как сохранить объект python в базе данных в колонке типа блоб.

import _pickle
import pymysql.cursors

## Для простоты возьмём лёгкий объект
# однако обученная модель может достигать сотни мегабайт, а то и ещё больше
listToPickle = [(10, 10), ("example", 10.0), (1.0, 2.0), "object"]

## Преобразование нашего объекта в строку
pickledList = _pickle.dumps(listToPickle)

## Соединение с базой данных mysql
connection = pymysql.connect(host='localhost', port=3306, user='dbuser', password='pass', db='somedb', cursorclass=pymysql.cursors.DictCursor)

## создание курсора
with connection.cursor() as cursor:
    ## Вставка в БД
    cursor.execute("""INSERT INTO pickleTest VALUES (NULL, 'testCard', %s)""", (pickledList, ))
    connection.commit()

    ## Выборка по сохраненной модели
    cursor.execute("""SELECT features FROM pickleTest WHERE card = 'testCard'""")

    ## Получим и распечатаем результат
    res = cursor.fetchone()

    ## Обратное преобразование
    unpickledList = _pickle.loads(res['pickledStoredList'])
    print(unpickledList)

Как видим всё просто, достаточно перед сохранием в блоб преобразовать объект с помощью _pickle.dumps() а при загрузке объекта обратно преобразовать в строку в объект с помощью _pickle.loads().
Также отмечу, что в третьем питоне не надо устанавливать дополнительных библиотек, используется встроенная библиотека _pickle. Таким образом Pickling в Python — это способ хранения весьма сложных структур данных в двоичном представлении, которые требуется восстановить через некоторое время, для получения той же структуры данных.

Как обойти строки dataframe в цикле (pandas)

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

import pandas as pd

dataframe_from_list = [[1,2], [3,4], [10,20]]
df = pd.DataFrame(dataframe_from_list, columns=['col1', 'col2'])

for index, row in df.iterrows():
    print(index, row)
    print(row['col1'], row['col2'], row['col1'] + row['col2'])

В данном примере использовалась функция iterrows для обхода датафрейма. Для обращения к колоночным значениям в строке используется row['название_колонки'].

А теперь давайте подумаем, зачем нам итерироваться по датафрейму: самое очевидно это взять некоторые колоночные значения из строки и подсчитать некоторую функцию.
Но это можно сделать и с помощью apply метода с указанием направления по оси x:

result = df.apply(lambda row: row['col1'] + row['col2'], axis=1)
print(result)
# 3, 7, 30

Соответсвенно, вместо lambda функции можно поставить свою, или в простом случае использовать оптимизированные numpy функции, например np.sum

Ну а на последок, давайте представим что у нас большое кол-во строк, сравнимое с миллионом, и нам надо подсчитать некую функцию. Проблема в том, что pandas вычисляет apply в один процесс, а все современные процессоры имют несколько ядер. Поэтому необходимо распараллелить apply функцию для оптимального расчета. Для распараллеливания и быстрого расчета функции по датафрейму воспользуемя функцией ниже:

from multiprocessing import Pool
import numpy as np

# для примера возьмем функцию суммы по строке, приведенную выше
def calculate_sum_column(df):
    df['sum_column'] = df.apply(lambda row: row['col1'] + row['col2'], axis=1)
    return df

# в данном примере расспараллеливаем на восемь потоков. Будьте аккуратны - при распараллеливании тратится больше оперативной памяти
def parallelize_dataframe(df, func):
    a,b,c,d,e,f,g,h = np.array_split(df, 8)
    pool = Pool(8)
    some_res = pool.map(func, [a,b,c,d,e,f,g,h])
    df = pd.concat(some_res)
    pool.close()
    pool.join()
    return df

# имитация большого датасета
df = pd.concat([df, df, df], ignore_index=True)

df = parallelize_dataframe(df, calculate_sum_column)
print(df.head(10))

С помощью данного гибкого «многоядерного» подхода можно многократно ускорить обход датафрейма и вычислить необходимую функцию

Классификация текстов методом Doc2vec в python

Используя любой метод классификации текстов, первоночально необходимо придумать способ перевода текстового документа в набор чисел. Ранее уже был рассмотрен метод на основе TF-IDF, в этот раз рассмотрим метод DOC2VEC. Из названия сразу понятно, что метод переводит документ в вектор, причём делается это просто. Сразу приведу код:

# определим датасет
some_texts = [
   'текст номер один',
   'Текст следуюйщих под номером два',
   'третий набор слов',
   'что-то ещё для полноценного дата-сета',
   'пример текстового документа'
]

from gensim.models.doc2vec import Doc2Vec, TaggedDocument
documents = [TaggedDocument(doc, [i]) for i, doc in enumerate(some_texts)]

model = Doc2Vec(documents, vector_size=5, workers=4, epochs=3)

Обучение, в данном примере (взятом из документации), происходит сразу при создании объекта. (Мне это показалось немного странным, после sklearn с методами fit и transform).
Некоторые основные параметры:
vector_size — какой конечный размер будет у вектора. Тонкий параметр, который придётся подбирать. Чем больше выборка и длиннее каждый документ — тем имеет смысл ставить параметр больше, порядка 100-300. Для небольших текстов и датасетов может хватить размерности 5-10.
epoch — кол-во эпох на обучении. Тоже потребует настройки, обычно 10-20

workers — сколько ядер хотите задействовать на обучении. (Узнать кол-во: from multiprocessing import cpu_count)
Стоит также отметить, что начальные веса для документов берутся случайно, и в дальнейшем происходит утряска-оптимизация весов. Поэтому после каждого обучения результирующий вектор для одного и того же документа будет разным.

Полезные методы Doc2Vec

# сохранение модели для дальнейшего использования
model.save("my_doc2vec_model")
# загрузка модели
model = Doc2Vec.load("my_doc2vec_model")
# нахождение наиболее похожего документа
vector_to_search = model.infer_vector(["ищем", "похожий", "текст"])
# три наиболее похожих
similar_documents = model.docvecs.most_similar([vector_to_search], topn=3)
for s in similar_documents:
    print(some_texts[s[0]])

И так, модель Doc2vec обучена, теперь можно любой документ перевести в вектор и обучить вашим любимым классификатором. Давайте для пример возьмем RandomForest

from sklearn.ensemble import RandomForestClassifier

clf = RandomForestClassifier()
clf.fit([model.infer_vector([x.words]) for x in documents], [1, 1, 1, 0, 0])
 
res = clf.predict([model.infer_vector(['текст', 'номер', 'три'])])
print(res)  # [1]

По своему опыту могу сказать, что doc2vec несколько капризная штука и по настоящему хорошо работать этот метод мне не удалось заставить. Особенно неприятно удивили результаты с находением наиболее похожих документов в датасете. Однако, т.к. это другой подход, отличный от классического bag of words, tf-idf а также от LDA и подходов на нейронных сетях, то можно его смело рекомендовать использовать в дополнение с другими алгоритмами для достижения максимальной точности классификации.

Скачать список доменов в зоне .ru

В доменной зоне .ru находится примерно 5 000 000 зарегистрированных доменов. Если учитывать домены третьего уровня, то эта цифра увеличивается примерно до восьми миллионов. Файл для скачивания представлят собой .txt файл в архиве, в нём находится примерно 1 000 000 доменов актуальных на май 2019. Думаю миллиона вполне хватит для проверки статистических гипотез, которые возникают в ходе исследовательских задач. (ссылка на скачиване находится чуть ниже)

Так же для интереса приведу данные по количеству доменов в других расспространённых зонах, а всего в мире зарегистрировано порядка 400 000 000 доменов.
зона .com 150 миллионов
зона .net 14 миллионов
зона .org 11 миллионов
зона .info 7 миллионов
зона .uk 12 миллионов
зона .de 5 миллионов

Как быстро получить информацию со всех сайтов, указанных в базе доменов? Для этого воспользуемся python скриптом. Обходить все домены лучше всего не поочереди, а асинхронно или в несколько потоков, т.к. задержки, возникающие при запросе будут тормозить следующий запрос и накапливаться, а это нам не к чему. Самый быстрый и мощный вариан asyncio, однако уровень вхождения в библиотеку чуть выше среднего, поэтому ниже показан самый простой вариант как быстро обойти все домены. С помощью данного скрипта можно обойти миллион доменов из списка за 3-4 часа. В качестве информации, которую будем брать с сайтов, давайте возьмем данные о IP адресе, где расположен хостинг.

скрипт на python для парсинга инфорамации о сайтах из базы доменов на примере ip:

import socket
# с помощью multiprocessing сможем обходить сайты не последовательно
from multiprocessing.dummy import Pool as ThreadPool
import dns.resolver
import pandas as pd

# определим функцию для получения IP адреса
def get_ip(url):
    try:
        data_ip = socket.gethostbyname(url)
    except:
        return None
    return data_ip


df_all_ru = pd.read_csv('all_ru.csv')
sites_list = df_all_ru['domain_name'].tolist()

# за раз давайте будем брать сразу 100 доменов
# имеет смысл подобрать этот параметр в зависимости от скорости вашего интернета и задачи которую решаете
p = ThreadPool(100)
result_ip = p.map(get_ip, sites_list)
p.close()
p.join()

df_all_ru['ip'] = result_ip
df_all_ru.to_csv('ru_domains_ips.csv', index=False)

С помощью данного простого скрипта можно быстро получить данные с большого количества сайтов.

Непосредственно скачать файл со списком ру доменов здесь

Реализация алгоритма кластеризации шумных данных 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