Watson Conversation を NLP として使う（Java）（自然言語処理）


Watson Conversation には System Entity として日付、時刻、数値、通貨をテキストから抽出する機能があるので、それに特化して利用する。

利用するライブラリは watson Java SDK
https://github.com/watson-developer-cloud/java-sdk

概要

入力："平成30年４月１日です。"
出力：
EntityKeyword [facet=sys-date, begin=0, end=9, lex=2018-04-01, str=平成30年４月１日]
EntityKeyword [facet=sys-number, begin=2, end=4, lex=30, str=30]
EntityKeyword [facet=sys-number, begin=5, end=6, lex=4, str=４]
EntityKeyword [facet=sys-number, begin=7, end=8, lex=1, str=１]


SDK のライブラリを以下のようにWrapして使う。

サービス本体

```java:ConversationNLPService.java

package com.ibm.watson.developer_cloud.conversation;
import java.util.*;
import com.ibm.watson.developer_cloud.conversation.v1.ConversationService;
import com.ibm.watson.developer_cloud.conversation.v1.model.Entity;
import com.ibm.watson.developer_cloud.conversation.v1.model.MessageRequest;
import com.ibm.watson.developer_cloud.conversation.v1.model.MessageResponse;
public class ConversationNLPService {
	String username;
	String password;
	String workspaceid;
	public ConversationNLPService(String username, String password, String workspaceid) {
		this.username = username;
		this.password = password;
		this.workspaceid = workspaceid;
	}
	public ConversationNLPServiceResponse nlp(String input) throws Exception {
	ConversationService service = new ConversationService(ConversationService.VERSION_DATE_2016_09_20);
		service.setUsernameAndPassword(username, password);
		Map<String, Object> context = new HashMap<String, Object>();
		{
			// SET TIMEZONE
			context.put("timezone", TimeZone.getDefault().getID());
		}
		// remove control code
		input = input.replace("\t", " ").replace("\n", " ").replace("\r", " ");
	MessageRequest newMessage = new MessageRequest.Builder() //
				.inputText(input) //
				.context(context) //
				.build();
		MessageResponse response = service.message(workspaceid, newMessage).execute();
		String s = response.getInputText();
		ConversationNLPServiceResponse rsp = new ConversationNLPServiceResponse();
		List<Entity> list = response.getEntities();
		for (Entity e : list) {
			EntityKeyword kwd = new EntityKeyword(e.getEntity(), e.getLocation()[0], e.getLocation()[1], e.getValue(),
					s.substring(e.getLocation()[0], e.getLocation()[1]));
			rsp.addKeyword(kwd);
		}
		return rsp;
	}
}
```


```java:ConversationNLPServiceResponse 

package com.ibm.watson.developer_cloud.conversation;
import java.util.ArrayList;
public class ConversationNLPServiceResponse {
	ArrayList<EntityKeyword> kwds = new ArrayList<>();
	protected void addKeyword(EntityKeyword kwd) {
		for (EntityKeyword kw : kwds) {
			if (kw.getFacet().equals(kwd.getFacet()) && kw.isLongerMach(kwd)) {
				System.err.println("!! " + kwd);
				return;
			}
		}
		this.kwds.add(kwd);
	}
	public ArrayList<EntityKeyword> asList() {
		return kwds;
	}
}

```


```java:EntityKeyword.java

package com.ibm.watson.developer_cloud.conversation;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class EntityKeyword {
	public static String SYS_DATE = "sys-date";
	public static String SYS_NUMBER = "sys-number";
	public static String SYS_CURRENCY = "sys-currency";
	String facet;
	int begin;
	int end;
	String lex;
	String str;
	static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
	public boolean isLongerMach(EntityKeyword kwd) {
		return (this.begin <= kwd.begin) && (kwd.end <= this.end) && ((kwd.end - kwd.begin) < (this.end - this.begin));
	}
	public Date asDate() {
		if (isDate() == false) {
			return null;
		} else {
			try {
				return sdf.parse(this.lex);
			} catch (ParseException e) {
				e.printStackTrace();
				return null;
			}
		}
	}

	public boolean isDate() {
		return facet != null && facet.equals(SYS_DATE);
	}
	public boolean isCurrency() {
		return facet != null && facet.equals(SYS_CURRENCY);
	}
	public boolean isNumber() {
		return facet != null && facet.equals(SYS_NUMBER);
	}
	public EntityKeyword(String facet, int begin, int end, String lex, String str) {
		super();
		this.facet = facet;
		this.begin = begin;
		this.end = end;
		this.lex = lex;
		this.str = str;
	}
	public int getBegin() {
		return begin;
	}
	public int getEnd() {
		return end;
	}
	public String getLex() {
		return lex;
	}
	public String getFacet() {
		return facet;
	}
	public String getStr() {
		return str;
	}
	@Override
	public String toString() {
		return "EntityKeyword [facet=" + facet + ", begin=" + begin + ", end=" + end + ", lex=" + lex + ", str=" + str
				+ "]";
	}
}


```



使い方

```java:ConversationNLPServiceMain.java
package com.ibm.watson.developer_cloud.conversation;
import java.util.ArrayList;

public class ConversationNLPServiceMain {
	public static void main(String[] args) throws Exception {
		// PLEASE GET credentials from https://console.bluemix.net/home/
	
		// Enablement_Conversation
		String username = "xxx";
		String password = "xxx";
		// CONV_NLP
		String workspaceid = "xxx";
	
		ConversationNLPService service = new ConversationNLPService(username, password, workspaceid);
	
		String input = "平成30年４月１日です。";
	
		ConversationNLPServiceResponse response = service.nlp(input);
		ArrayList<EntityKeyword> kwds = response.asList();
	
		for (EntityKeyword kwd : kwds) {
			System.err.println(kwd.toString());
		}
	}
}


```



```text:System.err

EntityKeyword [facet=sys-date, begin=0, end=9, lex=2018-04-01, str=平成30年４月１日]
EntityKeyword [facet=sys-number, begin=2, end=4, lex=30, str=30]
EntityKeyword [facet=sys-number, begin=5, end=6, lex=4, str=４]
EntityKeyword [facet=sys-number, begin=7, end=8, lex=1, str=１]
```


だいたいこんな感じ...

「平成30年４月１日」が「2018-04-01」に正規化されます。
「明日」とか「来週の日曜」でも正規化できます。
ただし、間違いもあるので過度に依存しないようにする必要はあると思います。
このクラスを改造して「結果の中に日付が１つだけの場合の処理」「日付と時刻が連続している場合の扱い」などを追加するとよいと思います。


```text:所感
Watson API に NLP系のAPIもあってよいと思いますが、なぜか提供されていないのでこのようなものを作ってみました。
```