使用server版的哈工大LTP进行NLP任务(Java实现)

10 篇文章 0 订阅
哈工大的LTP可以说是开源中评测得分相对高的(比HanLP, jieba,ansj等都要好很多) 本篇文章记录了使用Java语言发送POST请求,请求搭建在服务器端的LTP-server进行NLP 处理。
LTP 的官方文档如下:

https://github.com/HIT-SCIR/ltp/wiki/%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8ltp

这篇文章不是记录的LTP的java 接口调用
LTP的server版本有很多的限制,并不是很完善。但是个人觉得在windows端进行的LTP调用是相对简单的。

1. 启动LTP-Service

具体操作异步官方文档。

这里写图片描述

2. 请求服务

LTP仅仅允许POST方式提交服务,且提交进行处理的句子长度不能超过1024个单词,分词结果不能超过256个(大坑,谁用谁知道)。并且由于POST的原因,导致如果在句子中存在#&这些关键符号的时候,也是一个bug

下面给出一个demo

package model.ner;

import model.bean.LtpRes;
import org.json.JSONException;
import org.json.JSONObject;
import util.IOUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by Macan on 2017/10/13.
 *
 * 在服务端搭建LTP service 服务,使用HTTP 请求进行NER标注, HTTP请求为POST请求
 * 请求参数:
 *  s   输入字符串,在xml选项x为n的时候,代表输入句子;为y时代表输入xml
    x   用以指明是否使用xml
    c   用以指明输入编码方式
    t   用以指明分析目标,t可以为分词(ws),词性标注(pos),命名实体识别(ner),依存句法分析(dp),语义角色标注(srl)或者全部任务(all)

 */
public class LtpServer {

    /**
     * 服务地址
     */
    private final static String baseURL = "http://118.118.118.113:12345/ltp";

    /**
     * NLP task parameters 设置请求中的t参数
     */
    private final static String defaultParameters = "t=ner&x=n";


    /**
     * 发送POST 请求到服务器中请求NER service
     * @param url
     * @param param
     * @return
     */
    public static String sendPost(String url, String param) {
        boolean isBug = false;

        PrintWriter out = null;
        BufferedReader in = null;
        URLConnection conn = null;
        String result = "";

        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);

            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));

            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (IOException e) {
            System.out.println("发送 POST 请求出现异常!"+ e.getLocalizedMessage());
            e.printStackTrace();
            isBug = true;
            System.out.println(param);
        }
        //使用finally块来关闭输出流、输入流
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        if (isBug){
            return null;
        }
        return result;
    }

    /**
     * 使用LTP 进行解析一句话,进行NER Task
     * @param sentence 需要解析的句子
     *                 句子长度应该小于1024个字,256个词
     * @return
     */
    public static List<LtpRes> parser(String sentence){
        //设置请求参数
//        String parameter = "s=" + sentence + "&" + defaultParameters;
        return parser(sentence, defaultParameters);
    }


    /**
     * 解析成List<LtpRes> 格式数据
     * @param sentence
     * @param parameters
     * @return
     */
    public static List<LtpRes> parser(String sentence, String parameters) {
        sentence = sentence.replaceAll("[ ]+", " ");
        if (sentence.equals("")) {
            return null;
        }
        String strResult = "";
        //如果需要处理的句子长度超过了1024个字符,进行切分处理,避免抛出异常,时间上的消耗
        if (sentence.length() > 600){
            for (String tmp : IOUtil.splitWithReg(sentence, "。|!|?|;|;|,|,")) {
                String s = "s=" + tmp + "&" + parameters;
                String rst = sendPost(baseURL, s);
                //如果还是句子过长,继续更细粒度的切分
                if (rst == null){
                    return null;
                }
                strResult += rst;
            }
        }else {
            String p = "s=" + sentence + "&" + parameters;
            //请求处理任务
            strResult = sendPost(baseURL, p);
        }

        Pattern pattern = Pattern.compile("(?=\\{)(.+?)(?<=})");
        if (strResult == null){
            //处理句子过长问题,切分句子成两个较小的子句
            for (String tmp : IOUtil.splitWithReg(sentence, "。|!|?|;|;|,|,")) {
                String s = "s=" + tmp + "&" + parameters;
                String rst = sendPost(baseURL, s);
                //如果还是句子过长,继续更细粒度的切分
                if (rst == null){
                    return null;
                }
                strResult += rst;
            }
        }

        // 可能是網絡請求失敗
        if (strResult == null) {
            return null;
        }

        List<LtpRes> result = new ArrayList<>();
        //将xml结果转化为json
        Matcher matcher = pattern.matcher(strResult);
        while (matcher.find()) {
            JSONObject object = null;
            try {
                object = new JSONObject(matcher.group());
            }catch (JSONException e){
                e.printStackTrace();
                System.out.println(matcher.group());
            }
            result.add(new LtpRes(object.getString("cont"),
                    object.getInt("id"),
                    object.getString("ne"),
                    object.getString("pos")));
        }

        return result;
    }

    /**
     * print
     * @param data
     */
    public static void show(List<LtpRes> data) {
        for (LtpRes ltpRes : data) {
            System.out.println(ltpRes.getTerm() + "\t" +
                        ltpRes.getId() + "\t" +
                        ltpRes.getNer() + "\t" +
                        ltpRes.getPos() + "\t");
        }
    }


    public static void main(String[] args) {
        String data = "然后…… 献给天眼的诗 静旅游 天眼 我们静成没长嘴的稻草人 睡你怀里,体验 静,旅游 (一) “中国天眼”,什么都逃不脱 你的法眼,但你能 看穿善变的女人和多情的男人吗? 不管、不想、不看、不听、也不问 静成没长嘴的稻草人 睡你怀里,体验,探索 揭穿,天神合一的奥秘 (二) 五公里以外的手机,静默在 黑匣子里会不会心慌,会不会 想念,安静在帐篷里的我 反正,离开后,我总是左手拉着右手 伸进口袋里摸 你的影子 (三) 摸黑爬八百级木塑台阶,只为 看一眼你的夜色朦胧 细细弯弯的月里盛的光太浅,黑暗 念着咒语把恐惧变成手电筒 瞭望秒杀天际的虚幻 你和我一前一后,寻 那个叫天眼的那口锅,翻炒 昨日的温柔 溢出豆蔻年华的香 (四) 第一次住帐篷,身边有男人 保护 第二次住帐篷,身边有男人 守护 生长二十年的光阴,压碎 男人的脸,重叠 六年前,父亲摘下了最爱的那朵花 脸色苍白,头发也苍白 帐外上百人与虫叫蛙鸣合奏 交响曲,一浪高过一浪 我在海中安静划船,独享 远离尘嚣的畅快,直到 摇不动桨 天眼的秘密 田花 是谁静立群山之巅 却在谷底闭关,修炼 用天之眼 偷窥未知世界的喜悦 破译天神合一的秘密 赞赏苍穹臂弯的浩淼 是谁拉紧了馈源线 在几百米的高空架起馈源塔 正是这六名壮汉的坚守 才让生命之门打开 从此,平塘的白天黑夜不会孤单 因为全宇宙都在这,聚焦 世界的眼上帝的眼,再大 都比不上500米口径的那口“锅” 炒出一支天外的秘密之歌 光怪陆离,流光溢彩 与天地合拍 与梦想舞蹈 (作者来自花溪区新闻信息中心,系花溪区作协会员)";//        String data = "";
        String data2 = "我爱北京天安门,我爱北京。我爱旅游。";
        String data3 = "记者 施昱凌 周滔 黄浩 不带手机等任何电子设备,体验两天“静旅游”。11月4日下午,经过两个小时的车程,参加此次网络媒体网络名人感知“中国天眼”FAST静旅游体验式集中采访活动的百余名大咖顺利抵达目的地。 从此刻开始,百余网媒网络名人将在静默区进行体验,集体下线20余个小时!他们将在这里回归大自然,用眼拍照,用心思考,用笔写作…… 此时无声胜有声! 真的好想你 亲爱的手机 5日早上,参加此次网络媒体网络名人感知“中国天眼”FAST静旅游体验式集中采访活动的百余名大咖回到现实中。 在享受了一段时间的静默时光之后,网媒网络名人纷纷拿回了自己的手机。 然后…… 献给天眼的诗 静旅游 天眼 我们静成没长嘴的稻草人 睡你怀里,体验 静,旅游 (一) “中国天眼”,什么都逃不脱 你的法眼,但你能 看穿善变的女人和多情的男人吗? 不管、不想、不看、不听、也不问 静成没长嘴的稻草人 睡你怀里,体验,探索 揭穿,天神合一的奥秘 (二) 五公里以外的手机,静默在 黑匣子里会不会心慌,会不会 想念,安静在帐篷里的我 反正,离开后,我总是左手拉着右手 伸进口袋里摸 你的影子 (三) 摸黑爬八百级木塑台阶,只为 看一眼你的夜色朦胧 细细弯弯的月里盛的光太浅,黑暗 念着咒语把恐惧变成手电筒 瞭望秒杀天际的虚幻 你和我一前一后,寻 那个叫天眼的那口锅,翻炒 昨日的温柔 溢出豆蔻年华的香 (四) 第一次住帐篷,身边有男人 保护 第二次住帐篷,身边有男人 守护 生长二十年的光阴,压碎 男人的脸,重叠 六年前,父亲摘下了最爱的那朵花 脸色苍白,头发也苍白 帐外上百人与虫叫蛙鸣合奏 交响曲,一浪高过一浪 我在海中安静划船,独享 远离尘嚣的畅快,直到 摇不动桨 天眼的秘密 田花 是谁静立群山之巅 却在谷底闭关,修炼 用天之眼 偷窥未知世界的喜悦 破译天神合一的秘密 赞赏苍穹臂弯的浩淼 是谁拉紧了馈源线 在几百米的高空架起馈源塔 正是这六名壮汉的坚守 才让生命之门打开 从此,平塘的白天黑夜不会孤单 因为全宇宙都在这,聚焦 世界的眼上帝的眼,再大 都比不上500米口径的那口“锅” 炒出一支天外的秘密之歌 光怪陆离,流光溢彩 与天地合拍 与梦想舞蹈 (作者来自花溪区新闻信息中心,系花溪区作协会员)";
        List<LtpRes> res = LtpServer.parser(data3);

        System.out.println(data.length());
        System.out.println(data.toCharArray().length);
        LtpServer.show(res);
    }
}

其中用到的数据结构LtpRes :

package model.bean;

import java.io.Serializable;

/**
 * Created by Macan on 2017/10/13.
 * LTP NER后返回的序列
 */
public class LtpRes implements Serializable {

    private String term;
    private int id;
    private String ner;
    private String pos;

    public LtpRes() {
    }

    public LtpRes(String term, int id, String ner, String pos) {
        this.term = term;
        this.id = id;
        this.ner = ner;
        this.pos = pos;
    }

    @Override
    public String toString() {
        return "{" +
                "term='" + term +
                ", id:" + id +
                ", ner:'" + ner +
                ", pos:'" + pos +
                '}';
    }

    public String getTerm() {
        return term;
    }

    public void setTerm(String term) {
        this.term = term;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getNer() {
        return ner;
    }

    public void setNer(String ner) {
        this.ner = ner;
    }

    public String getPos() {
        return pos;
    }

    public void setPos(String pos) {
        this.pos = pos;
    }
}

这些代码运行是没有问题的,但是放在大规模的任务中进行处理的bug不断,最后还是换成了相对稳定的自己实现的NLP 工具,但是效果就要差一点了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值