前言
接上,一个聚合类的阅读软件当然少不了文章分类系统,模型之类的根本来不及看,来不及学,就采用了 TF-IDF 算法来写,自己构建词库,自己算,准确率还算比较高的,对于我们这个小项目来说够用了反正
原理
- 将已分类的文章做分词,保留 TF-IDF 算法前五的名词
- 将每篇文章的五个名词去重合成一个大的集合作为词库
- 根据字典生成每篇文章的单独向量
- 将所有相同类别的文章向量相加求平均得到文章类别的平均向量
- 将未分类的文章做分词,保留前 20 的名词做向量
- 与所有文章类别的平均向量做比对,保留匹配度最高的三个
文章分类系统
参考文章:
【python】爬虫篇:通过文章内容使用TF-IDF算法对文章进行分类(五)
【python】爬虫篇:最后一篇之TF-IDF分类代码篇(六)
项目地址:
本项目已在 github 上开源:github地址
源代码:
-
Dictionnary_Builder.py
# encoding = utf-8 import sys import pymysql import jieba import jieba.analyse import numpy as np np.set_printoptions(threshold = sys.maxsize) np.set_printoptions(suppress = True) # 不使用科学计数法 mysql = pymysql.connect(host="bj-cynosdbmysql-grp-2w3ca8rc.sql.tencentcdb.com", port=25197, user="tmp", passwd="Aa1@0000", db="lsilencej_test_post", charset = "utf8") # 连接数据库 cur = mysql.cursor() # 生成游标 word_num = 0 def Dictionary_Builder(posts): merged_words = [] # 用来存放所有词的 list post_words = [] # post_words 是把每一条数据的 id 和生成的分词情况保存下来的list for index in range(len(posts)): # 遍历每一篇文章 words = jieba.analyse.extract_tags(posts[index][1], withWeight = True, allowPOS = 'n', topK = 5) # 为每一篇文章分词(allowPOS = 'n'代表分为名词词性),为每个词打上权重,取权重最大的前5个词语 words_weight = {tag: weight for tag, weight in words} # 分开词和权重并存入 words_weight 中 merged_words = set(words_weight.keys()) | set(merged_words) # 将词放入 set 中进行去重并整合成一个大的 list post_words.append((posts[index][0], words_weight)) # 把该条数据的 id 和所分的词加入到 post_words 中 dictionary = ','.join(merged_words) # 通过逗号分隔所有词,构成词库 word_num = len(merged_words) # 记录词的总数, 后面作为维数使用 sql = "insert into article_dictionary values(null, '{dictionary}')".format(dictionary = dictionary) # 把词库插入数据库表 article_dictionary cur.execute(sql) mysql.commit() print('词库写入完毕') print('正在生成已分类文章的向量') for post_word in post_words: # post_word 是 (id, (tag, weight)) 的形式 vector = [] # 清空 vector 集合 for merged_word in merged_words: # 在词库中遍历每个词 if merged_word in post_word[1].keys(): # 如果文章中存在词库中的遍历到的这个词 vector.append(post_word[1][merged_word]) # 把权重放入对应词的位置 else: vector.append(0) # 否则为0 sql = "update article_category_data set weight = '{vector}' where id = {post_id}".format(vector = vector, post_id = post_word[0]) # 把每条数据的权重值集合插入表中 cur.execute(sql) mysql.commit() # print('-----------------提交成功------------------------') def Ave_Vector_Builder(index, category): # 计算每一类文章的平均向量 sql = "select id, weight from article_category_data where category='{category}'".format(category = category) cur.execute(sql) cated_posts = cur.fetchall() # 所有的这一类的文章的 id, 权重 集合 null_vector = np.zeros(word_num) # 创造一个一维,长度为 word_num 的零向量 print('一维零向量生成完毕') for i in range(len(cated_posts)): # 遍历每一条数据 dat = cated_posts[i][1][1:-1].split(',') # 取出对应的数据 dat_vector = list(map(float, dat)) # 把取出来的字符串转为 float 类型并放入集合中 temp_vector = np.array(dat_vector) # 生成向量 null_vector += temp_vector # 与零向量相加 print('遍历完毕') ave_vector = (null_vector / len(cated_posts)).tolist() # 获得平均向量 result = '['+','.join([str('{:.10f}'.format(item) if item != 0.0 else item) for item in ave_vector])+']' # 固定生成的字符串格式,如果不为零保留十位小数,为零不改变 print('字符串格式固定完毕') sql = "insert into article_category_weight values({index}, '{category}', '{result}')".format(index = index, category = category, result = result) # 把 id, 分类, 该分类的平均向量插入 article_category_weight 中 cur.execute(sql) mysql.commit() print('提交完毕') if __name__ == '__main__': sql = 'select id, category, content from article_category_data where content is not null' # 在数据库表 article_category_data 中查询内容不为空的文章数据 cur.execute(sql) # 执行sql datas = cur.fetchall() # 每一行的数据的集合 posts = [] # 存储文章的列表 print('拉取数据') for index in range(len(datas)): posts.append((datas[index][0], datas[index][2])) # 把 id 和 content 作为一个值构建一个 posts 集合 print('数据拉取并且遍历完毕') Dictionary_Builder(posts) # 运行 print(' Dictionary_Builder 运行结束, 词库已生成') cate =['文化', '娱乐', '体育', '财经', '科技', '游戏'] # 类别 for index in range(len(cate)): # 对每一类文章进行枚举 Ave_Vector_Builder(index + 1, cate[index]) # id 从 1 开始 mysql.close()
-
Post_Classify.py
# encoding = utf-8 import pymysql import jieba import jieba.analyse mysql = pymysql.connect(host="bj-cynosdbmysql-grp-2w3ca8rc.sql.tencentcdb.com", port=25197, user="tmp", passwd="Aa1@0000", db="lsilencej_test_post", charset = "utf8") # 连接数据库 cur = mysql.cursor() # 生成游标 def Vector_Reader(): # 读取所有文章类型的平均向量 sql = "select category, category_weight from article_category_weight" # 在 article_category_weight 中读取文章种类和该种类的平均向量 cur.execute(sql) categories_weight = cur.fetchall() # 每一行的数据 vectors = [] for index in range(len(categories_weight)): vectors.append((categories_weight[index][0], categories_weight[index][1])) return vectors def cosine_similarity(vector1, vector2): # 求两向量之间的夹角 molecule = 0.0 # 初始化 denominatorA = 0.0 denominatorB = 0.0 for a, b in zip(vector1, vector2): if(a != 0 or b != 0): molecule += a * b # 计算分子 denominatorA += a ** 2 # 计算分母 denominatorB += b ** 2 if denominatorB == 0.0 or denominatorB == 0.0: # 分母不为0 return 0 else: # 计算两个向量的夹角 return round(molecule / ((denominatorA ** 0.5) * (denominatorB ** 0.5)) * 100, 2) # 乘以 100 防止数据过小 def Cos_Comparer(result, vectors, dictionary): # 通过比较两个向量的cos值来判断相似度,传入的参数分别是未分类的文章,多个类型的平均向量,字典 post_id = result[0] # 获取当前数据的id post = result[1] # 获取当前数据的文本内容 words = jieba.analyse.extract_tags(post, withWeight = True, allowPOS = 'n', topK = 20) words_weight = {tag: weight for tag, weight in words} # print('words_weight : ', words_weight) # print("============") dictionary_list = ','.join(dictionary).split(',') # 字符串变成列表 vector = [] # 未分类的文章的文章向量 for index_word in dictionary_list: # 枚举词库中的每个词 if index_word in words_weight: # 如果新文章中存在这个词 vector.append(words_weight[index_word]) # 把权重加入 vector 列表中 else: vector.append(0) # 否则为0 # print(vector) # 新输入的文章向量 topK = [] # 将该文章的向量和所有文章类别的平均向量比较,获取相关度为前三的类别 cos = () for v in vectors: temp_v = v[1][1:-1].split(',') # 取每类文章的平均向量 temp_f_v = list(map(float, temp_v)) # 字符串转数的集合 num_cos = cosine_similarity(vector, temp_f_v) # 计算未分类文章和每类文章的平均向量的相关度 cos = (v[0], num_cos) # 格式 ('科技', 9.8) if (len(topK) < 3): # 只保留前三个数据, 此时集合中少于三个元素 topK.append(cos) # 加入 topK 当中 topK.sort(reverse = True) # 从大到小排序 else: # 集合中多于三个元素 if (cos[1] > topK[0][1]): # 如果现在这个余弦夹角比 topK 中的最大值还大,即更加相关,则插入到头部 tmp = topK[1] topK[1] = topK[0] topK[0] = cos topK[2] = tmp print('post', post_id, ':', topK) sql = "update article set category = '{top}' where id = {post_id}".format(top = topK[0][0], post_id = post_id) # print(sql) cur.execute(sql) mysql.commit() vectors = Vector_Reader() # 获得所有类型的平均向量 print('已获得向量') sql = "select dictionary from article_dictionary" # 在 article_dictionary 中读取词库 cur.execute(sql) dictionary = cur.fetchone() # 只有一行数据 print('已查询到字典') sql = "select id, content from article where category is null" # 在 article 中读取未分类的文章 cur.execute(sql) results = cur.fetchall() print('拉取到数据') for result in results: Cos_Comparer(result, vectors, dictionary) mysql.close()
文件
Dictionnary_Builder.py
构建词库和各类文章的平均向量
Post_Classify.py
将未分类的文章和各类文章的平均向量进行比对
数据库表
article
未分类的文章,字段最低需求:
-
id int 非空 键
-
category varchar 可空
-
content longtext 非空
article_category_data
已分类的文章,字段最低需求:
-
id int 非空 键
-
category varchar 非空
-
content longtext 非空
-
weight longtext 可空
article_category_weight
各类文章的平均向量,字段最低需求:
-
id int 非空 键
-
category varchar 非空
-
category_weight longtext 非空
article_dictionary
词库,字段最低需求:
-
id int 非空 键
-
dictionary longtext 非空
Tips
- 由于大部分代码和 id 绑定在一起,如果原数据库表有数据可能会有bug存在
- 代码中的分类可调,相应的已分类的文章需要提供