IBM Watson 作为认知计算的代表,近来受到诸多关注。以 Watson 为核心构建的企业级应用已逐步上线,例如 Watson Engagement Advisor 和 Chef Watson。然而一般开发人员接触到这些企业级应用的机会不多,因此 Watson 团队提供了多个具有代表性的功能模块,同时也部署在 Bluemix 中,作为“服务”为 Bluemix 中的应用提供与认知相关的计算能力。本文将会介绍 Bluemix 中的 Watson 服务,并通过一个简单的 Web 应用程序,描述如何调用这些 Watson 服务实现相关功能,以及相关的注意事项。
使用 IBM id 登录到 Bluemix 后,在 Catalog 中可以找到所有可用的 IBM Watson 服务,如图 1 所示:
图 1. Bluemix 中的 IBM Waston 服务
各个服务的功能描述和应用示例见表 1:
表 1.Bluemix 中 IBM Watson 服务的功能描述与应用举例
服务名称 | 功能描述 | 应用举例 |
---|---|---|
Concept Expansion | 分析输入的文字,给出在其他相似上下文中该文字所代表的含义 | 输入“New York City”,Watson 会给出“The Big Apple”等相近概念 |
Language Identification | 分析输入的文字,给出该文字的语言种类 | 输入“中国”,Watson 会给出“ZH-CN” |
Machine Translation | 分析输入的文字,给出该文字所对应的目标语言的翻译结果 | 输入“我是中国人”,并指定目标语言为英文,则 Watson 会给出“I am Chinese” |
Message Resonance | 分析输入的文字,根据指定的目标受众类型,给出该文字中所包含的单词的接受程度。 | 输入“Big data is popular”,并指定目标受众类型为“Cloud Computing”,则 Watson 会将输入的每个单词的接受度用不同颜色显示出来。 |
Question Answer | 分析输入的问题,根据指定的问题领域,给出多个具有不同可信度的答案 | 输入“How to stop smoking”,并指定问题领域为“Healthcare”,则 Watson 会给出多个关于如何戒烟的方法以及相应的可信度。 |
Relationship Extraction | 分析输入的句子,给出其中每个单词之间的相互关系或是该单词在 Wikipedia 中的具体解释(如果存在) | 输入“Michael Jordan is a basketball player”,则 Watson 会高亮“Michael Jordan”和“player”,认为他们代表同一个人,并会给出 Wikipedia 关于该人物的解释 |
Speech Recognition | 分析输入的语音信息,给出该语音信息所表达的文字信息(并非直接地语音识别) | 输入语音信息“I usually get up at 6:00 in the morning”,则 Watson 会分析该语音并给出文字信息“I get up early every morning, usually at 6:00” |
Travel Ideation label Service | 缺少官方文档说明 | N/A |
User Modeling | 从社交网络中分析抽取相关信息,识别潜在的购买意图或是商品喜好 | 暂未对外开放 |
回页首
Watson 服务在使用之前,需要将其绑定到一个 Bluemix 应用中。绑定之后,可在 Bluemix 的 Web 控制台中看到该服务的 Credential 信息,即调用服务的资源地址、用户名和密码等信息。这些信息默认是存储在 Bluemix 应用中的系统环境变量中,我们也可将这些信息赋值给其他变量,从而使 Watson 服务也可以被 Bluemix 平台以外的其他应用进行调用,见清单 1:
清单 1.使用 Java 获取 Bluemix 中 Watson 服务的 Crendential 信息:
String VCAP_SERVICES = System.getenv("VCAP_SERVICES"); if(VCAP_SERVICES ==null){ //将 Watson 服务的 Credential 信息拷贝至此, VCAP_SERVICES="{ /"Watson QAAPI-0.1/" : [ { /"name/" : /"mt-svc/"...... //此处略去后面具体用户信息 } if (VCAP_SERVICES != null) { try { JSONObject obj = (JSONObject)JSON.parse(VCAP_SERVICES); JSONArray service = obj.getJSONArray("Watson QAAPI-0.1"); // 获取该服务的 Credential 信息 JSONObject catalog = service.getJSONObject(0); // 解析 Credential 信息,并把相关字段赋值给本地成员属性 JSONObject credentials = catalog.getJSONObject("credentials"); endpoint = credentials.getString("uri"); username = credentials.getString("userid"); password = credentials.getString("password"); } catch (Exception e) { e.printStackTrace(); } }
清单 1 展示的是使用 Java 语言来获取并解析 Watson 服务中的“Question and Answer”服务的 Credential 信息。如果该应用是部署在 Bluemix 的 Liberty 容器中,则默认情况下,VCAP_SERVICES 是非空的。而当该应用是在 Bluemix 外部时,我们需要指定 VCAP_SERVICES 的值,如清单 1 的第三行所示。VCAP_SERVICES 中存储的是当前应用所绑定的所有 Service 的 Credential 信息,因此我们可以根据 Service 的标识 ID 取出相应的 Credential 信息。在清单 1 中,Question and Answer 服务的标识 ID 为 Watson QAAPI-0.1。之后,对于 Credential 信息的解析以及赋值,同样适应于其他 Bluemix 服务。在拿到了这些信息之后,便可根据 Bluemix 服务所提供的调用 API,进行各种请求操作。
回页首
为使您能够更方便直观地使用 Watson 服务,本小节会通过一个具体的 Web 应用开发来展示如何使用 Watson 服务实现相应的功能需求。
Web 应用所要实现的功能,是使用 Watson 的 Question and Answer 服务,对用户输入的问题进行回答,然后使用 Watson 的 Machine Translation 服务,将返回的答案由英文翻译成中文。
由于我们要实现的功能较简单,我们可以使用 JSP + Servlet 来实现 Web 前端和后端服务。因此我们可以使用 Eclipse 构建一个 Dynamic Web Project,如图 2:
图 2.Eclipse 中构建 Dynamic Web Project
由于本练习工程主要侧重后端 Watson 服务的使用,所以在前端方面设计较为简单即可。只需要构建两个页面,一个用于输入用户信息(即具体问题),一个用于显示 Watson 的返回信息(即问题的答案)。这两个页面如下图所示:
图 3.Watson 练习工程中的信息输入页面
图 4.Watson 练习工程中的返回信息显示页面
页面 1 的文本输入域可放在一个 Form 中,通过 ask Watson 按钮提交到后端的一个 servlet 进行处理。页面 2 的返回信息可通过在 servlet 中使用 request.setAttribute()方法将 Watson 返回的信息放入其中,然后在页面 2 中调用 request.getAttribute()将其取出并在页面显示。由于页面逻辑较为简单,因此这里不再给出有关页面方面的代码。
在后端,我们需要有一个 servlet 来处理通过前端页面提交过来的请求,还需要有一个服务类来专门处理同 Watson 服务之间的交互,因此,后端的类图如下:
图 5.Watson 练习工程后端类图
QAServlet 比较简单,它所调用的大部分方法都在 WatsonService 类中。WatsonService 负责获取与 Watson 服务相关的各种认证信息,以及与远程 Watson 服务之间的交互。关于认证信息的获取,这里会在 WatsonService 类被创建时来初始化 username 和 password 等,具体方法请参照前面的清单一。
与 Watson 服务之间的交互方面,按照设想我们会使用两个服务,所以每个服务各对应一个方法。askWatson 方法的具体代码如清单二所示:
清单 2.Watson 练习工程后端 askWatson 方法代码
public static String askWatson(String question) throws Exception { CredentialsProvider provider = new BasicCredentialsProvider(); UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password); provider.setCredentials(AuthScope.ANY, credentials); SSLContextBuilder builder = new SSLContextBuilder(); builder.loadTrustMaterial(null, new TrustSelfSignedStrategy()); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( builder.build(),SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); CloseableHttpClient client = HttpClients.custom(). setSSLSocketFactory(sslsf). setDefaultCredentialsProvider(provider). build(); HttpPost httpPost = new HttpPost(requestURI); StringEntity ent = new StringEntity("{/"question/" : {/"questionText/" : /"" + question + "/"}}"); ent.setContentType("application/json"); httpPost.setEntity(ent); httpPost.setHeader("X-SyncTimeOut", "60"); HttpResponse response = client.execute(httpPost); HttpEntity entity = response.getEntity(); String responseString = EntityUtils.toString(entity, "UTF-8"); return responseString; }
askWatson 方法接受一个字符串类型的参数作为方法的输入,然后使用 Apache 的 httpclient 包中的相关类构建了一个 client 对象,包含相关 credential 信息。接下来构造一个 HttpPost 对象,它的 Entity 需要使用 StringEntity,String 的格式需要参考 Watson 服务的 API(关于 Watson 服务的 API,它的文档链接都会放在该服务在 Bluemix 中的图标下面)。然后使用 client 对象来执行 HttpPost 请求,并将返回结果保存到 HttpResponse 对象中,再进行进一步处理,就可得到返回的答案字符串。
WatsonService 类中的另一方法 translateText 会对返回的答案字符串进行翻译。由于 Question and Answer 服务返回的都是英文结果,因此我们可以使用 Watson 的翻译服务将其由英文翻译为中文。
translateText 方法同样接受一个字符串类型的参数作为方法的输入,方法的返回值即是对输入参数的翻译结果。具体代码如清单三所示:
清单 3.Watson 练习工程后端 translateText 方法代码
public String translateText(String text){ //指定字符串的语言类型及所需翻译的目标语言类型 String sid = "mt-enus-zhcn"; String result = ""; try { String post = "rt=text&sid=" + URLEncoder.encode(sid, "UTF-8") + "&txt=" + URLEncoder.encode(text, "UTF-8"); // 准备 Http 连接 HttpURLConnection conn = (HttpURLConnection)new URL(endpoint).openConnection(); conn.setDoInput(true); conn.setDoOutput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Accept", "*/*"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); String auth = username + ":" + password; String encoded = new String(Base64.encodeBase64(auth.getBytes())); conn.setRequestProperty("Authorization", String.format("Basic %s", encoded)); DataOutputStream output = new DataOutputStream(conn.getOutputStream()); //建立连接 conn.connect(); //发送 post 请求 output.writeBytes(post); output.flush(); output.close(); // 读取返回结果 BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); if (conn.getResponseCode()==HttpURLConnection.HTTP_OK) System.out.println("Response OK from: "+conn.getURL().toString()); else System.err.println("Unsuccesful response: "+conn.getResponseCode()+ " from: "+conn.getURL().toString()); String line = ""; StringBuffer buf = new StringBuffer(); while ((line = rdr.readLine()) != null) { buf.append(line); buf.append("/n"); } rdr.close(); return buf.toString(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return result; }
不同于 askWatson 方法,这里对 Http 连接和请求的构建,使用的是 JDK 的 API 而并非 Apache HttpClient,对返回结果的处理也同样使用 JDK 的 API。当然,使用 Apahce HttpClient 也可实现这一功能。代码中需要特别说明的是对 sid 变量的指定。sid 变量声明的是输入字符串的语言类型及所要翻译的目标语言类型。该变量必须被包含在 HttpPost 请求中,否则 Watson 翻译服务会认为请求非法。目前 Bluemix 中的 Watson 翻译服务只开放了有限的几种语言之间的文字翻译,其实用性还有待提高。
WatsonService 类中还有一个私有方法 parseResult,作用是对 Watson 服务所返回的字符串信息进行处理。本练习工程中用到的这两个 Watson 服务所返回的字符串格式是不同的。Watson 翻译服务所返回的字符串格式较为简单,不需要做特殊处理即可直接使用。而 Question and Answer 服务返回的是 JSON 字符串,因此需要使用 parseResult 方法来处理这个 JSON 字符串,将其中有用的域值抽取出来,作为结果返回给调用层。由于对 JSON 字符串处理的代码较为常见,因此这里不再给出 parseResult 方法的具体内容。
对于另一个类 QAServlet 而言,其 doGet 或 doPost 方法可直接调用 WatsonService 中提供的各种方法来对从前端得到的字符串进行处理,其代码如下:
清单 4.Watson 练习工程后端 QAServlet 类的主要代码
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //读取前端页面输入的问题 String question = req.getParameter("question"); WatsonService watsonSvc = new WatsonService(); String answer =null; String translatedAnswer = null; try { //对问题进行回答 answer = watsonSvc.askWatson(question); //对答案进行翻译 translatedAnswer = watsonSvc.translateText(answer); req.setAttribute("answer", translatedAnswer); req.getRequestDispatcher("result.jsp").forward(req,resp); } catch (Exception e) { e.printStackTrace(); } }
QAServlet 会将从前端得到的问题进行回答并翻译,然后放到 request 对象中,并使前端页面跳转到 result.jsp 来对结果进行显示。
以上是对 Watson 练习工程中主要的类及方法的详细说明。通过该练习工程,希望您在实际应用中能注意到,Bluemix 中的 Watson 服务有着不同的 API,在进行调用时,需要首先参考其 API 文档,使用合适的方式进行调用。同时,不同的 Watson 服务所返回的结果格式也不尽相同,有可能需要做进一步处理才能够使用。
Watson 练习工程可被导出成 WAR 包,部署在 Bluemix 外部的应用服务器上直接运行。Bluemix 提供了 Liberty server 来运行 Java Web 程序,因此我们也可以将该 WAR 包部署到 Bluemix 平台中,具体的部署方法及步骤请参考 Bluemix 官方文档 。
回页首
Bluemix 为开发人员提供了一个可快速开发、部署和管理应用的平台,它的开放性使得开发者可以提供自己的服务并发布到平台上,供其他开发人员使用。IBM Watson 团队目前在 Bluemix 平台开发并提供的这 9 种服务,虽然成熟度不高,但这已彰显出一种趋势,即 Bluemix 所提供的平台,是今后认知计算服务发挥作用的舞台之一。开发都通过 Bluemix 使用认知计算服务,可以避免各种环境的重复搭建,更专注于认知计算本身的研究与使用。本文通过一个实际的练习工程的开发,希望起到抛砖引玉的作用,使更多开发人员对 Bluemix 和 Watson 服务产生兴趣,并运用它们开发出更加富有创新性的应用。