全文基于《射雕英雄传》语料库,下面是读入数据的一个基于Pandas的通用操作框架。
#准备工作,具体可参照学习笔记一,将处理的文章分章节
import pandas as pd
# 有的环境配置下read_table出错,也可用用read_csv
raw = pd.read_table("金庸-射雕英雄传txt精校版.txt",
names = ['txt'], encoding ="GBK")
# 章节判断用变量预处理
def m_head(tmpstr):
return tmpstr[:1]
def m_mid(tmpstr):
return tmpstr.find("回 ")
#取出进行判断的变量
raw['head'] = raw.txt.apply(m_head)
raw['mid'] = raw.txt.apply(m_mid)
raw['len'] = raw.txt.apply(len)#防止特殊情况,设置长度
chapnum = 0
for i in range(len(raw)):
if raw['head'][i] == "第" and raw['mid'][i] > 0 and raw['len'][i] < 30 :
chapnum += 1
if chapnum >= 40 and raw['txt'][i] == "附录一:成吉思汗家族" :
chapnum = 0
raw.loc[i, 'chap'] = chapnum
# 删除临时变量,这里必须删除,否则后续sum函数处会出错
del raw['head']
del raw['mid']
del raw['len']
rawgrp = raw.groupby('chap')
chapter = rawgrp.agg(sum) # 只有字符串列的情况下,sum函数自动转为合并字符串
chapter = chapter[chapter.index != 0]#排除前言和附录
计算两个词相似度的原理:简单的说,就是将每个词的向量在空间上进行余弦运算,当cos越接近0时候,两者越相似。
词袋模型不考虑词条之间的相关性,因此无法用于计算词条相似度。
分布式表达会考虑词条的上下文关联,因此能够提取出词条上下文中的相关性信息,而词条之间的相似度就可以直接利用此类信息加以计算。
目前主要使用gensim实现相应的算法。
gensim 通用格式list of list格式
gensim也提供了sklearn的API接口:sklearn_api.w2vmodel,可以在sklearn中直接使用。
class gensim.models.word2vec.Word2Vec(
sentences = None : 类似list of list的格式,对于特别大的文本,尽量考虑流式处理
size = 100 : 词条向量的维度,数据量充足时,300/500的效果会更好
window = 5 : 上下文窗口大小
workers = 3 : 同时运行的线程数,多核系统可明显加速计算
其余细节参数设定:
min_count = 5 : 低频词过滤阈值,低于该词频的不纳入模型
max_vocab_size = None : 每1千万词条需要1G内存,必要时设定该参数以节约内存
sample=0.001 : 负例采样的比例设定
negative=5 : 一般为5-20,设为0时不进行负例采样
iter = 5 : 模型在语料库上的迭代次数,该参数将被取消
与神经网络模型有关的参数设定:
seed=1, alpha=0.025, min_alpha=0.0001, sg=0, hs=0
)
chapter.head()
txt | |
---|---|
chap | |
1.0 | 第一回 风雪惊变 那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。只听他两... |
2.0 | 第二回 江南七怪 颜烈跨出房门,过道中一个中年士人拖着鞋皮,踢跶踢跶的直响,一路打着哈... |
3.0 | 第三回 黄沙莽莽 寺里僧众见焦木圆寂,尽皆悲哭。有的便为伤者包扎伤处,抬入客舍。 忽... |
4.0 | 第四回 黑风双煞 完颜洪熙笑道:“好,再打他个痛快。”蒙古兵前哨报来:“王罕亲自前来迎... |
5.0 | 第五回 弯弓射雕 一行人下得山来,走不多时,忽听前面猛兽大吼声一阵阵传来。韩宝驹一提缰... |
生成list of list格式,注意方法,后面要用到
# 分词和预处理,生成list of list格式
import jieba
chapter['cut'] = chapter.txt.apply(jieba.lcut)
chapter.head()
txt | cut | |
---|---|---|
chap | ||
1.0 | 第一回 风雪惊变 那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。只听他两... | [第一回, , 风雪, 惊变, , , , , , , 那, 说话, 人, 五... |
2.0 | 第二回 江南七怪 颜烈跨出房门,过道中一个中年士人拖着鞋皮,踢跶踢跶的直响,一路打着哈... | [第二回, , 江南七怪, , , , , 颜烈, 跨出, 房门, ,, 过道, ... |
3.0 | 第三回 黄沙莽莽 寺里僧众见焦木圆寂,尽皆悲哭。有的便为伤者包扎伤处,抬入客舍。 忽... | [第三回, , 黄沙, 莽莽, , , , , 寺里, 僧众, 见, 焦木, 圆寂... |
4.0 | 第四回 黑风双煞 完颜洪熙笑道:“好,再打他个痛快。”蒙古兵前哨报来:“王罕亲自前来迎... | [第四回, , 黑风双, 煞, , , , , 完颜洪熙, 笑, 道, :, “,... |
5.0 | 第五回 弯弓射雕 一行人下得山来,走不多时,忽听前面猛兽大吼声一阵阵传来。韩宝驹一提缰... | [第五回, , 弯弓, 射雕, , , , , 一行, 人下, 得, 山来, ,,... |
# 初始化word2vec模型和词表
from gensim.models.word2vec import Word2Vec
n_dim = 300 # 指定向量维度,大样本量时300~500较好
w2vmodel = Word2Vec(size = n_dim, min_count = 10)
w2vmodel.build_vocab(chapter.cut) # 生成词表
w2vmodel#这是生成的模型
word2vecmodel.train(
sentences : iterable of iterables格式,对于特别大量的文本,尽量考虑流式处理
total_examples = None : 句子总数,int,可直接使用model.corpus_count指定
total_words = None : 句中词条总数,int,该参数和total_examples至少要指定一个
epochs = None : 模型迭代次数,需要指定
其他带默认值的参数设定:
start_alpha=None, end_alpha=None, word_count=0, queue_factor=2,
report_delay=1.0, compute_loss=False, callbacks=()
)
# 在评论训练集上建模(大数据集时可能会花费几分钟)
# 本例消耗内存较少,w2vmodel.corpus_count是模型可以直接返回的值
w2vmodel.train(chapter.cut, \
total_examples = w2vmodel.corpus_count, epochs = 10)
(3444321, 6113050)
# 训练完毕的模型实质
print(w2vmodel.wv["郭靖"].shape)#因为前面规定了是300
w2vmodel.wv["郭靖"]
(300,)
array([-5.71486771e-01, -6.11459553e-01, 9.88356650e-01, -2.19969201e+00,
-6.24286681e-02, -2.08215073e-01, -4.94846284e-01, 4.11184639e-01,
3.51217866e-01, -7.16320872e-01, 2.41676435e-01, -5.17797768e-01,
......
-5.74352980e-01, 3.34270656e-01, -8.82370621e-02, -2.53521979e-01,
6.62185490e-01, -1.17333210e+00, -5.62149405e-01, -1.21558267e-04,
-1.54235452e-01, 4.67433631e-01, -3.35694969e-01, -1.63811371e-01],
dtype=float32)
w2v模型的保存和复用:
w2vmodel.save(存盘路径及文件名称)
w2vmodel.load(存盘路径及文件名称)
w2vmodel.wv.most_similar(词条)
w2vmodel.wv.most_similar("郭靖",topn = 20)#又结果可以看出和黄蓉相似度最高
[('黄蓉', 0.932035505771637),
('欧阳克', 0.8728183507919312),
('穆念慈', 0.7740538120269775),
('梅超风', 0.7715764045715332),
('李萍', 0.7549037337303162),
('完颜康', 0.7546292543411255),
('黄药师', 0.7424267530441284),
('陆冠英', 0.7395749688148499),
('杨康', 0.7385720014572144),
('鲁有脚', 0.7347890138626099),
('周伯通', 0.7241591811180115),
('裘千仞', 0.722597599029541),
('欧阳锋', 0.7211623787879944),
('华筝', 0.7194427847862244),
('洪七公', 0.7096649408340454),
('程瑶迦', 0.7055790424346924),
('穆易', 0.7018767595291138),
('那公子', 0.6987137198448181),
('那道人', 0.6943396329879761),
('一灯', 0.6929605007171631)]
# 寻找对应关系
w2vmodel.wv.most_similar(positive=['郭靖', '小红马'], \
negative=['黄药师'], topn = 5)#positive negative可以省
#意思为由'郭靖'和'小红马'的关系,寻找'黄药师'的对应关系
[('奔', 0.8444687128067017),
('过去', 0.7988641858100891),
('晕', 0.7967531681060791),
('刚', 0.7961419224739075),
('几步', 0.7911766767501831)]
# 计算两个词的相似度/相关程度
print(w2vmodel.wv.similarity("郭靖", "黄蓉"))
print(w2vmodel.wv.similarity("郭靖", "杨康"))
print(w2vmodel.wv.similarity("郭靖", "杨铁心"))
0.93203545
0.73857194
0.6454911
# 寻找不合群的词
w2vmodel.wv.doesnt_match("小红马 黄药师 鲁有脚".split())
'小红马'
sklearn.metrics.pairwise.pairwise_distances(
X : 用于计算距离的数组
[n_samples_a, n_samples_a] if metric == 'precomputed'
[n_samples_a, n_features] otherwise
Y = None : 用于计算距离的第二数组,当metric != 'precomputed’时可用
metric = ‘euclidean’ : 空间距离计算方式
scikit-learn原生支持 : ['cityblock', 'cosine', 'euclidean',
'l1', 'l2', 'manhattan'],可直接使用稀疏矩阵格式
来自scipy.spatial.distance : ['braycurtis', 'canberra',
'chebyshev', 'correlation', 'dice', 'hamming', 'jaccard',
'kulsinski', 'mahalanobis', 'matching', 'minkowski',
'rogerstanimoto', 'russellrao', 'seuclidean', 'sokalmichener',
'sokalsneath', 'sqeuclidean', 'yule'] 不支持稀疏矩阵格式
n_jobs = 1 : 用于计算的线程数,为-1时,所有CPU内核都用于计算
)
#得出词频矩阵
cleanchap = [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5]]
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer()
resmtx = countvec.fit_transform(cleanchap)
resmtx
<5x11623 sparse matrix of type ''
with 16884 stored elements in Compressed Sparse Row format>
from sklearn.metrics.pairwise import pairwise_distances
pairwise_distances(resmtx, metric = 'cosine')#得出两两的关联度
#结果阅读竖着和横着都表示第几章,同理,对角线看一半即可
array([[0. , 0.63104216, 0.77665559, 0.78745025, 0.82985512],
[0.63104216, 0. , 0.62572437, 0.61666388, 0.73192845],
[0.77665559, 0.62572437, 0. , 0.51645443, 0.5299046 ],
[0.78745025, 0.61666388, 0.51645443, 0. , 0.42108002],
[0.82985512, 0.73192845, 0.5299046 , 0.42108002, 0. ]])
pairwise_distances(resmtx) # 默认值为euclidean,欧式距离,得出词条的空间直线距离
array([[ 0. , 290.85391522, 312.60678176, 315.54872841,
311.46267834],
[290.85391522, 0. , 266.95130642, 265.77622166,
277.24898557],
[312.60678176, 266.95130642, 0. , 233.9615353 ,
226.09290126],
[315.54872841, 265.77622166, 233.9615353 , 0. ,
202.57344347],
[311.46267834, 277.24898557, 226.09290126, 202.57344347,
0. ]])
# 使用TF-IDF矩阵进行相似度计算
from sklearn.feature_extraction.text import TfidfTransformer
txtlist = [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5]]
vectorizer = CountVectorizer() #构造一个实例,联想C语言
X = vectorizer.fit_transform(txtlist) # 将文本中的词语转换为词频矩阵
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(X) #基于词频矩阵X计算TF-IDF值
tfidf#结果是一个稀疏矩阵,较大,压缩存储,所以结果没有展示出来
pairwise_distances(tfidf[:5], metric = 'cosine')
array([[0. , 0.69112864, 0.84741572, 0.85725434, 0.89178365],
[0.69112864, 0. , 0.74381846, 0.70586805, 0.81759092],
[0.84741572, 0.74381846, 0. , 0.60083695, 0.63539482],
[0.85725434, 0.70586805, 0.60083695, 0. , 0.54123168],
[0.89178365, 0.81759092, 0.63539482, 0.54123168, 0. ]])
主要是用于基于LDA计算余弦相似度
需要使用的信息:
拟合完毕的lda模型
按照拟合模型时矩阵种类转换的需检索文本
需检索的文本
建模时使用的字典
#计算相似矩阵,不过很难使用
from gensim import similarities
simmtx = similarities.MatrixSimilarity(corpus)
simmtx
检索和第1章内容最相似(所属主题相同)的章节
simmtx = similarities.MatrixSimilarity(corpus) # 使用的矩阵种类需要和拟合模型时相同
simmtx
simmtx.index[:2]#一列为一章节,内容为其相应词条在空间的坐标值
array([[0.00370857, 0.11125696, 0.00370857, ..., 0. , 0. ,
0. ],
[0. , 0.15784042, 0. , ..., 0. , 0. ,
0. ]], dtype=float32)
# 使用gensim的LDA拟合结果进行演示
query = chapter.txt[1]
query_bow = dictionary.doc2bow(m_cut(query))#转化为bow向量
lda_vec = ldamodel[query_bow] # 转换为lda模型下的向量
sims = simmtx[lda_vec] # 进行矩阵内向量和所提供向量的余弦相似度查询
sims = sorted(enumerate(sims), key=lambda item: -item[1])#进行排序
sims#有结果可以看出,和第一章相似度最的就是第一章
#有于数据量等问题,较好的拟合后的模型应该是接近于1的
[(38, 0.011200049),
(1, 0.009635941),
(26, 0.0077462136),
......
(20, 0.0016734296),
(19, 0.0012708347),
(13, 0.0012344222)]
doc2vec建议多大量文本时候使用
word2vec用来计算词条相似度非常合适。
较短的文档如果希望计算文本相似度,可以将各自内部的word2vec向量分别进行平均,用平均后的向量作为文本向量,从而用于计算相似度。
但是对于长文档,这种平均的方式显然过于粗糙。
doc2vec是word2vec的拓展,它可以直接获得sentences/paragraphs/documents的向量表达,从而可以进一步通过计算距离来得到sentences/paragraphs/documents之间的相似性。
模型概况
分析目的:获得文档的一个固定长度的向量表达。
数据:多个文档,以及它们的标签,一般可以用标题作为标签。
影响模型准确率的因素:语料的大小,文档的数量,越多越高;文档的相似性,越相似越好。
import jieba
import gensim
from gensim.models import doc2vec
def m_doc(doclist):
reslist = []
for i, doc in enumerate(doclist):
reslist.append(doc2vec.TaggedDocument(jieba.lcut(doc), [i]))#添加每个文档的标题
return reslist
corp = m_doc(chapter.txt)
corp[:2]
[TaggedDocument(words=['第一回', ' ', '风雪', '惊变', '那', '说话', '人', '五十', '来', '岁', '年纪', ',', '一件', '青布', '长袍', '早洗', '得', '褪成', '了', '蓝灰', '带白', '。', '只',...... '听', '他', '两片', '梨花', '木, '过', '了', '一会', ',', '颜烈', '道', ':', '“', '娘子', '请', '自', '宽', '便', ',', '小人', '出去', '买', '了', '物品', '就', '回', '。', '”', '包惜弱', '点', '了', '点头', ',', '道', ':', '“', '相公', '可别', '太多', '花费', '了', '。', '”', '颜烈', '微笑', '道', ':', '“', '就', '可惜', '娘子', '在', '服丧', ',', '不能', '戴用', '珠宝', ',', '要', '多花钱', '也', '花', '不了', '。', '”', '], tags=[0]),
TaggedDocument(words=['第二回', ' ', '江南七怪', '颜烈', '跨出', '房门', ',', '过道', '中', '一个', ......'中年', '士人', '拖', '着', '鞋', '皮', ',', '踢', '跶', '踢', '跶', '的', '直响', ',', '一路打', '着', '哈欠', '迎面', '过来', ',', '那', '士人', '似笑非笑', ',', '挤眉弄眼', ',', ''没', '洗脸', '了', ',', '拿', '着', '一', '柄', '破烂', '的', '油纸', '黑扇', ',', '边摇边行', '。段天德', '吓', '得', '魂不附体', ',', '哪里', '还敢', '停留', ',', '拉', '了', '李萍', ',', '急奔', '而出', '。', '李萍大', '叫', ':', '“', '救命', '啊', ',', '我', '不', '去', ',', '救命', '啊', '!', '”', '终于', '声音', '越来越', '远', '。], tags=[1])]
#进行模型的设定与拟合
d2vmodel = gensim.models.Doc2Vec(vector_size = 300,
window = 20, min_count = 5)#300维,窗口为20
%time d2vmodel.build_vocab(corp)#进行拟合
Wall time: 1.88 s
d2vmodel.wv.vocab#查看生成的结果,为每一个词都生成一个向量
{' ': ,
'风雪': ,
'那': ,
'说话': ,
......
'曲三': ,
'掘': ,
'最后': ,
'一具': ,
...}
# 如果有个新文本想要呈现的话,将新文本转换为相应维度空间下的向量
newvec = d2vmodel.infer_vector(jieba.lcut(chapter.txt[1]))
#检索出和新文本相比最相似的章节
d2vmodel.docvecs.most_similar([newvec], topn = 10)#如果只有一个list,需要打上[]
[(33, 0.17437607049942017),
(36, 0.10029015690088272),
(22, 0.07945042848587036),
(18, 0.04506886377930641),
(14, 0.03940583020448685),
(23, 0.035881608724594116),
(6, 0.03350480645895004),
(31, 0.03314536064863205),
(16, 0.028236132115125656),
(13, 0.022862425073981285)]
在得到文档相似度的计算结果后,文档聚类问题在本质上已经和普通的聚类分析没有区别。
注意:最常用的Kmeans使用的是平方欧氏距离,这在文本聚类中很可能无法得到最佳结果。
算法的速度和效果同样重要。
# 为章节增加名称标签
chapter.index = [raw.txt[raw.chap == i].iloc[0] for i in chapter.index]
chapter.head()
txt | cut | |
---|---|---|
第一回 风雪惊变 | 第一回 风雪惊变 那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。只听他两... | [第一回, , 风雪, 惊变, , , , , , , 那, 说话, 人, 五... |
第二回 江南七怪 | 第二回 江南七怪 颜烈跨出房门,过道中一个中年士人拖着鞋皮,踢跶踢跶的直响,一路打着哈... | [第二回, , 江南七怪, , , , , 颜烈, 跨出, 房门, ,, 过道, ... |
第三回 黄沙莽莽 | 第三回 黄沙莽莽 寺里僧众见焦木圆寂,尽皆悲哭。有的便为伤者包扎伤处,抬入客舍。 忽... | [第三回, , 黄沙, 莽莽, , , , , 寺里, 僧众, 见, 焦木, 圆寂... |
第四回 黑风双煞 | 第四回 黑风双煞 完颜洪熙笑道:“好,再打他个痛快。”蒙古兵前哨报来:“王罕亲自前来迎... | [第四回, , 黑风双, 煞, , , , , 完颜洪熙, 笑, 道, :, “,... |
第五回 弯弓射雕 | 第五回 弯弓射雕 一行人下得山来,走不多时,忽听前面猛兽大吼声一阵阵传来。韩宝驹一提缰... | [第五回, , 弯弓, 射雕, , , , , 一行, 人下, 得, 山来, ,,... |
#进行文本清理
import jieba
cuttxt = lambda x: " ".join(m_cut(x)) #循环
cleanchap = chapter.txt.apply(cuttxt)
cleanchap[:2]
第一回 风雪惊变 第一回 风雪 惊变 说话 五十 年纪 一件 青布 长袍 早洗 褪成 蓝灰 带白 两片 梨花 ...
第二回 江南七怪 第二回 江南七怪 颜烈 跨出 房门 过道 一个 中年 士人 直响 一路打 哈欠 迎面 士人 ...
Name: txt, dtype: object
# 计算TF-IDF矩阵
from sklearn.feature_extraction.text import TfidfTransformer
vectorizer = CountVectorizer()
wordmtx = vectorizer.fit_transform(cleanchap) # 将文本中的词语转换为词频矩阵
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(wordmtx) #基于词频矩阵计算TF-IDF值
tfidf
<40x43931 sparse matrix of type ''
with 129387 stored elements in Compressed Sparse Row format>
# 进行聚类分析
#kmeas用词频矩阵不合适,因此使用tfidf比较合适
from sklearn.cluster import KMeans
clf = KMeans(n_clusters = 5) #聚为5类,即:原来分40个章节,现在聚为4大类
s = clf.fit(tfidf)
print(s)
clf.cluster_centers_ #把中心的情况打印出来
KMeans(n_clusters=5)
array([[0. , 0. , 0. , ..., 0. , 0.00281383,
0. ],
[0.00048249, 0. , 0. , ..., 0.00098597, 0. ,
0.00048249],
[0. , 0. , 0. , ..., 0. , 0. ,
0. ],
[0. , 0. , 0. , ..., 0. , 0. ,
0. ],
[0. , 0.00080214, 0.00080214, ..., 0. , 0. ,
0. ]])
clf.cluster_centers_.shape
(5, 43931)
clf.labels_#分别看下每个章节都被归为哪一类
array([2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 1, 4, 1, 1, 1, 1, 1, 1, 1,
1, 4, 1, 1, 1, 1, 0, 0, 0, 1, 4, 1, 1, 1, 1, 1, 1, 1])
#为了便于阅读,直接把类别情况加入数据框中
chapter['clsres'] = clf.labels_
chapter.head()
txt | cut | clsres | |
---|---|---|---|
第一回 风雪惊变 | 第一回 风雪惊变 那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。只听他两... | [第一回, , 风雪, 惊变, , , , , , , 那, 说话, 人, 五... | 2 |
第二回 江南七怪 | 第二回 江南七怪 颜烈跨出房门,过道中一个中年士人拖着鞋皮,踢跶踢跶的直响,一路打着哈... | [第二回, , 江南七怪, , , , , 颜烈, 跨出, 房门, ,, 过道, ... | 2 |
第三回 黄沙莽莽 | 第三回 黄沙莽莽 寺里僧众见焦木圆寂,尽皆悲哭。有的便为伤者包扎伤处,抬入客舍。 忽... | [第三回, , 黄沙, 莽莽, , , , , 寺里, 僧众, 见, 焦木, 圆寂... | 3 |
第四回 黑风双煞 | 第四回 黑风双煞 完颜洪熙笑道:“好,再打他个痛快。”蒙古兵前哨报来:“王罕亲自前来迎... | [第四回, , 黑风双, 煞, , , , , 完颜洪熙, 笑, 道, :, “,... | 3 |
第五回 弯弓射雕 | 第五回 弯弓射雕 一行人下得山来,走不多时,忽听前面猛兽大吼声一阵阵传来。韩宝驹一提缰... | [第五回, , 弯弓, 射雕, , , , , 一行, 人下, 得, 山来, ,,... | 3 |
chapter.sort_values('clsres').clsres#进行排序
第三十回 一灯大师 0
第三十一回 鸳鸯锦帕 0
第二十九回 黑沼隐女 0
......
第十三回 五湖废人 4
第九回 铁枪破犁 4
Name: clsres, dtype: int32
#把对应类别的文档归为一个大文档在进行处理
chapgrp = chapter.groupby('clsres')
chapcls = chapgrp.agg(sum) # 只有字符串列的情况下,sum函数自动转为合并字符串
cuttxt = lambda x: " ".join(m_cut(x))
chapclsres = chapcls.txt.apply(cuttxt)
chapclsres
clsres
0 第二十九回 黑沼 隐女 郭靖 呼叫 召唤 小红马 地下 转眼之间 双雕 飞出 老远 雌雄 双...
1 第十四回 桃花 岛主 五男一女 走进 厅来 却是 江南 六怪 北南 故乡 日近 这天 太湖 ...
2 第一回 风雪 惊变 说话 五十 年纪 一件 青布 长袍 早洗 褪成 蓝灰 带白 两片 梨花 ...
3 第三回 黄沙 莽莽 寺里 僧众 焦木 圆寂 悲哭 伤者 包扎 伤处 抬入 客舍 巨钟 缸内 ...
4 第七回 比武招亲 江南 六怪 郭靖 晓行夜宿 东南 进发 非止 一日 大漠 草原 这天 张家...
Name: txt, dtype: object
# 列出关键词以刻画类别特征
import jieba.analyse as ana
ana.set_stop_words('停用词.txt')
#为了方便,直接使用jieba分词里面列关键词的功能
for item in chapclsres:
print(ana.extract_tags(item, topK = 10))
['黄蓉', '郭靖', '一灯', '黄蓉道', '那书生', '渔人', '一灯大师', '农夫', '段皇爷', '郭靖道']
['郭靖', '黄蓉', '欧阳锋', '黄药师', '洪七公', '周伯通', '裘千仞', '武功', '郭靖道', '欧阳克']
['丘处机', '杨铁心', '包惜弱', '郭啸天', '段天德', '颜烈', '完颜洪烈', '柯镇恶', '金兵', '道长']
['郭靖', '铁木真', '柯镇恶', '韩小莹', '梅超风', '哲别', '朱聪', '韩宝驹', '拖雷', '华筝']
['郭靖', '黄蓉', '洪七公', '完颜康', '师父', '彭连虎', '陆冠英', '梁子翁', '欧阳克', '穆念慈']
在计算词条相似度时进行停用词清理,然后再进行拟合,思考为什么会有这样的结果出现。
在基于词袋模型,使用原始词频计算文本余弦相似度时,比较清理停用词前后的结果。
#第一题进行停用词清理,在计算词条相似度
import jieba
import pandas as pd
tmpdf = pd.read_table('停用词.txt',names = ['w'], encoding = 'utf-8')
chapter['cut'] = chapter.txt.apply(jieba.lcut)
chapter['cut'] =[ w for w in chapter['cut'] if w not in list(tmpdf.w) ]
chapter.head()
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\A\AppData\Local\Temp\jieba.cache
Loading model cost 0.557 seconds.
Prefix dict has been built successfully.
txt | cut | |
---|---|---|
chap | ||
1.0 | 第一回 风雪惊变 那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。只听他两... | [第一回, , 风雪, 惊变, , , , , , , 那, 说话, 人, 五... |
2.0 | 第二回 江南七怪 颜烈跨出房门,过道中一个中年士人拖着鞋皮,踢跶踢跶的直响,一路打着哈... | [第二回, , 江南七怪, , , , , 颜烈, 跨出, 房门, ,, 过道, ... |
3.0 | 第三回 黄沙莽莽 寺里僧众见焦木圆寂,尽皆悲哭。有的便为伤者包扎伤处,抬入客舍。 忽... | [第三回, , 黄沙, 莽莽, , , , , 寺里, 僧众, 见, 焦木, 圆寂... |
4.0 | 第四回 黑风双煞 完颜洪熙笑道:“好,再打他个痛快。”蒙古兵前哨报来:“王罕亲自前来迎... | [第四回, , 黑风双, 煞, , , , , 完颜洪熙, 笑, 道, :, “,... |
5.0 | 第五回 弯弓射雕 一行人下得山来,走不多时,忽听前面猛兽大吼声一阵阵传来。韩宝驹一提缰... | [第五回, , 弯弓, 射雕, , , , , 一行, 人下, 得, 山来, ,,... |
# 初始化word2vec模型和词表
from gensim.models.word2vec import Word2Vec
n_dim = 300 # 指定向量维度,大样本量时300~500较好
w2vmodel = Word2Vec(size = n_dim, min_count = 10)
w2vmodel.build_vocab(chapter.cut) # 生成词表
# 在评论训练集上建模(大数据集时可能会花费几分钟)
w2vmodel.train(chapter.cut, \
total_examples = w2vmodel.corpus_count, epochs = 10)
# 训练完毕的模型实质
print(w2vmodel.wv["郭靖"].shape)
w2vmodel.wv["郭靖"]
(300,)
array([ 0.34924605, 0.21265522, 0.5632632 , 0.3580753 , 0.17115285,
0.48449898, 0.11169011, 0.33496168, 0.8114401 , 0.6040344 ,
0.27803087, -0.56468904, -0.05460463, -0.11949269, -0.08181898,
......
0.2831581 , 0.60918677, -0.17329499, 0.11961225, -0.30500183,
-0.31079102, -0.64060545, 0.03041249, 0.66229236, 0.88437986],
dtype=float32)
#基于词袋模型计算文本余弦相似度时,比较清理停用词前后的结果
#实例给出了加入停用词前的,基于前面的现在计算停用词后的
import jieba
import pandas as pd
tmpdf = pd.read_table('停用词.txt',names = ['w'], encoding = 'utf-8')
chapter['cut'].iloc[:5]= [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5] ]
chapter['cut'].iloc[:5]=[ w for w in chapter['cut'].iloc[:5] if w not in list(tmpdf.w) ]
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer()
resmtx = countvec.fit_transform(chapter['cut'].iloc[:5])
resmtx
<5x11623 sparse matrix of type ''
with 16884 stored elements in Compressed Sparse Row format>
from sklearn.metrics.pairwise import pairwise_distances
pairwise_distances(resmtx, metric = 'cosine')
#根据结果,停用词前后结果相同
array([[0. , 0.63104216, 0.77665559, 0.78745025, 0.82985512],
[0.63104216, 0. , 0.62572437, 0.61666388, 0.73192845],
[0.77665559, 0.62572437, 0. , 0.51645443, 0.5299046 ],
[0.78745025, 0.61666388, 0.51645443, 0. , 0.42108002],
[0.82985512, 0.73192845, 0.5299046 , 0.42108002, 0. ]])
朴素贝叶斯算法(优先考虑使用)
sklearn是标准的数据挖掘建模工具包,在语料转换为d2m矩阵结构之后,就可以使用所有标准的DM建模手段在sklearn中进行分析。
在sklearn中也实现了朴素贝叶斯算法,使用方式上也和其他模型非常相似。
# 从原始语料df中提取出所需的前两章段落
raw12 = raw[raw.chap.isin([1,2])]
raw12ana = raw12.iloc[list(raw12.txt.apply(len) > 50), :] # 只使用超过50字的段落
raw12ana.reset_index(drop = True, inplace = True)#对索引再排序
print(len(raw12ana))
raw12ana.head()
376
txt | chap | |
---|---|---|
0 | 那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。只听他两片梨花木板碰了几下,左手... | 1.0 |
1 | 那说话人将木板敲了几下,说道:“这首七言诗,说的是兵火过后,原来的家家户户,都变成了断墙... | 1.0 |
2 | “叶老汉和叶妈妈吓得呆了,扑将上去,搂住了儿子的尸体,放声大哭。那长官提起狼牙棒,一棒一... | 1.0 |
3 | “可是那金兵占了我大宋天下,杀人放火,奸淫掳掠,无恶不作,却又不见他遭到什么报应。只怪我... | 1.0 |
4 | 村民中走出一个二十来岁的大汉,说道:“张先生,你可是从北方来吗?”说的是北方口音。张十五... | 1.0 |
import jieba
cuttxt = lambda x: " ".join(jieba.lcut(x)) # 这里不做任何清理工作,以保留情感词
raw12ana["cleantxt"] = raw12ana.txt.apply(cuttxt)
raw12ana.head()
:3: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
raw12ana["cleantxt"] = raw12ana.txt.apply(cuttxt)
txt | chap | cleantxt | |
---|---|---|---|
0 | 那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。只听他两片梨花木板碰了几下,左手... | 1.0 | 那 说话 人 五十 来 岁 年纪 , 一件 青布 长袍 早洗 得 褪成 了 蓝灰 带... |
1 | 那说话人将木板敲了几下,说道:“这首七言诗,说的是兵火过后,原来的家家户户,都变成了断墙... | 1.0 | 那 说话 人 将 木板 敲 了 几下 , 说道 : “ 这首 七言诗 , 说 的 是... |
2 | “叶老汉和叶妈妈吓得呆了,扑将上去,搂住了儿子的尸体,放声大哭。那长官提起狼牙棒,一棒一... | 1.0 | “ 叶老汉 和 叶 妈妈 吓 得 呆 了 , 扑 将 上去 , 搂住 了 儿子 的 ... |
3 | “可是那金兵占了我大宋天下,杀人放火,奸淫掳掠,无恶不作,却又不见他遭到什么报应。只怪我... | 1.0 | “ 可是 那金兵 占 了 我 大宋 天下 , 杀人放火 , 奸淫掳掠 , 无恶不作 ... |
4 | 村民中走出一个二十来岁的大汉,说道:“张先生,你可是从北方来吗?”说的是北方口音。张十五... | 1.0 | 村民 中 走出 一个二十 来 岁 的 大汉 , 说道 : “ 张 先生 , 你 可是... |
#转换为词频矩阵
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer()
wordmtx = countvec.fit_transform(raw12ana.cleantxt)
wordmtx
<376x6724 sparse matrix of type ''
with 13642 stored elements in Compressed Sparse Row format>
# 作用:将数据集划分为 训练集和测试集
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(wordmtx, raw12ana.chap,
test_size = 0.3, random_state = 111)
#要训练的矩阵,期待的结果,测试集的比例(30%),随机数(相同则相同结果重现)
#拟合朴素贝叶斯模型
from sklearn import naive_bayes#导入贝叶斯
NBmodel = naive_bayes.MultinomialNB()
# 拟合模型
NBmodel.fit(x_train, y_train)
# 进行验证集预测
x_test
<113x6724 sparse matrix of type ''
with 4127 stored elements in Compressed Sparse Row format>
NBmodel.predict(x_test)#查看预测结果
array([2., 2., 1., 2., 1., 2., 1., 1., 2., 1., 2., 1., 2., 2., 1., 2., 1.,
2., 1., 1., 2., 2., 1., 1., 2., 1., 2., 1., 1., 2., 1., 2., 1., 1.,
2., 1., 1., 1., 1., 1., 1., 2., 1., 1., 2., 2., 2., 1., 1., 1., 2.,
1., 1., 1., 2., 1., 2., 1., 2., 2., 2., 2., 1., 1., 1., 1., 1., 1.,
1., 1., 2., 2., 1., 1., 1., 1., 1., 2., 1., 2., 1., 1., 2., 2., 2.,
1., 1., 1., 2., 1., 1., 2., 1., 1., 1., 2., 2., 2., 2., 2., 1., 1.,
2., 1., 1., 2., 2., 2., 2., 1., 1., 2., 2.])
# 预测准确率(给模型打分)
print('训练集:', NBmodel.score(x_train, y_train),
',验证集:', NBmodel.score(x_test, y_test))
训练集: 1.0 ,验证集: 0.9557522123893806
#做出更详细的验证报告
from sklearn.metrics import classification_report
print(classification_report(y_test, NBmodel.predict(x_test)))#验证集的实测结果与验证集的预测结果相比较
precision recall f1-score support
1.0 0.94 0.98 0.96 61
2.0 0.98 0.92 0.95 52
accuracy 0.96 113
macro avg 0.96 0.95 0.96 113
weighted avg 0.96 0.96 0.96 113
将需要预测的文本转换为和建模时格式完全对应的d2m矩阵格式,随后即可进行预测。
countvec.vocabulary_#查看转换的时候的字典
{'说话': 5703,
'五十': 765,
'年纪': 2855,
......
'握住': 3565,
'猎叉': 4588,
'随即': 6438,
...}
string = "杨铁心和包惜弱收养穆念慈"
words = " ".join(jieba.lcut(string))
words_vecs = countvec.transform([words]) #需要用[] 转换为可迭代的list格式
words_vecs
<1x6724 sparse matrix of type ''
with 2 stored elements in Compressed Sparse Row format>
NBmodel.predict(words_vecs)
array([1.])
from sklearn.linear_model import LogisticRegression
logitmodel = LogisticRegression() # 定义Logistic回归模型
# 拟合模型
logitmodel.fit(x_train, y_train)
print(classification_report(y_test, logitmodel.predict(x_test)))
precision recall f1-score support
1.0 0.94 0.98 0.96 61
2.0 0.98 0.92 0.95 52
accuracy 0.96 113
macro avg 0.96 0.95 0.96 113
weighted avg 0.96 0.96 0.96 113
NLTK中内置了朴素贝叶斯算法,可直接实现文档分类。
数据集中语料的格式
用于训练的语料必须是分词完毕的字典形式,词条为键名,键值则可以是数值、字符、或者T/F
{‘张三’ : True, ‘李四’ : True, ‘王五’ : False}
{‘张三’ : 1, ‘李四’ : 1, ‘王五’ : 0}
{‘张三’ : ‘有’, ‘李四’ : ‘有’, ‘王五’ : ‘无’}
训练用数据集为list of list格式,每个成员为list[语料字典, 结果变量]
[
[{‘张三’ : 1, ‘李四’ : 1, ‘王五’ : 0}, ‘合格’],
[{‘张三’ : 0, ‘李四’ : 1, ‘王五’ : 0}, ‘不合格’]
]
考虑到过拟合问题,此处需要先拆分好训练集和测试集
model = NaiveBayesClassifier.train(training_data)
# 这里直接以章节为一个单元进行分析,以简化程序结构
import nltk
from nltk import FreqDist
# 生成完整的词条频数字典,这部分也可以用遍历方式实现
fdist1 = FreqDist(m_cut(chapter.txt[1]))
fdist2 = FreqDist(m_cut(chapter.txt[2]))
fdist3 = FreqDist(m_cut(chapter.txt[3]))
fdist1
FreqDist({'第一回': 1,
'风雪': 3,
'惊变': 1,
'说话': 9,
......
'一具': 1,
'黑色': 1,
'盘形': 1,
...})
#导入贝叶斯
from nltk.classify import NaiveBayesClassifier
#分别引入第一二三章,生成训练数据
training_data = [ [fdist1, 'chap1'], [fdist2, 'chap2'], [fdist3, 'chap3'] ]
# 训练分类模型
NLTKmodel = NaiveBayesClassifier.train(training_data)
#进行预测
print(NLTKmodel.classify(FreqDist(m_cut("杨铁心收养穆念慈"))))
print(NLTKmodel.classify(FreqDist(m_cut("钱塘江 日日夜夜 包惜弱 颜烈 使出杨家枪"))))
chap3
chap2
#模型拟合效果的考察
nltk.classify.accuracy(NLTKmodel, training_data) # 准确度评价
#参数:哪个模型、相应的数据集
1.0
#显示出最有价值的词条特征
NLTKmodel.show_most_informative_features(5)#得到似然比,检测对于哪些特征有用
Most Informative Features
飞将 = None chap3 : chap2 = 1.0 : 1.0
几茎 = None chap2 : chap1 = 1.0 : 1.0
对射雕的前两个章节提取关键字,然后使用关键字而不是原始文本进行文档分类,比较这样两种方式的分类效果有何变化。
减少用于训练的样本量,考察使用朴素贝叶斯算法或者其他标准分类算法时,模型效果的变化趋势。
自行实现基于NLTK的按段落为单位进行章节分类的程序。
import jieba
import jieba.analyse
import jieba
chapter['cut'] = chapter.txt.apply(jieba.lcut)
chapter.head()
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\A\AppData\Local\Temp\jieba.cache
Loading model cost 0.555 seconds.
Prefix dict has been built successfully.
txt | cut | |
---|---|---|
chap | ||
1.0 | 第一回 风雪惊变 那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。只听他两... | [第一回, , 风雪, 惊变, , , , , , , 那, 说话, 人, 五... |
2.0 | 第二回 江南七怪 颜烈跨出房门,过道中一个中年士人拖着鞋皮,踢跶踢跶的直响,一路打着哈... | [第二回, , 江南七怪, , , , , 颜烈, 跨出, 房门, ,, 过道, ... |
3.0 | 第三回 黄沙莽莽 寺里僧众见焦木圆寂,尽皆悲哭。有的便为伤者包扎伤处,抬入客舍。 忽... | [第三回, , 黄沙, 莽莽, , , , , 寺里, 僧众, 见, 焦木, 圆寂... |
4.0 | 第四回 黑风双煞 完颜洪熙笑道:“好,再打他个痛快。”蒙古兵前哨报来:“王罕亲自前来迎... | [第四回, , 黑风双, 煞, , , , , 完颜洪熙, 笑, 道, :, “,... |
5.0 | 第五回 弯弓射雕 一行人下得山来,走不多时,忽听前面猛兽大吼声一阵阵传来。韩宝驹一提缰... | [第五回, , 弯弓, 射雕, , , , , 一行, 人下, 得, 山来, ,,... |
#对前两章提取关键词
key1=jieba.analyse.extract_tags(chapter.txt[1])
key2=jieba.analyse.extract_tags(chapter.txt[2])
key=key1+key2
key
['杨铁心',
'包惜弱',
'郭啸天',
'颜烈',
'丘处机',
'杨二人',
......
'金兵',
'武功',
'道长',
'南希仁',
'全金发',
'李萍']
# 转为D2M矩阵
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer()
wordmtx = countvec.fit_transform(key)
wordmtx
<40x34 sparse matrix of type ''
with 40 stored elements in Compressed Sparse Row format>
# 作用:将数据集划分为 训练集和测试集
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(wordmtx, raw12ana.chap[:40],
test_size = 0.3, random_state = 1)
#测试图片(train_images)和测试标签(train_labels)的长度必须一致
from sklearn import naive_bayes
NBmodel = naive_bayes.MultinomialNB()
# 拟合模型
NBmodel.fit(x_train, y_train)
# 进行验证集预测
x_test
# 预测准确率(给模型打分)
print('训练集:', NBmodel.score(x_train, y_train),
',验证集:', NBmodel.score(x_test, y_test))
训练集: 1.0 ,验证集: 1.0
#减少用于训练的样本量
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer()
wordmtx = countvec.fit_transform(raw12ana.cleantxt)
wordmtx
<376x6724 sparse matrix of type ''
with 13642 stored elements in Compressed Sparse Row format>
# 作用:将数据集划分为 训练集和测试集,训练样本量改为20%
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(wordmtx, raw12ana.chap,
test_size = 0.8, random_state = 111)
from sklearn import naive_bayes
NBmodel = naive_bayes.MultinomialNB()
# 拟合模型
NBmodel.fit(x_train, y_train)
#模型评估
from sklearn.metrics import classification_report
print(classification_report(y_test, NBmodel.predict(x_test)))
precision recall f1-score support
1.0 0.84 0.96 0.89 156
2.0 0.94 0.81 0.87 145
accuracy 0.88 301
macro avg 0.89 0.88 0.88 301
weighted avg 0.89 0.88 0.88 301
可见训练集过少会直接影响训练的效果
情感分析概述
数据概况:
取自购物网站的正向、负向评价各约1万条。
# 读入原始数据集
import pandas as pd
dfpos = pd.read_excel("购物评论.xlsx", sheet_name = "正向", header=None)
dfpos['y'] = 1
dfneg = pd.read_excel("购物评论.xlsx", sheet_name = "负向", header=None)
dfneg['y'] = 0
df0 = dfpos.append(dfneg, ignore_index = True)#两个表格接到一起
df0.head()
0 | y | |
---|---|---|
0 | 感觉用这个手机下载MP3听很方便,用T-Flash卡装上歌,就可以当一个小MP3用了,很不错... | 1 |
1 | 外观美观,速度也不错。上面一排触摸键挺实用。应该对得起这个价格。当然再降点大家肯定也不反对。... | 1 |
2 | 我刚拿到书,也没有仔细阅读,就是粗粗的翻了点,觉得还行。里面是蓝黑两种颜色的,有些单词的下面... | 1 |
3 | 对于二胡曲的精选,应该出版一个系列套装,可分别出售,单独购买。精品二胡曲是有年代和阶段性的,... | 1 |
4 | 用了一年半的 e680 终于送小偷了。刚刚买了一台e850,用了三天,说说我自己的感受。1:... | 1 |
# 分词和预处理
import jieba
cuttxt = lambda x: " ".join(jieba.lcut(x)) # 这里不去停用词,以保留情感词
df0["cleantxt"] = df0[0].apply(cuttxt)
df0.tail()
0 | y | cleantxt | |
---|---|---|---|
20577 | 服务员太差,房间很吵,隔音不好,房间味道很重,很一般,不如稀土国际大酒店好,收费有低,还是四... | 0 | 服务员 太 差 , 房间 很 吵 , 隔音 不好 , 房间 味道 很 重 , 很 一般 , ... |
20578 | 很早我就说等“爱氏晨曦”吃进蒙牛股份后,对蒙牛的质疑就会停止,如今已应验。想知道单是这一笔... | 0 | 很早 我 就 说 等 “ 爱氏 晨曦 ” 吃进 蒙牛 股份 后 , 对 蒙牛 的 质疑 ... |
20579 | 2——10岁是什么意思?我不认为有一本书可以覆盖这样的范围,事实上,我两岁的女儿不能理解这套... | 0 | 2 — — 10 岁 是 什么 意思 ? 我 不 认为 有 一 本书 可以 覆盖 这样 的 ... |
20580 | 刚住过这里,感觉不好,房间味道太大,一股子霉味,房间各种设施都很陈旧,我所预订的大床间,却没... | 0 | 刚住 过 这里 , 感觉不好 , 房间 味道 太 大 , 一股 子 霉味 , 房间 各种 设... |
20581 | 一本书200页,综合一下只有3页可看 却要花200页的钱 唉 唉 | 0 | 一 本书 200 页 , 综合 一下 只有 3 页 可 看 却 要 花 200 页 ... |
#构建矩阵
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer(min_df = 5) # 出现5次以上的才纳入
wordmtx = countvec.fit_transform(df0.cleantxt)
wordmtx
<20582x12014 sparse matrix of type ''
with 513919 stored elements in Compressed Sparse Row format>
# 按照7:3的比例生成训练集和测试集
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(
wordmtx, df0.y, test_size=0.3) # 这里可以直接使用稀疏矩阵格式
x_train[0]
<1x12014 sparse matrix of type ''
with 14 stored elements in Compressed Sparse Row format>
# 使用SVM进行建模,也可考虑多个建模方法
from sklearn.svm import SVC
clf=SVC(kernel = 'rbf', verbose = True)#常规设定
clf.fit(x_train, y_train) # 内存占用可能较高
clf.score(x_train, y_train)#得出模型的评分
[LibSVM]
0.9535642396057472
# 对模型效果进行评估
from sklearn.metrics import classification_report
print(classification_report(y_test, clf.predict(x_test)))
precision recall f1-score support
0 0.87 0.90 0.88 3055
1 0.89 0.86 0.88 3120
accuracy 0.88 6175
macro avg 0.88 0.88 0.88 6175
weighted avg 0.88 0.88 0.88 6175
#将相应的数据集转为向量,进行预测。下面的函数更为清晰
clf.predict(countvec.transform([df0.cleantxt[0]]))[0]
1
# 模型预测,书写函数,使结果易读
import jieba
def m_pred(string, countvec, model) :
words = " ".join(jieba.lcut(string))
words_vecs = countvec.transform([words]) # 数据需要转换为可迭代格式
result = model.predict(words_vecs)
if int(result[0]) == 1:
print(string, ":正向")
else:
print(string, ":负向")
comment = "外观美观,速度也不错。上面一排触摸键挺实用。应该对得起这个价格。当然再降点大家肯定也不反对。风扇噪音也不大。"
m_pred(comment, countvec, clf)
外观美观,速度也不错。上面一排触摸键挺实用。应该对得起这个价格。当然再降点大家肯定也不反对。风扇噪音也不大。 :正向
comment = "作为女儿6.1的礼物。虽然晚到了几天。等拿到的时候,女儿爱不释手,上洗手间也看,告知不好。上周末,告诉我她把火鞋和风鞋拿到学校,好多同学羡慕她。呵呵,我也看了其中的人鸦,只可惜没有看完就在老公的催促下睡了。说了这么多,归纳为一句:这套书买的值。"
m_pred(comment, countvec, clf)
作为女儿6.1的礼物。虽然晚到了几天。等拿到的时候,女儿爱不释手,上洗手间也看,告知不好。上周末,告诉我她把火鞋和风鞋拿到学校,好多同学羡慕她。呵呵,我也看了其中的人鸦,只可惜没有看完就在老公的催促下睡了。说了这么多,归纳为一句:这套书买的值。 :正向
和词袋模型相比,分布式表达主要是改变了文本信息的提取方式。
目前主要使用gensim实现相应的算法。
注意:由于矩阵不再是频数值(正负想间),因此不能使用朴素贝叶斯算法来进行拟合。
# 读入原始数据集,和上面完全相同
import pandas as pd
dfpos = pd.read_excel("购物评论.xlsx", sheet_name = "正向", header=None)
dfpos['y'] = 1
dfneg = pd.read_excel("购物评论.xlsx", sheet_name = "负向", header=None)
dfneg['y'] = 0
df0 = dfpos.append(dfneg, ignore_index = True)
df0.head()
0 | y | |
---|---|---|
0 | 感觉用这个手机下载MP3听很方便,用T-Flash卡装上歌,就可以当一个小MP3用了,很不错... | 1 |
1 | 外观美观,速度也不错。上面一排触摸键挺实用。应该对得起这个价格。当然再降点大家肯定也不反对。... | 1 |
2 | 我刚拿到书,也没有仔细阅读,就是粗粗的翻了点,觉得还行。里面是蓝黑两种颜色的,有些单词的下面... | 1 |
3 | 对于二胡曲的精选,应该出版一个系列套装,可分别出售,单独购买。精品二胡曲是有年代和阶段性的,... | 1 |
4 | 用了一年半的 e680 终于送小偷了。刚刚买了一台e850,用了三天,说说我自己的感受。1:... | 1 |
# 分词和预处理,生成list of list格式
import jieba
df0['cut'] = df0[0].apply(jieba.lcut)
df0.head()
0 | y | cut | |
---|---|---|---|
0 | 感觉用这个手机下载MP3听很方便,用T-Flash卡装上歌,就可以当一个小MP3用了,很不错... | 1 | [感觉, 用, 这个, 手机, 下载, MP3, 听, 很, 方便, ,, 用, T, -,... |
1 | 外观美观,速度也不错。上面一排触摸键挺实用。应该对得起这个价格。当然再降点大家肯定也不反对。... | 1 | [外观, 美观, ,, 速度, 也, 不错, 。, 上面, 一排, 触摸, 键, 挺, 实用... |
2 | 我刚拿到书,也没有仔细阅读,就是粗粗的翻了点,觉得还行。里面是蓝黑两种颜色的,有些单词的下面... | 1 | [我刚, 拿到, 书, ,, 也, 没有, 仔细阅读, ,, 就是, 粗粗, 的, 翻, 了... |
3 | 对于二胡曲的精选,应该出版一个系列套装,可分别出售,单独购买。精品二胡曲是有年代和阶段性的,... | 1 | [对于, 二胡曲, 的, 精选, ,, 应该, 出版, 一个系列, 套装, ,, 可, 分别... |
4 | 用了一年半的 e680 终于送小偷了。刚刚买了一台e850,用了三天,说说我自己的感受。1:... | 1 | [用, 了, 一年, 半, 的, , e680, , 终于, 送, 小偷, 了, 。, ... |
# 按照7:3的比例生成训练集和测试集
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(
df0.cut, df0.y, test_size=0.3)
x_train[:2]
10965 [儿子, 1, 年, 7, 个, 月, ,, 虽然, 好多, 都, 不是, 很, 懂, ,,...
17209 [这家, 酒店, 绝对, 没有, 四星级, ,, 而且, 房间, 的, 隔音, 太, 差, ...
Name: cut, dtype: object
# 初始化word2vec模型和词表
from gensim.models.word2vec import Word2Vec
n_dim = 300 # 指定向量维度,大样本量时300~500较好
w2vmodel = Word2Vec(vector_size = n_dim, min_count = 10)
w2vmodel.build_vocab(x_train) # 生成词表
# 在评论训练集上建模(大数据集时可能会花费几分钟)
# 本例消耗内存较少
%time w2vmodel.train(x_train, \
total_examples = w2vmodel.corpus_count, epochs = 10)
Wall time: 4.67 s
(6346434, 9495160)
# 情感词向量间的相似度
w2vmodel.wv.most_similar("不错")
[('好', 0.6664835810661316),
('行', 0.6593450903892517),
('挺不错', 0.658033549785614),
('比较满意', 0.6418668627738953),
('很漂亮', 0.5996091365814209),
('还行', 0.5758983492851257),
('很快', 0.5542255640029907),
('整体', 0.5516241788864136),
('漂亮', 0.5488754510879517),
('说得过去', 0.5477145910263062)]
w2vmodel.wv.most_similar("失望")
[('遗憾', 0.7359662652015686),
('不爽', 0.6579391360282898),
('气愤', 0.647860586643219),
('后悔', 0.6342949271202087),
('不满', 0.6023170351982117),
('生气', 0.5882432460784912),
('上当', 0.5846213698387146),
('难得', 0.5696483254432678),
('糟糕', 0.5675068497657776),
('浅', 0.5560178756713867)]
对购物评论、微博等短文本而言,一般是将所有词向量的平均值作为分类算法的输入值。
# 生成整句所对应的所有词条的词向量矩阵
pd.DataFrame([w2vmodel.wv[w] for w in df0.cut[0] if w in w2vmodel.wv]).head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | -0.911332 | 2.487151 | -0.445355 | 0.959004 | -0.077999 | -0.157231 | -0.511012 | 0.636210 | 0.123468 | -1.307333 | ... | -0.798626 | -0.133126 | 2.084686 | 0.084197 | 0.662218 | -0.711858 | 1.772625 | 1.529452 | -0.640140 | -0.091147 |
1 | 0.046230 | -0.035792 | 1.241354 | -1.354366 | 0.447580 | -0.560420 | 1.272424 | 1.154507 | -0.583416 | -0.777637 | ... | -1.028960 | 0.141009 | -0.139010 | -0.353615 | -0.938453 | 0.084964 | 0.510275 | -0.698084 | 0.632293 | -0.493197 |
2 | 0.588286 | 0.292953 | -0.215384 | -0.420847 | -0.803060 | -0.033820 | 0.018333 | 0.384169 | -0.659887 | -0.146808 | ... | 0.277545 | 1.046261 | 0.742007 | 0.230653 | 0.011729 | -1.664741 | 0.118631 | 0.510197 | -0.013587 | -0.138566 |
3 | 0.191487 | -0.297445 | -0.385975 | -1.030350 | -1.255563 | -1.214032 | 0.616230 | 0.202745 | -1.133247 | 0.830488 | ... | 1.589015 | -0.153522 | -0.118775 | -1.095460 | -0.661065 | -1.319211 | 0.029742 | -0.847319 | -0.723597 | 1.276379 |
4 | -0.704394 | -0.071939 | 0.100993 | 0.326895 | -0.685714 | -0.722568 | -0.205036 | 0.934630 | -0.240266 | 0.022127 | ... | 0.595830 | -0.101508 | -0.191669 | -0.558731 | -0.897231 | -0.376409 | -0.159503 | -0.568233 | 0.471685 | 0.287301 |
5 rows × 300 columns
# 用各个词向量直接平均的方式生成整句对应的向量
def m_avgvec(words, w2vmodel):
return pd.DataFrame([w2vmodel.wv[w]
for w in words if w in w2vmodel.wv]).agg("mean")
# 生成建模用矩阵,耗时较长
%time train_vecs = pd.DataFrame([m_avgvec(s, w2vmodel) for s in x_train])
train_vecs.head()
Wall time: 2min 55s
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | -0.138882 | 0.243906 | 0.155527 | -0.057517 | -0.243359 | 0.038505 | 0.123457 | -0.029797 | -0.251002 | 0.130891 | ... | -0.158522 | 0.328610 | 0.468559 | 0.006440 | -0.071326 | 0.046429 | 0.262878 | -0.073597 | 0.083408 | -0.133627 |
1 | 0.123953 | 0.249146 | 0.031028 | -0.103072 | -0.012683 | -0.061241 | 0.016736 | 0.236264 | -0.238410 | -0.349272 | ... | -0.094874 | 0.316148 | 0.283809 | 0.035215 | 0.100668 | -0.150388 | 0.151585 | 0.202299 | 0.011277 | -0.346626 |
2 | 0.009675 | 0.063508 | 0.298085 | -0.113564 | -0.394117 | -0.197484 | -0.143916 | -0.264382 | -0.165245 | 0.044551 | ... | -0.077013 | 0.219353 | 0.319499 | -0.054280 | -0.017593 | 0.068092 | -0.043105 | -0.314632 | 0.061136 | -0.315324 |
3 | -0.201478 | 0.054600 | -0.197219 | 0.132496 | -0.130859 | 0.059931 | 0.090897 | 0.447195 | -0.066142 | -0.080050 | ... | 0.216520 | 0.336789 | 0.242757 | 0.192332 | 0.255502 | -0.000268 | 0.000050 | -0.100486 | 0.013850 | -0.163652 |
4 | 0.012418 | 0.231921 | -0.169535 | -0.027108 | 0.162233 | 0.089345 | 0.123997 | 0.351209 | -0.036844 | -0.328844 | ... | 0.154105 | 0.471911 | 0.131034 | 0.205431 | -0.007795 | 0.122620 | 0.165309 | 0.214215 | -0.148311 | -0.170526 |
5 rows × 300 columns
%time train_vecs_t = pd.DataFrame([m_avgvec(x, w2vmodel) for x in x_test])
train_vecs_t
Wall time: 1min 14s
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | -0.360456 | -0.168126 | -0.187902 | 0.139328 | -0.580499 | -0.476809 | 0.382865 | 0.896478 | 0.004519 | 0.060998 | ... | 0.337908 | 0.313125 | -0.248374 | -0.192784 | 0.191395 | -0.003297 | 0.034540 | -0.024345 | 0.307705 | 0.060049 |
1 | -0.006573 | 0.364138 | 0.056596 | 0.062842 | -0.164288 | -0.038841 | 0.004568 | 0.271801 | -0.263575 | -0.250043 | ... | -0.107846 | 0.247452 | 0.225257 | 0.020039 | 0.135461 | -0.112377 | 0.059987 | -0.093346 | 0.092084 | -0.338679 |
2 | -0.342170 | -0.107518 | 0.150887 | -0.012432 | -0.119799 | -0.151469 | 0.062810 | 0.317839 | -0.264509 | -0.119258 | ... | -0.146024 | 0.251700 | 0.428559 | 0.112363 | 0.088508 | 0.010839 | 0.133946 | 0.158355 | -0.091741 | -0.333099 |
3 | -0.036196 | 0.323747 | 0.038649 | -0.041602 | -0.247251 | -0.055818 | -0.080103 | 0.083953 | -0.244907 | -0.281211 | ... | -0.227069 | 0.363051 | 0.472190 | 0.076083 | 0.067558 | -0.141818 | 0.051008 | -0.112334 | -0.019767 | -0.421210 |
4 | 0.085083 | 0.189793 | 0.049033 | 0.066349 | -0.216909 | -0.071397 | 0.010042 | 0.301876 | -0.342764 | -0.218100 | ... | -0.164497 | 0.430071 | 0.122870 | 0.071452 | 0.140936 | -0.096815 | -0.089997 | -0.006523 | 0.104536 | -0.479253 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
6170 | -0.264900 | 0.084384 | 0.011239 | 0.049277 | -0.141444 | 0.100749 | 0.315126 | 0.146341 | -0.133169 | -0.027791 | ... | 0.147190 | 0.336536 | 0.451692 | 0.175938 | 0.192036 | 0.165460 | 0.162585 | 0.089151 | -0.057888 | -0.233666 |
6171 | 0.026205 | -0.071874 | 0.270545 | -0.027888 | -0.195445 | -0.299105 | -0.079034 | 0.288189 | -0.127330 | -0.068026 | ... | -0.192192 | 0.248696 | 0.323543 | 0.067723 | -0.030734 | 0.041797 | 0.171027 | -0.015046 | 0.115095 | -0.231721 |
6172 | -0.405061 | 0.005883 | 0.037920 | 0.075662 | -0.039078 | 0.162015 | 0.093300 | 0.189479 | -0.148963 | -0.012335 | ... | 0.210263 | 0.391104 | 0.387380 | 0.136165 | 0.423261 | 0.131836 | 0.346203 | -0.070695 | 0.047509 | -0.027406 |
6173 | -0.062726 | -0.054550 | -0.029740 | -0.169576 | -0.004502 | -0.092310 | 0.181026 | 0.379483 | -0.213536 | -0.264653 | ... | 0.060071 | 0.408561 | 0.300502 | 0.174386 | 0.060165 | 0.039983 | 0.138749 | 0.112043 | 0.105036 | -0.180624 |
6174 | -0.122603 | 0.116725 | 0.177533 | -0.111880 | -0.154657 | -0.210409 | -0.083918 | 0.100732 | -0.164870 | 0.042741 | ... | -0.196023 | 0.122320 | 0.098776 | -0.089089 | -0.085931 | -0.149250 | -0.006701 | -0.196286 | 0.078928 | -0.180735 |
6175 rows × 300 columns
train_vecs_t.isnull().any()
0 True
1 True
2 True
3 True
4 True
...
295 True
296 True
297 True
298 True
299 True
Length: 300, dtype: bool
# 用转换后的矩阵拟合SVM模型
from sklearn.svm import SVC
clf2 = SVC(kernel = 'rbf', verbose = True)
clf2.fit(train_vecs, y_train) # 占用内存小于1G
clf2.score(train_vecs, y_train)
[LibSVM]
0.8899146248351496
from sklearn.metrics import classification_report
print(classification_report(y_train, clf2.predict(train_vecs)))
precision recall f1-score support
0 0.89 0.89 0.89 7129
1 0.89 0.89 0.89 7278
accuracy 0.89 14407
macro avg 0.89 0.89 0.89 14407
weighted avg 0.89 0.89 0.89 14407
# 模型预测
import jieba
def m_pred(string, model):
words = jieba.lcut(string)
words_vecs = pd.DataFrame(m_avgvec(words, w2vmodel)).T
result = model.predict(words_vecs)
if int(result[0]) == 1:
print(string, ":正向")
else:
print(string, ":负向")
comment = "作为女儿6.1的礼物。虽然晚到了几天。等拿到的时候,女儿爱不释手,上洗手间也看,告知不好。上周末,告诉我她把火鞋和风鞋拿到学校,好多同学羡慕她。呵呵,我也看了其中的人鸦,只可惜没有看完就在老公的催促下睡了。说了这么多,归纳为一句:这套书买的值。"
m_pred(comment, clf2)
作为女儿6.1的礼物。虽然晚到了几天。等拿到的时候,女儿爱不释手,上洗手间也看,告知不好。上周末,告诉我她把火鞋和风鞋拿到学校,好多同学羡慕她。呵呵,我也看了其中的人鸦,只可惜没有看完就在老公的催促下睡了。说了这么多,归纳为一句:这套书买的值。 :正向
在本章所用数据中,各抽取1千条正向、负向评论,重新拟合基于词袋的和基于分布式表达的模型,比较前两种模型效果的改变情况。
# 只需在读入原始数据集时增加nrows属性,然后在进行训练,训练集减少,模型的效果会变差
import pandas as pd
dfpos = pd.read_excel("购物评论.xlsx", sheet_name = "正向", header=None,nrows=1000)
dfpos['y'] = 1
dfneg = pd.read_excel("购物评论.xlsx", sheet_name = "负向", header=None,nrows=1000)
dfneg['y'] = 0
df0 = dfpos.append(dfneg, ignore_index = True)#两个表格接到一起
df0.head()
0 | y | |
---|---|---|
0 | 感觉用这个手机下载MP3听很方便,用T-Flash卡装上歌,就可以当一个小MP3用了,很不错... | 1 |
1 | 外观美观,速度也不错。上面一排触摸键挺实用。应该对得起这个价格。当然再降点大家肯定也不反对。... | 1 |
2 | 我刚拿到书,也没有仔细阅读,就是粗粗的翻了点,觉得还行。里面是蓝黑两种颜色的,有些单词的下面... | 1 |
3 | 对于二胡曲的精选,应该出版一个系列套装,可分别出售,单独购买。精品二胡曲是有年代和阶段性的,... | 1 |
4 | 用了一年半的 e680 终于送小偷了。刚刚买了一台e850,用了三天,说说我自己的感受。1:... | 1 |
chapter.txt[1]
'第一回 风雪惊变那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。只听他两片梨花木板碰了几下,左手中竹棒在一面小羯鼓上敲起得得连声。唱道:“小桃无主自开花,烟草茫茫带晚鸦。几处败垣围故井,向来一一是人家。”那说话人将木板敲了几下,说道:“这首七言诗,,.......”颜烈道:“既有姓”包惜弱点了点头,道:“相公可别太多花费了。”颜烈微笑道:“就可惜娘子在服丧,不能戴用珠宝,要多花钱也花不了。’
#将句子以。!?分开,并转化为表格
def cut_sentence(intxt):
delimiters = frozenset('。!?')
buf = []
for ch in intxt:
buf.append(ch)
if delimiters.__contains__(ch):
yield ''.join(buf)
buf = []
if buf:
yield ''.join(buf)
sentdf = pd.DataFrame(cut_sentence(chapter.txt[1]))
sentdf
0 | |
---|---|
0 | 第一回 风雪惊变 那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。 |
1 | 只听他两片梨花木板碰了几下,左手中竹棒在一面小羯鼓上敲起得得连声。 |
2 | 唱道:“小桃无主自开花,烟草茫茫带晚鸦。 |
3 | 几处败垣围故井,向来一一是人家。 |
4 | ” 那说话人将木板敲了几下,说道:“这首七言诗,说的是兵火过后,原来的家家户户,都变成了断... |
... | ... |
908 | 包惜弱想要他另要一间客房,却又不知如何启齿才好,脸上一阵红一阵白,心事重重。 |
909 | 过了一会,颜烈道:“娘子请自宽便,小人出去买了物品就回。 |
910 | ”包惜弱点了点头,道:“相公可别太多花费了。 |
911 | ”颜烈微笑道:“就可惜娘子在服丧,不能戴用珠宝,要多花钱也花不了。 |
912 | ” |
913 rows × 1 columns
# 去除过短的句子,避免摘要出现无意义的内容
sentdf['txtlen'] = sentdf[0].apply(len)
sentlist = sentdf[0][sentdf.txtlen > 20]
print(len(sentlist))
sentlist
583
0 第一回 风雪惊变 那说话人五十来岁年纪,一件青布长袍早洗得褪成了蓝灰带白。
1 只听他两片梨花木板碰了几下,左手中竹棒在一面小羯鼓上敲起得得连声。
4 ” 那说话人将木板敲了几下,说道:“这首七言诗,说的是兵火过后,原来的家家户户,都变成了断...
5 小人刚才说到那叶老汉一家四口,悲欢离合,聚了又散,散了又聚。
6 他四人给金兵冲散,好容易又再团聚,欢天喜地地回到故乡卫州,却见房屋已给金兵烧得干干净净,无可...
...
907 漱洗罢,颜烈与包惜弱一起吃了些点心,两人相对坐在房中。
908 包惜弱想要他另要一间客房,却又不知如何启齿才好,脸上一阵红一阵白,心事重重。
909 过了一会,颜烈道:“娘子请自宽便,小人出去买了物品就回。
910 ”包惜弱点了点头,道:“相公可别太多花费了。
911 ”颜烈微笑道:“就可惜娘子在服丧,不能戴用珠宝,要多花钱也花不了。
Name: 0, Length: 583, dtype: object
# 将文本中的词语转换为词频矩阵
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
txtlist = [ " ".join(jieba.lcut(w)) for w in sentlist]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(txtlist)
Building prefix dict from the default dictionary ...
Dumping model to file cache C:\Users\lenovo\AppData\Local\Temp\jieba.cache
Loading model cost 0.758 seconds.
Prefix dict has been built successfully.
#转为tfidf矩阵
tfidf_matrix = TfidfTransformer().fit_transform(X)
# 利用nx包实现pagerank算法
import networkx as nx
similarity = nx.from_scipy_sparse_matrix(tfidf_matrix * tfidf_matrix.T)
scores = nx.pagerank(similarity)
scores#算出每个句子的分值,越大,越适合做摘要
{0: 0.0010912193346531665,
1: 0.0010514692451123236,
2: 0.0016169794514779936,
3: 0.0013256313810434896,
......
579: 0.0022975642232750905,
580: 0.0027147294491056168,
581: 0.0016757809711476844,
582: 0.0017710726716536757}
#按得分值进行由高到低排序
tops = sorted(scores.items(), key = lambda x: x[1], reverse = True)
tops[:3]
[(104, 0.003887937743683278),
(440, 0.0035158614971836305),
(442, 0.0034495123422054637)]
#显示出分值最高的前3列的内容
print(sentlist.iloc[tops[0][0]])
print(sentlist.iloc[tops[1][0]])
sentlist.iloc[tops[2][0]]
”郭啸天道:“自己兄弟,说什么还请不还请?
杨铁心再是一箭,射死了武官,抢将过去,只见那女子在地下挣扎着坐起身来,正是自己妻子。
'”包惜弱道:“在前面,给……给官兵捉去啦!'
#取出前五列,按照......将其拼接
topn = 5
topsent = sorted(tops[:topn])
abstract = ''
for item in topsent:
abstract = abstract + sentlist.iloc[item[0]] + "......"
abstract[:-6]#去除最后六个字符的省略号
'”郭啸天道:“自己兄弟,说什么还请不还请?......杨铁心提起铁枪要出屋助战,郭啸天一把拉住,低声道:“道长叫咱们别出去。......杨铁心再是一箭,射死了武官,抢将过去,只见那女子在地下挣扎着坐起身来,正是自己妻子。......”包惜弱道:“在前面,给……给官兵捉去啦!......这时颜烈已走出房去,包惜弱问道:“这是什么?'