使用NLP从文章中自动提取关键字详细指南
背景
在研究和新闻文章中,关键词构成了一个重要的组成部分,因为它们提供了文章内容的简洁表示。关键词在从信息检索系统,书目数据库和搜索引擎优化中定位文章方面也起着至关重要的作用。关键词还有助于将文章分类为相关主题或学科。
提取关键词的传统方法涉及基于文章内容和作者的判断手动分配关键词。这涉及大量时间和精力,并且在选择适当的关键字方面也可能不准确。随着自然语言处理(NLP)的出现,关键字提取已经发展为有效且高效。
在本文中,我们将结合这两者 - 我们将在一系列文章上应用NLP来提取关键字。
关于数据集
在本文中,我们将从包含大约3,800个摘要的机器学习数据集中提取关键字。机器学习原始数据集来自Kaggle - NIPS Paper(https://www.kaggle.com/benhamner/nips-papers/home)。神经信息处理系统(NIPS)是世界顶级机器学习会议之一。该数据集包括迄今为止所有NIPS论文的标题和摘要(从1987年的第一次会议到当前的2016年会议)。
原始数据集还包含文章文本。然而,由于重点是理解关键字提取的概念,使用全文可能需要大量的计算,所以只有摘要被用于NLP建模。可以在全文中使用相同的代码块来获得更好的增强关键字提取。
高级方法
导入数据集
本文使用的数据集是Kaggle上NIPS Paper数据集中提供的papers.csv数据集的子集。仅使用了包含摘要的那些行。标题和摘要已连接在一起,之后文件将另存为制表符分割的* .txt文件。
import pandas # load the dataset dataset = pandas.read_csv('papers2.txt', delimiter = ' ') dataset.head()
我们可以看到,机器学习数据集包含文章ID,发布年份和摘要。
初步文本探索
在我们继续进行任何文本预处理之前,建议您根据字数,最常见和最不常见的单词快速浏览数据集。
获取每个摘要的字数
#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
文字预处理
文本预处理的目标
稀疏性:在文本挖掘中,基于词频创建巨大的矩阵,其中许多单元具有零值。这个问题被称为稀疏性,并使用各种技术最小化。
文本预处理可分为两大类 - 噪声消除和归一化。对核心文本分析而言冗余的数据组件可视为噪声。
文本预处理
处理同一个单词的多个出现/表示称为标准化。归一化有两种类型 - 词干提取和词形还原。让我们考虑一下learn这个词的各种版本的例子 - learn, learned, learning, learner。归一化会将所有这些单词转换为单个标准化版本 - “learn”。
词干通过删除后缀来归一化文本。
Lemmatisation是一种更先进的技术,它基于基于词根。
以下示例说明了词干和lemmatisation的工作方式:
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"))
要对我们的数据集执行文本预处理,我们将首先导入所需的Python库。
# 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
删除停止词(stopwords):停止词包括句子中的大量介词,代词,连词等。在分析文本之前,需要删除这些单词,以便经常使用的单词主要是与上下文相关的单词,而不是文本中使用的常用单词。
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)
词云
文本准备
语料库中的文本需要转换为可由机器学习算法解释的格式。这种转换有两部分 - 标记化和矢量化。
标记化是将连续文本转换为单词列表的过程。然后通过矢量化过程将单词列表转换为整数矩阵。矢量化也称为特征提取。
对于文本准备,我们使用bag of words模型,它忽略了单词的顺序,只考虑单词频率。
创建单词计数向量
作为转换的第一步,我们将使用CountVectoriser来标记文本并构建已知单词的词汇表。我们首先创建CountVectoriser类的变量“cv”,然后调用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-gram范围 - 我们希望查看单个单词,两个单词(bi-gram)和三个单词(tri-gram)组合的列表。
返回具有整个词汇长度的编码向量。
list(cv.vocabulary_.keys())[:10]
可视化前N个uni-grams, bi-grams & tri-grams
我们可以使用CountVectoriser来显示前20个unigrams,bi-gram和tri-gram。
#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)
最频繁出现的uni-grams的条形图
#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)
最频繁出现的bi-grams的条形图
#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)
最频繁发生的tri-grams的条形图
转换为整数矩阵
细化单词计数的下一步是使用TF-IDF向量化器。从countVectoriser获得的单词计数的不足之处在于,大量的某些常用单词可能会稀释语料库中更多上下文特定单词的影响。这被TF-IDF矢量化器克服,该矢量化器惩罚在整个文档中出现若干次的单词。TF-IDF是词频分数,它突出显示对上下文更重要的单词,而不是那些在文档中频繁出现的单词。
TF-IDF由2个组成部分组成:
- TF - TF意思是词频(Term Frequency)
- IDF - IDF意思是逆文本频率指数(Inverse Document Frequency)
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(" Abstract:") print(doc) print(" Keywords:") for k in keywords: print(k,keywords[k])
最后
理想情况下,对于IDF计算有效,它应该基于大型语料库和需要提取关键字的文本的良好代表。在我们的示例中,如果我们使用完整的文章文本而不是摘要,IDF提取将更加有效。但是,考虑到数据集的大小,我将语料库限制为仅用于演示目的的摘要。
这是一种相当简单的方法来理解NLP的基本概念,并在现实生活中使用一些python代码提供良好的实践练习。可以使用相同的方法从新闻源和社交媒体源中提取关键字。