Azure Cognitive Search の CustomEntityLookup スキル (Preview) を使って、辞書的に 検索 および Knowledge Graph 用文字列を抽出する - AI エンリッチメント拡張 -

# 背景

`Azure Cognitive Search` には、`Azure Cognitive Services` や REST call 出来る任意の API経由でドキュメント解析処理を追加できます。`AI エンリッチメント` と呼ばれています。

Blob Storage などの元のデータソースを、`Indexer` がクローリングして、転置インデックス と Knowledge Store に格納します。そのクローリングの際にに`Skillset`として、幾つかの処理を `Skill` として呼び出すことが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/eec4aa7a-c165-ce2c-d791-ab5ceb4b75c8.png)

  - この図では、Index で「転置インデックス」のみ表現しています😅

- 良い点😍
    - 対象ドキュメントの定期的なクローリング
    - ドキュメントのパーサー : 意味をもった文章として処理ができる
    - 高い生産性
        - Cognitive Services 利用 - Computer Vision も NLP も
        - REST Call での任意のサービスの組み込み
  
- 今後の改善点😭
    - 開発ツールが揃っていない
    - 独自の開発スタイルなので、学んだスキルが全て他のPlatformで活かせるわけではない。ただ、応用は効く

# Microsoft.Skills.Text.CustomEntityLookupSkill
執筆時点 **Preview** です。公式アナウンスもありません😅
実装が簡単な上に、効果も大変高いので、早期に取り扱ってみます。

カスタム エンティティの参照認知スキル (プレビュー):
https://docs.microsoft.com/ja-jp/azure/search/cognitive-search-skill-custom-entity-lookup

# 前準備

まずは、Azure Cognitive Service の `AIエンリッチメント` 機能を実装します。
以下、ご参考までに。

Azure Search の Cognitive Search 機能を使って、ドキュメントの言語解析のプロトタイプ作成:
https://qiita.com/dahatake/items/2dc3f279340280d2b0cb

Blob に置くファイル数は、**1-2個くらい**にしましょう。うまく動く前に、多くのデータを入れると、処理時間がかかるだけでなく、デバッグも対象範囲が増えるので、大変になります。1つが個人的にはおススメ。動いたら増やしましょう😎

# 手順

ざっくり以下の通り。

- 言語処理パイプライン処理に入出力をする `Skill` を定義し、
- `Index`に出力を受け付ける器を定義し、
- それらのマッピングを行う `Indexer` を定義します。
- ここまで出来れば、クローリングのジョブとして、`Indexer`を実行します。

## 1. Entity ファイルの準備

`CustomEntityLookup` で Entity データを参照するには、以下の2つの方式があります。いずれかを、スキル設定時のパラメーターとして指定をします。

- スキル設定の内部: `inlineEntitiesDefinition`
- ファイル参照: `entitiesDefinitionUri`

ここでは、メンテナンスのしやすさを考慮して、**ファイル参照**を選択します。
ファイルは CSV もしくは JSON のファイル形式で用意します。ファイルの文字コードは**UTF-8** で作成してください。

JSON ファイル例:

```json
[
    {"name":"機械学習"},
    {"name":"Azure Machine Learning"},
    {"name":"Deep Learning"},
    {"name":"Tensorflow"},
    {"name":"Hackfest"}
]
```

これを、Blob Storage に置きます。

- `Azure Storage Explorer` を使うのが便利ですね。
    - https://azure.microsoft.com/ja-jp/features/storage-explorer/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/eb9e5f61-e25c-23e3-5cd7-46b66c7343e5.png)

ファイルの `SAS URL` を取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/dc9dff16-95ed-bc97-d239-000e2a1dd5f3.png)

SAS URL例:

```
https://dahatakesearchstore.blob.core.windows.net/dict/CustomEntityLookupSkill_Sample.json?st=2020-02-10T16%3A47%3A53Z&se=2020-02-17T10%3A47%3A00Z&sp=rl&sv=2018-03-28&sr=b&sig=w6FQkA1B6vjoJxsAstbETD99D0ydXfGbHW7evDMMuGA%3D
```
- 注意: 単なるサンプルなので、この文字列は動きません!!!

## 2. Skillset 変更

Azure の Portal 上で、作業をしていきます。

- [Search サービス] - [スキルセット] から
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/0c7e38d3-b403-1ac5-71fc-00d84fed31dc.png)

- [スキルセットの定義 (JSON)] へ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/6fe890eb-8802-9e8b-3041-48bb99c32a49.png)

- [スキル定義テンプレート] の Text 中に [カスタム エンティティの参照スキル] があります。
![custom01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/b90e5b14-e885-d4de-9aa0-4ff51b8afb8c.jpeg)

以下の様なテンプレート文字列が容易されています。

```JSON
{
  "@odata.type": "#Microsoft.Skills.Text.CustomEntityLookupSkill",
  "defaultLanguageCode": "",
  "entitiesDefinitionUri": "",
  "inlineEntitiesDefinition": [
    {
      "name": "",
      "description": "",
      "type": "",
      "subtype": "",
      "id": "",
      "caseSensitive": true,
      "accentSensitive": true,
      "fuzzyEditDistance": 0,
      "defaultCaseSensitive": true,
      "defaultAccentSensitive": true,
      "defaultFuzzyEditDistance": 0,
      "aliases": ""
    }
  ],
  "name": "",
  "description": "",
  "context": "",
  "inputs": [
    {
      "name": "text",
      "source": ""
    }
  ],
  "outputs": [
    {
      "name": "entities",
      "targetName": "entities"
    }
  ]
}
```

以下のように置き換えます。


```JSON

    {
      "@odata.type": "#Microsoft.Skills.Text.CustomEntityLookupSkill",
      "name": "customEntityLookup",
      "description": "",
      "context": "/document/content/sentences/*",
      "defaultLanguageCode": "en",
      "entitiesDefinitionUri": "<<Blob SAS URL>>",
      "inputs": [
        {
          "name": "text",
          "source": "/document/content/sentences/*"
        }
      ],
      "outputs": [
        {
          "name": "entities",
          "targetName": "mlentities"
        }
      ]
    }


```

大事なところだけ。

|   項目   |  内容  |  説明  |
| :--- | :--- | :--- |
| name | 何でもいいです | skillset の中で一意になるようにしてください |
| context | `/document/content/sentences/*` | 最後の`sentences` は、エンリッチメントの追加画面で、選択した **粒度レベル** で変わります。**文章**を選択しましたので、**sentences** になっています。　|
| defaultLanguageCode | en | 残念ながら、日本語対応はしていない...ただ、動くは動く |
| input - source | `/document/content/sentences/*` | 同上 |
| outputs - name | entities | 固定です。出力名は事前定義済み |
| outputs - targetName | 任意 | その後のエンリッチ処理で参照される名称 |


  - [データのインポートウィザード] の [エンリッチメントの追加画面]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/f83a722e-5695-70d3-9c02-ba507f6c8839.png)

  - スキルのパラメーター
  https://docs.microsoft.com/ja-jp/azure/search/cognitive-search-skill-custom-entity-lookup#skill-parameters

  - スキルの出力項目
  https://docs.microsoft.com/ja-jp/azure/search/cognitive-search-skill-custom-entity-lookup#skill-outputs

そして、[スキルセットの定義(JSON)] の中に入れます。
ウィザードで作成したものですと、`#Microsoft.Skills.Util.ShaperSkill`の**前** に入れるのが良いかと思います。

![custom02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/b7356cfa-6710-1aa4-d60c-2e5798b161ac.jpeg)

## 3. Index 変更

同じく Azure Portal から行います。

- [Search サービス] - [インデックス]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/5d3e502d-49b8-28ee-4aa8-db2c031cff4c.png)

- [インデックス定義 (JSON)]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/546da0fe-3985-bae5-fff6-6809cc6e4e08.png)

- 以下の文字列を適時変更して追加します。

```json
    {
      "name": "mlentities",
      "type": "Collection(Edm.String)",
      "facetable": true,
      "filterable": true,
      "retrievable": true,
      "searchable": true,
      "analyzer": "ja.lucene",
      "indexAnalyzer": null,
      "searchAnalyzer": null,
      "synonymMaps": [],
      "fields": []
    }
```

| 項目 | 値 | 説明 |
| :--- | :---| :---|
| name | Indexの中で一意にしてください | Skill の Output と同じにするのがおススメです。一貫性という観点だけですが |
| analyzer | ja.lucene | 言語にあった Analyzer を指定ください |


  - Azure Cognitive Search インデックスの文字列フィールドに言語アナライザーを追加する
      https://docs.microsoft.com/ja-jp/azure/search/index-add-language-analyzer

そして、[インデックス定義(JSON)]の中に入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/7f607017-831c-f236-e3b0-2b4fd83b794c.png)

## 4. Indexer の変更

ここも同じく Azure Portal からです。

- [Search サービス] - [インデクサー]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/1c009c19-34ce-b696-fe90-9e574278f22e.png)

- [インデクサー定義 (JSON)]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/00c793bb-4df1-ca96-4272-0915293b96cc.png)

- 以下の文字列を追加します。

```json
{
    "sourceFieldName": "/document/content/sentences/*/mlentities/*/name",
    "targetFieldName": "mlentities"
}
```

| 項目 | 値 | 説明 |
| :--- | :--- | :--- |
| sourceFieldName | `/document/content/sentences/*/mlentities/*/name` | `*`  が複数アイテムを示しています。`name` は、出力項目に定義されています。 |
| targetFieldName | mlentities | 出力としてのIndex のフィールド名です |

そして、[インデクサー定義(JSON)]の中に入れます。

## 4. Indexer 実行

新規にIndexにフィールドも加えましたので、既存のデータを全てクリアして、再度クローリングします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/68744258-d84e-3b03-f905-b0e25fc3892d.png)

- 一度**リセット** をしてから、**実行** をします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/fa5186d9-1f35-5cbd-5864-974f5a5804cd.png)

## 5. 結果の確認

Azure Portal に組み込まれている **[検索エクスプローラー]** で見ていきましょう。

![custom04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108143/227dc4a2-3132-8e21-54fc-071d2bfa7492.jpeg)

mlentities に結果が抽出できているのが分かります!
これで、検索時に使えますね!

# カスタマイズの検討事項

`Azure Cognitive Services` の `Skillset` の開発は、以下の順序で行うのがおススメです。いずれも `Azure Functions` に載せるのをおススメします。課金、開発生産性 (デバッグも出来る!)、各種ログ出力など何せ便利です。
モデルの開発だけでなく、モデルは**運用**に載せるための、画面・自動化などの方がずっと大事ですので。既存のあるものは使い倒しましょう!

1. 標準の組み込み Skillset 群
    - Vision, NLP, 翻訳, PII検出 などコア機能がある程度揃っている。文書をスキャンして、画像ファイルで管理しているケースも十二分にあり得る
    - Utility としてのテキスト処理群もある
    - Cognitive Services には、帳票しそうな `Form Recognizer` や `LUIS` など、組み込みやすいサービスもある
    - Container 化させ、独自の Azure Functions上で動作も選択肢
2. CustomEntityLookup
    - まずは、単語ベース
    - かなりのシナリオをカバーできる
    - JSON であれば、Type / SubTypeなどメタデータの設定も可能
3. Cognitive Services の各種カスタマイズ関連
    - LUIS | Custom Vision Services | Speech Service
    - Classification | Entity Extraction | 諸々
    - 何せ管理ポータルあり
4. それらでも不足していたら、BERT など Deep Learning
    - NLP on Azure Best Practice
      - https://github.com/microsoft/nlp-recipes 
    - BERT on Azure Machine Learning
      - https://github.com/Microsoft/AzureML-BERT
    -  Computer Vision on Azure Best Practice
      - https://github.com/microsoft/computervision-recipes  

それら全てが `カスタムスキル` という扱いです。
こちらのドキュメントが参考になるでしょう。

Azure コグニティブ検索エンリッチメント パイプラインにカスタム スキルを追加する方法:
https://docs.microsoft.com/ja-jp/azure/search/cognitive-search-custom-skill-interface

# 開発中の Tips

これは、ほぼ必須といっていいです😅
Skillsetの処理途中のデータを見る手段が殆どありません。Indexに `enriched` という名前のフィールドを追加してください。これによって、データの確認ができます。

```json

    {
      "name": "enriched",
      "type": "Edm.String",
      "facetable": false,
      "filterable": false,
      "key": false,
      "retrievable": true,
      "searchable": false,
      "sortable": false,
      "analyzer": null,
      "indexAnalyzer": null,
      "searchAnalyzer": null,
      "synonymMaps": [],
      "fields": []
    }

```

  - エンリッチされたドキュメントの内部を確認する
　　https://docs.microsoft.com/ja-jp/azure/search/cognitive-search-concept-troubleshooting#tip-4-looking-at-enriched-documents-under-the-hood