头歌-机器学习在 NLP 中的实战
第2关:基于 K-Means 算法的文本聚类
任务描述
本关任务:根据本关所学有关文本聚类的知识,编写基于 K-Means 算法的文本聚类并通过所有测试用例。
相关知识
为了完成本关任务,你需要掌握:
文本聚类的思想;
使用 K-Means 算法进行文本聚类。
机器学习与 NLP
机器学习是一门多领域交叉学科,涉及概率论、统计学、逼近论等多门学科,专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。
而自然语言处理作为人工智能领域的一部分,机器学习算法在人工智能领域得到了很好的应用。其中,文本聚类即使一个很典型的例子,本实训将以文本聚类为例,介绍机器学习在自然语言处理领域的实际应用。
文本聚类
聚类又称群分析,是数据挖掘的一种重要的思想,聚类分析是由若干模式组成的,通常,模式是一个度量的向量,或者是多维空间中的一个点。聚类分析以相似性为基础,在一个聚类中的模式之间比不在同一聚类中的模式之间具有更多的相似性。
文本聚类则聚类是在文本方向上的应用,首先我们要把一个个文档的自然语言转换成数学信息,这样形成高维空间点之后再去计算点与点之间的距离,然后将这些距离比较近的聚成一个簇,这些簇的中心成为簇心。而我们做的就是保证簇内点的距离足够近,簇与簇的距离足够远。
1、分词处理
我们要把中文文章要进行分词,这一点中文文章和英文文章有一些区别,因为英文单词是单个构成的,也就不需要分词了,而中文是需要分词的,并且中文之间有一些词尽管大量出现,但是对于文章的分类结构起不到太大的意义,比如“的”、“了”等等,因此,首先我们就加入一个停用词表,在进行分词的时候进行去掉。
2、分词后将分词转换为词向量
关于词向量我们有一些比较常用的模型,比如 BOW 词袋模型,连续词袋模型( CBOW )和 Skip-Gram 模型等等,在本实训中我们使用的是 BOW 词袋模型,在转换为词向量值时,我们要将其转换成 tf-idf 矩阵, tf-idf 其实可以看作是对提取的特征的一次加权,是根据一个单词在当前文章中出现的频率和该单词在所有语料中出现的频率评估一个单词的重要性,当一个单词在这篇文章中出现的次数很多的时候,这个词语更加重要;但如果它在所有文章中出现的次数都很多,那么它就显得不那么重要。
3、选择聚类算法
常用的聚类算法有 K-means 、 DBSCAN 和 LDA ,这几种算法用的最多,但在高维空间里中 K-means 并不是很好,究其原因是因为维度太高,簇与簇之间的距离太小了,如果直接去聚类,这一部分似乎效果不太好,这时候就需要用到主成分分析 PCA ,大致的思路是大致意思就是取这个高维向量中方差最大的方向经过一些数学变换将有用的部分保留,没用的部分舍弃,这种办法同样适合分类算法中寻找最大的特征。
import jieba
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.cluster import KMeans
class KmeansClustering():
def __init__(self, stopwords_path=None):
self.stopwords = self.load_stopwords(stopwords_path)
self.vectorizer = CountVectorizer()
self.transformer = TfidfTransformer()
def load_stopwords(self, stopwords=None):
# 加载停用词
if stopwords:
with open(stopwords, 'r', encoding='utf-8') as f:
return [line.strip() for line in f]
else:
return []
def preprocess_data(self, corpus_path):
# 文本预处理,每行一个文本
corpus = []
with open(corpus_path, 'r', encoding='utf-8') as f:
for line in f:
corpus.append(' '.join([word for word in jieba.lcut(line.strip()) if word not in self.stopwords]))
return corpus
def get_text_tfidf_matrix(self, corpus):
# 获取tfidf矩阵
tfidf = self.transformer.fit_transform(self.vectorizer.fit_transform(corpus))
# 获取tfidf矩阵中权重
weights = tfidf.toarray()
return weights
def kmeans(self, corpus_path, n_clusters=2):
"""
KMeans文本聚类
:param corpus_path: 语料路径(每行一篇),文章id从0开始
:param n_clusters: :聚类类别数目
:return: {cluster_id1:[text_id1, text_id2]}
"""
corpus = self.preprocess_data(corpus_path)
weights = self.get_text_tfidf_matrix(corpus)
result = {}
#任务:完成基于K-Means算法的文本聚类,并将结果保存到result变量中。
# ********** Begin *********#
clf = KMeans(n_clusters=n_clusters)
result = clf.fit_predict(weights)
# ********** End **********#
return result
第3关:基于 DBSCAN 的文本聚类
任务描述
本关任务:根据本关所学有关 DBSCAN 文本聚类的知识,编写基于 DBSCAN 算法的文本聚类并通过所有测试用例。
相关知识
为了完成本关任务,你需要掌握:
DBSCAN 聚类的思想;
使用 DBSCAN 算法进行文本聚类。
DBSCAN 聚类简介
DBSCAN 属于密度聚类算法,该算法通过在样本空间中不断搜索最大集合完成聚类,能够在带有噪点的样本空间中发现任意形状的聚类并排除噪点。DBSCAN 算法不需要预先指定聚类数量,但对用户设定的参数非常敏感。当空间聚类的密度不均匀、聚类间距差相差很大时,聚类质量较差。
DBSCAN 的基本假设是一个集群的密度要显著高于噪声点的密度。因此,其基本思想是对于集群中的每一个点,在给定的半径范围内,相邻点的数量必须超过预先设定的某一个阈值。DBSCAN 算法中包含两个重要的参数:
eps:聚类类别中样本的相似度衡量,与类别内样本相似度成反比。可以理解为同一个类别当中,对两个样本之间距离的最大值限定;
min_samples:每个聚类类别中的最小样本数,会对未分类样本数量造成影响,与未分类样本数量成正比。当相似样本数量少于该参数时,不会聚到一起。
在实际应用过程中,根据样本的大小,以及样本的大致分布,了解聚类结果会随着这两个参数如何变化之后,可以根据自己的经验对两个参数进行调整。只有两个模型参数需要调整,因此调参过程也不会太麻烦。
第4关:基于机器学习的情感分析
任务描述
本关任务:根据所学知识,完成右侧对应的练习题。
相关知识
为了完成本关任务,你需要掌握:
基于机器学习的情感分析算法思想;
基于机器学习的情感分析算法实现。
基于机器学习的情感分析简介
目前,情感倾向分析的方法主要分为两类:一种是基于情感词典的方法;一种是基于机器学习的方法,如基于大规模语料库的机器学习。前者需要用到标注好的情感词典,英文的词典有很多,中文主要有知网整理的情感词典 Hownet 和台湾大学整理发布的 NTUSD 两个情感词典,还有哈工大信息检索研究室开源的《同义词词林》可以用于情感词典的扩充。基于机器学习的方法则需要大量的人工标注的语料作为训练集,通过提取文本特征,构建分类器来实现情感的分类。
机器学习的方法精确度更高,因为词典匹配会由于语义表达的丰富性而出现很大误差,而机器学习方法不会。而且 它可使用的场景更多样。无论是主客观分类还是正负面情感分类,机器学习都可以完成任务。而无需像词典匹配那 样要深入到词语、句子、语法这些层面。
而词典方法适用的语料范围更广,无论是手机、电脑这些商品,还是书评、影评这些语料,都可以适用。但机器学 习则极度依赖语料,把手机语料训练出来的的分类器拿去给书评分类,那是注定要失败的。使用机器学习进行情感分析,可以换一个相同意思的说法,就是用有监督的(需要人工标注类别)机器学习方法来 对文本进行分类。
这点与词典匹配有着本质的区别。词典匹配是直接计算文本中的情感词,得出它们的情感倾向分值。而机器学习方法的思路是先选出一部分表达积极情感的文本和一部分表达消极情感的文本,用机器学习方法进行训练,获得一个情感分类器。再通过这个情感分类器对所有文本进行积极和消极的二分分类。最终的分类可以为文本给出0或1这样的类别,也可以给出一个概率值,比如这个文本的积极概率是90%,消极概率是10%。
基于机器学习的情感分析算法实现
1、准备数据集
先以带有正向标签和负向标签的评论语料作为训练集用以训练分类器,剩余带有正向标签和负向标签的评论语料作为测试集测试不同分类算法、不同特征提取方法、不同维度的准确度。
最后选择准确度最高的方案,将上述带有正向标签和负向标签的评论语料作为训练集训练最终存储的分类器。
2、对语料进行分词
利用 python 程序包中文分词工具 python 对语料进行分词。
3、特征提取
提取语料中的文本特征。
4、特征选择
在计算整个语料里面所有的信息量后,根据信息量进行倒序排序,选择排名靠前的信息量的词,把选出的这些词作为特征。
5、分割数据及赋予类标签
对数据集进行处理,给数据贴上标签。
6、训练模型
使用训练集用不同的分类算法训练分类器,用分类器对开发测试集里面的数据进行分类,给出分类预测的标签, 对比分类标签和人工标注的差异,计算出准确度。
7、选择分类器并存储
在完成机器学习的训练后,我们需要将分类器进行存储,以便后续的使用。
import xlwt
import pickle
import itertools
import nltk
import os
import sklearn
from nltk.collocations import BigramCollocationFinder
from nltk.metrics import BigramAssocMeasures
from nltk.probability import FreqDist, ConditionalFreqDist
from nltk.classify.scikitlearn import SklearnClassifier
from sklearn.svm import SVC, LinearSVC, NuSVC
from sklearn.naive_bayes import MultinomialNB, BernoulliNB
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
pos_f = 'src/step3/pkl_data/1000/pos_review.pkl'
neg_f = 'src/step3/pkl_data/1000/neg_review.pkl'
def load_data(): # 加载训练集数据
global pos_review, neg_review
pos_review = pickle.load(open(pos_f, 'rb'))
neg_review = pickle.load(open(neg_f, 'rb'))
def create_word_bigram_scores(): # 计算整个语料里面每个词和双词搭配的信息量
posdata = pickle.load(open(pos_f, 'rb'))
negdata = pickle.load(open(neg_f, 'rb'))
posWords = list(itertools.chain(*posdata))
negWords = list(itertools.chain(*negdata))
bigram_finder = BigramCollocationFinder.from_words(posWords)
posBigrams = bigram_finder.nbest(BigramAssocMeasures.chi_sq, 5000)
bigram_finder = BigramCollocationFinder.from_words(negWords)
negBigrams = bigram_finder.nbest(BigramAssocMeasures.chi_sq, 5000)
pos = posWords + posBigrams # 词和双词搭配
neg = negWords + negBigrams
word_fd = FreqDist()
cond_word_fd = ConditionalFreqDist()
for word in pos:
word_fd[word] += 1
cond_word_fd["pos"][word] += 1
for word in neg:
word_fd[word] += 1
cond_word_fd["neg"][word] += 1
pos_word_count = cond_word_fd['pos'].N()
neg_word_count = cond_word_fd['neg'].N()
total_word_count = pos_word_count + neg_word_count
word_scores = {}
for word, freq in word_fd.items():
pos_score = BigramAssocMeasures.chi_sq(cond_word_fd['pos'][word], (freq, pos_word_count), total_word_count) # 计算积极词的卡方统计量,这里也可以计算互信息等其它统计量
neg_score = BigramAssocMeasures.chi_sq(cond_word_fd['neg'][word], (freq, neg_word_count), total_word_count)
word_scores[word] = pos_score + neg_score
return word_scores
def find_best_words(word_scores, number): # 根据信息量进行倒序排序,选择排名靠前的信息量的词
best_vals = sorted(word_scores.items(), key=lambda w_s: w_s[1], reverse=True)[:number] # 把词按信息量倒序排序。number是特征的维度,是可以不断调整直至最优的
best_words = set([w for w, s in best_vals])
return best_words
def pos_features(feature_extraction_method): # 赋予积极的文本类标签
posFeatures = []
for i in pos_review:
posWords = [feature_extraction_method(i), 'pos'] # 为积极文本赋予"pos"
posFeatures.append(posWords)
return posFeatures
def neg_features(feature_extraction_method): # 赋予消极的文本类标签
negFeatures = []
for j in neg_review:
negWords = [feature_extraction_method(j), 'neg'] # 为消极文本赋予"neg"
negFeatures.append(negWords)
return negFeatures
def best_word_features(words): # 把选出的这些词作为特征(这就是选择了信息量丰富的特征)
global best_words
return dict([(word, True) for word in words if word in best_words])
def score(classifier):
# 任务:构建分类器模型并进行训练
# ********** Begin *********#
def store_classifier():
load_data()
word_scores = create_word_bigram_scores()
global best_words
best_words = find_best_words(word_scores, 7500)
posFeatures = pos_features(best_word_features) # 选出积极的语料
negFeatures = neg_features(best_word_features) # 选出负面的语料
trainSet = posFeatures + negFeatures # 创建训练集
MultinomialNB_classifier = SklearnClassifier(MultinomialNB()) # 构建分类器
MultinomialNB_classifier.train(trainSet) # 训练分类器
pickle.dump(MultinomialNB_classifier, open('../out/classifier.pkl', 'wb')) # 保存分类器
# ********** End **********#
pred = classifier.classify_many(dev) # 对开发测试集的数据进行分类,给出预测的标签
return accuracy_score(tag_dev, pred) # 对比分类预测结果和人工标注的正确结果,给出分类器准确度
# 使用测试集测试分类器的最终效果
def use_the_best():
word_scores = create_word_bigram_scores() # 使用词和双词搭配作为特征
best_words = find_best_words(word_scores, 4000) # 特征维度1500
load_data()
posFeatures = pos_features(best_word_features, best_words)
negFeatures = neg_features(best_word_features, best_words)
cut_data(posFeatures, negFeatures)
trainSet = posFeatures[1500:] + negFeatures[1500:] # 使用了更多数据
testSet = posFeatures[:500] + negFeatures[:500]
test, tag_test = zip(*testSet)
# 存储分类器
def final_score(classifier):
classifier = SklearnClassifier(classifier)
classifier.train(trainSet)
pred = classifier.classify_many(test)
return accuracy_score(tag_test, pred)
print(final_score(MultinomialNB())) # 使用开发集中得出的最佳分类器
# 把分类器存储下来(存储分类器和前面没有区别,只是使用了更多的训练数据以便分类器更为准确)
def store_classifier():
load_data()
word_scores = create_word_bigram_scores()
global best_words
best_words = find_best_words(word_scores, 7500)
posFeatures = pos_features(best_word_features)
negFeatures = neg_features(best_word_features)
trainSet = posFeatures + negFeatures
MultinomialNB_classifier = SklearnClassifier(MultinomialNB())
MultinomialNB_classifier.train(trainSet)
pickle.dump(MultinomialNB_classifier, open('src/step3/out/classifier.pkl', 'wb'))
以上内容仅供本人学习参考