├── .gitignore ├── .idea ├── CrimeKgAssistant.iml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── workspace.xml ├── README.md ├── build_qa_database.py ├── crime_classify.py ├── crime_classify_train.py ├── crime_qa.py ├── data ├── kg_crime.json └── qa_corpus.json.zip ├── dict └── crime.txt ├── model ├── cnn_question_classify.h5 ├── crime_predict.model └── lstm_question_predict.h5 ├── question_classify.py └── question_classify_train.py /.gitignore: -------------------------------------------------------------------------------- 1 | embedding/*.bin 2 | -------------------------------------------------------------------------------- /.idea/CrimeKgAssistant.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 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 | 70 | 71 | 72 | 82 | 83 | 84 | 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 | 120 | 121 | 122 | 123 | 126 | 127 | 130 | 131 | 132 | 133 | 136 | 137 | 140 | 141 | 144 | 145 | 146 | 147 | 150 | 151 | 154 | 155 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 198 | 199 | 215 | 216 | 232 | 233 | 243 | 244 | 260 | 261 | 272 | 273 | 291 | 292 | 310 | 311 | 331 | 332 | 353 | 354 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 393 | 394 | 395 | 396 | 1541920128942 397 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 430 | 433 | 434 | 435 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CrimeKgAssitant 2 | Crime assistant including crime type prediction and crime consult service based on nlp methods and crime kg,罪名法务智能项目,内容包括856项罪名知识图谱, 基于280万罪名训练库的罪名预测,基于20W法务问答对的13类问题分类与法律资讯问答功能. 3 | 4 | # 项目功能 5 | 目前知识图谱在各个行业中应用逐步打开,尤其在金融,医疗,法律,旅游方面.知识图谱助力法律智能,能够在一定程度上利用现有大数据以及机器学习/深度学习与自然语言处理技术,提供一些智能的解决方案.本项目将完成两个大方向的工作: 6 | 1, 以罪名为核心,收集相关数据,建成基本的罪名知识图谱,法务资讯对话知识库,案由量刑知识库. 7 | 2, 分别基于步骤1的结果,完成以下四个方面的工作: 8 | 1) 基于案由量刑知识库的罪名预测模型 9 | 2) 基于法务咨询对话知识库的法务问题类型分类 10 | 3) 基于法务咨询对话知识库的法务问题自动问答服务 11 | 4) 基于罪行知识图谱的知识查询 12 | 13 | # 罪名预测 14 | 1, 问题类型: 15 | 罪名一共包括202种罪名,文件放在dict/crime.txt中, 详细内容举例如下: 16 | 17 | 妨害公务 18 | 寻衅滋事 19 | 盗窃、侮辱尸体 20 | 危险物品肇事 21 | 非法采矿 22 | 组织、强迫、引诱、容留、介绍卖淫 23 | 开设赌场 24 | 聚众斗殴 25 | 绑架 26 | 非法持有毒品 27 | 销售假冒注册商标的商品 28 | 容留他人吸毒 29 | 假冒注册商标 30 | 交通肇事 31 | 破坏电力设备 32 | 组织卖淫 33 | 合同诈骗 34 | 走私武器、弹药 35 | 抢劫 36 | 非法处置查封、扣押、冻结的财产 37 | 38 | 2, 问题模型: 39 | 罪刑数据库一共有288万条训练数据,要做的是202类型的罪名多分类问题.本项目采用的方式为: 40 | 41 | | 训练数据规模 | 数据向量表示 | 模型 |训练时长 | 准确率 | 42 | | :--- | :---: | :---: | :--- | :--- | 43 | | 20W | doc embedding | svm | 0.5h| 0.83352184| 44 | | 288W | doc embedding | svm | 12h| 0.9203119| 45 | 46 | 3, 效果: 47 | 执行 python crime_classify.py 48 | 49 | crime desc:这宗案情凶残的案件中,受害人樊敏仪是一名夜总会舞女,1997年因筹措祖母的医药费,偷取任职皮条客的首被告陈文乐数千元港币及其他财物(另一说是指毒品债)。首被告陈文乐于是吩咐次被告梁胜祖及第三被告梁伟伦向女受害人追债。女受害人为求还清债项,怀孕后仍继续接客,3名被告将欠款不断提高,受害人因无力偿还,因而触怒三人。1999年3月17日梁胜祖及梁伟伦按照首被告要求,将受害人从葵涌丽瑶邨富瑶楼一单位押走,禁锢于尖沙咀加连威老道31号3楼一单位。当回到单位后,梁伟伦质问受害人为何不还钱、为何不肯回电话,连踢受害人超过50次。3名被告用木板封着该单位的玻璃窗,以滚油泼向受害人的口腔,在伤口上涂上辣椒油,逼她吞吃粪便及喝尿。被告之后把烧溶的塑胶吸管滴在她的腿上,并命令受害人发出笑声。受害人开始神志不清,并不时挑起伤口上的焦疤,被告于是以电线紧紧捆缠受害人双手多个小时,之后又用铁棍殴打她双手。 50 | crime label: 非法拘禁 51 | ********************************************************* 52 | crime desc:有很多人相信是莉齐进行了这次谋杀,虽然她始终没有承认,陪审团也得出了她无罪的结论。莉齐·鲍顿是一个32岁的老姑娘,她被指控用刀杀死了自己的父亲和继母。虽然她最后无罪获释,但人们知道,她对继母一直怀恨在心,而在谋杀发生的前一天,她曾预言了将要发生的事。凶杀案发生时她已30岁。1892年8月4日中午,莉齐·鲍顿叫唤她的邻居说,她的父亲被杀了,警察到来时,发现她的母亲也死了。母亲被斧子砍了18下,父亲被砍了10下。消息立即被传开了,媒体认为莉齐本人极有谋杀嫌疑。然而次年六月,法庭宣判莉齐无罪。此后,她的故事广为流传,被写成了小说,芭蕾,百老汇,歌剧。最后是日本的教科书将她的童谣作为鹅妈妈童话收录的。 53 | crime label: 故意杀人 54 | ********************************************************* 55 | crime desc:017年5月26日11时许,被告人陈某、李某林与一同前去的王某,在信阳市羊山新区中级人民法院工地南大门门口,拦住被害人张某军,对其进行殴打,致其右手受伤,损伤程度属轻伤一级。2017年7月22日,李某林主动到信阳市公安局羊山分局投案。在审理过程中,被告人陈某、李某林与被害人张某军自愿达成赔偿协议,由陈某、李某林赔偿张祖军全部经济损失共计10万元,张某军对二被告人予以谅解。 56 | crime label: 故意伤害 57 | ********************************************************* 58 | crime desc:被告人赵某某于1999年5月起在某医院眼科开展医师执业活动,2010年11月其与医院签订事业单位聘用合同,从事专业技术工作,并于2011年取得临床医学主任医师职称。2014年3月起其担任眼科主任,在院长、分管院长和医务科领导下负责本科医疗、教学、科研和行政管理等工作。赵某某担任眼科主任期间,利用职务之便,收受人工晶体供货商给付的回扣共计37万元。赵某某作为眼科主任,在医院向供货商订购进口人工晶体过程中,参与了询价、谈判、合同签订和采购的过程。2015年4月12日,赵某某接受检察院调查,如实供述了收受人工晶体销售商回扣的事实。 59 | crime label: 受贿 60 | ********************************************************* 61 | crime desc:金陵晚报报道 到人家家里偷东西,却没发现可偷之物,丧尽天良的小偷为了报复竟将屋内熟睡的老太太强奸。日前,卢勇(化名) 在潜逃了一年后因再次出手被抓获。   31岁的卢勇是安徽枞阳县人,因家境贫寒,到现在仍是单身。今年6月份,他从老家来到南京,连续作案多起。7月1日凌晨,当他窜至莫愁新村再次作案时,当场被房主抓获。  经审讯又查明,去年8月30日清晨4时许,卢勇来宁行窃未遂后,贼心不死。又到附近的另一户人家行窃。他在房内找了一圈都没找到任何值钱的东西,只有个女人在床上睡觉。卢勇觉得没偷到东西亏了,想报复一下这户人家,就走到床边捂住女人的嘴,不顾反抗将其强奸后逃跑。  据卢勇供述,他当时并没注意女人的年纪,直到事后他才发现对方竟然是个早已上了年纪的老太太。日前,卢勇因涉嫌盗窃和强奸被检方审查起诉。 62 | crime label: 强奸 63 | 64 | # 法务咨询问题分类 65 | 1, 问题类型: 66 | 法务资讯问题一共包括13类,详细内容如下: 67 | 68 | 0: "婚姻家庭", 69 | 1: "劳动纠纷", 70 | 2: "交通事故", 71 | 3: "债权债务", 72 | 4: "刑事辩护", 73 | 5: "合同纠纷", 74 | 6: "房产纠纷", 75 | 7: "侵权", 76 | 8: "公司法", 77 | 9: "医疗纠纷", 78 | 10: "拆迁安置", 79 | 11: "行政诉讼", 80 | 12: "建设工程" 81 | 2, 问题模型: 82 | 法务咨询数据库一共有20万条训练数据,要做的是13类型咨询问题多分类问题.本项目采用的方式为: 83 | 84 | | 训练数据规模 |测试集规模 | 模型 |训练时长 | 训练集准确率 |测试集准确率| 85 | | :--- | :---: | :---: | :--- | :--- | :--- | 86 | | 4W | 1W | CNN | 15*20s| 0.984|0.959| 87 | | 4W | 1W | LSTM | 51*20s| 0.838|0.717| 88 | 89 | 3, 效果: 90 | 执行 python question_classify.py 91 | 92 | question desc:他们俩夫妻不和睦,老公总是家暴,怎么办 93 | question_type: 婚姻家庭 0.9994359612464905 94 | ********************************************************* 95 | question desc:我们老板总是拖欠工资怎么办,怎么起诉他 96 | question_type: 劳动纠纷 0.9999903440475464 97 | ********************************************************* 98 | question desc:最近p2p暴雷,投进去的钱全没了,能找回来吗 99 | question_type: 刑事辩护 0.3614000678062439 100 | ********************************************************* 101 | question desc:有人上高速,把车给刮的不像样子,如何是好 102 | question_type: 交通事故 0.9999163150787354 103 | ********************************************************* 104 | question desc:有个老头去世了,儿女们在争夺财产,闹得不亦乐乎 105 | question_type: 婚姻家庭 0.9993444085121155 106 | 107 | # 法务咨询自动问答 108 | 运行 python crime_qa.py 109 | 110 | question:朋友欠钱不还咋办 111 | answers: ['欠款金额是多少 ', '多少钱呢', '律师费诉讼费都非常少都很合理,一定要起诉。', '大概金额多少?', '需要看标的额和案情复杂程度,建议细致面谈'] 112 | ******************************************************* 113 | question:昨天把人家车刮了,要赔多少 114 | answers: ['您好,建议协商处理,如果对方告了你们,就只能积极应诉了。', '您好,建议尽量协商处理,协商不成可起诉'] 115 | ******************************************************* 116 | question:最近丈夫经常家暴,我受不了了 117 | answers: ['报警要求追究刑事责任。', '您好,建议起诉离婚并请求补偿。', '你好!可以起诉离婚,并主张精神损害赔偿。'] 118 | ******************************************************* 119 | question:毕业生拿了户口就跑路可以吗 120 | answers: 您好,对于此类问题,您可以咨询公安部门 121 | ******************************************************* 122 | question:孩子离家出走,怎么找回来 123 | answers: ['孩子父母没有结婚,孩子母亲把孩子带走了?这样的话可以起诉要求抚养权的。毕竟母亲也是孩子的合法监护人,报警警察一般不受理。'] 124 | ******************************************************* 125 | question:村霸把我田地给占了,我要怎么起诉 126 | answers: ['可以向上级主管部门投诉解决', '您好,您可以及时向土地管理部门投诉的!', '对方侵权,可以向法院起诉。', '你好,对方侵权,可以向法院起诉。', '你好,可起诉处理,一、当事人起诉,首先应提交起诉书,并按对方当事人人数提交相应份数的副本。当事人是公民的,应写明双方当事人的姓名、性别、年龄、籍贯、住址;当事人是单位的,应写明单位名称、地址、法定代表人或负责人姓名。起诉书正文应写明请求事项和起诉事实、理由,尾部须署名或盖公章。二、根据"谁主张谁举证"原则,原告向法院起诉应提交下列材料:1、原告主体资格的材料。如居民身份证、户口本、护照、港澳同胞回乡证、结婚证等证据的原件和复印件;企业单位作为原告的应提交营业执照、商业登记证明等材料的复印件。2、证明原告诉讼主张的证据。如合同、协议、债权文书(借条、欠条等)、收发货凭证、往来信函等。', '您好,起诉维权。', '您好,可以起诉解决。'] 127 | ******************************************************* 128 | question:售卖危违禁物品,有什么风险 129 | answers: ['没什么'] 130 | ******************************************************* 131 | question:找不到女朋友啊.. 132 | answers: 您好,对于此类问题,您可以咨询公安部门 133 | ******************************************************* 134 | question:我要离婚 135 | answers: ['现在就可向法院起诉离婚。', '不需要分开两年起诉离婚。感情完全破裂就可以提起诉讼离婚。', '你可以直接起诉离婚', '直接起诉'] 136 | ******************************************************* 137 | question:醉驾,要坐牢吗 138 | answers: ['要负刑事责任很可能坐牢', '由警方处理.,'] 139 | ******************************************************* 140 | question:你好,我向大学提出退学申请,大学拒绝,理由是家长不同意。我该怎么办? 141 | answers: ['自己可决定的 '] 142 | ******************************************************* 143 | question:请问在上班途中,出车祸我的责任偏大属于工伤吗? 144 | answers: ['属于工伤'] 145 | ******************************************************* 146 | question:结婚时女方拿了彩礼就逃了能要回来吗 147 | answers: ['可以要求退还彩礼。,'] 148 | ******************************************************* 149 | question:房产证上是不是一定要写夫妻双方姓名 150 | answers: ['可以不填,即使一个人的名字,婚后买房是共同财产。', '不是必须的', '可以写一方名字,对方公证,证明该房产系你一人财产', '你好,不是必须'] 151 | ******************************************************* 152 | question:儿女不履行赡养义务是不是要判刑 153 | answers: ['什么情况了?'] 154 | ******************************************************* 155 | question:和未成年人发生关系,需要坐牢吗 156 | answers: ['女孩子在发生关系的时候是否满14周岁,如果是且自愿就不是犯罪', '你好,如果是双方愿意的情况下是不犯法的。', '发生性关系时已满十四岁并且是自愿的依法律规定不构成强奸罪,不构成犯罪的。', '若是自愿,那就没什么可说了。', '双方愿意不犯法', '你好 如果是自愿的 不犯法 ', '自愿的就没事'] 157 | ******************************************************* 158 | question:撞死人逃跑要怎么处理 159 | answers: ['等待警察处理。,'] 160 | 161 | # 总结 162 | 1, 本项目实现的是以罪刑为核心的法务应用落地的一个demo尝试. 163 | 2, 本项目采用机器学习,深度学习的方法完成了罪名预测,客服问句类型预测多分类任务,取得了较好的性能,模型可以直接使用. 164 | 3, 本项目构建起了一个20万问答集,856个罪名的知识库,分别存放在data/kg_crime.json和data/qa_corpus.json文件中. 165 | 4, 法务问答,可以是智能客服在法律资讯网站中的一个应用场景落地. 本项目采用的是ES+语义相似度加权打分策略实现的问答技术路线, 权值计算与阈值设定可以用户指定. 166 | 5, 对于罪名知识图谱中的知识可以进一步进行结构化处理,这是后期可以完善的地方. 167 | 6, 如何将罪名,咨询,智能研判结合在一起,形成通路,其实可以进一步提升知识图谱在法务领域的应用. 168 | 169 | # contact 170 | 如有自然语言处理、知识图谱、事理图谱、社会计算、语言资源建设等问题或合作,请联系我: 171 | 邮箱:lhy_in_blcu@126.com 172 | csdn:https://blog.csdn.net/lhy2014 173 | 我的自然语言处理项目: https://liuhuanyong.github.io/ 174 | 刘焕勇,中国科学院软件研究所 175 | -------------------------------------------------------------------------------- /build_qa_database.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # File: insert_es.py 4 | # Author: lhy 5 | # Date: 18-10-10 6 | 7 | import os 8 | import time 9 | 10 | import json 11 | from elasticsearch import Elasticsearch 12 | from elasticsearch.helpers import bulk 13 | import pymongo 14 | 15 | class ProcessIntoES: 16 | def __init__(self): 17 | self._index = "crime_data" 18 | self.es = Elasticsearch([{"host": "127.0.0.1", "port": 9200}]) 19 | self.doc_type = "crime" 20 | cur = '/'.join(os.path.abspath(__file__).split('/')[:-1]) 21 | self.music_file = os.path.join(cur, 'qa_corpus.json') 22 | 23 | '''创建ES索引,确定分词类型''' 24 | def create_mapping(self): 25 | node_mappings = { 26 | "mappings": { 27 | self.doc_type: { # type 28 | "properties": { 29 | "question": { # field: 问题 30 | "type": "text", # lxw NOTE: cannot be string 31 | "analyzer": "ik_max_word", 32 | "search_analyzer": "ik_smart", 33 | "index": "true" # The index option controls whether field values are indexed. 34 | }, 35 | "answers": { # field: 问题 36 | "type": "text", # lxw NOTE: cannot be string 37 | "analyzer": "ik_max_word", 38 | "search_analyzer": "ik_smart", 39 | "index": "true" # The index option controls whether field values are indexed. 40 | }, 41 | } 42 | } 43 | } 44 | } 45 | if not self.es.indices.exists(index=self._index): 46 | self.es.indices.create(index=self._index, body=node_mappings) 47 | print("Create {} mapping successfully.".format(self._index)) 48 | else: 49 | print("index({}) already exists.".format(self._index)) 50 | 51 | '''批量插入数据''' 52 | def insert_data_bulk(self, action_list): 53 | success, _ = bulk(self.es, action_list, index=self._index, raise_on_error=True) 54 | print("Performed {0} actions. _: {1}".format(success, _)) 55 | 56 | 57 | '''初始化ES,将数据插入到ES数据库当中''' 58 | def init_ES(): 59 | pie = ProcessIntoES() 60 | # 创建ES的index 61 | pie.create_mapping() 62 | start_time = time.time() 63 | index = 0 64 | count = 0 65 | action_list = [] 66 | BULK_COUNT = 1000 # 每BULK_COUNT个句子一起插入到ES中 67 | 68 | for line in open(pie.music_file): 69 | if not line: 70 | continue 71 | item = json.loads(line) 72 | index += 1 73 | action = { 74 | "_index": pie._index, 75 | "_type": pie.doc_type, 76 | "_source": { 77 | "question": item['question'], 78 | "answers": '\n'.join(item['answers']), 79 | } 80 | } 81 | action_list.append(action) 82 | if index > BULK_COUNT: 83 | pie.insert_data_bulk(action_list=action_list) 84 | index = 0 85 | count += 1 86 | print(count) 87 | action_list = [] 88 | end_time = time.time() 89 | 90 | print("Time Cost:{0}".format(end_time - start_time)) 91 | 92 | 93 | if __name__ == "__main__": 94 | # 将数据库插入到elasticsearch当中 95 | # init_ES() 96 | # 按照标题进行查询 97 | question = '我老公要起诉离婚 我不想离婚怎么办' 98 | 99 | -------------------------------------------------------------------------------- /crime_classify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # File: crime_classify.py.py 4 | # Author: lhy 5 | # Date: 18-11-11 6 | 7 | 8 | import os 9 | import numpy as np 10 | import jieba.posseg as pseg 11 | from sklearn.externals import joblib 12 | 13 | class CrimeClassify(object): 14 | def __init__(self): 15 | cur = '/'.join(os.path.abspath(__file__).split('/')[:-1]) 16 | crime_file = os.path.join(cur, 'dict/crime.txt') 17 | self.label_dict = self.build_crime_dict(crime_file) 18 | self.id_dict = {j:i for i,j in self.label_dict.items()} 19 | self.embedding_path = os.path.join(cur, 'embedding/word_vec_300.bin') 20 | self.embdding_dict = self.load_embedding(self.embedding_path) 21 | self.embedding_size = 300 22 | self.model_path = 'model/crime_predict.model' 23 | return 24 | 25 | '''构建罪名词类型''' 26 | def build_crime_dict(self, crimefile): 27 | label_dict = {} 28 | i = 0 29 | for line in open(crimefile): 30 | crime = line.strip() 31 | if not crime: 32 | continue 33 | label_dict[crime] = i 34 | i +=1 35 | return label_dict 36 | 37 | '''加载词向量''' 38 | def load_embedding(self, embedding_path): 39 | embedding_dict = {} 40 | count = 0 41 | for line in open(embedding_path): 42 | line = line.strip().split(' ') 43 | if len(line) < 300: 44 | continue 45 | wd = line[0] 46 | vector = np.array([float(i) for i in line[1:]]) 47 | embedding_dict[wd] = vector 48 | count += 1 49 | if count%10000 == 0: 50 | print(count, 'loaded') 51 | print('loaded %s word embedding, finished'%count, ) 52 | return embedding_dict 53 | 54 | '''对文本进行分词处理''' 55 | def seg_sent(self, s): 56 | wds = [i.word for i in pseg.cut(s) if i.flag[0] not in ['x', 'u', 'c', 'p', 'm', 't']] 57 | return wds 58 | 59 | '''基于wordvector,通过lookup table的方式找到句子的wordvector的表示''' 60 | def rep_sentencevector(self, sentence, flag='seg'): 61 | if flag == 'seg': 62 | word_list = [i for i in sentence.split(' ') if i] 63 | else: 64 | word_list = self.seg_sent(sentence) 65 | embedding = np.zeros(self.embedding_size) 66 | sent_len = 0 67 | for index, wd in enumerate(word_list): 68 | if wd in self.embdding_dict: 69 | embedding += self.embdding_dict.get(wd) 70 | sent_len += 1 71 | else: 72 | continue 73 | return embedding/sent_len 74 | 75 | '''对数据进行onehot映射操作''' 76 | def label_onehot(self, label): 77 | one_hot = [0]*len(self.label_dict) 78 | one_hot[int(label)] = 1 79 | return one_hot 80 | 81 | '''使用svm模型进行预测''' 82 | def predict(self, sent): 83 | model = joblib.load(self.model_path) 84 | represent_sent = self.rep_sentencevector(sent, flag='noseg') 85 | text_vector = np.array(represent_sent).reshape(1, -1) 86 | res = model.predict(text_vector)[0] 87 | label = self.id_dict.get(res) 88 | return label 89 | 90 | 91 | 92 | 93 | def test(): 94 | handler = CrimeClassify() 95 | while(1): 96 | sent = input('crime desc:') 97 | label = handler.predict(sent) 98 | print('crime label:', label) 99 | 100 | if __name__ == '__main__': 101 | test() 102 | -------------------------------------------------------------------------------- /crime_classify_train.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # File: crime_classify.py 4 | # Author: lhy 5 | # Date: 18-11-10 6 | 7 | import os 8 | import numpy as np 9 | from sklearn.svm import SVC, LinearSVC 10 | import jieba.posseg as pseg 11 | from collections import Counter 12 | from sklearn.externals import joblib 13 | 14 | class CrimeClassify(object): 15 | def __init__(self): 16 | cur = '/'.join(os.path.abspath(__file__).split('/')[:-1]) 17 | crime_file = os.path.join(cur, 'crime.txt') 18 | self.label_dict = self.build_crime_dict(crime_file) 19 | self.id_dict = {j:i for i,j in self.label_dict.items()} 20 | self.train_file = os.path.join(cur, 'crime_train_all.txt') 21 | self.embedding_path = os.path.join(cur, 'embedding/word_vec_300.bin') 22 | self.embdding_dict = self.load_embedding(self.embedding_path) 23 | self.embedding_size = 300 24 | self.model_path = 'crime_predict_svm_all.model' 25 | return 26 | 27 | '''构建罪名词类型''' 28 | def build_crime_dict(self, crimefile): 29 | label_dict = {} 30 | i = 0 31 | for line in open(crimefile): 32 | crime = line.strip() 33 | if not crime: 34 | continue 35 | label_dict[crime] = i 36 | i +=1 37 | return label_dict 38 | 39 | '''加载词向量''' 40 | def load_embedding(self, embedding_path): 41 | embedding_dict = {} 42 | count = 0 43 | for line in open(embedding_path): 44 | line = line.strip().split(' ') 45 | if len(line) < 300: 46 | continue 47 | wd = line[0] 48 | vector = np.array([float(i) for i in line[1:]]) 49 | embedding_dict[wd] = vector 50 | count += 1 51 | if count%10000 == 0: 52 | print(count, 'loaded') 53 | print('loaded %s word embedding, finished'%count, ) 54 | return embedding_dict 55 | 56 | '''对文本进行分词处理''' 57 | def seg_sent(self, s): 58 | wds = [i.word for i in pseg.cut(s) if i.flag[0] not in ['x', 'u', 'c', 'p', 'm', 't']] 59 | return wds 60 | 61 | '''基于wordvector,通过lookup table的方式找到句子的wordvector的表示''' 62 | def rep_sentencevector(self, sentence, flag='seg'): 63 | if flag == 'seg': 64 | word_list = [i for i in sentence.split(' ') if i] 65 | else: 66 | word_list = self.seg_sent(sentence) 67 | embedding = np.zeros(self.embedding_size) 68 | sent_len = 0 69 | for index, wd in enumerate(word_list): 70 | if wd in self.embdding_dict: 71 | embedding += self.embdding_dict.get(wd) 72 | sent_len += 1 73 | else: 74 | continue 75 | return embedding/sent_len 76 | 77 | '''对数据进行onehot映射操作''' 78 | def label_onehot(self, label): 79 | one_hot = [0]*len(self.label_dict) 80 | one_hot[int(label)] = 1 81 | return one_hot 82 | 83 | '''加载数据集''' 84 | def load_traindata(self): 85 | train_X = [] 86 | train_Y = [] 87 | count = 0 88 | for line in open(self.train_file): 89 | line = line.strip().strip().split('\t') 90 | if len(line) < 2: 91 | continue 92 | count += 1 93 | # if count > 1000: 94 | # break 95 | sent = line[1] 96 | label_id = int(line[0]) 97 | sent_vector = self.rep_sentencevector(sent, flag='seg') 98 | train_X.append(sent_vector) 99 | train_Y.append(label_id) 100 | if count % 10000 == 0: 101 | print('loaded %s lines'%count) 102 | return np.array(train_X), np.array(train_Y) 103 | 104 | '''使用SVM进行分类''' 105 | def train_classifer(self): 106 | x_train, y_train = self.load_traindata() 107 | model = LinearSVC() 108 | model.fit(x_train, y_train) 109 | joblib.dump(model, self.model_path) 110 | y_predict = model.predict(x_train) 111 | all = len(y_predict) 112 | right = 0 113 | for i in range(len(y_train)): 114 | y = y_train[i] 115 | y_pred = y_predict[i] 116 | if y_pred == y: 117 | right += 1 118 | print('precision:%s/%s=%s'%(right, all, right/all)) 119 | 120 | '''使用svm模型进行预测''' 121 | def predict(self, sent): 122 | model = joblib.load(self.model_path) 123 | represent_sent = self.rep_sentencevector(sent, flag='noseg') 124 | text_vector = np.array(represent_sent).reshape(1, -1) 125 | res = model.predict(text_vector)[0] 126 | label = self.id_dict.get(res) 127 | return label 128 | 129 | 130 | '''检查测试合准确率''' 131 | def check_precision(self): 132 | model = joblib.load(self.model_path) 133 | x_train, y_train = self.load_traindata() 134 | y_predict = model.predict(x_train) 135 | all = len(y_predict) 136 | right = 0 137 | for i in range(len(y_train)): 138 | y = y_train[i] 139 | y_pred = y_predict[i] 140 | if y_pred == y: 141 | right += 1 142 | print('precision:%s/%s=%s'%(right, all, right/all)) 143 | # precision:170231 / 204231 = 0.83352184536138 144 | # precision:2650780 / 2880306 = 0.9203119390786951 145 | 146 | 147 | def test(): 148 | handler = CrimeClassify() 149 | # handler.train_classifer() 150 | while(1): 151 | sent = input('enter an sent to search:') 152 | label = handler.predict(sent) 153 | print(label) 154 | 155 | def build_data(): 156 | label_dict = {} 157 | i = 0 158 | for line in open('crime.txt'): 159 | crime = line.strip() 160 | if not crime: 161 | continue 162 | label_dict[crime] = i 163 | i += 1 164 | 165 | f = open('crime_train_all.txt', 'w+') 166 | count = 0 167 | for line in open('accu_train.txt'): 168 | line = line.strip().split('###') 169 | if len(line) < 3: 170 | continue 171 | crime = line[1].split(';')[0] 172 | sent = line[-1] 173 | label = label_dict.get(crime) 174 | f.write(str(label) + '\t' + sent + '\n') 175 | count += 1 176 | print(count) 177 | f.close() 178 | 179 | 180 | if __name__ == '__main__': 181 | test() 182 | #build_data() 183 | #handler = CrimeClassify() 184 | #handler.check_precision() 185 | -------------------------------------------------------------------------------- /crime_qa.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # File: crime_qa_server.py 4 | # Author: lhy 5 | # Date: 18-11-10 6 | 7 | import os 8 | import time 9 | import json 10 | from elasticsearch import Elasticsearch 11 | import numpy as np 12 | import jieba.posseg as pseg 13 | 14 | class CrimeQA: 15 | def __init__(self): 16 | self._index = "crime_data" 17 | self.es = Elasticsearch([{"host": "127.0.0.1", "port": 9200}]) 18 | self.doc_type = "crime" 19 | cur = '/'.join(os.path.abspath(__file__).split('/')[:-1]) 20 | self.embedding_path = os.path.join(cur, 'embedding/word_vec_300.bin') 21 | self.embdding_dict = self.load_embedding(self.embedding_path) 22 | self.embedding_size = 300 23 | self.min_score = 0.4 24 | self.min_sim = 0.8 25 | 26 | '''根据question进行事件的匹配查询''' 27 | def search_specific(self, value, key="question"): 28 | query_body = { 29 | "query": { 30 | "match": { 31 | key: value, 32 | } 33 | } 34 | } 35 | searched = self.es.search(index=self._index, doc_type=self.doc_type, body=query_body, size=20) 36 | # 输出查询到的结果 37 | return searched["hits"]["hits"] 38 | 39 | '''基于ES的问题查询''' 40 | def search_es(self, question): 41 | answers = [] 42 | res = self.search_specific(question) 43 | for hit in res: 44 | answer_dict = {} 45 | answer_dict['score'] = hit['_score'] 46 | answer_dict['sim_question'] = hit['_source']['question'] 47 | answer_dict['answers'] = hit['_source']['answers'].split('\n') 48 | answers.append(answer_dict) 49 | return answers 50 | 51 | 52 | '''加载词向量''' 53 | def load_embedding(self, embedding_path): 54 | embedding_dict = {} 55 | count = 0 56 | for line in open(embedding_path): 57 | line = line.strip().split(' ') 58 | if len(line) < 300: 59 | continue 60 | wd = line[0] 61 | vector = np.array([float(i) for i in line[1:]]) 62 | embedding_dict[wd] = vector 63 | count += 1 64 | if count%10000 == 0: 65 | print(count, 'loaded') 66 | print('loaded %s word embedding, finished'%count, ) 67 | return embedding_dict 68 | 69 | 70 | '''对文本进行分词处理''' 71 | def seg_sent(self, s): 72 | wds = [i.word for i in pseg.cut(s) if i.flag[0] not in ['x', 'u', 'c', 'p', 'm', 't']] 73 | return wds 74 | 75 | '''基于wordvector,通过lookup table的方式找到句子的wordvector的表示''' 76 | def rep_sentencevector(self, sentence, flag='seg'): 77 | if flag == 'seg': 78 | word_list = [i for i in sentence.split(' ') if i] 79 | else: 80 | word_list = self.seg_sent(sentence) 81 | embedding = np.zeros(self.embedding_size) 82 | sent_len = 0 83 | for index, wd in enumerate(word_list): 84 | if wd in self.embdding_dict: 85 | embedding += self.embdding_dict.get(wd) 86 | sent_len += 1 87 | else: 88 | continue 89 | return embedding/sent_len 90 | 91 | 92 | '''计算问句与库中问句的相似度,对候选结果加以二次筛选''' 93 | def similarity_cosine(self, vector1, vector2): 94 | cos1 = np.sum(vector1*vector2) 95 | cos21 = np.sqrt(sum(vector1**2)) 96 | cos22 = np.sqrt(sum(vector2**2)) 97 | similarity = cos1/float(cos21*cos22) 98 | if similarity == 'nan': 99 | return 0 100 | else: 101 | return similarity 102 | 103 | '''问答主函数''' 104 | def search_main(self, question): 105 | candi_answers = self.search_es(question) 106 | question_vector = self.rep_sentencevector(question,flag='noseg') 107 | answer_dict = {} 108 | for indx, candi in enumerate(candi_answers): 109 | candi_question = candi['sim_question'] 110 | score = candi['score']/100 111 | candi_vector = self.rep_sentencevector(candi_question, flag='noseg') 112 | sim = self.similarity_cosine(question_vector, candi_vector) 113 | if sim < self.min_sim: 114 | continue 115 | final_score = (score + sim)/2 116 | if final_score < self.min_score: 117 | continue 118 | answer_dict[indx] = final_score 119 | if answer_dict: 120 | answer_dict = sorted(answer_dict.items(), key=lambda asd:asd[1], reverse=True) 121 | final_answer = candi_answers[answer_dict[0][0]]['answers'] 122 | else: 123 | final_answer = '您好,对于此类问题,您可以咨询公安部门' 124 | # 125 | # for i in answer_dict: 126 | # answer_indx = i[0] 127 | # score = i[1] 128 | # print(i, score, candi_answers[answer_indx]) 129 | # print('******'*6) 130 | return final_answer 131 | 132 | 133 | if __name__ == "__main__": 134 | handler = CrimeQA() 135 | while(1): 136 | question = input('question:') 137 | final_answer = handler.search_main(question) 138 | print('answers:', final_answer) 139 | 140 | -------------------------------------------------------------------------------- /data/qa_corpus.json.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhuanyong/CrimeKgAssitant/9945d046e9d6ecb307edb2b385fa391ae362537b/data/qa_corpus.json.zip -------------------------------------------------------------------------------- /dict/crime.txt: -------------------------------------------------------------------------------- 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 | 走私普通货物、物品 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 | 非法捕捞水产品 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 | 引诱、教唆、欺骗他人吸毒 160 | 脱逃 161 | 扰乱无线电通讯管理秩序 162 | 保险诈骗 163 | 非法生产、销售间谍专用器材 164 | 非法组织卖血 165 | 强迫交易 166 | 串通投标 167 | 破坏易燃易爆设备 168 | 传授犯罪方法 169 | 妨害信用卡管理 170 | 拐骗儿童 171 | 单位行贿 172 | 打击报复证人 173 | 拒不执行判决、裁定 174 | 经济犯 175 | 金融凭证诈骗 176 | 虚开增值税专用发票、用于骗取出口退税、抵扣税款发票 177 | 走私废物 178 | 组织、领导传销活动 179 | 单位受贿 180 | 盗窃、抢夺枪支、弹药、爆炸物、危险物质 181 | 过失以危险方法危害公共安全 182 | 过失致人重伤 183 | 引诱、容留、介绍卖淫 184 | 遗弃 185 | 走私 186 | 信用卡诈骗 187 | 对单位行贿 188 | 故意杀人 189 | 聚众扰乱公共场所秩序、交通秩序 190 | 盗窃 191 | 故意伤害 192 | 非法侵入住宅 193 | 强制猥亵、侮辱妇女 194 | 伪证 195 | 污染环境 196 | 巨额财产来源不明 197 | 非国家工作人员受贿 198 | 侮辱 199 | 隐匿、故意销毁会计凭证、会计帐簿、财务会计报告 200 | 过失损坏武器装备、军事设施、军事通信 201 | 敲诈勒索 202 | 职务侵占 -------------------------------------------------------------------------------- /model/cnn_question_classify.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhuanyong/CrimeKgAssitant/9945d046e9d6ecb307edb2b385fa391ae362537b/model/cnn_question_classify.h5 -------------------------------------------------------------------------------- /model/crime_predict.model: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhuanyong/CrimeKgAssitant/9945d046e9d6ecb307edb2b385fa391ae362537b/model/crime_predict.model -------------------------------------------------------------------------------- /model/lstm_question_predict.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhuanyong/CrimeKgAssitant/9945d046e9d6ecb307edb2b385fa391ae362537b/model/lstm_question_predict.h5 -------------------------------------------------------------------------------- /question_classify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # File: question_classify.py 4 | # Author: lhy 5 | # Date: 18-11-11 6 | 7 | 8 | import os 9 | import numpy as np 10 | import jieba.posseg as pseg 11 | from keras.models import Sequential, load_model 12 | from keras.layers import Conv1D, GlobalAveragePooling1D, MaxPooling1D, Dense, Dropout, LSTM, Bidirectional 13 | 14 | 15 | class QuestionClassify(object): 16 | def __init__(self): 17 | self.label_dict = { 18 | 0: "婚姻家庭", 19 | 1: "劳动纠纷", 20 | 2: "交通事故", 21 | 3: "债权债务", 22 | 4: "刑事辩护", 23 | 5: "合同纠纷", 24 | 6: "房产纠纷", 25 | 7: "侵权", 26 | 8: "公司法", 27 | 9: "医疗纠纷", 28 | 10: "拆迁安置", 29 | 11: "行政诉讼", 30 | 12: "建设工程" 31 | } 32 | cur = '/'.join(os.path.abspath(__file__).split('/')[:-1]) 33 | self.embedding_path = os.path.join(cur, 'embedding/word_vec_300.bin') 34 | self.embdding_dict = self.load_embedding(self.embedding_path) 35 | self.max_length = 60 36 | self.embedding_size = 300 37 | self.lstm_modelpath = 'model/lstm_question_classify.h5' 38 | self.cnn_modelpath = 'model/cnn_question_classify.h5' 39 | return 40 | 41 | '''加载词向量''' 42 | def load_embedding(self, embedding_path): 43 | embedding_dict = {} 44 | count = 0 45 | for line in open(embedding_path): 46 | line = line.strip().split(' ') 47 | if len(line) < 300: 48 | continue 49 | wd = line[0] 50 | vector = np.array([float(i) for i in line[1:]]) 51 | embedding_dict[wd] = vector 52 | count += 1 53 | if count % 10000 == 0: 54 | print(count, 'loaded') 55 | print('loaded %s word embedding, finished' % count, ) 56 | return embedding_dict 57 | 58 | '''对文本进行分词处理''' 59 | 60 | def seg_sent(self, s): 61 | wds = [i.word for i in pseg.cut(s) if i.flag[0] not in ['w', 'x']] 62 | return wds 63 | 64 | '''基于wordvector,通过lookup table的方式找到句子的wordvector的表示''' 65 | 66 | def rep_sentencevector(self, sentence): 67 | word_list = self.seg_sent(sentence)[:self.max_length] 68 | embedding_matrix = np.zeros((self.max_length, self.embedding_size)) 69 | for index, wd in enumerate(word_list): 70 | if wd in self.embdding_dict: 71 | embedding_matrix[index] = self.embdding_dict.get(wd) 72 | else: 73 | continue 74 | len_sent = len(word_list) 75 | embedding_matrix = self.modify_sentencevector(embedding_matrix, len_sent) 76 | 77 | return embedding_matrix 78 | 79 | '''对于OOV词,通过左右词的词向量作平均,作为词向量表示''' 80 | 81 | def modify_sentencevector(self, embedding_matrix, len_sent): 82 | context_window = 2 83 | for indx, vec in enumerate(embedding_matrix): 84 | left = indx - context_window 85 | right = indx + context_window 86 | if left < 0: 87 | left = 0 88 | if right > len(embedding_matrix) - 1: 89 | right = -2 90 | context = embedding_matrix[left:right + 1] 91 | if vec.tolist() == [0] * 300 and indx < len_sent: 92 | context_vector = context.mean(axis=0) 93 | embedding_matrix[indx] = context_vector 94 | 95 | return embedding_matrix 96 | 97 | '''对数据进行onehot映射操作''' 98 | 99 | def label_onehot(self, label): 100 | one_hot = [0] * len(self.label_dict) 101 | one_hot[int(label)] = 1 102 | return one_hot 103 | 104 | 105 | '''构造CNN网络模型''' 106 | def build_cnn_model(self): 107 | model = Sequential() 108 | model.add(Conv1D(64, 3, activation='relu', input_shape=(self.max_length, self.embedding_size))) 109 | model.add(Conv1D(64, 3, activation='relu')) 110 | model.add(MaxPooling1D(3)) 111 | model.add(Conv1D(128, 3, activation='relu')) 112 | model.add(Conv1D(128, 3, activation='relu')) 113 | model.add(GlobalAveragePooling1D()) 114 | model.add(Dropout(0.5)) 115 | model.add(Dense(13, activation='softmax')) 116 | model.compile(loss='categorical_crossentropy', 117 | optimizer='rmsprop', 118 | metrics=['accuracy']) 119 | model.summary() 120 | return model 121 | 122 | '''构造LSTM网络''' 123 | def build_lstm_model(self): 124 | model = Sequential() 125 | model.add(LSTM(32, return_sequences=True, input_shape=( 126 | self.max_length, self.embedding_size))) # returns a sequence of vectors of dimension 32 127 | model.add(LSTM(32, return_sequences=True)) # returns a sequence of vectors of dimension 32 128 | model.add(LSTM(32)) # return a single vector of dimension 32 129 | model.add(Dense(13, activation='softmax')) 130 | model.compile(loss='categorical_crossentropy', 131 | optimizer='rmsprop', 132 | metrics=['accuracy']) 133 | 134 | return model 135 | 136 | '''问题分类''' 137 | def predict(self, sent): 138 | model = load_model(self.cnn_modelpath) 139 | sentence_vector = np.array([self.rep_sentencevector(sent)]) 140 | res = model.predict(sentence_vector)[0].tolist() 141 | prob = max(res) 142 | label = self.label_dict.get(res.index(prob)) 143 | return label, prob 144 | 145 | if __name__ == '__main__': 146 | handler = QuestionClassify() 147 | while (1): 148 | sent = input('question desc:') 149 | label, prob = handler.predict(sent) 150 | print('question_type:', label, prob) 151 | -------------------------------------------------------------------------------- /question_classify_train.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # File: question_classify.py 4 | # Author: lhy 5 | # Date: 18-11-10 6 | 7 | import os 8 | import numpy as np 9 | import jieba.posseg as pseg 10 | from keras.models import Sequential, load_model 11 | from keras.layers import Conv1D, GlobalAveragePooling1D, MaxPooling1D, Dense, Dropout, LSTM, Bidirectional 12 | 13 | class QuestionClassify(object): 14 | def __init__(self): 15 | self.label_dict = { 16 | 0:"婚姻家庭", 17 | 1:"劳动纠纷", 18 | 2:"交通事故", 19 | 3:"债权债务", 20 | 4:"刑事辩护", 21 | 5:"合同纠纷", 22 | 6:"房产纠纷", 23 | 7:"侵权", 24 | 8:"公司法", 25 | 9:"医疗纠纷", 26 | 10:"拆迁安置", 27 | 11:"行政诉讼", 28 | 12:"建设工程" 29 | } 30 | cur = '/'.join(os.path.abspath(__file__).split('/')[:-1]) 31 | self.train_file = os.path.join(cur, 'question_train.txt') 32 | self.embedding_path = os.path.join(cur, 'word_vec_300.bin') 33 | self.embdding_dict = self.load_embedding(self.embedding_path) 34 | self.max_length = 60 35 | self.embedding_size = 300 36 | self.lstm_modelpath = 'model/lstm_question_classify.h5' 37 | self.cnn_modelpath = 'model/cnn_question_classify.h5' 38 | return 39 | 40 | '''加载词向量''' 41 | def load_embedding(self, embedding_path): 42 | embedding_dict = {} 43 | count = 0 44 | for line in open(embedding_path): 45 | line = line.strip().split(' ') 46 | if len(line) < 300: 47 | continue 48 | wd = line[0] 49 | vector = np.array([float(i) for i in line[1:]]) 50 | embedding_dict[wd] = vector 51 | count += 1 52 | if count%10000 == 0: 53 | print(count, 'loaded') 54 | print('loaded %s word embedding, finished'%count, ) 55 | return embedding_dict 56 | 57 | '''对文本进行分词处理''' 58 | def seg_sent(self, s): 59 | wds = [i.word for i in pseg.cut(s) if i.flag[0] not in ['w', 'x']] 60 | return wds 61 | 62 | '''基于wordvector,通过lookup table的方式找到句子的wordvector的表示''' 63 | def rep_sentencevector(self, sentence): 64 | word_list = self.seg_sent(sentence)[:self.max_length] 65 | embedding_matrix = np.zeros((self.max_length, self.embedding_size)) 66 | for index, wd in enumerate(word_list): 67 | if wd in self.embdding_dict: 68 | embedding_matrix[index] = self.embdding_dict.get(wd) 69 | else: 70 | continue 71 | len_sent = len(word_list) 72 | embedding_matrix = self.modify_sentencevector(embedding_matrix, len_sent) 73 | 74 | return embedding_matrix 75 | 76 | '''对于OOV词,通过左右词的词向量作平均,作为词向量表示''' 77 | def modify_sentencevector(self, embedding_matrix, len_sent): 78 | context_window = 2 79 | for indx, vec in enumerate(embedding_matrix): 80 | left = indx-context_window 81 | right = indx+context_window 82 | if left < 0: 83 | left = 0 84 | if right > len(embedding_matrix)-1: 85 | right = -2 86 | context = embedding_matrix[left:right+1] 87 | if vec.tolist() == [0]*300 and indx < len_sent: 88 | context_vector = context.mean(axis=0) 89 | embedding_matrix[indx] = context_vector 90 | 91 | return embedding_matrix 92 | 93 | '''对数据进行onehot映射操作''' 94 | def label_onehot(self, label): 95 | one_hot = [0]*len(self.label_dict) 96 | one_hot[int(label)] = 1 97 | return one_hot 98 | 99 | '''加载数据集''' 100 | def load_traindata(self): 101 | train_X = [] 102 | train_Y = [] 103 | count = 0 104 | for line in open(self.train_file): 105 | 106 | line = line.strip().strip().split('\t') 107 | if len(line) < 2: 108 | continue 109 | count += 1 110 | sent = line[0] 111 | label = line[1] 112 | sent_vector = self.rep_sentencevector(sent) 113 | label_vector = self.label_onehot(label) 114 | train_X.append(sent_vector) 115 | train_Y.append(label_vector) 116 | 117 | if count % 10000 == 0: 118 | print('loaded %s lines'%count) 119 | 120 | return np.array(train_X), np.array(train_Y) 121 | 122 | '''构造CNN网络模型''' 123 | def build_cnn_model(self): 124 | model = Sequential() 125 | model.add(Conv1D(64, 3, activation='relu', input_shape=(self.max_length, self.embedding_size))) 126 | model.add(Conv1D(64, 3, activation='relu')) 127 | model.add(MaxPooling1D(3)) 128 | model.add(Conv1D(128, 3, activation='relu')) 129 | model.add(Conv1D(128, 3, activation='relu')) 130 | model.add(GlobalAveragePooling1D()) 131 | model.add(Dropout(0.5)) 132 | model.add(Dense(13, activation='softmax')) 133 | model.compile(loss='categorical_crossentropy', 134 | optimizer='rmsprop', 135 | metrics=['accuracy']) 136 | model.summary() 137 | return model 138 | 139 | '''构造LSTM网络''' 140 | def build_lstm_model(self): 141 | model = Sequential() 142 | model.add(LSTM(32, return_sequences=True, input_shape=(self.max_length, self.embedding_size))) # returns a sequence of vectors of dimension 32 143 | model.add(LSTM(32, return_sequences=True)) # returns a sequence of vectors of dimension 32 144 | model.add(LSTM(32)) # return a single vector of dimension 32 145 | model.add(Dense(13, activation='softmax')) 146 | model.compile(loss='categorical_crossentropy', 147 | optimizer='rmsprop', 148 | metrics=['accuracy']) 149 | 150 | return model 151 | 152 | '''训练CNN模型''' 153 | def train_cnn(self): 154 | X_train, Y_train, X_test, Y_test = self.split_trainset() 155 | model = self.build_cnn_model() 156 | model.fit(X_train, Y_train, batch_size=100, epochs=20, validation_data=(X_test, Y_test)) 157 | model.save(self.cnn_modelpath) 158 | 159 | '''训练CNN模型''' 160 | def train_lstm(self): 161 | X_train, Y_train, X_test, Y_test = self.split_trainset() 162 | model = self.build_lstm_model() 163 | model.fit(X_train, Y_train, batch_size=100, epochs=50, validation_data=(X_test, Y_test)) 164 | model.save(self.lstm_modelpath) 165 | 166 | '''划分数据集,按一定比例划分训练集和测试集''' 167 | def split_trainset(self): 168 | X, Y = self.load_traindata() 169 | split_rate = 0.8 170 | indx = int(len(X)*split_rate) 171 | X_train = X[:indx] 172 | Y_train = Y[:indx] 173 | X_test = X[indx:] 174 | Y_test = Y[indx:] 175 | return X_train, Y_train, X_test, Y_test 176 | 177 | 178 | if __name__ == '__main__': 179 | handler = QuestionClassify() 180 | handler.train_cnn() 181 | handler.train_lstm() 182 | --------------------------------------------------------------------------------