├── FAQ_减肥.txt ├── FAQrobot.py ├── README.md ├── doc └── 截图展示1.jpg └── utils.py /FAQ_减肥.txt: -------------------------------------------------------------------------------- 1 | #FAQ文件必须是UTF-8的无bom格式的文本文件。 2 | #注释:注释文字由#开头。(整个一行都是注释内容) 3 | #问答块格式如下: 4 | #【问题】问题标题(可以有1或多个,至少有1个。必须由"【问题】"开头。) 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 | 轻断食的主要思路是每周进行2天断食,断食日男性摄入600大卡,女性摄入500大卡的热量,其他日子可以正常进食。断食日尽量用饱腹感强的低热量食物维持一天的生活。这样每周可少摄入3000-4000大卡。轻断食的优点是比持续节食更加容易坚持。 32 | 33 | 34 | #【减肥攻略】 35 | #【问题】快速、高效减肥攻略 36 | #【问题】健康减肥攻略 37 | #【问题】学生减肥攻略 38 | #【问题】在校学生如何减肥? 39 | 40 | #【减肥鸡汤】 41 | 【问题】减肥的关键到底在于哪一点? 42 | 【问题】减脂的关键到底在于哪一点? 43 | 【问题】如何坚持减肥? 44 | 减肥的关键在于需要“坚持”。减肥需要我们改变不良的生活习惯,培养更好的生活习惯。当健康的生活方式成为习惯,我们的身体自然会保持在健康活力的状态。意志力也是帮助减肥的重要能力,想要了解如何利用自己的意志力减肥吗? 45 | 46 | 47 | 【问题】有什么办法刺激自己一直减肥坚持下去? 48 | 【问题】减肥没有动力怎么办? 49 | 【问题】为什么要减肥? 50 | 【问题】减肥有什么好处? 51 | 【问题】减肥的意义何在? 52 | 减肥可以使自己外观及精神得到改善。此外减肥还可以防治很多慢性疾病,例如:成年型糖尿病、高血压、高血脂症、心脏病、脂肪肝等。也可以使xing生活质量显著提高。 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 | 首先必须了解事物的营养和热量,用高营养低热量的食物代替高热量事物。一斤小番茄的热量相当于一小块巧克力(18克),相当于慢速骑自行车30分钟消耗的热量。高热量的食物主要有油脂、肥肉、奶油、巧克力、精加工的糖、饼干、大米和白面制品等。低热量的食物主要有蔬菜、水果、瘦肉、鸡蛋、牛奶等。另外也可以通过轻断食方法来控制摄入热量。 84 | 85 | 86 | 【问题】节食减肥有用吗? 87 | 【问题】节食减肥时徒劳吗? 88 | 【问题】减肥期间,节食可以快速瘦下来吗? 89 | 合理控制饮食对于减肥很有帮助,节食则会影响身体健康。瘦身是一个过程,健康的瘦身才是王道。 90 | 91 | 【问题】减肥期间,有哪些低脂又好吃方便的食物? 92 | 低热量的食物主要有蔬菜、水果、瘦肉、鸡蛋、牛奶等。另外也可以通过轻断食方法来控制摄入热量。 93 | 94 | 【问题】减肥期间不能吃什么? 95 | 减肥期间没有什么严格意义上不能吃的,但是要控制好量和吃的时间。少吃零食、含糖量高的饮料、酒精类饮品。 96 | 97 | 【问题】减肥时能吃零食吗? 98 | 【问题】如何打消饱了还想吃的念头? 99 | 【问题】减肥很饿,很馋,超级想吃零食的时候,怎么办? 100 | 【问题】减肥中,晚餐不吃又很饿怎么办? 101 | 请摸摸自己的小肚腩,和微笑时隐藏不住的双下颌。开玩笑啦~减肥期间的零食门槛要高一些,最好是海苔、鱿鱼丝这种有营养,低热量的零食 102 | 103 | 104 | #【减肥后期】 105 | 【问题】在家里做哪些运动可以减脂或增肌? 106 | 【问题】什么运动适合减肥? 107 | 【问题】没有锻炼基础的人,如何增肌与减脂? 108 | 略胖或普通身材的人其实完全可以在家里运动减肥。不用器材的运动有:平板支撑、深蹲、静力深蹲、开合跳、弓步蹲、俯卧撑、高抬腿。简单器材包括哑铃或弹力带等小物件。运动减肥的关键,一是坚持每周都有一定的运动量,形成良好的生活习惯。二是运动量不宜过大,这样既有助于坚持,也可避免运动损伤。 109 | 110 | 【问题】怎么才能不反弹? 111 | 【问题】减肥反弹怎么办? 112 | 减肥期运动量较大,吃的较少,所以总消耗热量大于摄入。不特意减肥时消耗热量值主要取决于基础代谢,所以增加肌肉,提高基础代谢是避免减肥反弹的好方法。还有在非减肥时期也要保持良好的饮食习惯。少油、少糖、少肥肉,多摄入水果、蔬菜、瘦肉、牛奶、鸡蛋等有营养食物。 113 | #【问题】减肥之后暴食反弹怎么办? 114 | 115 | 【问题】减肥后皮肤松弛怎么? 116 | 【问题】减肥后如何让皮肤不松弛? 117 | 皮肤松弛可能是减肥过快的原因。在减脂的同时要增肌,将肌肉力量训练安排进你的减肥计划。肌肉可以填补原来脂肪组织的空间,并起到收紧皮肤的作用。饮食方面要补充足够的水分,鸡蛋、牛奶及奶制品、豆类及豆制品、坚果、植物种子和鱼类都是合成胶原蛋白和弹性蛋白的优质蛋白来源。为了达到最佳吸收,可以在锻炼之后立即补充25~50克蛋白质。最后就是滋养呵护肌肤啦~使用紧肤霜、防晒霜,避免使用刺激性的洗涤剂、肥皂等。 118 | 119 | 120 | #日常聊天 121 | 【问题】怎么委婉的劝女朋友减肥? 122 | 带她去爬山、徒步、游泳等有助于减肥的活动,潜移默化地感染她,让她认识到运动减肥的乐趣~ 123 | 124 | 125 | #缺少答案的问题,先用#注释掉~~ 126 | #【减肥专业知识】 127 | #【问题】什么是无糖? 128 | #【问题】低脂和低热量有什么区别? 129 | #【问题】体重 120 斤和 100 斤的世界是完全不同的吗? 130 | #【问题】有没有什么健身的好app? 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /FAQrobot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import logging 4 | from collections import deque 5 | 6 | import jieba 7 | import jieba.posseg as pseg 8 | 9 | from utils import ( 10 | get_logger, 11 | similarity, 12 | ) 13 | 14 | 15 | jieba.dt.tmp_dir = "./" 16 | jieba.default_logger.setLevel(logging.ERROR) 17 | logger = get_logger('faqrobot', logfile="faqrobot.log") 18 | 19 | 20 | class zhishiku(object): 21 | def __init__(self, q): # a是答案(必须是1给), q是问题(1个或多个) 22 | self.q = [q] 23 | self.a = "" 24 | self.sim = 0 25 | self.q_vec = [] 26 | self.q_word = [] 27 | 28 | def __str__(self): 29 | return 'q=' + str(self.q) + '\na=' + str(self.a) + '\nq_word=' + str(self.q_word) + '\nq_vec=' + str(self.q_vec) 30 | # return 'a=' + str(self.a) + '\nq=' + str(self.q) 31 | 32 | 33 | class FAQrobot(object): 34 | def __init__(self, zhishitxt='FAQ_减肥.txt', lastTxtLen=10, usedVec=False): 35 | # usedVec 如果是True 在初始化时会解析词向量,加快计算句子相似度的速度 36 | self.lastTxt = deque([], lastTxtLen) 37 | self.zhishitxt = zhishitxt 38 | self.usedVec = usedVec 39 | self.reload() 40 | 41 | def load_qa(self): 42 | print('问答知识库开始载入') 43 | self.zhishiku = [] 44 | with open(self.zhishitxt, encoding='utf-8') as f: 45 | txt = f.readlines() 46 | abovetxt = 0 # 上一行的种类: 0空白/注释 1答案 2问题 47 | for t in txt: # 读取FAQ文本文件 48 | t = t.strip() 49 | if not t or t.startswith('#'): 50 | abovetxt = 0 51 | elif abovetxt != 2: 52 | if t.startswith('【问题】'): # 输入第一个问题 53 | self.zhishiku.append(zhishiku(t[4:])) 54 | abovetxt = 2 55 | else: # 输入答案文本(非第一行的) 56 | self.zhishiku[-1].a += '\n' + t 57 | abovetxt = 1 58 | else: 59 | if t.startswith('【问题】'): # 输入问题(非第一行的) 60 | self.zhishiku[-1].q.append(t[4:]) 61 | abovetxt = 2 62 | else: # 输入答案文本 63 | self.zhishiku[-1].a += t 64 | abovetxt = 1 65 | 66 | for t in self.zhishiku: 67 | for question in t.q: 68 | t.q_word.append(set(jieba.cut(question))) 69 | 70 | def load_embedding(self): 71 | from gensim.models import Word2Vec 72 | if not os.path.exists('Word60.model'): 73 | self.vecModel = None 74 | return 75 | 76 | # 载入60维的词向量(Word60.model,Word60.model.syn0.npy,Word60.model.syn1neg.npy) 77 | self.vecModel = Word2Vec.load('Word60.model') 78 | for t in self.zhishiku: 79 | t.q_vec = [] 80 | for question in t.q_word: 81 | t.q_vec.append({t for t in question if t in self.vecModel.index2word}) 82 | 83 | def reload(self): 84 | self.load_qa() 85 | self.load_embedding() 86 | 87 | print('问答知识库载入完毕') 88 | 89 | def maxSimTxt(self, intxt, simCondision=0.1, simType='simple'): 90 | """ 91 | 找出知识库里的和输入句子相似度最高的句子 92 | simType=simple, simple_POS, vec 93 | """ 94 | self.lastTxt.append(intxt) 95 | if simType not in ('simple', 'simple_pos', 'vec'): 96 | return 'error: maxSimTxt的simType类型不存在: {}'.format(simType) 97 | 98 | # 如果没有加载词向量,那么降级成 simple_pos 方法 99 | embedding = self.vecModel 100 | if simType == 'vec' and not embedding: 101 | simType = 'simple_pos' 102 | 103 | for t in self.zhishiku: 104 | questions = t.q_vec if simType == 'vec' else t.q_word 105 | in_vec = jieba.lcut(intxt) if simType == 'simple' else pseg.lcut(intxt) 106 | 107 | t.sim = max( 108 | similarity(in_vec, question, method=simType, embedding=embedding) 109 | for question in questions 110 | ) 111 | maxSim = max(self.zhishiku, key=lambda x: x.sim) 112 | logger.info('maxSim=' + format(maxSim.sim, '.0%')) 113 | 114 | if maxSim.sim < simCondision: 115 | return '抱歉,我没有理解您的意思。请您询问有关减肥的话题。' 116 | 117 | return maxSim.a 118 | 119 | def answer(self, intxt, simType='simple'): 120 | """simType=simple, simple_POS, vec, all""" 121 | if not intxt: 122 | return '' 123 | 124 | if simType == 'all': # 用于测试不同类型方法的准确度,返回空文本 125 | for method in ('simple', 'simple_pos', 'vec'): 126 | outtext = 'method:\t' + self.maxSim(intxt, simType=method) 127 | print(outtext) 128 | 129 | return '' 130 | else: 131 | outtxt = self.maxSimTxt(intxt, simType=simType) 132 | # 输出回复内容,并计入日志 133 | return outtxt 134 | 135 | 136 | if __name__ == '__main__': 137 | robot = FAQrobot('FAQ_减肥.txt', usedVec=False) 138 | while True: 139 | # simType=simple, simple_pos, vec, all 140 | print('回复:' + robot.answer(input('输入:'), 'simple_pos') + '\n') 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FAQrobot 2 | 3 | 一个自动回复FAQ问题的聊天机器人。目前使用了简单词汇对比、词性权重、词向量3种相似度计算模式。输入符合格式的FAQ文本文件即可立刻使用。欢迎把无法正确区分的问题和FAQ文件发送到评论区。 4 |   5 | ## 程序版本和依赖库 6 | 使用 python3 运行 7 | jieba 分词使用的库 8 | gensim  词向量使用的库,如果使用词向量vec模式,则需要载入 9 | 10 | ## 依赖的文件 11 | 如果使用词向量vec模式,需要下载3个文件:Word60.model,Word60.model.syn0.npy,Word60.model.syn1neg.npy 12 | 下载地址:http://pan.baidu.com/s/1kURNutT 密码:1tq1 13 | 14 | ## FAQ知识库文件 15 | FAQ文件包含想要告知用户的问答内容。 16 | FAQ文件必须是UTF-8的无bom格式的文本文件。 17 | 18 | 注释:注释文字由#开头。(整个一行都是注释内容) 19 | 20 | 问答块格式如下: 21 | 【问题】问题标题(可以有1或多个,至少有1个。必须由"【问题】"开头。) 22 | 答案内容(可以有多行,必须紧跟着上面的【问题】,多行答案中间不能有空白的行。) 23 | 多个问答块之间可以用空白行分割 24 |   25 | 程序默认使用的是减肥问答FAQ文件。你可以载入自己编辑的FAQ文件。   26 | ![展示效果](https://github.com/ofooo/FAQrobot/blob/master/doc/%E6%88%AA%E5%9B%BE%E5%B1%95%E7%A4%BA1.jpg) 27 |   28 | ## 主程序FAQrobot.py 29 | 直接运行该文件,即可对减肥问题进行问答。你可以载入自己的FAQ文件,请保证FAQ文件格式正确。 30 | robot.answer(inputtxt,'simple_POS') 可得出输入问题的返回答案。 31 | simType参数有如下模式: 32 | simple:简单的对比相同词汇数量,得到句子相似度 33 | simple_POS:简单的对比相同词汇数量,并对词性乘以不同的权重,得到句子相似度 34 | vec:用词向量计算相似度,并对词性乘以不同的权重,得到句子相似度 35 | all:调试模式,把以上几种模式的结果都显示出来,方便对比和调试 36 | 37 | inputtxt 可输入的特殊文本命令: 38 | -zsk 显示当前知识库 39 | -s -1 查看上一个问句的结果和中间参数 40 | -q -1 重复提问,把当一个问句当做输入 41 | -reload 重新载入QA知识库 42 | -------------------------------------------------------------------------------- /doc/截图展示1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofooo/FAQrobot/76216f24ac286d47e4d91ee01e86ec1360d5e97a/doc/截图展示1.jpg -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from os.path import join, dirname 3 | 4 | 5 | POS_WEIGHT = { 6 | "Ag": 1, # 形语素 7 | "a": 0.5, # 形容词 8 | "ad": 0.5, # 副形词 9 | "an": 1, # 名形词 10 | "b": 1, # 区别词 11 | "c": 0.2, # 连词 12 | "dg": 0.5, # 副语素 13 | "d": 0.5, # 副词 14 | "e": 0.5, # 叹词 15 | "f": 0.5, # 方位词 16 | "g": 0.5, # 语素 17 | "h": 0.5, # 前接成分 18 | "i": 0.5, # 成语 19 | "j": 0.5, # 简称略语 20 | "k": 0.5, # 后接成分 21 | "l": 0.5, # 习用语 22 | "m": 0.5, # 数词 23 | "Ng": 1, # 名语素 24 | "n": 1, # 名词 25 | "nr": 1, # 人名 26 | "ns": 1, # 地名 27 | "nt": 1, # 机构团体 28 | "nz": 1, # 其他专名 29 | "o": 0.5, # 拟声词 30 | "p": 0.3, # 介词 31 | "q": 0.5, # 量词 32 | "r": 0.2, # 代词 33 | "s": 1, # 处所词 34 | "tg": 0.5, # 时语素 35 | "t": 0.5, # 时间词 36 | "u": 0.5, # 助词 37 | "vg": 0.5, # 动语素 38 | "v": 1, # 动词 39 | "vd": 1, # 副动词 40 | "vn": 1, # 名动词 41 | "w": 0.01, # 标点符号 42 | "x": 0.5, # 非语素字 43 | "y": 0.5, # 语气词 44 | "z": 0.5, # 状态词 45 | "un": 0.3 # 未知词 46 | } 47 | 48 | 49 | def get_logger(name, logfile=None): 50 | """ 51 | name: logger 的名称,建议使用模块名称 52 | logfile: 日志记录文件,如无则输出到标准输出 53 | """ 54 | formatter = logging.Formatter( 55 | '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s', 56 | datefmt='%m/%d/%Y %I:%M:%S' 57 | ) 58 | 59 | if not logfile: 60 | handler = logging.StreamHandler() 61 | else: 62 | handler = logging.FileHandler(logfile) 63 | 64 | handler.setFormatter(formatter) 65 | 66 | logger = logging.getLogger(name) 67 | logger.addHandler(handler) 68 | logger.setLevel(logging.DEBUG) 69 | 70 | return logger 71 | 72 | 73 | def similarity(a, b, method='simple', pos_weight=None, embedding=None): 74 | """a 和 b 是同类型的可迭代对象,比如都是词的 list""" 75 | if not a or not b: 76 | return 0 77 | 78 | pos_weight = pos_weight or POS_WEIGHT 79 | if method == 'simple': 80 | # 词重叠率 81 | return len(set(a) & set(a)) / len(set(a)) 82 | 83 | elif method == 'simple_pos': 84 | # 带词性权重的词重叠率 85 | sim_weight = 0 86 | for word, pos in set(a): 87 | sim_weight += pos_weight.get(pos, 1) if word in b else 0 88 | 89 | total_weight = sum(pos_weight.get(pos, 1) for _, pos in set(a)) 90 | return sim_weight / total_weight if total_weight > 0 else 0 91 | 92 | elif method == 'vec' and embedding: 93 | # 词向量+词性权重 94 | sim_weight = 0 95 | total_weight = 0 96 | for word, pos in a: 97 | if word not in embedding.index2word: 98 | continue 99 | 100 | cur_weight = pos_weight.get(pos, 1) 101 | 102 | max_word_sim = max(embedding.similarity(bword, word) 103 | for bword in b) 104 | sim_weight += cur_weight * max_word_sim 105 | 106 | total_weight += cur_weight 107 | 108 | return sim_weight / total_weight if total_weight > 0 else 0 109 | --------------------------------------------------------------------------------