背景介绍
在平时的NLP任务中,我们经常用到命名实体识别(NER),常用的识别实体类型为人名、地名、组织机构名,但是我们往往也会有识别其它实体的需求,比如时间、品牌名等。在利用算法做实体识别的时候,我们一般采用序列标注算法,这就对标注的文本格式有一定的要求,因此,一个好的序列标注的平台必不可少,将会大大减少我们标注的工作量,有效提升算法的更新迭代速度。
本文将介绍笔者的一个工作:自制的序列标注平台。我们以时间识别为例。比如,在下面的文章中:
按计划,2019年8月10日,荣耀智慧屏将在华为开发者大会上正式亮相,在8月6日,荣耀官微表示该产品的预约量已破十万台,8月7日下午,荣耀总裁赵明又在微博上造势率先打出差异化牌,智慧屏没有开关机广告,并表态以后也不会有,消费者体验至上,营销一波接一波,可谓来势汹汹。
我们需要从该文章中标注出三个时间:2019年8月10日
,8月6日
,8月7日下午
,并形成标注序列。
下面将详细介绍笔者的工作。
序列标注平台
由于开发时间仓促以及笔者能力有限,因此,序列标注平台的功能还没有很完善,希望笔者的工作能抛砖引玉。
项目的结构图如下:
templates中存放静态资源,time_index.html为平台的操作界面,time_output为平台标注完实体后的文件保存路径,time_server.py是用tornado写的服务端路径控制代码,utils.py中是获取某个路径下的txt文件的最大数值的函数。
其中,utils.py的完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import osdef get_max_num (path ): files = os.listdir(path) if files: numbers = list (map (lambda x: int (x.replace('.txt' , '' )), files)) return max (numbers) else : return 0
time_server.py的完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import os.pathimport tornado.httpserverimport tornado.ioloopimport tornado.optionsimport tornado.webfrom tornado.options import define, optionsfrom utils import get_max_num define("port" , default=9005 , help ="run on the given port" , type =int )class QueryHandler (tornado.web.RequestHandler): def get (self ): self.render('time_index.html' , data = ['' , []])class PostHandler (tornado.web.RequestHandler): def post (self ): event = self.get_argument('event' ) times = self.get_arguments('time' ) indices = self.get_arguments('index' ) print (event) print (times) print (indices) tags = ['O' ] * len (event) for time, index in zip (times, indices): index = int (index) tags[index] = 'B-TIME' for i in range (1 , len (time)): tags[index+i] = 'I-TIME' data = [event, tags] self.render('time_index.html' , data=data) dir_path = './time_output' with open ('./%s/%s.txt' % (dir_path, get_max_num(dir_path)+1 ), 'w' , encoding='utf-8' ) as f: for char, tag in zip (event, tags): f.write(char+'\t' +tag+'\n' )def main (): tornado.options.parse_command_line() app = tornado.web.Application( handlers=[(r'/query' , QueryHandler), (r'/result' , PostHandler) ], template_path=os.path.join(os.path.dirname(__file__), "templates" ) ) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start() main()
time_index.html文件如下:
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 <!DOCTYPE html > <html > <head > <meta charset ="utf-8" > <title > 时间抽取标注平台</title > <link rel ="stylesheet" href ="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" > <script src ="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js" > </script > <script src ="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js" > </script > <style > mark { background-color :#00ff90 ; font-weight :bold; } p {text-indent :2em ;} </style > <script > var click_cnt = 0 ; function select_click (i ){ var content = document .getElementById ('event' ).value ; var time = document .getElementById ('time_' +i.toString ()).value ; for (var j=0 ; j<=content.length -time.length ; j++){ if (content.substr (j, time.length ) == time){ var select = document .getElementById ('index_' +i.toString ()); var option = document .createElement ("option" ); option.value = j; option.innerHTML = j; select.appendChild (option); } } } $(document ).ready (function ( ){ $("#add_time" ).click (function ( ){ click_cnt = click_cnt + 1 ; var input_id = new String ('time_' +click_cnt.toString ()); var index_id = new String ('index_' +click_cnt.toString ()); var content = "<input type='text' id=" + input_id + " class='form-control' style='width:306px;' name='time' /> \ <select class='form-control' name='index' id=" + index_id + " style='width:120px;' \ ondblclick='select_click(" +click_cnt.toString ()+")'></select>" ; $(content).appendTo ($("#time_column" )); }); }); </script > </head > <body > <center > <br > <br > <br > <form class ="form-horizontal" role ="form" method ="post" action ="/result" style ="width:600px" > <div class ="form-group" > <label for ="event" class ="col-sm-2 control-label" > 输入语料</label > <div class ="col-sm-10" > <textarea type ="text" class ="form-control" id ="event" style ="width:490px; height:200px" name ="event" > </textarea > </div > </div > <div class ="form-inline" style ="text-align:left;" > <label for ="time_0" class ="col-sm-2 control-label" > 时间</label > <div class ="col-sm-10" id ="time_column" > <input type ="text" class ="form-control" id ="time_0" style ="width:306px;" name ="time" /> <select class ="form-control" id ="index_0" name ="index" style ="width:120px;" ondblclick ="select_click(0)" > </select > </div > </div > <div class ="form-group" > <div class ="col-sm-offset-2 col-sm-10" > <br > <button type ="button" class ="btn btn-default" id ="add_time" > 添加时间</button > <button type ="submit" class ="btn btn-success" > 显示标签</button > <a href ="/query" > <button type ="button" class ="btn btn-danger" > 返回</button > </a > <button type ="reset" class ="btn btn-warning" > 重置</button > </div > </div > </form > <br > <div style ="width:600px" > <p > 原文:{{data[0]}} </p > <table class ="table table-striped" > {% for char, tag in zip(data[0], data[1]) %} <tr > <td > {{char}} </td > <td > {{tag}} </td > </tr > {%end%} </table > </div > </center > </body > </html >
平台使用
运行上述time_server.py后,在浏览器端输入网址: http://localhost:9005/query ,
则会显示如下界面:
在输入语料框
中,我们输入语料:
8月8日是“全民健身日”,推出重磅微视频《我们要赢的,是自己》。
在时间这个输入框中,可以标注语料中的时间,同时双击同一行中的下拉列表,就能显示该标注时间在语料中的起始位置,有时候同样的标注时间会在语料中出现多次,那么我们在下拉列表中选择我们需要的标注的起始位置即可。
点击添加时间
按钮,它会增加一行标注,允许我们在同一份预料中标注多个时间。我们的一个简单的标注例子如下:
点击显示标注
,则会显示我们标注完后形成的序列标注信息,同时将该序列信息保存为txt文件,该txt文件位于time_output目录下。在网页上的序列标注信息如下:
同时,我们也可以查看保存的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 按 O 计 O 划 O , O 2 B - TIME 0 I - TIME 1 I - TIME 9 I - TIME 年 I - TIME 8 I - TIME 月 I - TIME 1 I - TIME 0 I - TIME 日 I - TIME , O 荣 O 耀 O 智 O 慧 O 屏 O 将 O 在 O 华 O 为 O 开 O 发 O 者 O 大 O 会 O 上 O 正 O 式 O 亮 O 相 O , O 在 O 8 B - TIME 月 I - TIME 6 I - TIME 日 I - TIME , O 荣 O 耀 O 官 O 微 O 表 O 示 O 该 O 产 O 品 O ......
总结
本平台仅作为序列标注算法的前期标注工具使用,并不涉及具体的算法。另外,后续该平台也会陆续开放出来,如果大家有好的建议,也可以留言~
本项目已上传只Github, 网址为: https://github.com/percent4/entity_tagging_platform
欢迎关注我的公众号
NLP奇幻之旅 ,原创技术文章第一时间推送。
欢迎关注我的知识星球“自然语言处理奇幻之旅 ”,笔者正在努力构建自己的技术社区。