用户输入 >>> "你好"
Unit回复 >>> "你好,想聊什么呢~"
用户输入 >>> "我想有一个女朋友!"
Unit回复 >>> "我也是想要一个女朋友~"
用户输入 >>> "晚吃啥呢想想"
Unit回复 >>> "想吃火锅"
- 点击获取API Key进入百度云应用管理页面.
- 点击创建应用, 进入应用信息表单填写页面.
- 填写完毕后, 点击立即创建, 成功后会提示创建完毕.
- 可以看到创建的API Key和Secret Key, 至此创建流程结束.
import json
import random
import requests
# client_id 为官网获取的AK, client_secret 为官网获取的SK
client_id = "1xhPonkmHqwolDt3GCICLX39"
client_secret = "SRYsfjMGNuW8G265paMXLEjDTjO6O4RC"
def unit_chat(chat_input, user_id="88888"):
"""
description:调用百度UNIT接口,回复聊天内容
Parameters
----------
chat_input : str
用户发送天内容
user_id : str
发起聊天用户ID,可任意定义
Return
----------
返回unit回复内容
"""
# 设置默认回复内容, 一旦接口出现异常, 回复该内容
chat_reply = "不好意思,俺们正在学习中,随后回复你。"
# 根据 client_id 与 client_secret 获取access_token
url = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s" % (
client_id, client_secret)
res = requests.get(url)
access_token = eval(res.text)["access_token"]
# 根据 access_token 获取聊天机器人接口数据
unit_chatbot_url = "https://aip.baidubce.com/rpc/2.0/unit/service/chat?access_token=" + access_token
# 拼装聊天接口对应请求发送数据,主要是填充 query 值
post_data = {
"log_id": str(random.random()),
"request": {
"query": chat_input,
"user_id": user_id
},
"session_id": "",
"service_id": "S23245",
"version": "2.0"
}
# 将封装好的数据作为请求内容, 发送给Unit聊天机器人接口, 并得到返回结果
res = requests.post(url=unit_chatbot_url, json=post_data)
# 获取聊天接口返回数据
unit_chat_obj = json.loads(res.content)
# print(unit_chat_obj)
# 打印返回的结果
# 判断聊天接口返回数据是否出错 error_code == 0 则表示请求正确
if unit_chat_obj["error_code"] != 0: return chat_reply
# 解析聊天接口返回数据,找到返回文本内容 result -> response_list -> schema -> intent_confidence(>0) -> action_list -> say
unit_chat_obj_result = unit_chat_obj["result"]
unit_chat_response_list = unit_chat_obj_result["response_list"]
# 随机选取一个"意图置信度"[+response_list[].schema.intent_confidence]不为0的技能作为回答
unit_chat_response_obj = random.choice(
[unit_chat_response for unit_chat_response in unit_chat_response_list if
unit_chat_response["schema"]["intent_confidence"] > 0.0])
unit_chat_response_action_list = unit_chat_response_obj["action_list"]
unit_chat_response_action_obj = random.choice(unit_chat_response_action_list)
unit_chat_response_say = unit_chat_response_action_obj["say"]
return unit_chat_response_say
if __name__ == '__main__':
while True:
chat_input = input("请输入:")
print(chat_input)
chat_reply = unit_chat(chat_input)
print("用户输入 >>>", chat_input)
print("Unit回复 >>>", chat_reply)
if chat_input == 'Q' or chat_input == 'q':
break
python unit.py
请输入:你好啊
你好啊
用户输入 >>> 你好啊
Unit回复 >>> 你也好啊~
请输入:今天天气棒棒哒
今天天气棒棒哒
用户输入 >>> 今天天气棒棒哒
Unit回复 >>> 必须的
请输入:晚饭吃点什么?
晚饭吃点什么?
用户输入 >>> 晚饭吃点什么?
Unit回复 >>> 晚饭没吃,减肥
请输入:
本章总结:
- 简介:
* Flask框架是当下最受欢迎的python轻量级框架, 也是pytorch官网指定的部署框架. Flask的基本模式为在程序里将一个视图函数分配给一个URL,每当用户访问这个URL时,系统就会执行给该URL分配好的视图函数,获取函数的返回值,其工作过程见图.
- 作用:
* 在项目中, Flask框架是主逻辑服务和句子相关模型服务使用的服务框架.
- 安装:
# 使用pip安装Flask
pip install Flask==1.1.1
- 基本使用方法:
# 导入Flask类
from flask import Flask
# 创建一个该类的实例app, 参数为__name__, 这个参数是必需的,
# 这样Flask才能知道在哪里可找到模板和静态文件等东西.
app = Flask(__name__)
# 使用route()装饰器来告诉Flask触发函数的URL
@app.route('/')
def hello_world():
"""请求指定的url后,执行的主要逻辑函数"""
# 在用户浏览器中显示信息:'Hello, World!'
return 'Hello, World!'
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)
- 代码位置: /data/doctor_onine/main_serve/app.py
- 启动服务:
python app.py
- 启动效果:
* 通过浏览器打开地址http://127.0.0.1:5000/可看见打印了’Hello, World’.
先在redis目录下cmd: redis-server.exe redis.windows.conf 启动redis服务
再在redis目录下另开cmd,输入redis-cli -h 127.0.0.1 -p 6379 -a 密码
- 简介:
* Redis(全称:Remote Dictionary Server 远程字典服务)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API.
- 作用:
* 在项目中, Redis用于会话管理数据库, 保存用户聊天历史.
- 安装:
# 使用yum安装redis
yum install redis -y
- 基本使用方法:
* Redis支持四种数据结构的存储: String(字符串), Hash(散列), List(列表), Set(集合), Sorted Set(有序集合).
* 在这里我们将着重介绍如何在python中使用Hash(散列)进行读写.
- 安装python中的redis驱动:
# 使用pip进行安装
pip install redis
- 启动redis服务:
# 启动redis-server, 这里使用了默认配置, 端口是6379.
redis-server
- 在python中使用Hash(散列)进行读写:
如果redis 设置了密码
需要
REDIS_CONFIG = {
‘host’:‘127.0.0.1’,
‘port’:6379,
‘password’:‘123456’
}
否则报错:Authentication required.
# coding=utf-8
# redis配置
REDIS_CONFIG = {
"host": "0.0.0.0",
"port": 6379
}
# 导入redis驱动
import redis
# 创建一个redis连接池
pool = redis.ConnectionPool( **REDIS_CONFIG)
# 从连接池中初始化一个活跃的连接对象
r = redis.StrictRedis(connection_pool=pool)
# hset表示使用hash数据结构进行数据写入
# uid代表某个用户的唯一标识
uid = "8888"
# key是需要记录的数据描述
key = "该用户最后一次说的话:".encode('utf-8')
# value是需要记录的数据具体内容
value = "再见, 董小姐".encode('utf-8')
r.hset(uid, key, value)
# hget表示使用hash数据结构进行数据读取
result = r.hget(uid, key)
print(result.decode('utf-8'))
- 输出效果:
再见, 董小姐
- 简介:
* Gunicorn是一个被广泛使用的高性能的Python WSGI UNIX HTTP服务组件(WSGI: Web Server Gateway Interface),移植自Ruby的独角兽(Unicorn )项目,具有使用非常简单,轻量级的资源消耗,以及高性能等特点.
- 作用:
* 在项目中, Gunicorn和Flask框架一同使用, 能够开启服务, 处理请求,因其高性能的特点能够有效减少服务丢包率.
- 安装:
# 使用pip安装gunicorn
pip install gunicorn==20.0.4
- 基本使用方法:
# 使用其启动Flask服务:
gunicorn -w 1 -b 0.0.0.0:5000 app:app
# -w 代表开启的进程数, 我们只开启一个进程
# -b 服务的IP地址和端口
# app:app 是指执行的主要对象位置, 在app.py中的app对象
# 如果使其在后台运行可使用:
# nohup gunicorn -w 1 -b 0.0.0.0:5001 app:app &
- 简介:
* Supervisor是用Python开发的一个client/server服务,是Linux/Unix系统下的一个进程管理工具。它可以很方便的监听、启动、停止、重启一个或多个进程, 并守护这些进程。
- 作用:
* 在项目中, Supervisor用于监控和守护主要逻辑服务和redis数据库服务.
- 安装:
# 使用yum安装supervisor
yum install supervisor -y
- 基本使用方法:
# 编辑配置文件, 指明监控和守护的进程开启命令,
# 请查看/data/doctor_online/supervisord.conf文件
# 开启supervisor, -c用于指定配置文件
sueprvisord -c /data/doctor_online/main_server/supervisord.conf
# 查看监控的进程状态:
supervisorctl status
# main_server RUNNING pid 31609, uptime 0:32:20
# redis RUNNING pid 31613, uptime 0:32:18
# 关闭supervisor
supervisorctl shutdown
- 还可以通过浏览器查看可视化监控页面: http://0.0.0.0:9001
本章总结:
警告: ERROR! Neo4j cannot be started using java version 1.7.0_75
解决: java JDK下载
并重新配置java JDK 环境变量后,可以正常启动neo4j
浏览器登录http://localhost:7474/browser/ 初始账号密码均为:neo4j
若出现问题:“ NotFoundError:无法在’Node’上执行’removeChild’:要删除的节点不是该节点的子节点。”并且应用程序无法恢复。 更换chrome或者其他浏览器
- 节点
* 节点是主要的数据元素, 节点通过关系连接到其他节点, 节点可以具有一个或多个属性
(即存储为键/值对的属性), 节点有一个或多个标签, 用于描述其在图表中的作用. 示例: Person>节点.
* 可以将节点类比为关系型数据库中的表, 对应的标签可以类比为不同的表名, 属性就是表中的列.
- 关系
* 关系连接两个节点, 关系是方向性的, 关系可以有一个或多个属性(即存储为键/值对的
属性).
- 属性
* 属性是命名值, 其中名称(或键)是字符串, 属性可以被索引和约束, 可以从多个属性创
建复合索引.
- 标签
* 标签用于组节点到集, 节点可以具有多个标签, 对标签进行索引以加速在图中查找节点.
cd /tmp
wget http://debian.neo4j.org/neotechnology.gpg.key
rpm --import neotechnology.gpg.key
cat <<EOF> /etc/yum.repos.d/neo4j.repo
# 写入下面内容
[neo4j]
name=Neo4j RPM Repository
baseurl=http://yum.neo4j.org/stable
enabled=1
gpgcheck=1
yum install neo4j-3.3.5
# 数据库的存储库存储位置、日志位置等
dbms.directories.data=/var/lib/neo4j/data
dbms.directories.plugins=/var/lib/neo4j/plugins
dbms.directories.certificates=/var/lib/neo4j/certificates
dbms.directories.logs=/var/log/neo4j
dbms.directories.lib=/usr/share/neo4j/lib
dbms.directories.run=/var/run/neo4j
# 导入的位置
dbms.directories.import=/var/lib/neo4j/import
# 初始化内存大小
dbms.memory.heap.initial_size=512m
# Bolt 连接地址
dbms.connector.bolt.enabled=true
dbms.connector.bolt.tls_level=OPTIONAL
dbms.connector.bolt.listen_address=0.0.0.0:7687
# 启动命令
neo4j start
# 终端显示如下, 代表启动成功
Active database: graph.db
Directories in use:
home: /usr/neo4j
config: /etc/neo4j
logs: /var/log/neo4j
plugins: /var/lib/neo4j/plugins
import: /var/lib/neo4j/import
data: /var/lib/neo4j/data
certificates: /var/lib/neo4j/certificates
run: /var/run/neo4j
Starting Neo4j.
小节总结:
# 创建命令格式:
# 此处create是关键字, 创建节点名称node_name, 节点标签Node_Label, 放在小括号里面()
# 后面把所有属于节点标签的属性放在大括号'{}'里面, 依次写出属性名称:属性值, 不同属性用逗号','分隔
# 例如下面命令创建一个节点e, 节点标签是Employee, 拥有id, name, salary, deptnp四个属性:
CREATE (e:Employee{
id:222, name:'Bob', salary:6000, deptnp:12})
# match命令专门用来匹配查询, 节点名称:节点标签, 依然放在小括号内, 然后使用return语句返回查询结果, 和SQL很相似.
MATCH (e:Employee) RETURN e.id, e.name, e.salary, e.deptno
MERGE (e:Employee {
id:146, name:'Lucer', salary:3500, deptno:16})
MERGE (e:Employee {
id:146, name:'Lucer', salary:3500, deptno:16})
# 创建一个节点p1到p2的有方向关系, 这个关系r的标签为Buy, 代表p1购买了p2, 方向为p1指向p2
CREATE (p1:Profile1)-[r:Buy]->(p2:Profile2)
# 创建一个节点p1到p2的无方向关系, 这个关系r的标签为miss, 代表p1-miss-p2, 方向为相互的
MERGE (p1:Profile1)-[r:miss]-(p2:Profile2)
# 查询节点Employee中, id值等于123的那个节点
MATCH (e:Employee) WHERE e.id=123 RETURN e
# 注意: 删除节点的同时, 也要删除关联的关系边
MATCH (c1:CreditCard)-[r]-(c2:Customer) DELETE c1, r, c2
# 匹配查询标签Employee, 将所有匹配结果按照id值升序排列后返回结果
MATCH (e:Employee) RETURN e.id, e.name, e.salary, e.deptno ORDER BY e.id
# 如果要按照降序排序, 只需要将ORDER BY e.salary改写为ORDER BY e.salary DESC
MATCH (e:Employee) RETURN e.id, e.name, e.salary, e.deptno ORDER BY e.salary DESC
MATCH (e:Employee) RETURN e.id, toUpper(e.name), e.salary, e.deptno
MATCH (e:Employee) RETURN e.id, toLower(e.name), e.salary, e.deptno
# 输入字符串为input_str, 返回从索引start_index开始, 到end_index-1结束的子字符串
substring(input_str, start_index, end_index)
# 示例代码, 返回员工名字的前两个字母
MATCH (e:Employee) RETURN e.id, substring(e.name,0,2), e.salary, e.deptno
# 输入字符串为input_str, 将输入字符串中符合origin_str的部分, 替换成new_str
replace(input_str, origin_str, new_str)
# 示例代码, 将员工名字替换为添加后缀_HelloWorld
MATCH (e:Employee) RETURN e.id, replace(e.name,e.name,e.name + "_HelloWorld"), e.salary, e.deptno
# 返回匹配标签Employee成功的记录个数
MATCH (e:Employee) RETURN count( * )
# 返回匹配标签Employee成功的记录中, 最高的工资数字
MATCH (e:Employee) RETURN max(e.salary)
# 返回匹配标签Employee成功的记录中, 最低的工资数字
MATCH (e:Employee) RETURN min(e.salary)
# 返回匹配标签Employee成功的记录中, 所有员工工资的和
MATCH (e:Employee) RETURN sum(e.salary)
# 返回匹配标签Employee成功的记录中, 所有员工工资的平均值
MATCH (e:Employee) RETURN avg(e.salary)
# 创建节点Employee上面属性id的索引
CREATE INDEX ON:Employee(id)
# 删除节点Employee上面属性id的索引
DROP INDEX ON:Employee(id)
小节总结:
pip install neo4j-driver
config.py
# 设置neo4j图数据库的配置信息
NEO4J_CONFIG = {
"uri": "bolt://127.0.0.1:7687",
"auth": ("username", "password"),
"encrypted": False
}
from neo4j import GraphDatabase
# 关于neo4j数据库的用户名,密码信息已经配置在同目录下的config.py文件中
from config import NEO4J_CONFIG
driver = GraphDatabase.driver( **NEO4J_CONFIG)
# 直接用python代码形式访问节点Company, 并返回所有节点信息
with driver.session() as session:
cypher = "CREATE(c:Company) SET c.name='在线医生' RETURN c.name"
record = session.run(cypher)
result = list(map(lambda x: x[0], record))
print("result:", result)
result: ['在线医生']
def _some_operations(tx, cat_name, mouse_name):
tx.run("MERGE (a:Cat{name: $cat_name})"
"MERGE (b:Mouse{name: $mouse_name})"
"MERGE (a)-[r:And]-(b)",
cat_name=cat_name, mouse_name=mouse_name)
with driver.session() as session:
session.write_transaction(_some_operations, "Tom", "Jerry")
查询多个节点多个属性
match(c:Cat) - [r] - (m:Mouse) return c.name, m.name
小节总结:
...
踝部急性韧带损伤.csv
踝部扭伤.csv
踝部骨折.csv
蹄铁形肾.csv
蹼状阴茎.csv
躁狂抑郁症.csv
躁狂症.csv
躁郁症.csv
躯体形式障碍.csv
躯体感染伴发的精神障碍.csv
躯体感染所致精神障碍.csv
躯体感觉障碍.csv
躯体疾病伴发的精神障碍.csv
转换性障碍.csv
转移性小肠肿瘤.csv
转移性皮肤钙化病.csv
转移性肝癌.csv
转移性胸膜肿瘤.csv
转移性骨肿瘤.csv
轮状病毒性肠炎.csv
轮状病毒所致胃肠炎.csv
软产道异常性难产.csv
...
- 每个csv文件的名字都是一种疾病名.
- 文件位置: /data/doctor_offline/structured/noreview/
- 以躁狂症.csv为例, 有如下内容:
躁郁样
躁狂
行为及情绪异常
心境高涨
情绪起伏大
技术狂躁症
攻击行为
易激惹
思维奔逸
控制不住的联想
精神运动性兴奋
- csv文件的内容是该疾病对应的症状, 每种症状占一行.
- 文件位置: /data/doctor_offline/structured/noreview/躁狂症.csv
# Linux 命令-- 删除当前文件夹下的空文件
find ./ -name "*" -type f -size 0c | xargs -n 1 rm -f
- 代码位置: 在/data/doctor_offline/structured/reviewed/目录下执行.
- 将命名实体写入图数据库的原因:
* 写入的数据供在线部分进行查询,根据用户输入症状来匹配对应疾病.
- 将命名实体写入图数据库代码:
# 引入相关包
import os
import fileinput
from neo4j import GraphDatabase
from config import NEO4J_CONFIG
driver = GraphDatabase.driver( **NEO4J_CONFIG)
def _load_data(path):
"""
description: 将path目录下的csv文件以指定格式加载到内存
:param path: 审核后的疾病对应症状的csv文件
:return: 返回疾病字典,存储各个疾病以及与之对应的症状的字典
{疾病1: [症状1, 症状2, ...], 疾病2: [症状1, 症状2, ...]
"""
# 获得疾病csv列表
disease_csv_list = os.listdir(path)
# 将后缀.csv去掉, 获得疾病列表
disease_list = list(map(lambda x: x.split(".")[0], disease_csv_list))
# 初始化一个症状列表, 它里面是每种疾病对应的症状列表
symptom_list = []
# 遍历疾病csv列表
for disease_csv in disease_csv_list:
# 将疾病csv中的每个症状取出存入symptom列表中
# symptom = list(map(lambda x : x.strip(), fileinput.FileInput(os.path.join(path, disease_csv), openhook= fileinput.hook_encoded('utf-8'))))
symptom = list(map(lambda x: x.strip(),
fileinput.FileInput(os.path.join(path, disease_csv))))
# 过滤掉所有长度异常的症状名
symptom = list(filter(lambda x: 0<len(x)<100, symptom))
symptom_list.append(symptom)
# 返回指定格式的数据 {疾病:对应症状}
return dict(zip(disease_list, symptom_list))
def write(path):
"""
description: 将csv数据写入到neo4j, 并形成图谱
:param path: 数据文件路径
"""
# 使用_load_data从持久化文件中加载数据
disease_symptom_dict = _load_data(path)
# 开启一个neo4j的session
with driver.session() as session:
for key, value in disease_symptom_dict.items():
cypher = "MERGE (a:Disease{name:%r}) RETURN a" %key
session.run(cypher)
for v in value:
cypher = "MERGE (b:Symptom{name:%r}) RETURN b" %v
session.run(cypher)
cypher = "MATCH (a:Disease{
name:%r}) MATCH (b:Symptom{
name:%r}) \
WITH a,b MERGE(a)-[r:dis_to_sym]-(b)" %(key, v)
session.run(cypher)
cypher = "CREATE INDEX ON:Disease(name)"
session.run(cypher)
cypher = "CREATE INDEX ON:Symptom(name)"
session.run(cypher)
- 调用:
# 输入参数path为csv数据所在路径
path = "/data/doctor_offline/structured/reviewed/"
write(path)
fileinput UnicodeDecodeError: gbk codec cant decode byte 0x80 in position 2: illegal multibyte sequence
解决:symptom = list(map(lambda x : x.strip(), fileinput.FileInput(os.path.join(path, disease_csv), openhook= fileinput.hook_encoded(‘utf-8’))))
symptom = list(map(lambda x: x.strip(), fileinput.FileInput(os.path.join(path, disease_csv))))
- 输出效果:
* 通过可视化管理后台查看写入效果.
MATCH(a:Disease) - [r:dis_to_sym] - (b:Symptom) RETURN a, r, b LIMIT 25
# 或者
MATCH p = () -[r : dis_to_sym] - () RETURN p LIMIT 25
...
麻疹样红斑型药疹.txt
麻疹病毒肺炎.txt
麻痹性臂丛神经炎.txt
麻风性周围神经病.txt
麻风性葡萄膜炎.txt
黄体囊肿.txt
黄斑囊样水肿.txt
黄斑裂孔性视网膜脱离.txt
黄韧带骨化症.txt
黏多糖贮积症.txt
黏多糖贮积症Ⅰ型.txt
黏多糖贮积症Ⅱ型.txt
黏多糖贮积症Ⅵ型.txt
黏多糖贮积症Ⅲ型.txt
黏多糖贮积症Ⅶ型.txt
黑色丘疹性皮肤病.txt
...
- 每个txt文件的名字都是一种疾病名.
- 文件位置: /data/doctor_offline/unstructured/norecognite/
- 以黑色丘疹性皮肤病.txt为例, 有如下内容:
初呈微小、圆形、皮肤色或黑色增深的丘疹,单个或少数发生于颌部或颊部,皮损逐渐增大增多,数年中可达数百,除眶周外尚分布于面部、颈部和胸上部。皮损大小形状酷似脂溢性角化病及扁平疣鶒。不发生鳞屑,结痂和溃疡,亦无瘙痒及其他主观症状
- txt中是对该疾病症状的文本描述.
- 文件位置: /data/doctor_offline/unstructured/norecognite/黑色丘疹性皮肤病.txt
本章总结:
1 手内肌萎缩
0 缩萎肌内手
1 尿黑酸
0 酸黑尿
1 单眼眼前黑影
0 影黑前眼眼单
1 忧郁
0 郁忧
1 红细胞寿命缩短
0 短缩命寿胞细红
1 皮肤黏蛋白沉积
0 积沉白蛋黏肤皮
1 眼神异常
0 常异神眼
1 阴囊坠胀痛
0 痛胀坠囊阴
1 动脉血氧饱和度降低
0 低降度和饱氧血脉动
import pandas as pd
from collections import Counter
# 读取数据
train_data_path = "./train_data.csv"
train_data= pd.read_csv(train_data_path, header=None, sep="\t")
# 打印正负标签比例
print(dict(Counter(train_data[0].values)))
# 转换数据到列表形式
train_data = train_data.values.tolist()
print(train_data[:10])
# 正负标签比例
{
1: 5740, 0: 5740}
# 取出10条训练数据查看
[[1, '枕部疼痛'], [0, '痛疼部枕'], [1, '陶瑟征阳性'], [0, '性阳征瑟陶'], [1, '恋兽型性变态'], [0, '态变性型兽恋'], [1, '进食困难'], [0, '难困食进'], [1, '会阴瘘管或窦道形成'], [0, '成形道窦或管瘘阴会']]
import torch
import torch.nn as nn
# 通过torch.hub(pytorch中专注于迁移学的工具)获得已经训练好的bert-base-chinese模型
model = torch.hub.load('huggingface/pytorch-transformers', 'model', 'bert-base-chinese')
# 获得对应的字符映射器, 它将把中文的每个字映射成一个数字
tokenizer = torch.hub.load('huggingface/pytorch-transformers', 'tokenizer', 'bert-base-chinese')
def get_bert_encode_for_single(text):
"""
description: 使用bert-chinese编码中文文本
:param text: 要进行编码的文本
:return: 使用bert编码后的文本张量表示
"""
# 首先使用字符映射器对每个汉字进行映射
# 这里需要注意, bert的tokenizer映射后会为结果前后添加开始和结束标记即101和102
# 这对于多段文本的编码是有意义的, 但在我们这里没有意义, 因此使用[1:-1]对头和尾进行切片
indexed_tokens = tokenizer.encode(text)[1:-1]
# 之后将列表结构转化为tensor
tokens_tensor = torch.tensor([indexed_tokens])
print(tokens_tensor)
# 使模型不自动计算梯度
with torch.no_grad():
# 调用模型获得隐层输出
encoded_layers, _ = model(tokens_tensor)
# 输出的隐层是一个三维张量, 最外层一维是1, 我们使用[0]降去它.
print(encoded_layers.shape)
encoded_layers = encoded_layers[0]
return encoded_layers
text = "你好, 周杰伦"
outputs = get_bert_encode_for_single(text)
print(outputs)
print(outputs.shape)
tensor([[ 3.2731e-01, -1.4832e-01, -9.1618e-01, ..., -4.4088e-01,
-4.1074e-01, -7.5570e-01],
[-1.1287e-01, -7.6269e-01, -6.4861e-01, ..., -8.0478e-01,
-5.3600e-01, -3.1953e-01],
[-9.3012e-02, -4.4381e-01, -1.1985e+00, ..., -3.6624e-01,
-4.7467e-01, -2.6408e-01],
[-1.6896e-02, -4.3753e-01, -3.6060e-01, ..., -3.2451e-01,
-3.4204e-02, -1.7930e-01],
[-1.3159e-01, -3.0048e-01, -2.4193e-01, ..., -4.5756e-02,
-2.0958e-01, -1.0649e-01],
[-4.0006e-01, -3.4410e-01, -3.8532e-05, ..., 1.9081e-01,
1.7006e-01, -3.6221e-01]])
torch.Size([6, 768])
注意:torch.hub.load无法下载时,利用迅雷等通过链接中 的地址进行下载model.bin config.json vocab.txt并更改名称
from transformers import BertModel, BertTokenizer
import torch
from transformers import BertModel, BertTokenizer
# 通过torch.hub(pytorch中专注于迁移学的工具)获得已经训练好的bert-base-chinese模型
# model = torch.hub.load('huggingface/pytorch-transformers', 'model', 'bert-base-chinese')
# 获得对应的字符映射器, 它将把中文的每个字映射成一个数字
# tokenizer = torch.hub.load('huggingface/pytorch-transformers', 'tokenizer', 'bert-base-chinese')
#
model = BertModel.from_pretrained('./bert-base-chinese')
tokenizer = BertTokenizer.from_pretrained('./bert-base-chinese/')
def get_bert_encode_for_single(text):
"""
使用bert-base-chinese对中文文本进行编码
:param text: 进行编码的中文文本
:return: 编号后的张量
"""
# 使用字符映射器对每个汉字进行映射
# bert中tokenizer映射后会加入开始和结束标记101,102 采用[1:-1]去除
indexed_tokens = tokenizer.encode(text)[1 : -1]
# 封装为tensor
tokens_tensor = torch.tensor([indexed_tokens])
print(tokens_tensor)
# 预测部分-不需要求导
with torch.no_grad():
encoded_layers, _ = model(tokens_tensor)
print('encoded_layers_shape:{}'.format(encoded_layers.shape))
# 模型的输出均为三维张量,第一维为1,只提取后两个维度张量,需要[0]来降维
encoded_layers = encoded_layers[0]
return encoded_layers
if __name__ == '__main__':
text = '你好, 周杰伦'
outputs = get_bert_encode_for_single(text)
print('outputs:{}'.format(outputs))
print('outputs.shape:{}'.format(outputs.shape))
tensor([[ 872, 1962, 117, 1453, 3345, 840]])
encoded_layers_shape:torch.Size([1, 6, 768])
outputs:tensor([[ 3.2731e-01, -1.4832e-01, -9.1618e-01, ..., -4.4088e-01,
-4.1074e-01, -7.5570e-01],
[-1.1287e-01, -7.6269e-01, -6.4861e-01, ..., -8.0478e-01,
-5.3600e-01, -3.1953e-01],
[-9.3014e-02, -4.4381e-01, -1.1985e+00, ..., -3.6624e-01,
-4.7467e-01, -2.6408e-01],
[-1.6897e-02, -4.3753e-01, -3.6060e-01, ..., -3.2451e-01,
-3.4204e-02, -1.7930e-01],
[-1.3159e-01, -3.0048e-01, -2.4193e-01, ..., -4.5757e-02,
-2.0958e-01, -1.0649e-01],
[-4.0006e-01, -3.4410e-01, -3.9786e-05, ..., 1.9081e-01,
1.7006e-01, -3.6221e-01]])
outputs.shape:torch.Size([6, 768])
小节总结:
class RNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
"""初始化函数中有三个参数,分别是输入张量最后一维的尺寸大小,
隐层张量最后一维的尺寸大小, 输出张量最后一维的尺寸大小"""
super(RNN, self).__init__()
# 传入隐含层尺寸大小
self.hidden_size = hidden_size
# 构建从输入到隐含层的线性变化, 这个线性层的输入尺寸是input_size + hidden_size
# 这是因为在循环网络中, 每次输入都有两部分组成,分别是此时刻的输入xt和上一时刻产生的输出ht-1.
# 这个线性层的输出尺寸是hidden_size
self.i2h = nn.Linear(input_size + hidden_size, hidden_size)
# 构建从输入到输出层的线性变化, 这个线性层的输入尺寸还是input_size + hidden_size
# 这个线性层的输出尺寸是output_size.
self.i2o = nn.Linear(input_size + hidden_size, output_size)
# 最后需要对输出做softmax处理, 获得结果.
self.softmax = nn.LogSoftmax(dim=-1)
def forward(self, input, hidden):
"""在forward函数中, 参数分别是规定尺寸的输入张量, 以及规定尺寸的初始化隐层张量"""
# 首先使用torch.cat将input与hidden进行张量拼接
combined = torch.cat((input, hidden), 1)
# 通过输入层到隐层变换获得hidden张量
hidden = self.i2h(combined)
# 通过输入到输出层变换获得output张量
output = self.i2o(combined)
# 对输出进行softmax处理
output = self.softmax(output)
# 返回输出张量和最后的隐层结果
return output, hidden
def initHidden(self):
"""隐层初始化函数"""
# 将隐层初始化成为一个1xhidden_size的全0张量
return torch.zeros(1, self.hidden_size)
- torch.cat演示:
>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497]])
>>> torch.cat((x, x, x), 0)
tensor([[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497],
[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497],
[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497]])
>>> torch.cat((x, x, x), 1)
ensor([[ 0.6580, -1.0969, -0.4614, 0.6580, -1.0969, -0.4614, 0.6580,-1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497, -0.1034, -0.5790, 0.1497, -0.1034,-0.5790, 0.1497]])
input_size = 768
hidden_size = 128
n_categories = 2 # ner审核通过或者不通过
input = torch.rand(1, input_size)
hidden = torch.rand(1, hidden_size)
from RNN_MODEL import RNN
rnn = RNN(input_size, hidden_size, n_categories)
outputs, hidden = rnn(input, hidden)
print("outputs:", outputs)
print("hidden:", hidden)
outputs: tensor([[-0.7858, -0.6084]], grad_fn=<LogSoftmaxBackward>) # [1, 2]
hidden: tensor([[-4.8444e-01, -5.9609e-02, 1.7870e-01,
-1.6553e-01, ... , 5.6711e-01]], grad_fn=<AddmmBackward>)) # [1, 128]
import pandas as pd
import random
from bert_chinese_encode import get_bert_encode_for_single
import torch
# 读取数据
train_data_path = './train_data.csv'
train_data = pd.read_csv(train_data_path, header = None, sep = '\t', encoding = 'utf-8')
trian_data = train_data.values.tolist()
def randomTrainingExample(train_data):
"""随机选取数据函数, train_data是训练集的列表形式数据"""
# 从train_data随机选择一条数据
category, line = random.choice(train_data)
# 将里面的文字使用bert进行编码, 获取编码后的tensor类型数据
line_tensor = get_bert_encode_for_single(line)
# 将分类标签封装成tensor
category_tensor = torch.tensor([int(category)])
# 返回四个结果
return category, line, category_tensor, line_tensor
- 代码位置: /data/doctor_offline/review_model/train.py
- 输入参数:
# 将数据集加载到内存获得的train_data
- 调用:
# 选择10条数据进行查看
for i in range(10):
category, line, category_tensor, line_tensor = randomTrainingExample(train_data)
print('category =', category, '/ line =', line)
- 输出效果:
category = 1 / line = 触觉失调
category = 0 / line = 颤震性理生
category = 0 / line = 征压血高娠妊
category = 1 / line = 食欲减退
category = 0 / line = 血淤道肠胃
category = 0 / line = 形畸节关
category = 0 / line = 咳呛水饮
category = 0 / line = 症痣巨
category = 1 / line = 昼盲
category = 1 / line = 眼神异常
# 选取损失函数为NLLLoss()
criterion = nn.NLLLoss()
# 学习率为0.005
learning_rate = 0.005
def train(category_tensor, line_tensor):
"""模型训练函数, category_tensor代表类别张量, line_tensor代表编码后的文本张量"""
# 初始化隐层
hidden = rnn.initHidden()
# 模型梯度归0
rnn.zero_grad()
# 遍历line_tensor中的每一个字的张量表示
for i in range(line_tensor.size()[0]):
# 然后将其输入到rnn模型中, 因为模型要求是输入必须是二维张量, 因此需要拓展一个维度, 循环调用rnn直到最后一个字
output, hidden = rnn(line_tensor[i].unsqueeze(0), hidden)
# 根据损失函数计算损失, 输入分别是rnn的输出结果和真正的类别标签
loss = criterion(output, category_tensor)
# 将误差进行反向传播
loss.backward()
# 更新模型中所有的参数
for p in rnn.parameters():
# 将参数的张量表示与参数的梯度乘以学习率的结果相加以此来更新参数
p.data.add_(-learning_rate, p.grad.data)
# 返回结果和损失的值
return output, loss.item()
def valid(category_tensor, line_tensor):
"""模型验证函数, category_tensor代表类别张量, line_tensor代表编码后的文本张量"""
# 初始化隐层
hidden = rnn.initHidden()
# 验证模型不自动求解梯度
with torch.no_grad():
# 遍历line_tensor中的每一个字的张量表示
for i in range(line_tensor.size()[0]):
# 然后将其输入到rnn模型中, 因为模型要求是输入必须是二维张量, 因此需要拓展一个维度, 循环调用rnn直到最后一个字
output, hidden = rnn(line_tensor[i].unsqueeze(0), hidden)
# 获得损失
loss = criterion(output, category_tensor)
# 返回结果和损失的值
return output, loss.item()
- 构建时间计算函数:
import time
import math
def timeSince(since):
"获得每次打印的训练耗时, since是训练开始时间"
# 获得当前时间
now = time.time()
# 获得时间差,就是训练耗时
s = now - since
# 将秒转化为分钟, 并取整
m = math.floor(s / 60)
# 计算剩下不够凑成1分钟的秒数
s -= m * 60
# 返回指定格式的耗时
return '%dm %ds' % (m, s)
- 输入参数:
# 假定模型训练开始时间是10min之前
since = time.time() - 10*60
- 调用:
period = timeSince(since)
print(period)
- 输出效果:
10m 0s
- 调用训练和验证函数并打印日志
# 设置迭代次数为50000步
n_iters = 50000
# 打印间隔为1000步
plot_every = 1000
# 初始化打印间隔中训练和验证的损失和准确率
train_current_loss = 0
train_current_acc = 0
valid_current_loss = 0
valid_current_acc = 0
# 初始化盛装每次打印间隔的平均损失和准确率
all_train_losses = []
all_train_acc = []
all_valid_losses = []
all_valid_acc = []
# 获取开始时间戳
start = time.time()
# 循环遍历n_iters次
for iter in range(1, n_iters + 1):
# 调用两次随机函数分别生成一条训练和验证数据
category, line, category_tensor, line_tensor = randomTrainingExample(train_data)
category_, line_, category_tensor_, line_tensor_ = randomTrainingExample(train_data)
# 分别调用训练和验证函数, 获得输出和损失
train_output, train_loss = train(category_tensor, line_tensor)
valid_output, valid_loss = valid(category_tensor_, line_tensor_)
# 进行训练损失, 验证损失,训练准确率和验证准确率分别累加
train_current_loss += train_loss
train_current_acc += (train_output.argmax(1) == category_tensor).sum().item()
valid_current_loss += valid_loss
valid_current_acc += (valid_output.argmax(1) == category_tensor_).sum().item()
# 当迭代次数是指定打印间隔的整数倍时
if iter % plot_every == 0:
# 用刚刚累加的损失和准确率除以间隔步数得到平均值
train_average_loss = train_current_loss / plot_every
train_average_acc = train_current_acc/ plot_every
valid_average_loss = valid_current_loss / plot_every
valid_average_acc = valid_current_acc/ plot_every
# 打印迭代步, 耗时, 训练损失和准确率, 验证损失和准确率
print("Iter:", iter, "|", "TimeSince:", timeSince(start))
print("Train Loss:", train_average_loss, "|", "Train Acc:", train_average_acc)
print("Valid Loss:", valid_average_loss, "|", "Valid Acc:", valid_average_acc)
# 将结果存入对应的列表中,方便后续制图
all_train_losses.append(train_average_loss)
all_train_acc.append(train_average_acc)
all_valid_losses.append(valid_average_loss)
all_valid_acc.append(valid_average_acc)
# 将该间隔的训练和验证损失及其准确率归0
train_current_loss = 0
train_current_acc = 0
valid_current_loss = 0
valid_current_acc = 0
- 输出效果:
Iter: 1000 | TimeSince: 0m 56s
Train Loss: 0.6127021567507527 | Train Acc: 0.747
Valid Loss: 0.6702297774022868 | Valid Acc: 0.7
Iter: 2000 | TimeSince: 1m 52s
Train Loss: 0.5190641692602076 | Train Acc: 0.789
Valid Loss: 0.5217500487511397 | Valid Acc: 0.784
Iter: 3000 | TimeSince: 2m 48s
Train Loss: 0.5398398997281778 | Train Acc: 0.8
Valid Loss: 0.5844468013737023 | Valid Acc: 0.777
Iter: 4000 | TimeSince: 3m 43s
Train Loss: 0.4700755337187358 | Train Acc: 0.822
Valid Loss: 0.5140456306522071 | Valid Acc: 0.802
Iter: 5000 | TimeSince: 4m 38s
Train Loss: 0.5260879981063878 | Train Acc: 0.804
Valid Loss: 0.5924804099237979 | Valid Acc: 0.796
Iter: 6000 | TimeSince: 5m 33s
Train Loss: 0.4702717279043861 | Train Acc: 0.825
Valid Loss: 0.6675750375208704 | Valid Acc: 0.78
Iter: 7000 | TimeSince: 6m 27s
Train Loss: 0.4734503294042624 | Train Acc: 0.833
Valid Loss: 0.6329268293256277 | Valid Acc: 0.784
Iter: 8000 | TimeSince: 7m 23s
Train Loss: 0.4258338176879665 | Train Acc: 0.847
Valid Loss: 0.5356959595441066 | Valid Acc: 0.82
Iter: 9000 | TimeSince: 8m 18s
Train Loss: 0.45773495503464817 | Train Acc: 0.843
Valid Loss: 0.5413714128659645 | Valid Acc: 0.798
Iter: 10000 | TimeSince: 9m 14s
Train Loss: 0.4856756244019302 | Train Acc: 0.835
Valid Loss: 0.5450502399195044 | Valid Acc: 0.813
plt.title(“your title name”, y=-0.1)设置y位置可以将title设置在图像下方
import matplotlib.pyplot as plt
plt.figure(0)
plt.plot(all_train_losses, label="Train Loss")
plt.plot(all_valid_losses, color="red", label="Valid Loss")
plt.legend(loc='upper left')
plt.savefig("./loss.png")
plt.figure(1)
plt.plot(all_train_acc, label="Train Acc")
plt.plot(all_valid_acc, color="red", label="Valid Acc")
plt.legend(loc='upper left')
plt.savefig("./acc.png")
- 代码位置: /data/doctor_offline/review_model/train.py
- 训练和验证损失对照曲线:
- 训练和验证准确率对照曲线:
- 分析:
* 损失对照曲线一直下降, 说明模型能够从数据中获取规律,正在收敛, 准确率对照曲线中验证准确率一直上升,最终维持在0.98左右.
# 保存路径
MODEL_PATH = './BERT_RNN.pth'
# 保存模型参数
torch.save(rnn.state_dict(), MODEL_PATH)
- 代码位置: /data/doctor_offline/review_model/train.py
- 输出效果:
* 在/data/doctor_offline/review_model/路径下生成BERT_RNN.pth文件.
import os
import torch
import torch.nn as nn
# 导入RNN模型结构
from RNN_MODEL import RNN
# 导入bert预训练模型编码函数
from bert_chinese_encode import get_bert_encode_for_single
# 预加载的模型参数路径
MODEL_PATH = './BERT_RNN.pth'
# 隐层节点数, 输入层尺寸, 类别数都和训练时相同即可
n_hidden = 128
input_size = 768
n_categories = 2
# 实例化RNN模型, 并加载保存模型参数
rnn = RNN(input_size, n_hidden, n_categories)
rnn.load_state_dict(torch.load(MODEL_PATH))
def _test(line_tensor):
"""模型测试函数, 它将用在模型预测函数中, 用于调用RNN模型并返回结果.它的参数line_tensor代表输入文本的张量表示"""
# 初始化隐层张量
hidden = rnn.initHidden()
# 与训练时相同, 遍历输入文本的每一个字符
for i in range(line_tensor.size()[0]):
# 将其逐次输送给rnn模型
output, hidden = rnn(line_tensor[i].unsqueeze(0), hidden)
# 获得rnn模型最终的输出
return output
def predict(input_line):
"""模型预测函数, 输入参数input_line代表需要预测的文本"""
# 不自动求解梯度
with torch.no_grad():
# 将input_line使用bert模型进行编码
output = _test(get_bert_encode_for_single(input_line))
# 从output中取出最大值对应的索引, 比较的维度是1
_, topi = output.topk(1, 1)
# 返回结果数值
return topi.item()
tensor.topk演示:
>>> tr = torch.randn(1, 2)
>>> tr
tensor([[-0.1808, -1.4170]])
>>> tr.topk(1, 1)
torch.return_types.topk(values=tensor([[-0.1808]]), indices=tensor([[0]]))
input_line = "点瘀样尖针性发多"
result = predict(input_line)
print("result:", result)
result: 0
def batch_predict(input_path, output_path):
"""批量预测函数, 以原始文本(待识别的命名实体组成的文件)输入路径
和预测过滤后(去除掉非命名实体的文件)的输出路径为参数"""
# 待识别的命名实体组成的文件是以疾病名称为csv文件名,
# 文件中的每一行是该疾病对应的症状命名实体
# 读取路径下的每一个csv文件名, 装入csv列表之中
csv_list = os.listdir(input_path)
# 遍历每一个csv文件
for csv in csv_list:
# 以读的方式打开每一个csv文件
with open(os.path.join(input_path, csv), "r") as fr:
# 再以写的方式打开输出路径的同名csv文件
with open(os.path.join(output_path, csv), "w") as fw:
# 读取csv文件的每一行
input_line = fr.readline()
# 使用模型进行预测
res = predict(input_line)
# 如果结果为1
if res:
# 说明审核成功, 写入到输出csv中
fw.write(input_line + "\n")
else:
pass
input_path = "/data/doctor_offline/structured/noreview/"
output_path = "/data/doctor_offline/structured/reviewed/"
batch_predict(input_path, output_path)