[iOS] [Swift] [Watson] DialogとNatural Language Classifier（自然言語分類）を組み合わせてチャットボットアプリを作ってみた

# 追記（2016.09.10）

[Watson] Dialog Serviceの廃止について
http://qiita.com/y_some/items/de4cdea1b60d36243c0d

# 概要

曖昧な問いかけに対して、映画のタイトル、または、出演者を答えてくれるチャットボットを作ってみました。

<img src="https://qiita-image-store.s3.amazonaws.com/0/122917/557be968-62c1-9d10-717c-0cd5d85c3f45.gif" width="320" alt="WatsonDialog.gif">

[動画はこちら](https://youtu.be/z6vhqKF9_6Q)

長くなりますが、複数の技術要素を寄せ集めた成果ですので、あえて記事を分割せず一気に解説します。

# 技術要素と開発環境

## Watson

- [Dialog](https://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/doc/dialog/)
- [Natural Language Classifier (NLC)](http://www.ibm.com/watson/developercloud/doc/nl-classifier/)

## iOS

- Xcode7.3
- Swift2.2
- Carthage
    - JSQMessagesViewController
    - Alamofire
    - SwiftyJSON
    - SVProgressHUD

## その他

[DBpedia](http://ja.dbpedia.org/)

# シーケンス図

<img width="683" alt="スクリーンショット 2016-07-05 6.49.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/122917/df4966f2-df19-5dbc-824f-237e60105e84.png">
サーバーサイド機能を実装して、iOSはUI部分のみにした方がベターかと思います。

# Watson Dialogについて

ネットに日本語情報が少ないので、私も試行錯誤中です。

Dialogの事始めは以下の記事を参照。
[[Watson] Dialogの利用開始手順と日本語化の設定](http://qiita.com/y_some/items/03858ed87aad15ac7998)

## Dialogシナリオ

[XML Notepad](https://www.microsoft.com/en-us/download/details.aspx?id=7973) などのXMLエディタがあると捗ります。

```xml
<?xml version="1.0" encoding="utf-8"?>
<dialog xsi:noNamespaceSchemaLocation="WatsonDialogDocument_1.1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <flow>
    <folder label="Main">
      <!--対話の入口-->
      <output id="output_main">
        <prompt selectionType="RANDOM">
          <!--itemからランダムに出力-->
          <item>知りたいのは映画作品ですか？それとも出演者ですか？</item>
        </prompt>
        <getUserInput>
          <!--ユーザの入力を受け付ける-->
          <search ref="folder_menu_top">
            <!--folder_menu_topを検索する-->
          </search>
          <default>
            <!--ユーザ入力を解釈できなかった場合の処理-->
            <output>
              <prompt selectionType="RANDOM">
                <item>すみません。違う表現でもう一度おねがいします。</item>
              </prompt>
              <goto ref="##special_DNR_GET_USER_INPUT_NODE_ID"></goto>
            </output>
          </default>
        </getUserInput>
      </output>
      <output id="output_main_end">
        <prompt selectionType="RANDOM">
          <item>お役に立てたら嬉しいです。</item>
        </prompt>
        <action varName="TITLE" operator="SET_TO_BLANK">
          <!--変数にBLANKをセットする-->
        </action>
        <action varName="STARRING" operator="SET_TO_BLANK" />
        <action varName="WORK" operator="SET_TO_BLANK" />
      </output>
    </folder>
    <folder label="Library">
      <!--対話の主要部分-->
      <folder id="folder_menu_top">
        <input>
          <grammar>
            <!--語彙を解釈する-->
            <!-- *: 任意の文字列の場合にヒット -->
            <!-- $: 文字列を含む場合にヒット -->
            <item>$映画作品</item>
          </grammar>
          <output>
            <goto ref="folder_title">
              <!--folder_titleに飛ぶ-->
            </goto>
          </output>
        </input>
        <input>
          <grammar>
            <item>$出演者</item>
          </grammar>
          <output>
            <goto ref="folder_starring" />
          </output>
        </input>
      </folder>
      <folder id="folder_title">
        <output>
          <prompt selectionType="RANDOM">
            <item>どんな映画でしたか？</item>
          </prompt>
          <getUserInput>
            <output>
              <!--変数に値をセットする（他にもいろいろなactionノードの定義の仕方あり）-->
              <action varName="WORK" operator="SET_TO_USER_INPUT"></action>
              <!--{変数}という書き方で変数値を取得できる-->
              <action varName="TITLE" operator="APPEND">{WORK} </action>
              <prompt selectionType="RANDOM">
                <!--%NAME%をiOS側でNLCのClassに置換する-->
                <item>それは%NAME%ではありませんか？</item>
              </prompt>
              <getUserInput>
                <input>
                  <grammar>
                    <item>$はい</item>
                    <item>$ありがとう</item>
                  </grammar>
                  <output>
                    <goto ref="output_main_end" />
                  </output>
                </input>
                <input>
                  <grammar>
                    <item>$いいえ</item>
                    <item>$他の</item>
                  </grammar>
                  <output>
                    <goto ref="folder_title" />
                  </output>
                </input>
              </getUserInput>
            </output>
          </getUserInput>
        </output>
      </folder>
      <folder id="folder_starring">
        <output>
          <prompt selectionType="RANDOM">
            <item>どんな映画に出演していましたか？</item>
          </prompt>
          <getUserInput>
            <output>
              <action varName="WORK" operator="SET_TO_USER_INPUT"></action>
              <action varName="STARRING" operator="APPEND">{WORK} </action>
              <prompt selectionType="RANDOM">
                <item>それは%NAME%ではありませんか？</item>
              </prompt>
              <getUserInput>
                <input>
                  <grammar>
                    <item>$はい</item>
                    <item>$ありがとう</item>
                  </grammar>
                  <output>
                    <goto ref="output_main_end" />
                  </output>
                </input>
                <input>
                  <grammar>
                    <item>$いいえ</item>
                    <item>$他の</item>
                  </grammar>
                  <output>
                    <goto ref="folder_starring" />
                  </output>
                </input>
              </getUserInput>
            </output>
          </getUserInput>
        </output>
      </folder>
    </folder>
    <folder label="Global" />
    <folder label="Concepts">
      <concept>
        <!--同じ語彙を定義-->
        <grammar>
          <item>出演者</item>
          <item>俳優</item>
          <item>女優</item>
          <item>役者</item>
        </grammar>
      </concept>
      <concept>
        <grammar>
          <item>映画作品</item>
          <item>タイトル</item>
          <item>作品</item>
          <item>名前</item>
        </grammar>
      </concept>
      <concept>
        <grammar>
          <item>ありがとう</item>
          <item>どうも</item>
          <item>サンキュー</item>
          <item>さようなら</item>
          <item>じゃあね</item>
          <item>バイバイ</item>
        </grammar>
      </concept>
      <concept>
        <grammar>
          <item>はい</item>
          <item>YES</item>
          <item>OK</item>
          <item>大丈夫</item>
          <item>いいね</item>
        </grammar>
      </concept>
      <concept>
        <grammar>
          <item>いいえ</item>
          <item>NO</item>
          <item>NG</item>
          <item>ダメ</item>
        </grammar>
      </concept>
      <concept>
        <grammar>
          <item>他の</item>
          <item>違う</item>
        </grammar>
      </concept>
    </folder>
  </flow>
  <constants>
    <var_folder name="Home" />
  </constants>
  <variables>
    <var_folder name="Home">
      <!--変数を定義(TEXT以外にも様々なtypeがある)-->
      <var name="TITLE" type="TEXT" />
      <var name="STARRING" type="TEXT" />
      <var name="WORK" type="TEXT" />
    </var_folder>
  </variables>
  <settings>
    <setting name="AUTOLEARN" type="USER">false</setting>
    <setting name="LANGUAGE" type="USER">ja-JP</setting>
    <setting name="RESPONSETIME" type="USER">-2</setting>
    <setting name="MAXAUTOLEARNITEMS" type="USER">4</setting>
    <setting name="NUMAUTOSETRELATED" type="USER">4</setting>
    <setting name="TIMEZONEID" type="USER">Australia/Sydney</setting>
    <setting name="AUTOSETRELATEDNODEID" type="USER">0</setting>
    <setting name="INPUTMASKTYPE" type="USER">0</setting>
    <setting name="CONCEPTMATCHING" type="USER">1</setting>
    <setting name="PARENT_ACCOUNT">en-us-legacy</setting>
    <setting name="PLATFORM_VERSION">10.1</setting>
    <setting name="USE_TRANSLATIONS">2</setting>
    <setting name="USE_SPELLING_CORRECTIONS">2</setting>
    <setting name="USE_STOP_WORDS">2</setting>
    <setting name="USE_CONCEPTS">3</setting>
    <setting name="ENTITIES_SCOPE">3</setting>
    <setting name="DNR_NODE_ID">-15</setting>
    <setting name="MULTISENT">0</setting>
    <setting name="USER_LOGGING">2</setting>
    <setting name="USE_AUTOMATIC_STOPWORDS_DETECTION">0</setting>
  </settings>
  <specialSettings>
    <specialSetting label="DNR Join Statement">
      <variations />
    </specialSetting>
    <specialSetting label="AutoLearn Statement">
      <variations />
    </specialSetting>
  </specialSettings>
</dialog>
```

今回は使っていませんが、変数の値によって分岐をさせるための\<if>ノードなど、他にも様々なノードがあります。
また、\<var>ノードのtypeや、\<action>ノードで指定できる操作も様々な種類があります。

現状は日本語のドキュメントがないので、詳細は[Dialog Service Documentation（英語）](https://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/doc/dialog/layout_layout.shtml#layout_nodes)を参照するしかないと思います。

## APIリクエストパラメータ

公式Documentの[API Reference（英語）](http://www.ibm.com/watson/developercloud/dialog/api/v1/)を参照。

## レスポンスサンプル（API Referenceからの転載）

### Converse（対話）

```json
{
  "response": [
    "Hi this is Watson"
  ],
  "input": "Hi Hello",
  "conversation_id": 11231,
  "confidence": 1,
  "client_id": 4435
}
```

conversation_id は対話を一意にするIDで、初回のレスポンスで返却されます。
以降のリクエスト時にその値をセットします。
client_id は conversation_id の中で発言者を一意にするためのID（らしい）です。

### Get profile variables（変数の取得）

```json
{
  "client_id": 4435,
  "name_values": [
    {
      "name": "topping",
      "value": "cheese"
    }, {
      "name": "size",
      "value": "large"
    }, {
      "name": "size",
      "value": "large"
    }
  ]
}
```

# Watson Natural Language Classifier (NLC) について

私が参考にさせていただいたのは、以下の記事です。
NLCについては他にもネット上にいろいろ情報があると思います。

[自然言語分類器 IBM Watson Natural Language Classifier(前編)](http://service.cresco.co.jp/blog/entry/193)
[自然言語分類器 IBM Watson Natural Language Classifier(後編)](http://service.cresco.co.jp/blog/entry/263)

この記事では、トレーニングデータをcurlコマンドでアップロードするよう記述されていますが、現在はGUIでアップロードできる [beta toolkit](https://natural-language-classifier-toolkit.ng.bluemix.net/885c6605-687a-4677-a7ff-f57ffa6bbfb9/login?region=us-south) が提供されており、Bluemixダッシュボードから利用できます。


だたし、beta toolkit は米国南部リージョンのみで提供されていますので、NLCサービスの登録先リージョンに注意が必要です。
（2016年7月5日時点）

[DialogのシナリオにNLCを統合することもできるらしい](https://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/doc/dialog/layout_layout.shtml#reference_nlc)ですが、今回はそこまで試している余裕がありませんでした。

## トレーニングデータ

Wikipediaからトレーニングデータを作ろうと考えました。
Wikipediaからスクレピイングすることは結構な手間ですが、Wikipediaのデータを二次加工しやすい形で提供してくれる[DBpedia](http://ja.dbpedia.org/)というサービスがあります。

DBpediaでは、クエリを投げて、その結果をHTMLやXML、CSVなどのフォーマットで受け取ることができます。

DBpediaのクエリを実行するフォーム
[http://ja.dbpedia.org/sparql](http://ja.dbpedia.org/sparql)

＜出演者名と出演映画の概要を取得するクエリの例＞

```
select distinct ?starring_name ?abstract where {
      <http://ja.dbpedia.org/resource/映画作品一覧> <http://dbpedia.org/ontology/wikiPageWikiLink> ?movie .
      ?movie <http://dbpedia.org/ontology/abstract> ?abstract .
      ?movie <http://ja.dbpedia.org/property/出演者> ?starring .
      ?starring rdfs:label ?starring_name.
}
```

＜実行結果イメージ＞
<img width="1003" alt="スクリーンショット 2016-07-04 21.16.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/122917/8e049d8f-c544-58c5-260e-aab934e7a562.png">

NLCのトレーニングデータはCSVですので、フォーマットにCSVを指定すれば、ほぼそのまま使えます。
今回のアプリの例で言うと、上記クエリ結果の映画の概要をtext、出演者名をclassとして使用する訳です。
（Watsonに解釈させるためには、Wikipediaの概要部分だけでは少し情報不足でしたが）

DBpediaの使い方についてのもう少し詳しい情報は、こちらの記事を参照すればよろしいかと。
[Wikipedia からスクレイピングして… とか言ってる人におすすめしたい，DBPedia からの情報抽出](http://qiita.com/pika_shi/items/eb56fc205e2d670062ae)
[DBpediaのある主語の述語プロパティwikiPageWikiLinkの目的語となる文字列を取得するためのSPARQLクエリ](http://qiita.com/sanoani/items/4bc7bdfdffe0db1eef36)

## APIリクエストパラメータ

公式Documentの[API Reference（英語）](http://www.ibm.com/watson/developercloud/natural-language-classifier/api/v1/)を参照。

## レスポンスサンプル（API Referenceからの転載）

### Classify

```json
{
  "classifier_id": "10D41B-nlc-1",
  "url": "https://gateway.watsonplatform.net/natural-language-classifier/api/v1/classifiers/10D41B-nlc-1/classify?text=How%20hot%20wil/10D41B-nlc-1",
  "text": "How hot will it be today?",
  "top_class": "temperature",
  "classes": [
    {
      "class_name": "temperature",
      "confidence": 0.9998201258549781
    },
    {
      "class_name": "conditions",
      "confidence": 0.00017987414502176904
    }
  ]
}
```

# iOS

## ライブラリ管理

Carthageを利用しました。
こちらの記事が参考になります。
[Carthage導入](http://qiita.com/kobad/items/dddab651c91b3dee9fbf)

## チャットUI（JSQMessagesViewController）

以下の記事を参考にさせていただきました。（ほぼコピペ）
[Swiftで超簡単にチャットアプリのUIを作る](http://qiita.com/kinopontas/items/a0c8240a922b971b3580)

プロジェクト作成時、テンプレートに"Single View Application"を選択し、ViewControllerのBaseClassにJSQMessagesViewControllerを指定するだけです。（下記コード参照）
Storyboardはデフォルトのままです。

## 通信制御およびJSONパース

AlamofireとSwiftyJSONを使用しました。
こちらの記事がわかりやすいと思います。
[AlamofireとSwiftyJSONでAPIを叩くチュートリアル](http://qiita.com/yutat93/items/1b6dfe34fa8537cf3329)

## プログレスアニメーション

SVProgressHUDを利用しました。
こちらの記事が参考になります。
[[iOS] 超簡単に処理中のUIを出せるSVProgressHUDについて](http://dev.classmethod.jp/smartphone/ios-svprogresshud/)

## App Transport Security (ATS) 問題

どうやらWatson APIはiOS 9以降のATSの要件をクリアしていないようで、APIリクエストでエラーが発生しました。
ATSの概要、エラーおよび回避方法は、以下の記事を参照です。
[[iOS,Swift] App Transport Security(ATS)を解除する方法](http://apploid.jp/ios/swift-ats-remove/)

## アイコン素材

こちらの素材を利用させていただきました。
[ICOOON MONO](http://icooon-mono.com/)

## コード

あくまで自己学習用なのでエラーハンドリングは甘いです。
アーキテクチャデザインもあえて簡素にしています。

```swift:ViewController.swift
import UIKit
import JSQMessages
import Alamofire
import SwiftyJSON
import SVProgressHUD

class ViewController: JSQMessagesViewController {
    
    // MARK: Declaration
    
    var messages: [JSQMessage]?
    var incomingBubble: JSQMessagesBubbleImage!
    var outgoingBubble: JSQMessagesBubbleImage!
    var incomingAvatar: JSQMessagesAvatarImage!
    var outgoingAvatar: JSQMessagesAvatarImage!
    
    // APIリクエスト時に必要となるユニークなDialogID
    let DIALOG_ID = "(実際の値を設定)"
    // WatsonDialogサービスのサービス資格情報
    let DIALOG_USER = "(実際の値を設定)"
    let DIALOG_PASS = "(実際の値を設定)"
    var conversationId: String = ""
    var clientId: String = ""
    
    // Natural Language Classifier(NLC)APIリクエスト時に必要となるユニークなClassifierId
    let CLASSIFIER_ID_TITLE = "(実際の値を設定)"
    let CLASSIFIER_ID_STARRING = "(実際の値を設定)"
    // WatsonNLCサービスのサービス資格情報
    let NLC_USER = "(実際の値を設定)"
    let NLC_PASS = "(実際の値を設定)"
    
    // WatsonのResponse文字列の中で、名前を表示させる固定文字列（この文字列を置換する）
    let REPLACE_STRING = "%NAME%"
    // WatsonDialogの変数名
    let DIALOG_VAR_TITLE = "TITLE"
    let DIALOG_VAR_STARRING = "STARRING"
    
    // MARK: Event
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 自分のsenderId, senderDisokayNameを設定
        self.senderId = "user1"
        self.senderDisplayName = "hoge"
        
        // 吹き出しの設定
        let bubbleFactory = JSQMessagesBubbleImageFactory()
        self.incomingBubble = bubbleFactory.incomingMessagesBubbleImageWithColor(UIColor.jsq_messageBubbleBlueColor())
        self.outgoingBubble = bubbleFactory.outgoingMessagesBubbleImageWithColor(UIColor.jsq_messageBubbleGreenColor())
        
        // アバターの設定
        self.incomingAvatar = JSQMessagesAvatarImageFactory.avatarImageWithImage(UIImage(named: "watson.png")!, diameter: 64)
        self.outgoingAvatar = JSQMessagesAvatarImageFactory.avatarImageWithImage(UIImage(named: "you.png")!, diameter: 64)
        
        // メッセージデータの配列を初期化
        self.messages = []
        
        receiveAutoMessage()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // MARK: Override Method
    
    /// Sendボタンが押された時に呼ばれる
    override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!) {
        
        // 新しいメッセージデータを追加する
        let message = JSQMessage(senderId: senderId, displayName: senderDisplayName, text: text)
        self.messages?.append(message)
        
        // メッセジの送信処理を完了する(画面上にメッセージが表示される)
        self.finishReceivingMessageAnimated(true)
        
        // メッセージを受信
        self.receiveAutoMessage()
        
    }
    
    /// アイテムごとに参照するメッセージデータを返す
    override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
        return self.messages?[indexPath.item]
    }
    
    /// アイテムごとのMessageBubble(背景)を返す
    override func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
        let message = self.messages?[indexPath.item]
        if message?.senderId == self.senderId {
            return self.outgoingBubble
        }
        return self.incomingBubble
    }
    
    /// アイテムごとにアバター画像を返す
    override func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
        let message = self.messages?[indexPath.item]
        if message?.senderId == self.senderId {
            return self.outgoingAvatar
        }
        return self.incomingAvatar
    }
    
    /// アイテムの総数を返す
    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return (self.messages?.count)!
    }
    
    // MARK: Private Method
    
    /// 返信メッセージを受信する
    func receiveAutoMessage() {
        SVProgressHUD.show()
        if self.conversationId == "" {
            postDialog("")
        } else {
            let inputText = self.inputToolbar.contentView.textView.text
            self.inputToolbar.contentView.textView.text = ""
            postDialog(inputText)
        }
    }
    
    /// Watson Dialogと対話を行う
    /// - parameter input: 入力テキスト
    func postDialog(input: String) {
        // conversation_idとは複数クライアントから同じDialogにアクセスしても独立した対話になるように割り振られるID
        // conversation_idは初回のAPIアクセス時に返却され、２回目以降の対話ではconversation_idが必須となる
        let params = [
            "conversation_id": self.conversationId,
            "client_id": self.clientId,
            "input": input,
            ]
        Alamofire.request(.POST, "https://gateway.watsonplatform.net/dialog/api/v1/dialogs/\(self.DIALOG_ID)/conversation", parameters: params)
            .authenticate(user: self.DIALOG_USER, password: self.DIALOG_PASS)
            .responseJSON { response in
                if response.result.error == nil {
                    guard let object = response.result.value else {
                        return
                    }
                    let json = JSON(object)
                    print("\n--Debug postDialog json:")
                    print(json)
                    if self.conversationId == "" {
                        self.conversationId = json["conversation_id"].stringValue
                        self.clientId = json["client_id"].stringValue
                    }
                    let messageText = self.editResponse(json["response"].arrayValue)
                    if messageText.containsString(self.REPLACE_STRING) {
                        // 固定文字列が含まれていたらDialogの変数より絞り込み条件を取得しNLCからclassを取得し文字列置換する
                        self.getDialogVariables(messageText)
                    } else {
                        self.dispResponse(messageText)
                    }
                }
        }
    }
    
    /// Response配列のメッセージを編集する
    /// - parameter responseArray: WatsonのResponse要素（配列）
    /// - returns: 編集後の文字列
    func editResponse(responseArray: Array<SwiftyJSON.JSON>) -> String {
        var messageText: String = ""
        for text in responseArray {
            if messageText != "" {
                messageText += "\n"
            }
            messageText += text.string!
        }
        return messageText
    }
    
    /// Watson Dialogの変数（＝絞り込み条件）を取得する
    /// - parameter messageText: 表示文字列
    func getDialogVariables(messageText: String) {
        Alamofire.request(.GET, "https://gateway.watsonplatform.net/dialog/api/v1/dialogs/\(self.DIALOG_ID)/profile?client_id=\(self.clientId)")
            .authenticate(user: self.DIALOG_USER, password: self.DIALOG_PASS)
            .responseJSON { response in
                print("\n--Debug getDialogVariables response:")
                print(response.result.value)
                if response.result.error == nil {
                    guard let object = response.result.value else {
                        return
                    }
                    let json = JSON(object)
                    print("\n--Debug getDialogVariables json:")
                    print(json)
                    // Dialogの変数名と値を配列化
                    let variables = json["name_values"].arrayValue
                    var classifierId = ""
                    var filterText = ""
                    for varText in variables {
                        guard let varName = varText["name"].string else {
                            return
                        }
                        if varName == self.DIALOG_VAR_TITLE {
                            // 作品名指定の場合
                            classifierId = self.CLASSIFIER_ID_TITLE
                            if varText["value"].stringValue != "" {
                                filterText = varText["value"].stringValue
                                break
                            }
                        }
                        if varName == self.DIALOG_VAR_STARRING {
                            // 出演者名指定の場合
                            classifierId = self.CLASSIFIER_ID_STARRING
                            if varText["value"].stringValue != "" {
                                filterText = varText["value"].stringValue
                                break
                            }
                        }
                    }
                    self.getClassFromNLC(classifierId, messageText: messageText, filterText: filterText)
                }
        }
    }
    
    /// NLCのResponseを取得する
    /// - parameter classifierId: ClassifierID
    /// - parameter messageText: 表示文字列
    /// - parameter filterText: フィルタ文字列
    func getClassFromNLC(classifierId: String, messageText: String, filterText: String) {
        let params = [
            "text": filterText,
            ]
        Alamofire.request(.GET, "https://gateway.watsonplatform.net/natural-language-classifier/api/v1/classifiers/\(classifierId)/classify", parameters: params)
            .authenticate(user: self.NLC_USER, password: self.NLC_PASS)
            .responseJSON { response in
                print("\n--Debug getClassFromNLC response:")
                print(response.result.value)
                if response.result.error == nil {
                    guard let object = response.result.value else {
                        return
                    }
                    let json = JSON(object)
                    print("\n--Debug getClassFromNLC json:")
                    print(json)
                    let classJson = json["classes"].arrayValue
                    var dispText = ""
                    if classJson.count > 0 {
                        for i in 0...classJson.count - 1 {
                            // classes配列は確信度の降順に並んでいる（らしい）のでそのまま使う
                            if i > 2 {
                                // 3件超は長くなりすぎるので
                                break
                            }
                            if dispText != "" {
                                dispText += "または"
                            }
                            dispText += ("「" + classJson[i]["class_name"].stringValue + "」")
                        }
                    }
                    let s = messageText.stringByReplacingOccurrencesOfString(self.REPLACE_STRING, withString: dispText)
                    self.dispResponse(s)
                }
        }
    }
    
    /// Responseをチャットに表示する
    /// - parameter responseText: 表示文字列
    func dispResponse (responseText: String) {
        let message = JSQMessage(senderId: "user2", displayName: "underscore", text: responseText)
        self.messages?.append(message)
        self.finishReceivingMessageAnimated(true)
        SVProgressHUD.dismiss()
    }   
}
```

# Githubリポジトリ

https://github.com/y-some/DialogTest-movie

# まとめ

- 今年日本語化対応が行われたWatsonですが、まだ日本語の解析精度は低いと感じます。今後の精度向上に期待します。
- Dialogの日本語情報が相変わらず少なくて苦戦しました。こちらも公式ドキュメントの日本語化が待たれます。
- 『映画のタイトル、または、出演者を答えてくれる』というのは、題材としてはイマイチかも知れません。（アイデア不足 :sweat_smile: ）NLCの用途というのは自然言語の意図を汲み取ることであって、データベースのような使い方ではないのかも。
- iOS部分の実装は各種ライブラリのおかげでカンタンでした♪