134 KiB
Анализ текста¶
Инициализация движка (модуля) для анализа текста¶
import spacy
sp = spacy.load("ru_core_news_lg")
Базовые операции над текстом¶
Текст для примеров
text = "Накануне Дня российской науки, 7 февраля, в Ульяновском государственном техническом университете открыли новую экспозицию «Они стояли у истоков ульяновской науки». Она посвящена выдающимся ученым XX – начала XXI веков, чьи достижения и изобретения сформировали научный и технологический облик Ульяновской области."
text_ner = "В рамках торжественного открытия экспозиции за активное участие в подготовке материалов были вручены благодарственные письма профессору кафедры летной эксплуатации и безопасности полетов Ульяновского института гражданской авиации имени Главного маршала авиации Б.П.Бугаева Сергею Косачевскому, начальнику управления научно-исследовательской и инновационной деятельности Ульяновского государственного педагогического университета имени И.Н.Ульянова Светлане Богатовой, руководителю пресс-службы Ульяновского государственного аграрного университета имени П.А.Столыпина Винере Насыровой, помощнику проректора по научной работе Ульяновского государственного университета Татьяне Лисовой."
Предобработка¶
Изменение регистра
print(text)
print(text.lower())
print(text.upper())
Регулярные выражения
import re
regex = re.compile("[^a-zA-Zа-яА-Я ]")
print(text)
print(" ".join(regex.sub("", text).split()))
Эмодзи
import emoji
print(emoji.emojize("Пример эмодзи :thumbs_up:"))
print(emoji.demojize("Пример эмодзи 👍"))
Диакритические знаки
import unicodedata
norm_text = unicodedata.normalize("NFKD", f"stävänger {text}")
res = "".join([char for char in norm_text if not unicodedata.combining(char)])
print(res)
Преобразование числа в строку
times = {"7": "семь", "XX": "двадцать", "XXI": "двадцать один"}
print([times[token.text] if token.text in times else token.text for token in sp(text)])
Токенизация¶
doc = sp(text)
print(doc)
print(doc[4])
print(doc[4].is_sent_start)
print(doc[4].is_sent_end)
print(doc[4].is_punct)
print()
for sentence in doc.sents:
print(sentence)
print()
for sentence in doc.sents:
for word in sentence:
print(word)
Удаление стоп-слов¶
Список стоп-слов
from string import punctuation
print(sorted(sp.Defaults.stop_words))
print(list(punctuation))
Найденные в тексте стоп-слова
stops = [token.text for token in doc if token.is_stop or token.is_punct or token.is_digit]
print(stops)
Текст без стоп-слов
without_stops = [token for token in doc if not token.is_stop and not token.is_punct and not token.is_digit]
print(without_stops)
Стемминг¶
Стеммер Портера
from nltk.stem.porter import PorterStemmer
porter = PorterStemmer()
print(list(map(porter.stem, map(str, without_stops))))
Стеммер Snowball
from nltk.stem.snowball import SnowballStemmer
snowball = SnowballStemmer(language="russian")
print(list(map(snowball.stem, map(str, without_stops))))
Лемматизация¶
print([token.lemma_ for token in without_stops])
Морфологический анализ (POS tagging)¶
print([f"{token.text} {token.pos_}" for token in without_stops])
print([f"{token.text} [{token.morph}]" for token in without_stops])
Синтаксический анализ (Dependency parsing)¶
print(text)
print(
"\n".join(
[
f"{token.text} | {token.dep_} | {token.head.text} | {token.head.pos_} {[child.text for child in token.children]}"
for token in sp(text.split(".")[0])
]
)
)
from spacy import displacy
displacy.render(sp(text.split(".")[0]), style="dep")
Обнаружение именованных сущностей¶
print([f"{entity.lemma_} {entity.label_}" for entity in sp(text).ents])
print([f"{entity.lemma_} {entity.label_}" for entity in sp(text_ner).ents])
displacy.render(sp(text_ner), style="ent")
Векторизация¶
Мешок слов (BoW, Bag of Words)¶
import pandas as pd
from scipy import sparse
from sklearn.feature_extraction.text import CountVectorizer
counts_vectorizer = CountVectorizer()
counts_matrix = sparse.csr_matrix(counts_vectorizer.fit_transform([text, text_ner]))
counts_df = pd.DataFrame(
counts_matrix.toarray(),
index=["text", "text_ner"],
columns=counts_vectorizer.get_feature_names_out(),
)
counts_df
Пропуск термов, которые содержатся не менее чем в двух документах
counts_min_vectorizer = CountVectorizer(min_df=2)
counts_min_matrix = sparse.csr_matrix(
counts_min_vectorizer.fit_transform(
[text, text_ner, text, text_ner, text, text_ner]
)
)
counts_min_df = pd.DataFrame(
counts_min_matrix.toarray(),
index=["text", "text_ner", "text1", "text_ner1", "text2", "text_ner2"],
columns=counts_min_vectorizer.get_feature_names_out(),
)
counts_min_df
Пропуск термов, которые содержатся более чем в двух документах
counts_max_vectorizer = CountVectorizer(max_df=2)
counts_max_matrix = sparse.csr_matrix(
counts_max_vectorizer.fit_transform(
[text, text_ner, text, text_ner, text, text_ner]
)
)
counts_max_df = pd.DataFrame(
counts_max_matrix.toarray(),
index=["text", "text_ner", "text1", "text_ner1", "text2", "text_ner2"],
columns=counts_max_vectorizer.get_feature_names_out(),
)
counts_max_df
n-граммы
counts_ng_vectorizer = CountVectorizer(ngram_range=(1, 2))
counts_ng_matrix = sparse.csr_matrix(
counts_ng_vectorizer.fit_transform([text, text_ner])
)
counts_ng_df = pd.DataFrame(
counts_ng_matrix.toarray(),
index=["text", "text_ner"],
columns=counts_ng_vectorizer.get_feature_names_out(),
)
counts_ng_df
Ограничение количества термов
counts_mf_vectorizer = CountVectorizer(max_features=15)
counts_mf_matrix = sparse.csr_matrix(
counts_mf_vectorizer.fit_transform([text, text_ner])
)
counts_mf_df = pd.DataFrame(
counts_mf_matrix.toarray(),
index=["text", "text_ner"],
columns=counts_mf_vectorizer.get_feature_names_out(),
)
counts_mf_df
Частотный портрет¶
$tfidf(t, d) = tf(t, d) * idf(t, D)$, \ где $tf(t, d)$ - частота терма $t$ в документе $d$; \ $idf$ - обратная частота терма (мера информативности терма $t$ в рамках всей коллекции документов $D$).
$tf(t, d) = \frac { f_{td} } { \sum_{t' \in d} f_{t'd} } $, \ где $f_{td}$ - количество терма $t$ в документе $d$; \ $\sum_{t' \in d} f_{t'd}$ - количество всех термов в документе $d$, является суммой $f_{td}$ всех термов документа $d$.
$idf(t, D) = \log ( \frac { N + 1} { | { d : d \in D, t \in d } | +1 } $ ) , \ где $N$ - общее количество документов; \ $| { d : d \in D, t \in d } |$ - количество документов, в которых есть термин $t$.
При использовании TfidfVectorizer $tf(t, d) = f_{td} $ (аналогично BoW).
При включении параметра sublinear_tf=True
$tf(t, d) = 1 + log ( f_{td} ) $, что уменьшает степень влияния терминов с высокой частотой на значение $tfidf(t, d)$.
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vectorizer = TfidfVectorizer(sublinear_tf=True)
tfidf_matrix = sparse.csr_matrix(tfidf_vectorizer.fit_transform([text, text_ner]))
tfidf_df = pd.DataFrame(
tfidf_matrix.toarray(),
index=["text", "text_ner"],
columns=tfidf_vectorizer.get_feature_names_out(),
)
tfidf_df
Эмбединги¶
print([f"{token.text} {token.vector_norm}" for token in sp(text)])
print([f"{token.text} {token.vector_norm}" for token in sp(text_ner)])
print(sp(text).similarity(sp(text_ner)))
print(sp("Мама мыла раму").similarity(sp("Биологический родитель выполнял очистку каркаса окна")))
Пример анализа текстов¶
Загрузка данных из документов¶
Столбец type позволяет использовать методы обучения с учителем для построения классификатора
import pandas as pd
from docx import Document
import os
def read_docx(file_path):
doc = Document(file_path)
full_text = []
for paragraph in doc.paragraphs:
full_text.append(paragraph.text)
return "\n".join(full_text)
def load_docs(dataset_path):
df = pd.DataFrame(columns=["doc", "text"])
for file_path in os.listdir(dataset_path):
if file_path.startswith("~$"):
continue
text = read_docx(dataset_path + file_path)
df.loc[len(df.index)] = [file_path, text]
return df
df = load_docs("data/text/")
df["type"] = df.apply(
lambda row: 0 if str(row["doc"]).startswith("tz_") else 1, axis=1
)
df.info()
df.sort_values(by=["doc"], inplace=True)
display(df.head(), df.tail())
Векторизация документов в виде мешка слов¶
counts_vectorizer = CountVectorizer()
counts_matrix = sparse.csr_matrix(counts_vectorizer.fit_transform(df["text"]))
words = counts_vectorizer.get_feature_names_out()
df["vector"] = df.apply(lambda row: counts_matrix.toarray()[row.name], axis=1)
display(df.head(), df.tail())
Вывод термов с частотой больше threshold для каждого документа¶
def get_terms(doc_name, words, threshold=0):
important_words = [
word
for word, score in zip(words, df.iloc[doc_name]["vector"])
if score > threshold
]
return ", ".join(important_words)
df["top_terms"] = df.apply(lambda row: get_terms(row.name, words, threshold=7), axis=1)
display(df.head(), df.tail())
Четкая неиерархическая кластеризация документов¶
from sklearn.cluster import KMeans
import numpy
num_clusters = 2
kmeans = KMeans(n_clusters=num_clusters, random_state=9)
kmeans.fit(sparse.csr_matrix(list(df["vector"])))
for cluster_id in range(num_clusters):
cluster_indices = numpy.where(kmeans.labels_ == cluster_id)[0]
print(f"Кластер {cluster_id + 1} ({len(cluster_indices)}):")
cluster_docs = [df.iloc[idx]["doc"] for idx in cluster_indices]
print("; ".join(cluster_docs))
print("--------")