Watson Discovery(フルサポート版)に日本語大量文書を投入

(2019-07-15) いつの間にかライブラリ名から新しくなってしまい、以下の記事が役にたたなくなってしまいました。新しい記事[Jupyter NotebookからWatson Discoveryを操作する(2019-07-15最新版)](https://qiita.com/makaishi2/items/b3bc4289049ad3f43fe1)を書き直したので、今後はこちらを参照して下さい。

(2018-07-01) シドニーのサイトの場合の認証方式追記

# はじめに
[リリースノート](https://console.bluemix.net/docs/services/discovery/release-notes.html#release-notes)に記載のとおり、2018-06-25にWatson Discoveryでようやく日本語のフルサポート(NLUによるアノテーション機能等が追加された)が行われました。
このタイミングで、日本語文書のテストをしてみたく、試すときに使ったJupyterNotebookを連携します。
試しに10000行のデータでやってみましたが、全件問題なくアップロードできました。
(10000行=10000件のデータ量はライトアカウントの場合上限を超えています。自分の環境でこのテストを行う場合、スタンダードライセンスが必要ですのでご注意下さい。)

Jupyternotebookのリンクは[こちら](https://github.com/makaishi2/watson-studio-sample-notebooks/blob/master/Discovery-Jp-Test.ipynb)

# 前提

## Jupyter Notebook環境
Jupyter NotebookはWatson Studioのものを使っています。
Watson Develpoer CloudのライブラリはNotebookから
``!pip install watson-developer-cloud``
としてもいいのですが、Watson StudioのExvironment設定に以下の記述をして対応しました。

```
# Please add conda channels here
channels:
- defaults

# Please add conda packages here
dependencies:

# Please add pip packages here
# To add pip packages, please comment out the next line
- pip:
  - watson-developer-cloud==1.4.0
```

## インスタンスの地域
(2018-07-01)
デフォルトの地域である「シドニー」でインスタンスを作ると、認証の方法が従来から変更になります。
この記事では従来型の「米国南部」ケースと両方のパターンを記載しました。

## 対象データ
以下の例では、こんな形式のCSVファイルを読み込んでアップロードすることを想定しています。
項目名、項目数はコードを変えれば変更可能な作りになっています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/206130/5cf673a4-617d-744c-f953-c4d0ee14756b.png)


# 手順解説

## コレクション作成まで
Level2対応のタイミングでUIから日本語コレクションを作れるのかと思いきや、まだ多少時間がかかるようです。
なので、現時点は従来通りAPI呼出しでコレクション作成まで行う必要があります。


### credentail情報設定
管理コンソールのcredentail情報をコピーします(アイコンクリックで可能)。
サイトにより情報は異なります。

【米国南部】

```py3
# 
credencial = {
  "url": "https://gateway.watsonplatform.net/discovery/api",
  "username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "password": "xxxxxxxxxxxx"
}
```

【シドニー】

```py3
credencial = {
  "apikey": "xxxxxx",
  "iam_apikey_description": "xxxxxx",
  "iam_apikey_name": "xxxxxx",
  "iam_role_crn": "xxxxxx",
  "iam_serviceid_crn": "xxxxxx",
  "url": "https://gateway-syd.watsonplatform.net/discovery/api"
}
```

### ライブラリ読み込み
このステップもサイトにより異なります。
この段階では、単に変数の定義をしているだけで、実際にAPIを発行するのはもう一つ先のステップになるので注意して下さい。

【米国南部】

```py3
import json
from watson_developer_cloud import DiscoveryV1
# In the constructor, letting the SDK manage the IAM token
discovery = DiscoveryV1(version = '2018-03-05',
                        username = credencial['username'],
                       password = credencial['password'])
```

【シドニー】

```py3
import json
from watson_developer_cloud import DiscoveryV1
discovery = DiscoveryV1(version='2018-03-05',
                        iam_api_key=credencial['apikey'],
                        url=credencial['url'])
```


### environment_id取得
ここから先の手順はサイトによらず共通になります。
environment_idがすでにあるときはそのIDを取得、ない場合は新規に作成を行うスクリプトです。

```py3
environments = discovery.list_environments()['environments']
if len(environments) == 1 :
    # Environment作成
    response = discovery.create_environment(name="my_environment")
    environment_id = response['environment_id']
else :
    # 既存env_id取得
    environment_id = environments[1]['environment_id']

print('environment_id: ', environment_id)
```

### collection_id取得
environment_id同様、すでにIDがある場合はその値を取得し、ない場合は新規に作成を行います。

```py3
collections = discovery.list_collections(environment_id = environment_id)['collections']
if len(collections) == 0:
    # Collection作成
    col_name = 'JP_TEST'
    collection = discovery.create_collection(
        environment_id = environment_id, 
        name = col_name, 
        language='ja')
    collection_id = collection['collection_id']
else:
    collection_id = collections[0]['collection_id']

print('collection_id: ', collection_id)
```



必要であれば、このタイミングでエンリッチ設定の変更を行います。
一度コレクションを日本語で作ってしまえば、エンリッチ設定変更はUIから可能です。

## CSVファイル読込み
取り込みたいCSVファイルはあらかじめ、Watson Studioのデータアセットとしてアップロードしておきます。
以下のコードではCSVファイルの先頭行はヘッダ行になっている前提です。

下記のコードは、Watson StudioのJupyter固有のもので、Watson Studioのツールで自動生成したコードに一部手直しをする想定です。
普通にローカルのcsvを読み込むのであれば、普通にpd.read_csv関数呼び出しでOKです。
要は、このセルが終わった段階で、CSVデータがdf_dataに入っていればいいです。

```py3
infile = 'data_xxxxxx.csv'

# このセルは、ツール生成のものを一部手で修正します。

import sys
import types
import pandas as pd
from botocore.client import Config
import ibm_boto3

def __iter__(self): return 0

client_xxxxxx = ibm_boto3.client(service_name='s3',
    ibm_api_key_id='xxxxxx',
    ibm_auth_endpoint="https://iam.ng.bluemix.net/oidc/token",
    config=Config(signature_version='oauth'),
    endpoint_url='https://s3-api.us-geo.objectstorage.service.networklayer.com')

# Key=のパラメータは変数利用に修正しました
body = client_38bc5a04c29444d093d357a963ea4f0d.get_object(Bucket='xxxxxxx',Key=infile)['Body']
# add missing __iter__ method, so pandas accepts body as file-like object
if not hasattr(body, "__iter__"): body.__iter__ = types.MethodType( __iter__, body )

# DataFrame名は df_dataに修正しました
df_data = pd.read_csv(body)
df_data.head()
```

## 読み込んだデータをDiscovey Collectionに書き込み

以下のサンプルコードは、CSVの項目名が``pat_id,pat_name,pat_sumamry,pat_text``となっていて、このうちの``pat_text``に対してエンリッチをかける前提のものです。
要件に応じて適宜修正をかけて下さい。
いきなり全量をアップロードすると、バグがあったとき面倒なので、

* 最初は数行
* うまくいったら100行程度
* これでうまくいったら全量

の3段階でアップロードすることをお勧めします。

21件以上処理中なのに追加文書登録をするとエラーになるようなので、1件登録する度に現在何件処理中か確認して、危なそうな時はタイマーで待ちを入れるロジックを追加しています。

```py3
import os
import time
for i in range(len(df_data)):
    item = df_data.iloc[i,:]
    data = {}
    
    # 以下はCSVの項目名によって修正して下さい
    data['pat_id'] = str(item['pat_id'])
    data['pat_name'] = item['pat_name']
    data['summury'] = item['pat_summury']
    data['text'] = item['pat_text']
    # print(data)
    filename = data['pat_id'] + '.json'
    f = open(filename, 'w')
    json.dump(data, f)
    f.close()
    collection = discovery.get_collection(environment_id, collection_id)
    proc_docs = collection['document_counts']['processing']
    while True:
        if proc_docs < 20:
            break
        print('busy. waiting..')
        time.sleep(10)
        collection = discovery.get_collection(environment_id, collection_id)
        proc_docs = collection['document_counts']['processing']

    # print(json.dumps(collection, indent=2))
    with open(filename) as f:
        add_doc = discovery.add_document(environment_id, collection_id, file = f)
    os.remove(filename)
    # print(json.dumps(add_doc, indent=2))
```

## (おまけ1)データ削除用スクリプト
テストでアップした少量データ削除用のスクリプトです。

```py3
# 文書件数取得
collection = discovery.get_collection(environment_id, collection_id)
doc_count = collection['document_counts']['available']

results = discovery.query(environment_id, collection_id, return_fields='id', count=doc_count)["results"]
ids = [item["id"] for item in results]

for id in ids:
    print('deleting doc: id =' + id)
    discovery.delete_document(environment_id, collection_id, id)
```

## (おまけ2) 類似検索
Discoveryでは類似検索も可能です。手順は[【マメ知識】Watson Discoveryで類似文書検索を行う](https://qiita.com/makaishi2/items/d36de895184a0eca6fbf)に書いておきましたので、参考とされて下さい。