TF-IDF的文章分类系统

本文最后更新于:25 天前

文章分类系统的实现

前言

接上,一个聚合类的阅读软件当然少不了文章分类系统,模型之类的根本来不及看,来不及学,就采用了 TF-IDF 算法来写,自己构建词库,自己算,准确率还算比较高的,对于我们这个小项目来说够用了反正

原理

  • 将已分类的文章做分词,保留 TF-IDF 算法前五的名词
  • 将每篇文章的五个名词去重合成一个大的集合作为词库
  • 根据字典生成每篇文章的单独向量
  • 将所有相同类别的文章向量相加求平均得到文章类别的平均向量
  • 将未分类的文章做分词,保留前 20 的名词做向量
  • 与所有文章类别的平均向量做比对,保留匹配度最高的三个

文章分类系统

参考文章:

【python】爬虫篇:通过文章内容使用TF-IDF算法对文章进行分类(五)

【python】爬虫篇:最后一篇之TF-IDF分类代码篇(六)

项目地址:

本项目已在 github 上开源:github地址

源代码:

  • Dictionnary_Builder.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    # 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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    # 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存在
  • 代码中的分类可调,相应的已分类的文章需要提供

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!