2022年 11月 16日

Python实现基于Fasttext的商品评论数据分类

在以往的文本分类型的任务中,基本的流程主要是就是:

  1. 文本数据加载
  2. 数据清洗
  3. 分词
  4. 向量化
  5. 分类模型训练
  6. 性能评估

这里面比如向量化和模型搭建是独立的两个节点,可以自由地进行设计,当然了也是一份工作量,今天使用的fasttext更像是一个集成的库,把向量化和分类一起做掉了,这个对于使用层面来讲就更方便了一些,不过也并不是绝对的,一般经验来说,封装程度越高的库对于个性化的开发越不友好,但是如果仅仅只是使用一下就行,能够实现自己的功能这样的想法的话倒是可以使用这种类型的库的,总之,没有绝对的最优,只有适合自己的模型。

本文主要也是基于具体的应用来体验fasttext,整体流程如下:

        为了清晰展示流程,这里我用不同的颜色来标识不同的功能部分:

       绿色部分为数据采集部分,这部分由于部分原因无法开放到这里

       蓝色部分为数据处理部分,这部分主要完成原始数据的清洗分词等工作

      黄色部分主要是实例化调用fasttext提供的模型来完成分类评估等工作

       首先看下原始数据样例如下:

  1. {
  2. '_id': '4c671f75cc20b28264c30c2ef158f32b',
  3. 'guid': 'b2422b96a015b85d9a47c83e65f01987',
  4. 'content': 'iPhone13收到了,很喜欢的苹果手机,一直都是苹果的忠实粉丝。13手机外观真的太惊艳了,最新款的手机就是不一样,非常的好看!午夜色也是很惊艳的,是我很喜欢的颜色。外观设计非常的好看,小巧精致,很喜欢iPhone的产品。新款的相机非常好,拍摄效果强大,很适合爱美的女生拍照。拍视频的电影模式也是非常惊艳,真的太棒了,滤镜自带美颜效果,非常特别!前置摄像头也有亮点,优化升级,升级后的像素真的超棒!萌萌的摄像头,手机大小合适,握着手感很舒服。屏幕非常细腻,通透,屏幕很喜欢!新一代运行速度快了很多,系统非常流畅,屏幕120hz刷新率。电池也还不错,正常使用,续航能力还是挺久的,玩游戏也不会发烫。买了套装一年延保的,感觉还是很不错的!A15速度还是很快的,加上iOS15的加持,手机很流畅,使用了一周了,手机各方面都很不错!',
  5. 'creationTime': '2021-11-2907: 13: 51',
  6. 'isDelete': False,
  7. 'isTop': False,
  8. 'userImageUrl': 'misc.360buyimg.com/user/myjd-2015/css/i/peisong.jpg',
  9. 'topped': 0,
  10. 'replies': [
  11. {
  12. 'id': 946189091,
  13. 'commentId': 16739194625,
  14. 'content': '谢谢您对本店的支持,我们会不断的努力,争取做的更好,我们成长的路上有您的支持,我们表示感谢,欢迎再次光临。祝您生活愉快,合家安康!',
  15. 'pin': 'jd_oMFlwVDJJjkA',
  16. 'userClient': 98,
  17. 'userImage': 'misc.360buyimg.com/user/myjd-2015/css/i/peisong.jpg',
  18. 'ip': '115.207.85.31',
  19. 'productId': 10039695828478,
  20. 'replyList': [
  21. ],
  22. 'nickname': 'jd_oMFlwVDJJjkA',
  23. 'creationTime': '2021-11-2910: 10: 54',
  24. 'parentId': 0,
  25. 'targetId': 0,
  26. 'venderShopInfo': {
  27. 'id': 10706414,
  28. 'appName': '//mall.jd.com/index-10706414.html',
  29. 'title': '京东之家官方旗舰店',
  30. 'venderId': 10955089
  31. }
  32. }
  33. ],
  34. 'replyCount': 28,
  35. 'score': 5,
  36. 'imageStatus': 1,
  37. 'usefulVoteCount': 28,
  38. 'userClient': 4,
  39. 'discussionId': 1006752884,
  40. 'imageCount': 8,
  41. 'anonymousFlag': 1,
  42. 'plusAvailable': 201,
  43. 'mobileVersion': '10.2.4',
  44. 'mergeOrderStatus': 2,
  45. 'productColor': '128G午夜色',
  46. 'productSize': '套装五:搭配店铺延保一年',
  47. 'textIntegral': 40,
  48. 'imageIntegral': 40,
  49. 'status': 1,
  50. 'referenceId': '10039695828478',
  51. 'referenceTime': '2021-11-0802: 31: 34',
  52. 'nickname': 'z***a',
  53. 'replyCount2': 39,
  54. 'userImage': 'misc.360buyimg.com/user/myjd-2015/css/i/peisong.jpg',
  55. 'orderId': 0,
  56. 'integral': 80,
  57. 'productSales': '[
  58. ]',
  59. 'referenceImage': 'jfs/t1/124476/38/25971/146827/622b14cfEec332c92/75f5bf4417c1fd1d.jpg',
  60. 'referenceName': '【12期免息可选】Apple苹果iPhone13(A2634)全网通5G手机128G绿色套装一:搭配90天品胜碎屏保障',
  61. 'firstCategory': 9987,
  62. 'secondCategory': 653,
  63. 'thirdCategory': 655,
  64. 'aesPin': None,
  65. 'days': 21,
  66. 'afterDays': 0,
  67. 'comp_con': 'iPhone13收到了很喜欢的苹果手机一直都是苹果的忠实粉丝13手机外观真的太惊艳了最新款的手机就是不一样非常的好看午夜色也是很惊艳的是我很喜欢的颜色外观设计非常的好看小巧精致很喜欢iPhone的产品新款的相机非常好拍摄效果强大很适合爱美的女生拍照拍视频的电影模式也是非常惊艳真的太棒了滤镜自带美颜效果非常特别前置摄像头也有亮点优化升级升级后的像素真的超棒萌的摄像头手机大小合适握着手感很舒服屏幕非常细腻通透屏幕很喜欢新一代运行速度快了很多系统非常流畅屏幕120hz刷新率电池也还不错正常使用续航能力还是挺久的玩游戏也不会发烫买了套装一年延保的感觉还是很不错的A15速度还是很快的加上iOS15的加持手机很流畅使用了一周了手机各方面都很不错',
  68. 'label': 1,
  69. 'cut_li': [
  70. 'iPhone',
  71. '13',
  72. '收到',
  73. '喜欢',
  74. '苹果',
  75. '手机',
  76. '一直',
  77. '苹果',
  78. '忠实',
  79. '粉丝',
  80. '13',
  81. '手机',
  82. '外观',
  83. '真的',
  84. '太',
  85. '惊艳',
  86. '最新款',
  87. '手机',
  88. '非常',
  89. '好看',
  90. '午夜',
  91. '色',
  92. '惊艳',
  93. '喜欢',
  94. '颜色',
  95. '外观设计',
  96. '非常',
  97. '好看',
  98. '小巧',
  99. '精致',
  100. '喜欢',
  101. 'iPhone',
  102. '产品',
  103. '新款',
  104. '相机',
  105. '非常',
  106. '拍摄',
  107. '效果',
  108. '强大',
  109. '适合',
  110. '爱美',
  111. '女生',
  112. '拍照',
  113. '拍',
  114. '视频',
  115. '电影',
  116. '模式',
  117. '非常',
  118. '惊艳',
  119. '真的',
  120. '太棒了',
  121. '滤镜',
  122. '自带',
  123. '美颜',
  124. '效果',
  125. '非常',
  126. '特别',
  127. '前置',
  128. '摄像头',
  129. '亮点',
  130. '优化',
  131. '升级',
  132. '升级',
  133. '像素',
  134. '真的',
  135. '超棒',
  136. '萌',
  137. '摄像头',
  138. '手机',
  139. '大小',
  140. '合适',
  141. '握',
  142. '手感',
  143. '舒服',
  144. '屏幕',
  145. '非常',
  146. '细腻',
  147. '通透',
  148. '屏幕',
  149. '喜欢',
  150. '新一代',
  151. '运行',
  152. '速度',
  153. '快',
  154. '很多',
  155. '系统',
  156. '非常',
  157. '流畅',
  158. '屏幕',
  159. '120hz',
  160. '刷新率',
  161. '电池',
  162. '不错',
  163. '正常',
  164. '使用',
  165. '续航',
  166. '能力',
  167. '挺久',
  168. '玩游戏',
  169. '不会',
  170. '发烫',
  171. '买',
  172. '套装',
  173. '一年',
  174. '延保',
  175. '感觉',
  176. '不错',
  177. 'A15',
  178. '速度',
  179. '很快',
  180. '加上',
  181. 'iOS15',
  182. '加持',
  183. '手机',
  184. '流畅',
  185. '使用',
  186. '一周',
  187. '手机',
  188. '方面',
  189. '不错'
  190. ],
  191. 'cut_con': 'iPhone13收到喜欢苹果手机一直苹果忠实粉丝13手机外观真的太惊艳最新款手机非常好看午夜色惊艳喜欢颜色外观设计非常好看小巧精致喜欢iPhone产品新款相机非常拍摄效果强大适合爱美女生拍照拍视频电影模式非常惊艳真的太棒了滤镜自带美颜效果非常特别前置摄像头亮点优化升级升级像素真的超棒萌摄像头手机大小合适握手感舒服屏幕非常细腻通透屏幕喜欢新一代运行速度快很多系统非常流畅屏幕120hz刷新率电池不错正常使用续航能力挺久玩游戏不会发烫买套装一年延保感觉不错A15速度很快加上iOS15加持手机流畅使用一周手机方面不错',
  192. 'label_con': '__label__1,
  193. iPhone13收到喜欢苹果手机一直苹果忠实粉丝13手机外观真的太惊艳最新款手机非常好看午夜色惊艳喜欢颜色外观设计非常好看小巧精致喜欢iPhone产品新款相机非常拍摄效果强大适合爱美女生拍照拍视频电影模式非常惊艳真的太棒了滤镜自带美颜效果非常特别前置摄像头亮点优化升级升级像素真的超棒萌摄像头手机大小合适握手感舒服屏幕非常细腻通透屏幕喜欢新一代运行速度快很多系统非常流畅屏幕120hz刷新率电池不错正常使用续航能力挺久玩游戏不会发烫买套装一年延保感觉不错A15速度很快加上iOS15加持手机流畅使用一周手机方面不错'
  194. }

        之后对原始数据进行解析处理:

  1. # 文本去重
  2. con_li = []
  3. data_clear = []
  4. for item in data:
  5. if item["content"] not in con_li:
  6. con_li.append(item["content"])
  7. data_clear.append(item)
  8. print("文本去重过滤条数:%s" % (len(data) - len(data_clear)))
  9. print("剩余评论个数:", len(con_li)) # 剩余评论个数
  10. def clean_txt(raw):
  11. """
  12. 提取清洗
  13. """
  14. fil = re.compile(r"[^0-9a-zA-Z\u4e00-\u9fa5]+")
  15. return fil.sub(" ", raw)
  16. compress_num = 0
  17. for i, item in enumerate(data_clear):
  18. temp_com = item["content"]
  19. compress_com = clean_txt(temp_com)
  20. if compress_com != temp_com:
  21. compress_num += 1
  22. item["comp_con"] = compress_com
  23. data_clear[i] = item
  24. print("data_clear_legnth: ", len(data_clear))
  25. for one in data_clear[:3]:
  26. print("one: ", one)

        接着对清洗处理好的数据记性分词和数据组装:

  1. # 进行结巴分词
  2. stop_words = []
  3. with open("cn_stopwords.txt", "r", encoding="utf-8") as f:
  4. stop_words = f.readlines()
  5. stop_words = [sw.strip() for sw in stop_words]
  6. stop_words.append("\n")
  7. data_li = []
  8. for i, item in enumerate(data_final):
  9. cut_li = list(jieba_fast.cut(item["comp_con"]))
  10. cut_clear_li = [c.strip() for c in cut_li if c.strip() and c not in stop_words]
  11. item["cut_li"] = cut_clear_li
  12. cut_con = " ".join(cut_clear_li)
  13. item["cut_con"] = cut_con
  14. label_con = "__label__%s , %s" % (item["label"], item["cut_con"])
  15. item["label_con"] = label_con
  16. data_li.append(label_con)
  17. data_final[i] = item
  18. print("data_final_legnth: ", len(data_final))
  19. for one in data_final[:3]:
  20. print("one: ", one)

       处理后的数据如下:

        虽然说看着有些奇怪,尤其是: __label__,但是这个没办法,fasttext需要的标准数据格式就是这个样子的。

     之后就可以进行模型训练了,核心实现如下:

  1. def train_model(ipt=None, opt=None, model="", dim=100, epoch=5, lr=0.5, loss="softmax"):
  2. np.set_printoptions(suppress=True)
  3. classifier = fasttext.train_supervised(
  4. ipt, label="__label__", dim=dim, epoch=epoch, lr=lr, wordNgrams=4, loss=loss
  5. )
  6. """
  7. 训练一个监督模型, 返回一个模型对象
  8. @param input: 训练数据文件路径
  9. @param lr: 学习率
  10. @param dim: 向量维度
  11. @param ws: cbow模型时使用
  12. @param epoch: 次数
  13. @param minCount: 词频阈值, 小于该值在初始化时会过滤掉
  14. @param minCountLabel: 类别阈值,类别小于该值初始化时会过滤掉
  15. @param minn: 构造subword时最小char个数
  16. @param maxn: 构造subword时最大char个数
  17. @param neg: 负采样
  18. @param wordNgrams: n-gram个数
  19. @param loss: 损失函数类型, softmax, ns: 负采样, hs: 分层softmax
  20. @param bucket: 词扩充大小, [A, B]: A语料中包含的词向量, B不在语料中的词向量
  21. @param thread: 线程个数, 每个线程处理输入数据的一段, 0号线程负责loss输出
  22. @param lrUpdateRate: 学习率更新
  23. @param t: 负采样阈值
  24. @param label: 类别前缀
  25. @param verbose: ??
  26. @param pretrainedVectors: 预训练的词向量文件路径, 如果word出现在文件夹中初始化不再随机
  27. @return model object
  28. """
  29. classifier.save_model(opt)
  30. return classifier

        这里同样实现了对于模型结果的评估方法:

  1. def cal_precision_and_recall(file="test.txt"):
  2. """
  3. 计算每个标签 的precision和recall
  4. """
  5. precision = defaultdict(int, 1)
  6. recall = defaultdict(int, 1)
  7. total = defaultdict(int, 1)
  8. with open(file, encoding="utf-8") as f:
  9. for line in f:
  10. label, content = line.split(",", 1)
  11. total[label.strip().strip("__label__")] += 1
  12. labels2 = classifier.predict([content.strip()])
  13. pre_label, sim = labels2[0][0][0], labels2[1][0][0]
  14. recall[pre_label.strip().strip("__label__")] += 1
  15. if label.strip() == pre_label.strip():
  16. precision[label.strip().strip("__label__")] += 1
  17. print("{:<10} {:<30}".format("precision", str(precision.dict)))
  18. print("{:<10} {:<30}".format("recall", str(recall.dict)))
  19. print("{:<10} {:<30}".format("total", str(total.dict)))
  20. for sub in precision.dict:
  21. pre = precision[sub] / total[sub]
  22. rec = precision[sub] / recall[sub]
  23. F1 = (2 * pre * rec) / (pre + rec)
  24. print(
  25. f"{sub.strip('__label__')} \t precision: {str(pre)} \t recall: {str(rec)} \t F1: {str(F1)}"
  26. )

       运行结果如下所示: