Автоматическое извлечение ключевых слов из статей с использованием НЛП
Фон
В исследовательских и новостных статьях ключевые слова являются важным компонентом, поскольку они обеспечивают краткое представление содержания статьи. Ключевые слова также играют решающую роль в поиске статьи из информационно-поисковых систем, библиографических баз данных и для поисковой оптимизации. Ключевые слова также помогают отнести статью к соответствующей теме или дисциплине.
Традиционные подходы к извлечению ключевых слов включают назначение ключевых слов вручную на основе содержания статьи и суждения авторов. Это требует много времени и усилий, а также может быть неточным с точки зрения выбора подходящих ключевых слов. С появлением обработки естественного языка (NLP) извлечение ключевых слов стало не только эффективным, но и эффективным.
И в этой статье мы объединим их - мы будем применять НЛП к коллекции статей (подробнее об этом ниже) для извлечения ключевых слов.
О наборе данных
В этой статье мы будем извлекать ключевые слова из набора данных, который содержит около 3800 рефератов. Исходный набор данных взят из Kaggle - NIPS Paper. Системы обработки нейронной информации (NIPS) - одна из ведущих конференций по машинному обучению в мире. Этот набор данных включает заголовок и аннотации всех документов NIPS на сегодняшний день (начиная с первой конференции 1987 года и заканчивая текущей конференцией 2016 года).
Исходный набор данных также содержит текст статьи. Однако, поскольку основное внимание уделяется пониманию концепции извлечения ключевых слов, а использование полного текста статьи может потребовать значительных вычислительных ресурсов, для моделирования НЛП использовались только отрывки. Тот же блок кода можно использовать в полном тексте статьи, чтобы получить лучшее и расширенное извлечение ключевых слов.
Подход высокого уровня
Импорт набора данных
Набор данных, используемый в этой статье, является подмножеством набора данных paper.csv, представленного в бумажных наборах данных NIPS на Kaggle. Были использованы только те строки, которые содержат аннотацию. Заголовок и аннотация были объединены, после чего файл сохраняется как файл * .txt, разделенный табуляцией.
import pandas # load the dataset dataset = pandas.read_csv('papers2.txt', delimiter = '\t') dataset.head()
Как видим, набор данных содержит идентификатор статьи, год публикации и аннотацию.
Предварительное исследование текста
Прежде чем приступить к какой-либо предварительной обработке текста, рекомендуется быстро изучить набор данных с точки зрения количества слов, наиболее распространенных и наиболее необычных слов.
Получить количество слов для каждого резюме
#Fetch wordcount for each abstract dataset['word_count'] = dataset['abstract1'].apply(lambda x: len(str(x).split(" "))) dataset[['abstract1','word_count']].head()
##Descriptive statistics of word counts dataset.word_count.describe()
Среднее количество слов в аннотации составляет около 156 слов. Количество слов варьируется от минимум 27 до максимум 325. Количество слов важно для того, чтобы дать нам представление о размере обрабатываемого нами набора данных, а также о различиях в количестве слов в строках.
Самые распространенные и необычные слова
Взгляд на наиболее распространенные слова дает представление не только о часто используемых словах, но и о словах, которые также могут быть потенциальными стоп-словами для конкретных данных. Сравнение наиболее распространенных слов и английских стоп-слов по умолчанию даст нам список слов, которые необходимо добавить в настраиваемый список стоп-слов.
#Identify common words freq = pandas.Series(' '.join(dataset['abstract1']).split()).value_counts()[:20] freq
#Identify uncommon words freq1 = pandas.Series(' '.join(dataset ['abstract1']).split()).value_counts()[-20:] freq1
Предварительная обработка текста
Разреженность. При интеллектуальном анализе текста создаются огромные матрицы на основе частотности слов, при этом многие ячейки имеют нулевые значения. Эта проблема называется разреженностью и сводится к минимуму с помощью различных методов.
Предварительную обработку текста можно разделить на две большие категории - удаление шума и нормализация. Компоненты данных, которые избыточны для основной текстовой аналитики, можно рассматривать как шум.
Обработка нескольких вхождений / представлений одного и того же слова называется нормализацией. Есть два типа нормализации - стемминг и лемматизация. Рассмотрим на примере различных вариантов слова учиться - учиться, учиться, учиться, учиться. Нормализация преобразует все эти слова в единую нормализованную версию - «учиться».
Stemming нормализует текст, удаляя суффиксы.
Лемматизация - это более сложная техника, которая работает на основе корня слова.
Следующий пример показывает, как работают стемминг и лемматизация:
from nltk.stem.porter import PorterStemmer from nltk.stem.wordnet import WordNetLemmatizer lem = WordNetLemmatizer() stem = PorterStemmer() word = "inversely" print("stemming:",stem.stem(word)) print("lemmatization:", lem.lemmatize(word, "v"))
Чтобы выполнить предварительную обработку текста в нашем наборе данных, мы сначала импортируем необходимые библиотеки.
# Libraries for text preprocessing import re import nltk #nltk.download('stopwords') from nltk.corpus import stopwords from nltk.stem.porter import PorterStemmer from nltk.tokenize import RegexpTokenizer #nltk.download('wordnet') from nltk.stem.wordnet import WordNetLemmatizer
Удаление игнорируемых слов. Стоп-слова включают в себя большое количество предлогов, местоимений, союзов и т. д. в предложениях. Эти слова необходимо удалить, прежде чем мы проанализируем текст, чтобы часто используемые слова были в основном словами, относящимися к контексту, а не общими словами, используемыми в тексте.
В библиотеке python nltk есть список стоп-слов по умолчанию. Кроме того, мы могли бы добавить контекстно-зависимые стоп-слова, для которых будут полезны «наиболее распространенные слова», перечисленные в начале. Теперь мы увидим, как создать список игнорируемых слов и как добавить собственные стоп-слова:
##Creating a list of stop words and adding custom stopwords stop_words = set(stopwords.words("english")) ##Creating a list of custom stopwords new_words = ["using", "show", "result", "large", "also", "iv", "one", "two", "new", "previously", "shown"] stop_words = stop_words.union(new_words)
Теперь мы выполним задачи предварительной обработки шаг за шагом, чтобы получить очищенный и нормализованный текстовый корпус:
corpus = [] for i in range(0, 3847): #Remove punctuations text = re.sub('[^a-zA-Z]', ' ', dataset['abstract1'][i]) #Convert to lowercase text = text.lower() #remove tags text=re.sub("</?.*?>"," <> ",text) # remove special characters and digits text=re.sub("(\\d|\\W)+"," ",text) ##Convert to list from string text = text.split() ##Stemming ps=PorterStemmer() #Lemmatisation lem = WordNetLemmatizer() text = [lem.lemmatize(word) for word in text if not word in stop_words] text = " ".join(text) corpus.append(text)
Давайте теперь рассмотрим элемент из корпуса:
#View corpus item corpus[222]
Исследование данных
Теперь мы визуализируем корпус текста, который мы создали после предварительной обработки, чтобы получить представление о наиболее часто используемых словах.
#Word cloud from os import path from PIL import Image from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator import matplotlib.pyplot as plt % matplotlib inline wordcloud = WordCloud( background_color='white', stopwords=stop_words, max_words=100, max_font_size=50, random_state=42 ).generate(str(corpus)) print(wordcloud) fig = plt.figure(1) plt.imshow(wordcloud) plt.axis('off') plt.show() fig.savefig("word1.png", dpi=900)
Подготовка текста
Текст в корпусе необходимо преобразовать в формат, который может интерпретироваться алгоритмами машинного обучения. Это преобразование состоит из двух частей - токенизации и векторизации.
Токенизация - это процесс преобразования непрерывного текста в список слов. Список слов затем преобразуется в матрицу целых чисел в процессе векторизации. Векторизацию также называют извлечением признаков.
Для подготовки текста мы используем модель мешка слов, которая игнорирует последовательность слов и учитывает только частоту слов.
Создание вектора количества слов
В качестве первого шага преобразования мы будем использовать CountVectoriser для разметки текста и создания словаря известных слов. Сначала мы создаем переменную cv класса CountVectoriser, а затем вызываем функцию fit_transform для изучения и создания словаря.
from sklearn.feature_extraction.text import CountVectorizer import re cv=CountVectorizer(max_df=0.8,stop_words=stop_words, max_features=10000, ngram_range=(1,3)) X=cv.fit_transform(corpus)
Давайте теперь разберемся с параметрами, переданными в функцию:
- cv = CountVectorizer (max_df = 0.8, stop_words = stop_words, max_features = 10000, ngram_range = (1,3))
- max_df - при построении словаря игнорируйте термины, частота которых в документе строго превышает заданный порог (стоп-слова для конкретного корпуса). Это сделано для того, чтобы у нас были только слова, относящиеся к контексту, а не часто используемые слова.
- max_features - определяет количество столбцов в матрице.
- Диапазон n-грамм - мы хотели бы посмотреть список отдельных слов, двух слов (биграммы) и трех слов (триграммы) комбинаций.
Кодированный вектор возвращается с длиной всего словаря.
list(cv.vocabulary_.keys())[:10]
Визуализируйте верхние N униграмм, биграмм и триграмм
Мы можем использовать CountVectoriser для визуализации 20 лучших униграмм, биграмм и триграмм.
#Most frequently occuring words def get_top_n_words(corpus, n=None): vec = CountVectorizer().fit(corpus) bag_of_words = vec.transform(corpus) sum_words = bag_of_words.sum(axis=0) words_freq = [(word, sum_words[0, idx]) for word, idx in vec.vocabulary_.items()] words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True) return words_freq[:n] #Convert most freq words to dataframe for plotting bar plot top_words = get_top_n_words(corpus, n=20) top_df = pandas.DataFrame(top_words) top_df.columns=["Word", "Freq"] #Barplot of most freq words import seaborn as sns sns.set(rc={'figure.figsize':(13,8)}) g = sns.barplot(x="Word", y="Freq", data=top_df) g.set_xticklabels(g.get_xticklabels(), rotation=30)
#Most frequently occuring Bi-grams def get_top_n2_words(corpus, n=None): vec1 = CountVectorizer(ngram_range=(2,2), max_features=2000).fit(corpus) bag_of_words = vec1.transform(corpus) sum_words = bag_of_words.sum(axis=0) words_freq = [(word, sum_words[0, idx]) for word, idx in vec1.vocabulary_.items()] words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True) return words_freq[:n] top2_words = get_top_n2_words(corpus, n=20) top2_df = pandas.DataFrame(top2_words) top2_df.columns=["Bi-gram", "Freq"] print(top2_df) #Barplot of most freq Bi-grams import seaborn as sns sns.set(rc={'figure.figsize':(13,8)}) h=sns.barplot(x="Bi-gram", y="Freq", data=top2_df) h.set_xticklabels(h.get_xticklabels(), rotation=45)
#Most frequently occuring Tri-grams def get_top_n3_words(corpus, n=None): vec1 = CountVectorizer(ngram_range=(3,3), max_features=2000).fit(corpus) bag_of_words = vec1.transform(corpus) sum_words = bag_of_words.sum(axis=0) words_freq = [(word, sum_words[0, idx]) for word, idx in vec1.vocabulary_.items()] words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True) return words_freq[:n] top3_words = get_top_n3_words(corpus, n=20) top3_df = pandas.DataFrame(top3_words) top3_df.columns=["Tri-gram", "Freq"] print(top3_df) #Barplot of most freq Tri-grams import seaborn as sns sns.set(rc={'figure.figsize':(13,8)}) j=sns.barplot(x="Tri-gram", y="Freq", data=top3_df) j.set_xticklabels(j.get_xticklabels(), rotation=45)
Преобразование в матрицу целых чисел
Следующим шагом по уточнению количества слов является векторизатор TF-IDF. Недостаток простого подсчета слов, полученного с помощью countVectoriser, заключается в том, что большое количество определенных общих слов может ослабить влияние более специфичных для контекста слов в корпусе. Это преодолевается векторизатором TF-IDF, который штрафует слова, которые встречаются в документе несколько раз. TF-IDF - это оценка частоты встречаемости слов, которая выделяет слова, которые более важны для контекста, чем те, которые часто встречаются в документах.
TF-IDF состоит из 2-х компонентов:
- TF - частота сроков
- IDF - частота обратных документов
from sklearn.feature_extraction.text import TfidfTransformer tfidf_transformer=TfidfTransformer(smooth_idf=True,use_idf=True) tfidf_transformer.fit(X) # get feature names feature_names=cv.get_feature_names() # fetch document for which keywords needs to be extracted doc=corpus[532] #generate tf-idf for the given document tf_idf_vector=tfidf_transformer.transform(cv.transform([doc]))
Основываясь на оценках TF-IDF, мы можем извлечь слова с наивысшими оценками, чтобы получить ключевые слова для документа.
#Function for sorting tf_idf in descending order from scipy.sparse import coo_matrix def sort_coo(coo_matrix): tuples = zip(coo_matrix.col, coo_matrix.data) return sorted(tuples, key=lambda x: (x[1], x[0]), reverse=True) def extract_topn_from_vector(feature_names, sorted_items, topn=10): """get the feature names and tf-idf score of top n items""" #use only topn items from vector sorted_items = sorted_items[:topn] score_vals = [] feature_vals = [] # word index and corresponding tf-idf score for idx, score in sorted_items: #keep track of feature name and its corresponding score score_vals.append(round(score, 3)) feature_vals.append(feature_names[idx]) #create a tuples of feature,score #results = zip(feature_vals,score_vals) results= {} for idx in range(len(feature_vals)): results[feature_vals[idx]]=score_vals[idx] return results #sort the tf-idf vectors by descending order of scores sorted_items=sort_coo(tf_idf_vector.tocoo()) #extract only the top n; n here is 10 keywords=extract_topn_from_vector(feature_names,sorted_items,5) # now print the results print("\nAbstract:") print(doc) print("\nKeywords:") for k in keywords: print(k,keywords[k])
Заключительные замечания
В идеале, чтобы расчет IDF был эффективным, он должен основываться на большом корпусе и хорошо представлять текст, для которого необходимо извлечь ключевые слова. В нашем примере, если мы будем использовать полный текст статьи вместо аннотаций, извлечение IDF будет намного эффективнее. Однако, учитывая размер набора данных, я ограничил корпуса только резюме с целью демонстрации.
Это довольно простой подход для понимания фундаментальных концепций НЛП и предоставления хорошей практической практики с некоторыми кодами Python в реальных случаях использования. Тот же подход можно использовать для извлечения ключевых слов из лент новостей и социальных сетей.