LUISで文章分類器モデルを作成するまでの手順

Microsoft AzureのCognitive Service [LUIS](https://azure.microsoft.com/ja-jp/services/cognitive-services/language-understanding-intelligent-service/)で文章分類器を作成するまでの手順メモです。
ただAPIのコマンドを確認しただけですので、内容は薄いです。（精度とかチューニングとかは一切触れません。）

LUISのIntentを分類先とみなします。（Entityとか他の機能はとりあえず一切使用しません。）
IBM WatsonのNatural Language Classifier（NLC）的な感じで使うことを想定しています。

LUISにはポータル画面が用意されており、そこからIntentやそれに紐付くUtteranceを画面でペコペコ追加できるけど、いっきにIntentやUtteranceの追加ができなさそうなので、コマンドでAPIを呼んで構築します。
（WatsonのNLCであればポータル画面上でcsvファイルをuploadできますが...）

API Referenceに書いてあることをただするだけです。↓
https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c2f

Documentはこちら↓
https://docs.microsoft.com/ja-jp/azure/cognitive-services/luis/home

## LUISのアカウントを作成
以下の方の手順で作成します。スクショを細かく載せられており非常にわかりやすいです。
https://qiita.com/Yuwji/items/dda88c0c19f2b02e8c26


## パラメータの確認
ポータル画面から「My apps」を開いて、画面右上の「PUBLISH」を選択。
以下のような画面が表示されます。
APIを呼び出すときのパラメータを確認しておきます。

![publish_app.PNG](https://qiita-image-store.s3.amazonaws.com/0/240999/b4f2b6c3-3cd0-1599-954b-54dcf1d2de1f.png)
locationごとにEndpointが異なるので注意

- West US - westus.api.cognitive.microsoft.com
- West Europe - westeurope.api.cognitive.microsoft.com
- Australia East - australiaeast.api.cognitive.microsoft.com

## API
### Intentの確認

```
curl -X GET https://[location].api.cognitive.microsoft.com/luis/api/v2.0/apps/[appID]/versions/[versionID]/intents -H 'ocp-apim-subscription-key: [subscription-key]'
```

以下のようなjsonが返ってくる。最初はデフォルトで登録されているNoneだけ。

```json
[
  {
    "id": "[Intent ID]",
    "name": "None",
    "typeId": 0,
    "readableType": "Intent Classifier"
  }
]
```

### Intentの追加
分類先を追加します。とりあえず今回はデモということで、[気象庁のQ&A](http://www.jma.go.jp/jma/kishou/know/faq/index.html)のカテゴリを分類先として使ってみます。
以下をIntentとしてみる

- 気象
- 地震・津波・火山
- 地球環境・海洋
- 全般・その他

たとえば「気象」Intentを追加するなら、以下のようにPOSTします。

```
curl -X POST https://[location].api.cognitive.microsoft.com/luis/api/v2.0/apps/[appID]/versions/[versionID]/intents -H 'content-type: application/json' -H 'ocp-apim-subscription-key: [subscription-key]' -d '{"name": "気象"}'
```
うまくいけばIntentIDが帰ってきます。

```
"d9c04sg5-gq35-987q-c290-e6a123456789" # ダミーです。
```
残りのIntentも同様に追加して、Intentの確認をすると、

```json
[
  {
    "id": "[Intent ID]",
    "name": "None",
    "typeId": 0,
    "readableType": "Intent Classifier"
  },
  {
    "id": "[Intent ID]",
    "name": "全般・その他",
    "typeId": 0,
    "readableType": "Intent Classifier"
  },
  {
    "id": "[Intent ID]",
    "name": "地球環境・海洋",
    "typeId": 0,
    "readableType": "Intent Classifier"
  },
  {
    "id": "[Intent ID]",
    "name": "地震・津波・火山",
    "typeId": 0,
    "readableType": "Intent Classifier"
  },
  {
    "id": "[Intent ID]",
    "name": "気象",
    "typeId": 0,
    "readableType": "Intent Classifier"
  }
]

```

Intentが追加されたことがわかります。

### Utteranceの追加
LUISでは各Intentに対して最低5つのUtteranceの追加を推奨してそうな感じなので、先ほど追加した各Intentに5つのUtteranceを追加してみます。
まずはとりあえず、「気象」と「地震・津波・火山」に1つずつUtteranceを追加してみます。

```
curl -X POST　https://[location].api.cognitive.microsoft.com/luis/api/v2.0/apps/[appID]/versions/[versionID]/examples -H 'content-type: application/json' -H 'ocp-apim-subscription-key: [subscription-key]' -d '[
	{
		"text": "気象等の特別警報の指標は、今後、変更されることはありますか？",
		"intentName": "気象",
		"entityLabels":[]
	},
	{
		"text": "津波の特別警報の基準を予想される津波の高さが３ｍ超から５ｍ超に変更すべきではないですか？",
		"intentName": "地震・津波・火山",
		"entityLabels":[]
	}
]'
```
結果は以下のようなjsonの配列で返ってきます。`hasError`が`false`ならちゃんと登録できているようです。

```json
[
    {
        "value": {
            "ExampleId": [exID],
            "UtteranceText": "気象等の特別警報の指標は、今後、変更されることはありますか？"
        },
        "hasError": false
    },
    {
        "value": {
            "ExampleId": [exID],
            "UtteranceText": "津波の特別警報の基準を予想される津波の高さが３ｍ超から５ｍ超に変更すべきではないですか？"
        },
        "hasError": false
    }
]
```

こんな感じで全てのIntentに最低5つのUtteranceを追加します。

### Utteranceの確認
```
curl -X GET https://[location].api.cognitive.microsoft.com/luis/api/v2.0/apps/[appID]/versions/[versionID]/examples -H 'content-type: application/json' -H 'ocp-apim-subscription-key: [subscription-key]'
```
結果は以下のように各Utteranceが何のIntentに紐付いてるか、tokenizeされたUtterance、そのUtteranceに対するIntentの予想が確認できます。まだ学習をしていないので、（None以外の）各Intentに対するscoreが-1になっているということでしょう。

```json
[
    {
        "id": [exID],
        "text": "気象等の特別警報の指標は、今後、変更されることはありますか？",
        "intentLabel": "気象",
        "tokenizedText": [
            "気象",
            "等",
            "の",
            "特別",
            "警報",
            "の",
            "指標",
            "は",
            "、",
            "今後",
            "、",
            "変更",
            "さ",
            "れる",
            "こと",
            "は",
            "あり",
            "ます",
            "か",
            "？"
        ],
        "entityLabels": [],
        "intentPredictions": [
            {
                "name": "None",
                "score": 0.7736272
            },
            {
                "name": "全般・その他",
                "score": -1
            },
            {
                "name": "地球環境・海洋",
                "score": -1
            },
            {
                "name": "地震・津波・火山",
                "score": -1
            },
            {
                "name": "気象",
                "score": -1
            }
        ],
        "entityPredictions": []
    },
～以下略～
```

### 学習
以下のコマンドでOK。`body`はないけど`POST`する。

```
curl -X POST https://[location].api.cognitive.microsoft.com/luis/api/v2.0/apps/[appID]/versions/[versionID]/train -H 'content-type: application/json' -H 'ocp-apim-subscription-key: [subscription-key]'
```
結果は以下のような簡単なjsonで返ってきます。これは学習中を意味するっぽいです。

```json
{
    "statusId": 9,
    "status": "Queued"
}
```
学習が完了していれば、以下のように返ってきます。

```json
{
    "statusId": 2,
    "status": "UpToDate"
}
```

学習が完了すれば先ほどのUtteranceの確認でscoreが-1でなく確信度的な値が入っていると思います。

### ApplicationをPublishする
ここまでの手順後に`query`を投げてもまだ各Intentに分類してくれないようで、Publishする必要があります。以下でApplicationをPublishします。versionは必要に応じて変更するのがよいでしょう。

```
curl -X POST https://[location].api.cognitive.microsoft.com/luis/api/v2.0/apps/[appID]/publish -H 'content-type: application/json' -H 'ocp-apim-subscription-key: [subscription-key]' -d '{
   "versionId": "0.1",
   "isStaging": false,
   "region": "westus"
}'
```

以下のようなjsonが返ってきます。

```json
{
    "versionId": "0.1",
    "isStaging": false,
    "endpointUrl": "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/[appID]",
    "region": "westus",
    "assignedEndpointKey": null,
    "endpointRegion": "westus",
    "publishedDateTime": "20xx-xx-xxTxx:xx:xxZ"
}
```
ここまでくれば、LUISを分類器として使えるようになると思います。

## 問い合わせてみる
以下のGET文で問い合わせます。ちょっと注意が必要なのですが、上記でいろいろAPIをつっついていたときと若干URLが違います。
（～/luis/**api**/～ではない。）

```
curl -X GET 'https://[location].api.cognitive.microsoft.com/luis/v2.0/apps/[appID]?verbose=true&q=[query]' -H 'content-type: application/json' -H 'ocp-apim-subscription-key: [subscription-key]'
```
とりあえず、「大地震が発生したらどうしたらいいですか？」と問い合わせてたら、以下のようなjsonが返ってきました。

```json
{
    "query": "大地震が発生したらどうしたらいいですか？",
    "topScoringIntent": {
        "intent": "地震・津波・火山",
        "score": 0.14173308
    },
    "intents": [
        {
            "intent": "地震・津波・火山",
            "score": 0.14173308
        },
        {
            "intent": "地球環境・海洋",
            "score": 0.1367866
        },
        {
            "intent": "全般・その他",
            "score": 0.0536470264
        },
        {
            "intent": "None",
            "score": 0.023606617
        },
        {
            "intent": "気象",
            "score": 0.006278038
        }
    ],
    "entities": []
}
```
精度があれですが、各Intentに追加したUtteranceもテキトーにサンプリングしただけだし、5つしか追加してないので、精度はここでは触れないこととします。

### queryのパラメーター
Documentによると、`query`には以下のパラメーターがあるようです。（今回は`verbose`だけ使用）


|クエリ文字列|Type|値の例|目的|
|:------------|:------------|:------------|:------------|
|verbose|ブール値|true|発話のすべての意図スコアを含めます|
|timezoneOffset|数 (単位は分)|60|datetimeV2 作成済みエンティティのタイム ゾーン オフセットを設定します|
|spellCheck|ブール値|true|発話のスペル訂正 -- bing-spell-check-subscription-key クエリ文字列パラメーターと組み合わせて使用します|
|bing-spell-check-subscription-key|サブスクリプション ID||spellCheck クエリ文字列パラメーターと組み合わせて使用します|
|staging|ブール値|false|ステージング エンドポイントまたは本番環境エンドポイントを選択します|
|log|ブール値|true|クエリと結果をログに追加します|




追加の学習とかIntentやUtteranceの削除とかはまたの機会に。