import numpy as np
import pandas as pd
# 查看训练数据
train_data = pd.read_csv('data/sohu_train.txt', sep='\t', header=None, dtype=np.str_, encoding='utf8', names=[u'频道', u'文章'])
train_data.head(10)#查看前十行
# 查看每个频道下文章数量
train_data.groupby(u'频道')[u'频道'].count() #按照频道分组 然后统计每一组的行数
# 查看每个频道下最短、最长文章字数
train_data.loc[:,u'文章长度'] = train_data[u'文章'].apply(len)#计算出每篇新闻的长度
train_data.head()
#按照频道分组 计算每组的长度的最小值和最大值
train_data.groupby(u'频道')[u'文章长度'].agg([np.min, np.max])#agg功能跟apply一样,agg允许多个函数执行
# 查看测试数据
test_data = pd.read_csv('data/sohu_test.txt', sep='\t', header=None, dtype=np.str_, encoding='utf8', names=[u'频道', u'文章'])
test_data.head()#没给数 默认是5
# 查看每个频道下文章数量
test_data.groupby(u'频道')[u'频道'].count()
# 查看每个频道下最短、最长文章字数
test_data[u'文章长度'] = train_data[u'文章'].apply(len)
test_data.groupby(u'频道')[u'文章长度'].agg([np.min, np.max])
import jieba
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer, TfidfVectorizer
import pandas as pd
contents = [
'小明喜欢看电影,小红也喜欢看电影。',
'小明还喜欢看足球比赛。'
]#就是我们的语料库
stopwords={',','。'}
# 计算TF(每个词的出现次数,未归一)
# tokenizer: 定义一个函数,接受文本,返回分词的list
# stop_words: 定义停用词词典,会在结果中删除词典中包含的词
tf = CountVectorizer(tokenizer=jieba.lcut, stop_words=stopwords)
res1 = tf.fit_transform(contents)#contents中是两句话 结果就是两个向量
res1#这是一个稀疏矩阵 稀疏矩阵保存数据更节省空间
res1.toarray() #调用fit_transform方法输入数据并转换 (注意返回格式,利用toarray()进行sparse矩阵转换array数组)
# 查看词汇对应关系 表示的就是我们所有句子中出现的词
tf.vocabulary_#词典 加_就是属性
# 查看TF结果
pd.DataFrame(res1.toarray(), columns=[x[0] for x in sorted(tf.vocabulary_.items(), key=lambda x: x[1])])
# use_idf: 表示在TF矩阵的基础上计算IDF,并相乘得到TF-IDF
# smooth_idf: 表示计算IDF时,分子上的总文档数+1
# sublinear_tf: 表示使用 1+log(tf)替换原来的tf
# norm: 表示对TF-IDF矩阵的每一行使用l2范数正则化(变成单位向量 模长为1) l1范数就是绝对值的和 max正则化---最大值
tfidf = TfidfTransformer(norm='l2', use_idf=True, smooth_idf=True, sublinear_tf=False)
res2=tfidf.fit_transform(res1)#res1是前面tf转换的结果
res2.toarray()#得到的就是tfidf向量
res2.toarray().dot(res2.toarray().T)
tf.vocabulary_.items()
# 查看每个词的IDF值
tfidf.idf_
# 查看TF-IDF结果
pd.DataFrame(res2.toarray(),columns=[x[0] for x in sorted(tf.vocabulary_.items(),key=lambda x:x[1])])
# 参数为 CounterVectorizer 和 TfidfTransformer 的所有参数
tfidf=TfidfVectorizer(tokenizer=jieba.lcut,stop_words=stopwords,norm='l2',use_idf=True,smooth_idf=True,sublinear_tf=False)
res=tfidf.fit_transform(contents)#直接对文档进行转换提取tfidf特征
res.toarray()#一步就得到了tfidf向量
# 查看每一列所代表的词
tfidf.vocabulary_
# 查看每个词的IDF,顺序和 tfidf.vocabulary_ 对应
tfidf.idf_
pd.DataFrame({'词':[x[0] for x in sorted(tfidf.vocabulary_.items(),key=lambda x:x[1])],'IDF':tfidf.idf_},columns=['词','IDF'])
pd.DataFrame(res.toarray(),columns=[x[0] for x in sorted(tf.vocabulary_.items(),key=lambda x:x[1])])
对文档进行自动分类——使用词袋模型
#词袋 不关注词的先后顺序---词袋模型(bow--一元模型) bag of words
# 二元模型 小明喜欢 喜欢小明
#n-gram
# 创建输出目录 保存训练好的模型
import os
output_dir = u'output'
if not os.path.exists(output_dir):
os.mkdir(output_dir)
1.加载数据
import numpy as np
import pandas as pd
# 查看训练数据
train_data = pd.read_csv('data/sohu_train.txt', sep='\t', header=None, dtype=np.str_, encoding='utf8', names=[u'频道', u'文章'])
train_data.head()
# 载入停用词
stopwords = set()
with open('data/stopwords.txt', 'r') as infile:
for line in infile:
line = line.rstrip('\n')
if line:
stopwords.add(line.lower())
2. 计算每个文章的tfidf特征
import jieba
from sklearn.feature_extraction.text import TfidfVectorizer
#min_df去掉df值小的词 这样的词一般是非常专业的名词或者是生僻词 是噪音
#max_df 去掉df值很大的词 这样词是常用词 去掉不要
tfidf = TfidfVectorizer(tokenizer=jieba.lcut, stop_words=stopwords, min_df=50, max_df=0.3)
x = tfidf.fit_transform(train_data[u'文章'])
print(u'词表大小: {}'.format(len(tfidf.vocabulary_)))#24939个词 24000x24939
3.训练分类器
# 编码目标变量 因为咱们的标签是字符串 sklearn只接受数值
from sklearn.preprocessing import LabelEncoder
y_encoder = LabelEncoder()
y = y_encoder.fit_transform(train_data[u'频道'])#将类别转换成0,1,2,3,4,5,6,7,8,9...
y[:10]
# 编码X变量
#x = tfidf.transform(train_data[u'文章'])
# 划分训练测试数据
from sklearn.model_selection import train_test_split
# 根据y分层抽样,测试数据占20%
#因为现在数据量很大 此时采用对下标进行分割
train_idx, test_idx = train_test_split(range(len(y)), test_size=0.2, stratify=y)
train_x = x[train_idx, :] #引用 不是复制
train_y = y[train_idx]
test_x = x[test_idx, :]
test_y = y[test_idx]
# 训练逻辑回归模型 我们是12分类 12种不同的新闻 属于多分类
from sklearn.linear_model import LogisticRegression
# 常用参数说明
# penalty: 正则项类型,l1还是l2
# C: 正则项惩罚系数的倒数,越大则惩罚越小
# fit_intercept: 是否拟合常数项
# max_iter: 最大迭代次数
# multi_class: 以何种方式训练多分类模型
# ovr = 对每个标签训练二分类模型
# multinomial ovo = 直接训练多分类模型,仅当solver={newton-cg, sag, lbfgs}时支持
# solver: 用哪种方法求解,可选有{liblinear, newton-cg, sag, lbfgs}
# 小数据liblinear比较好,大数据量sag更快
# 多分类问题,liblinear只支持ovr模式,其他支持ovr和multinomial
# liblinear支持l1正则,其他只支持l2正则
model = LogisticRegression(multi_class='multinomial', solver='lbfgs')
model.fit(train_x, train_y)
4.模型效果
from sklearn.metrics import confusion_matrix, precision_recall_fscore_support
# 在测试集上计算模型的表现
test_y_pred = model.predict(test_x)
# 计算混淆矩阵
pd.DataFrame(confusion_matrix(test_y, test_y_pred), columns=y_encoder.classes_, index=y_encoder.classes_)
# 计算各项评价指标
def eval_model(y_true, y_pred, labels):
# 计算每个分类的Precision, Recall, f1, support
p, r, f1, s = precision_recall_fscore_support(y_true, y_pred)
# 计算总体的平均Precision, Recall, f1, support
tot_p = np.average(p, weights=s)
tot_r = np.average(r, weights=s)
tot_f1 = np.average(f1, weights=s)
tot_s = np.sum(s)
res1 = pd.DataFrame({
u'Label': labels,
u'Precision': p,
u'Recall': r,
u'F1': f1,
u'Support': s
})
res2 = pd.DataFrame({
u'Label': [u'总体'],
u'Precision': [tot_p],
u'Recall': [tot_r],
u'F1': [tot_f1],
u'Support': [tot_s]
})
res2.index = [999]
res = pd.concat([res1, res2])
return res[[u'Label', u'Precision', u'Recall', u'F1', u'Support']]
eval_model(test_y, test_y_pred, y_encoder.classes_)
5.模型保存
# 保存模型到文件 pip install dill
#注意 我们要把tfidf特征提取模型保存 标签转换模型 预测模型
import dill
import pickle
model_file = os.path.join(output_dir, u'model.pkl')
with open(model_file, 'wb') as outfile:
dill.dump({
'y_encoder': y_encoder,
'tfidf': tfidf,
'lr': model
}, outfile)
对新文档预测
6.加载新文档
# 加载新文档数据
new_data = pd.read_csv('data/sohu_test.txt', sep='\t', header=None, dtype=np.str_, encoding='utf8', names=[u'频道', u'文章'])
new_data.head()
# 加载模型
import pickle
model_file = os.path.join(output_dir, u'model.pkl')
with open(model_file, 'rb') as infile:
model = pickle.load(infile)
# 对新文档预测(这里只对前10篇预测)
# 1. 转化为词袋表示
new_x = model['tfidf'].transform(new_data[u'文章'][:10])
# 2. 预测类别
new_y_pred = model['lr'].predict(new_x)
new_y_pred
# 3. 解释类别
pd.DataFrame({u'预测频道': model['y_encoder'].inverse_transform(new_y_pred), u'实际频道': new_data[u'频道'][:10]})
# 创建输出目录 用来保存训练好的词向量
output_dir='output_word2vec'
import os
if not os.path.exists(output_dir):
os.mkdir(output_dir)
1.导入数据
import numpy as np
import pandas as pd
# 查看训练数据
train_data=pd.read_csv('data/sohu_train.txt',sep='\t',header=None,dtype=np.str_,encoding='utf8',names=['频道','文章'])
train_data.info()
# 载入停用词
stopwords = set()
with open('data/stopwords.txt', 'r') as infile:
for line in infile:
line = line.rstrip('\n')
if line:
stopwords.add(line.lower())
2.分词
# 分词
import jieba
article_words=[]
# 遍历每篇文章
for article in train_data[u'文章']:
curr_words=[]
# 遍历文章中的每个词
for word in jieba.cut(article):
# 去除停用词
if word not in stopwords:
curr_words.append(word)
article_words.append(curr_words)
# 分词结果存储到文件
seg_word_file=os.path.join(output_dir,'seg_words.txt')
with open(seg_word_file,'wb') as outfile:
for words in article_words:
outfile.write(u' '.join(words).encode('utf8') + b'\n')
print('分词结果保存到文件:{}'.format(seg_word_file))
3.训练word2vec模型
#pip install gensim
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
# 创建一个句子迭代器,一行为一个句子,词和词之间用空格分开
# 这里我们把一篇文章当作一个句子
sentences=LineSentence(seg_word_file)
# 训练word2vec模型
# 参数说明:
# sentences: 包含句子的list,或迭代器
# size: 词向量的维数,size越大需要越多的训练数据,同时能得到更好的模型
# alpha: 初始学习速率,随着训练过程递减,最后降到 min_alpha
# window: 上下文窗口大小,即预测当前这个词的时候最多使用距离为window大小的词
# max_vocab_size: 词表大小,如果实际词的数量超过了这个值,过滤那些频率低的
# workers: 并行度
# iter: 训练轮数
# min_count: 忽略出现次数小于该值的词
model=Word2Vec(sentences=sentences,size=100,iter=10,min_count=20)
# 保存模型
model_file = os.path.join(output_dir, 'model.w2v')
model.save(model_file)
word2vec模型的使用
# 读取模型
model_file = os.path.join(output_dir, 'model.w2v')
model2=Word2Vec.load(model_file)
1 查找语义相近的词
# def invest_similar(*args, **kwargs):
# res = model2.most_similar(*args, **kwargs)
# print('\n'.join([u'{}:{}'.format(x[0], x[1]) for x in res]))
def invest_similar(*args,**kwargs):
res=model2.most_similar(*args,**kwargs)
print('\n'.join(['{}:{}'.format(x[0],x[1]) for x in res]))
invest_similar(u'摄影', topn=5)
# 女人 + 先生 - 男人 = 女士
# 先生 - 女士 = 男人 - 女人,这个向量的方向就代表了性别!
invest_similar(positive=[u'女人', u'先生'], negative=[u'男人'], topn=1)
2 计算两个词的相似度
model2.similarity('摄影','摄像')
3 查询某个词的词向量
model2[u'摄影'].shape
model2[u'摄影']
# 创建输出目录
import os
output_dir = u'output_w2v'
if not os.path.exists(output_dir):
os.mkdir(output_dir)
1 加载数据
import numpy as np
import pandas as pd
# 查看训练数据
train_data = pd.read_csv('data/sohu_train.txt', sep='\t', header=None, dtype=np.str_, encoding='utf8', names=[u'频道', u'文章'])
train_data.head()
# 载入停用词
stopwords = set()
with open('data/stopwords.txt', 'r') as infile:
for line in infile:
line = line.rstrip('\n')
if line:
stopwords.add(line.lower())
2 计算每个文章的词向量
# 加载训练好的Word2Vec模型
# 需要 4.0_训练word2vec模型.ipynb 的执行结果
from gensim.models import Word2Vec
w2v = Word2Vec.load('output_word2vec/model.w2v')
# 使用文章中所有词的平均词向量作为文章的向量
import jieba
def compute_doc_vec_single(article):
vec = np.zeros((w2v.layer1_size,), dtype=np.float32)
n = 0
for word in jieba.cut(article):
if word in w2v:
vec += w2v[word]#求所有词向量的和
n += 1#计算词的个数
return vec / n#求平均值
def compute_doc_vec(articles):
return np.row_stack([compute_doc_vec_single(x) for x in articles])
x = compute_doc_vec(train_data[u'文章'])
3 训练分类器
# 编码目标变量
from sklearn.preprocessing import LabelEncoder
y_encoder = LabelEncoder()
y = y_encoder.fit_transform(train_data[u'频道'])
# 划分训练测试数据
from sklearn.model_selection import train_test_split
# 根据y分层抽样,测试数据占20%
train_idx, test_idx = train_test_split(range(len(y)), test_size=0.2, stratify=y)
train_x = x[train_idx, :]
train_y = y[train_idx]
test_x = x[test_idx, :]
test_y = y[test_idx]
# 训练逻辑回归模型
from sklearn.linear_model import LogisticRegression
# 常用参数说明
# penalty: 正则项类型,l1还是l2
# C: 正则项惩罚系数的倒数,越大则惩罚越小
# fit_intercept: 是否拟合常数项
# max_iter: 最大迭代次数
# multi_class: 以何种方式训练多分类模型
# ovr = 对每个标签训练二分类模型
# multinomial = 直接训练多分类模型,仅当solver={newton-cg, sag, lbfgs}时支持
# solver: 用哪种方法求解,可选有{liblinear, newton-cg, sag, lbfgs}
# 小数据liblinear比较好,大数据量sag更快
# 多分类问题,liblinear只支持ovr模式,其他支持ovr和multinomial
# liblinear支持l1正则,其他只支持l2正则
model = LogisticRegression(multi_class='multinomial', solver='lbfgs')
model.fit(train_x, train_y)
4 模型效果评估
from sklearn.metrics import confusion_matrix, precision_recall_fscore_support
# 在测试集上计算模型的表现
test_y_pred = model.predict(test_x)
# 计算混淆矩阵
pd.DataFrame(confusion_matrix(test_y, test_y_pred), columns=y_encoder.classes_, index=y_encoder.classes_)
# 计算各项评价指标
def eval_model(y_true, y_pred, labels):
# 计算每个分类的Precision, Recall, f1, support
p, r, f1, s = precision_recall_fscore_support(y_true, y_pred)
# 计算总体的平均Precision, Recall, f1, support
tot_p = np.average(p, weights=s)
tot_r = np.average(r, weights=s)
tot_f1 = np.average(f1, weights=s)
tot_s = np.sum(s)
res1 = pd.DataFrame({
u'Label': labels,
u'Precision': p,
u'Recall': r,
u'F1': f1,
u'Support': s
})
res2 = pd.DataFrame({
u'Label': [u'总体'],
u'Precision': [tot_p],
u'Recall': [tot_r],
u'F1': [tot_f1],
u'Support': [tot_s]
})
res2.index = [999]
res = pd.concat([res1, res2])
return res[[u'Label', u'Precision', u'Recall', u'F1', u'Support']]
eval_model(test_y, test_y_pred, y_encoder.classes_)
5 模型保存
# 保存模型到文件
import dill
import pickle
model_file = os.path.join(output_dir, u'model.pkl')
with open(model_file, 'wb') as outfile:
pickle.dump({
'y_encoder': y_encoder,
'lr': model
}, outfile)
6 对新文档预测
from gensim.models import Word2Vec
import dill
import pickle
import jieba
# 把预测相关的逻辑封装在一个类中,使用这个类的实例来对新文档进行分类预测
class Predictor(object):
def __init__(self, w2v_model_file, lr_model_file):
self.w2v = Word2Vec.load(w2v_model_file)
with open(lr_model_file, 'rb') as infile:
self.model = pickle.load(infile)
def predict(self, articles):
x = self._compute_doc_vec(articles)
y = self.model['lr'].predict(x)
y_label = self.model['y_encoder'].inverse_transform(y)
return y_label
def _compute_doc_vec(self, articles):
return np.row_stack([compute_doc_vec_single(x) for x in articles])
def _compute_doc_vec_single(self, article):
vec = np.zeros((w2v.layer1_size,), dtype=np.float32)
n = 0
for word in jieba.cut(article):
if word in w2v:
vec += w2v[word]
n += 1
return vec / n
# 加载新文档数据
new_data = pd.read_csv('data/sohu_test.txt', sep='\t', header=None, dtype=np.str_, encoding='utf8', names=[u'频道', u'文章'])
new_data.head()
# 加载模型
predictor = Predictor('output_word2vec/model.w2v', model_file)
# 预测前10篇的分类
new_y_pred = predictor.predict(new_data[u'文章'][:10])
# 对比预测
pd.DataFrame({u'预测频道': new_y_pred, u'实际频道': new_data[u'频道'][:10]})
from flask import Flask
from flask import render_template
from flask import request
from predicter import TfidfPredicter,Word2vecPredictor
#模型路径
tfidf_model_file='../output/model.pkl'
word2vec_file='../output_word2vec/model.w2v'
word2vec_model_file='../output_w2v/model.pkl'
tfidf_predicter = TfidfPredicter(tfidf_model_file)#加载tfidf模型
word2vec_predicter = Word2vecPredictor(word2vec_file,word2vec_model_file)#加载word2vec模型
app = Flask(__name__)
@app.route('/')
def newsclass():
"""
显示文章预测页面
:return:
"""
return render_template('newsclass.html')
@app.route('/predict',methods=["GET", "POST"])
def predict():
"""
接受前端传递来的文章内容和预测方式,并用对应的预测方式对文章类型进行预测
:return:
"""
#接受前端传过来的新闻内容
if request.method == "POST":
news = request.form.get("news")
model_type=request.form.get("type")
else:
news = request.args.get("news")
model_type = request.args.get("type")
#判断用户选择的预测方式并采用对应的方式进行预测
if model_type=='tfidf':
labels=tfidf_predicter.predict([news])
else:
labels=word2vec_predicter.predict([news])
return labels[0]#由于每次只传递了一个新闻
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
from gensim.models import Word2Vec
import pickle
import jieba
import numpy as np
class Word2vecPredictor(object):
"""
把word2vec预测相关的逻辑封装在一个类中,使用这个类的实例来对新文档进行分类预测
"""
def __init__(self, w2v_model_file, lr_model_file):
"""
加载词向量和预测模型
:param w2v_model_file: 词向量文件路径
:param lr_model_file: 模型文件路径
"""
# 导入预训练的词向量
self.w2v = Word2Vec.load(w2v_model_file)
with open(lr_model_file, 'rb') as infile:
# 导入训练好的模型
self.model = pickle.load(infile)
def predict(self, articles):
"""
对文章类型进行预测
:param articles: 文章列表
:return:文章预测的结果列表
"""
# 1. 计算所有文章的向量
x = self._compute_doc_vec(articles)
# 2. 预测类别
y = self.model['lr'].predict(x)
# 3. 将预测的新闻下标转换成对应的新闻类别名称
y_label = self.model['y_encoder'].inverse_transform(y)
return y_label
def _compute_doc_vec(self, articles):
"""
计算所有文章的词向量
:param articles: 文章列表
:return:所有文章的向量
"""
return np.row_stack([self._compute_doc_vec_single(x) for x in articles])
def _compute_doc_vec_single(self, article):
"""
计算单个文章的词向量
:param article: 一篇文章的类容
:return:一篇文章的向量
"""
vec = np.zeros((self.w2v.layer1_size,), dtype=np.float32)
n = 0
for word in jieba.cut(article):
if word in self.w2v:#判断词在不在词向量中
vec += self.w2v[word]#词向量累积求和
n += 1#计算词的个数
return vec / n#求平均词向量作为文章的向量
class TfidfPredicter(object):
"""
把tfidf预测相关的逻辑封装在一个类中,使用这个类的实例来对新文档进行分类预测
"""
def __init__(self,model_file):
"""
实例化并加载模型
:param model_file: 模型路径
"""
with open(model_file, 'rb') as infile:
self.model = pickle.load(infile)
def predict(self,articles):
"""
实现文章预测
:param articles: 文章列表
:return: 文章预测的结果列表
"""
# 对新闻预测
# 1. 转化为词袋表示 得到tfidf特征
x = self.model['tfidf'].transform(articles)
# 2. 预测类别 是0,1,2,3,4数值
y= self.model['lr'].predict(x)
# 3. 将预测的新闻下标转换成对应的新闻类别名称
y_label = self.model['y_encoder'].inverse_transform(y)
return y_label