Введение
В этом лабораторном задании мы изучим векторизацию текста, которая представляет собой процесс представления нечисловых входных данных (например, словарей или текстовых документов) в виде векторов вещественных чисел. Мы сравним два метода, FeatureHasher и DictVectorizer, путём векторизации текстовых документов, предварительно обработанных (токенизированных) с помощью настраиваемой функции на Python.
Советы по использованию ВМ
После запуска ВМ перейдите в левый верхний угол и переключитесь на вкладку Ноутбук, чтобы приступить к практике в Jupyter Notebook.
Иногда вам может потребоваться подождать несколько секунд, пока Jupyter Notebook полностью загрузится. Проверка операций не может быть автоматизирована из-за ограничений Jupyter Notebook.
Если вы столкнетесь с проблемами во время обучения, не стесняйтесь обращаться к Labby. Оставьте отзыв после занятия, и мы оперативно решим проблему для вас.
Загрузка данных
Мы загрузим данные из 20newsgroups_dataset, который содержит около 18000 публикаций новостных групп по 20 темам, разделенных на две подмножества: одно для обучения, а другое для тестирования. Для простоты и снижения вычислительных затрат мы выбираем подмножество из 7 тем и используем только обучающий набор.
from sklearn.datasets import fetch_20newsgroups
categories = [
"alt.atheism",
"comp.graphics",
"comp.sys.ibm.pc.hardware",
"misc.forsale",
"rec.autos",
"sci.space",
"talk.religion.misc",
]
print("Loading 20 newsgroups training data")
raw_data, _ = fetch_20newsgroups(subset="train", categories=categories, return_X_y=True)
data_size_mb = sum(len(s.encode("utf-8")) for s in raw_data) / 1e6
print(f"{len(raw_data)} documents - {data_size_mb:.3f}MB")
Определение функций предварительной обработки
Токеном может быть слово, часть слова или что угодно, находящееся между пробелами или символами в строке. Здесь мы определяем функцию, которая извлекает токены с использованием простого регулярного выражения (regex), которое соответствует Unicode-символам, составляющим слово. Это включает в себя большинство символов, которые могут быть частью слова на любом языке, а также цифры и нижнее подчеркивание:
import re
def tokenize(doc):
"""Extract tokens from doc.
This uses a simple regex that matches word characters to break strings
into tokens. For a more principled approach, see CountVectorizer or
TfidfVectorizer.
"""
return (tok.lower() for tok in re.findall(r"\w+", doc))
Мы определяем дополнительную функцию, которая подсчитывает (частоту) появления каждого токена в заданном документе. Она возвращает словарь частот, который будет использоваться векторизаторами.
from collections import defaultdict
def token_freqs(doc):
"""Extract a dict mapping tokens from doc to their occurrences."""
freq = defaultdict(int)
for tok in tokenize(doc):
freq[tok] += 1
return freq
DictVectorizer
Мы проведем бенчмарк DictVectorizer, который является методом, принимающим словари в качестве входных данных.
from sklearn.feature_extraction import DictVectorizer
from time import time
t0 = time()
vectorizer = DictVectorizer()
vectorizer.fit_transform(token_freqs(d) for d in raw_data)
duration = time() - t0
print(f"done in {duration:.3f} s")
print(f"Found {len(vectorizer.get_feature_names_out())} unique terms")
FeatureHasher
Мы проведем бенчмарк FeatureHasher, который является методом, который строит вектор заданной длины, применяя хэш-функцию к признакам (например, токенам), а затем используя хэш-значения непосредственно в качестве индексов признаков и обновляя результирующий вектор по этим индексам.
from sklearn.feature_extraction import FeatureHasher
import numpy as np
t0 = time()
hasher = FeatureHasher(n_features=2**18)
X = hasher.transform(token_freqs(d) for d in raw_data)
duration = time() - t0
print(f"done in {duration:.3f} s")
print(f"Found {len(np.unique(X.nonzero()[1]))} unique tokens")
Сравнение с специализированными текстовыми векторизаторами
Мы сравним предыдущие методы с CountVectorizer и HashingVectorizer.
from sklearn.feature_extraction.text import CountVectorizer, HashingVectorizer, TfidfVectorizer
t0 = time()
vectorizer = CountVectorizer()
vectorizer.fit_transform(raw_data)
duration = time() - t0
print(f"done in {duration:.3f} s")
print(f"Found {len(vectorizer.get_feature_names_out())} unique terms")
t0 = time()
vectorizer = HashingVectorizer(n_features=2**18)
vectorizer.fit_transform(raw_data)
duration = time() - t0
print(f"done in {duration:.3f} s")
t0 = time()
vectorizer = TfidfVectorizer()
vectorizer.fit_transform(raw_data)
duration = time() - t0
print(f"done in {duration:.3f} s")
print(f"Found {len(vectorizer.get_feature_names_out())} unique terms")
Построение графиков результатов
Мы построим графики скорости вышеперечисленных методов векторизации.
import matplotlib.pyplot as plt
dict_count_vectorizers = {
"vectorizer": [
"DictVectorizer\nна нечастотных словарях",
"FeatureHasher\nна нечастотных словарях",
"FeatureHasher\nна необработанных токенах",
"CountVectorizer",
"HashingVectorizer",
"TfidfVectorizer"
],
"speed": [
2.4, 4.4, 7.2, 5.1, 11.7, 2.9
]
}
fig, ax = plt.subplots(figsize=(12, 6))
y_pos = np.arange(len(dict_count_vectorizers["vectorizer"]))
ax.barh(y_pos, dict_count_vectorizers["speed"], align="center")
ax.set_yticks(y_pos)
ax.set_yticklabels(dict_count_vectorizers["vectorizer"])
ax.invert_yaxis()
_ = ax.set_xlabel("скорость (МБ/с)")
Резюме
В этом лабаратории мы изучили векторизацию текста, сравнив два метода FeatureHasher и DictVectorizer и четыре специализированных текстовых векторизатора CountVectorizer, HashingVectorizer и TfidfVectorizer. Мы провели бенчмарк методов векторизации и построили графики результатов. Мы пришли к выводу, что HashingVectorizer показывает лучшие результаты, чем CountVectorizer, но в代价 этого теряется обратимость преобразования из-за коллизий хэшей. Кроме того, DictVectorizer и FeatureHasher показывают лучшие результаты, чем их аналог в текстовых векторизаторах для документов с ручным токенизацией, так как внутренний этап токенизации у первых векторизаторов компилирует регулярное выражение один раз и затем переиспользует его для всех документов.