We’ve migrated our documentation to a new site, which means some URLs have changed.
Audience

辞書管理チュートリアル

概要

以下のPiano 製品は辞書を導入することでより簡易な顧客管理を行えます。

  • Piano Insight

    • カスタムタクソノミ辞書

      • ユーザの関心事項の分類

      • ユーザの興味事項の分類

  • Cxense Search

    • クエリに影響する辞書

      • 登録用語補完

      • 同義語の登録

Pianoは、Insight用のタクソノミ関連とSearchのクエリ関連の2種類の辞書をサポートしています。
検索関連の辞書は、キー/値の検索に使用されます。 完全 or 部分的な検索ワードが入力されてキーとして与えられると、同義語もしくは補完された完全なワード(サジェスト)が返されます。 タクソノミー関連の辞書は異なり検索のクエリは利用せず、ウェブページコンテンツ(ユーザ関心を検出)もしくはURLパラメータのリスト(ユーザ興味を検出)を利用します。

  1. インスタンスの生成 - Piano Insightデータストレージが割り当てられ、ユーザが利用するための辞書IDが生成されます。

  2. 用途設定 - この手順はタクソノミ辞書にのみ関連します。 入力項目がウェブページコンテンツ(ユーザ関心を検出)もしくはURLパラメータのリスト(ユーザ興味を検出)を決定します。

  3. コンテンツのアップロード・更新 - ExcelもしくはJSONフォーマットで一番最初の辞書を作成、それ以降の更新を行います。

  4. 用途 - タクソノミ辞書の使用はすべてシステムで管理される為、検索辞書にのみ関連します。辞書のKey-Valueルックアップを行い、返された結果を使用して検索クエリを変更します。

  5. 削除 - コンテンツの削除とインスタンスの削除(directionaryIdが解放されます)

タクソノミ辞書にのみ適用されるステップ2)を除いて、上記の設定は全てお客様ご自身で行うことが出来ます。 ステップ2)はPiano担当者のみ実行できます。 カスタムタクソノミを設定されたい場合は、担当プロフェッショナルサービスまたはsupport@piano.ioにお問い合わせください。

辞書登録フォーマット

ステップ3)の辞書コンテンツはExcelファイルまたはJSONデータ構造で作成し、アップロードができます。
以下では、JSONやExcelと同じ辞書コンテンツを表示しますが、このチュートリアル内に利用する例はJSON形式のみを使用します。

JSON 登録フォーマット 

これは JSONフォーマットの辞書サンプルです。辞書はアルゴリズム設定("global-properties")とデータ("synonyms")で構成されています。 アルゴリズム設定用のテーブルは1つだけですが、データ用のテーブルは複数持つことができます。

{
    "synonyms": {
        "tv": "television",
        "television": "tv",
        "car": "automobile",
        "automobile": "car",
        "ipod": "mp3 player"
    },
    "global-properties": {
        "group-prefix": "sds",
        "mode": "complete",
        "leftmost-longest-match": true,   
        "key-normalization-flags": 4,
        "value-normalization-flags": 23
    }
}

Excel 登録フォーマット

こちらはJSON形式の辞書のExcel版です。 アルゴリズム設定とデータテーブルはExcelでは別々のシートに設定します。

image-20220114-080305.png

データシート名の接頭にある'@@'は、Excelでは必須ですが、JSONではオプションです。

辞書管理手順 - Function by Function

チュートリアルの解説前に、このセクションでは先に説明した5つの辞書に関するステップのそれぞれに使用するAPI関数について説明します。 API機能説明の後、Python 3.xテストスクリプトが用意されています。 さまざまな設定オプションについて学び、試していくうちに、このテストスクリプトを積極的に使用します。

このセクションの例にはcx.pyツールを使用します。

辞書設定作成

API /processing/dictionary/create を用いて辞書のインスタンスを作成します。

python cx.py /processing/dictionary/create '{"siteGroupId":"1234567890123456789", "name":"My dictionary", "description":"Test dictionary"}'

以降ユーザが利用するための辞書IDが生成され、ユーザに返却されます。

辞書用途設定

タクソノミ辞書を利用する場合、その目的を設定する必要があります。 ユーザーの関心やユーザーの興味を検出するために使用されます。 この2つの違いは何が入力として利用されるか、Webページのコンテンツ(ユーザーの興味)or URLクエリのパラメータ(ユーザーの興味)です 。Pianoのエンジニアだけが設定を行えますので、この機能を利用されたい場合には担当プロフェッショナルサービスまたはsupport@piano.ioにお問い合わせください。

辞書ファイル(項目)の登録/更新

次の2つのフォーマットのいずれかを使用して辞書コンテンツをアップロードすることができます - Base64でエンコードされたxlsxファイル or JSON形式。

python cx.py /processing/dictionary/update '{"dictionaryId":dictionaryId, "workbook":"<base 64 encoded xlsx spreadsheet formatted dictionary>"}'
python cx.py /processing/dictionary/update '{"dictionaryId":dictionaryId, "entries":"<dictionary as json formatted string>"}'

※辞書コンテンツのフォーマット要件があるのでPython用の辞書アップロードスクリプトを用意していることが望ましいです。

辞書の利用方法

API /processing/dictionary/searchで辞書検索を行います。 この例では、クエリコンプリション(サジェスト)辞書で"restaur"という用語を検索しています。

python cx.py /processing/dictionary/search '{"dictionaryId":"1234567890123456789", "input":"restaur"}'

辞書の削除方法

辞書の削除は API /processing/dictionary/delete で行います。

python cx.py /processing/dictionary/delete '{"dictionaryId":"1234567890123456789"}'

辞書設定テスト用スクリプト

以下のPython 3.xスクリプトは上記のすべての操作を実行します。 また、次の章で学習する辞書構成をテストするためのフレームワークを提供します。

_username       = '<YOUR USER NAME>'
_secret         = '<YOUR SECRET API KEY>'
_siteGroupId    = '<YOUR SITE GROUP ID>'
_prefix         = '<YOUR SITE GROUP PREFIX>'
 
# プロパティのjson構文とPython構文の両方を許可します。
false = False
true  = True
  
import json, base64, datetime, hmac, hashlib, http.client
  
def cxApi(path, obj):
    date = datetime.datetime.utcnow().isoformat() + "Z"
    signature = hmac.new(_secret.encode('utf-8'), date.encode('utf-8'), digestmod=hashlib.sha256).hexdigest()
    headers = {"X-cXense-Authentication": "username=%s date=%s hmac-sha256-hex=%s" % (_username, date, signature)}
    connection = http.client.HTTPSConnection("api.cxense.com", 443)
    connection.request("POST", path, json.dumps(obj), headers)
    response = connection.getresponse()
    status = response.status
    responseObj = json.loads(response.read().decode('utf-8'))
    connection.close()
    return status, responseObj
  
def errorHandling(status, response, message):
    if status != 200:
        raise Exception("%s (http status = %s, error details: '%s')" % (message, status, response['error']))
 
def createDictionary(siteGroupId, dictionaryName, description=''):
    status, response = cxApi('/processing/dictionary/create', {"siteGroupId":siteGroupId, "name":dictionaryName, "description":description})
    errorHandling(status, response, 'Unable to create dictionary')
    return response['dictionaryId']
 
def getAllDictionaries(siteGroupId):
    status, response = cxApi('/processing/dictionary/read', {"siteGroupId":siteGroupId})
    errorHandling(status, response, 'Unable to obtain dictionaries')
    return response['dictionaries']
 
def deleteDictionary(dictionaryId):
    status, response = cxApi('/processing/dictionary/delete', {"dictionaryId":dictionaryId})
    errorHandling(status, response, 'Unable to delete dictionaries')
 
def deleteAllDictionaries(siteGroupId):
    for dictionaryId in [dict['dictionaryId'] for dict in getAllDictionaries(siteGroupId)]:
        deleteDictionary(dictionaryId)
 
def uploadDictionary(dictionaryId, dictFilePath):
    if dictFilePath.lower().endswith('xlsx'):
        requestObj = {"dictionaryId":dictionaryId, "workbook":base64.b64encode(open(dictFilePath, 'rb').read()).decode('ascii')}
    else:
        requestObj = {"dictionaryId":dictionaryId, "entries":json.loads(open(dictFilePath, 'rb').read().decode('utf-8-sig'))}
    status, response = cxApi('/processing/dictionary/update', requestObj)
    errorHandling(status, response, 'Unable to upload new version of dictionary')
    if 'messages' in response and response['messages']:
        print ('\n'.join(response['messages']))
 
def dictionaryLookup(dictionaryId, input):
    status, response = cxApi('/processing/dictionary/search', {"dictionaryId":dictionaryId, "input":input})
    errorHandling(status, response, "Unable to do dictionary lookup")
    return response
 
def writeTestDictToFile(filepath, data, properties):
    testDict = {
        "myMappings": data,
        "global-properties": properties
    }
    testDict['global-properties']['group-prefix'] = _prefix
    with open(filepath, 'w') as f:
        f.write(json.dumps(testDict, indent=4, sort_keys=True))
 
def runLookupTest(dictionaryId, testData, testProperites, testQuery, expectedResult):
    # Creates file with new dictionary content and updates dictionary entry
    dictFilePath = 'testDict.json'
    writeTestDictToFile(dictFilePath, testData, testProperites)
    uploadDictionary(dictionaryId, dictFilePath)
    print('New dictionary uploaded "%s"' % testData)
    import time
    time.sleep(1)
 
    # ルックアップが期待どおりに機能しているかどうかをテストします。
    lookupValue = dictionaryLookup(dictionaryId, testQuery)
    if len(lookupValue['matches']):
        lookupValue = [match['value'] for match in lookupValue['matches']]
    else:
        lookupValue = None
    expected = ''
    if str(lookupValue) == str(expectedResult):
        result = 'SUCCESS'
    else:
        result = 'FAILURE'
        expected = ', but expected "%s"' % expectedResult
    print('%s: Looking up "%s" gives "%s"%s' % (result, testQuery, lookupValue, expected))
    
def setUpAndRunTest(testData, testProperites, testQuery, expectedResult):
    # すべての辞書を削除し、削除後の確認を行います。
    deleteAllDictionaries(_siteGroupId)
    existingDictionaries = getAllDictionaries(_siteGroupId)
    if len(existingDictionaries):
        raise Exception('Not able to delete these dictionaries: %s' % existingDictionaries)
    # テスト辞書を作成して、テストを実行します。
    dictionaryId = createDictionary(_siteGroupId, 'test dict')
    runLookupTest(dictionaryId, testData, testProperites, testQuery, expectedResult)
     
def main():
 
	# 作成した辞書データはこちら
    data = {
        "one": "first",
        "two": "second"
    }
    properties = {
        "group-prefix": "sds",
        "mode": "complete"
    }
 
	# テストはこちら。
    setUpAndRunTest(data, properties, testQuery="one", expectedResult=['first'])
    setUpAndRunTest(data, properties, testQuery="one of many", expectedResult=None)
    setUpAndRunTest(data, properties, testQuery="twoooooooooo", expectedResult=['second'])
 
main()

上記のmain()関数の最後にリストされている3つのテストを実行した結果が以下の通りです。
意図的に3番目のテストを失敗するように仕掛けていますが失敗時の結果がどのようになるかを確認します。 次以降のセクションで作業を進めながら、このスクリプトを使用して様々な辞書のコンテンツと設定をテストします。

New dictionary uploaded "{'two': 'second', 'one': 'first'}"
Lookup test SUCCEEDED (looking up "one" gives "['first']")
New dictionary uploaded "{'two': 'second', 'one': 'first'}"
Lookup test SUCCEEDED (looking up "one of many" gives "None")
New dictionary uploaded "{'two': 'second', 'one': 'first'}"
Lookup test FAILED (looking up "twoooooooooo" gives "None")

シンプルな辞書構成例 ('Hello World!' )

最も簡単な辞書構成を行います。これは「Hello Word」をマッピングした辞書の例です。

{
    "myMappings": {
        "hi world!": "hello world!"
    },
   "global-properties": {
        "group-prefix": "sds"
    }
}

この辞書には一つだけ検索登録があり、カスタマプリフィックス(この場合は「sds」 - Search Demo Site)以外のglobal-propertiesはありません。

テストスクリプトのmain()関数を辞書とテストで更新出来るようにします。

# 辞書データはこちら
data = {
    "hi world!": "hello world!"
}
properties = {
    "group-prefix": "sds"
}
# テストはこちら。
setUpAndRunTest(data, properties, testQuery="hi world!", expectedResult=['hello world!'])

テストの出力結果です。

New dictionary uploaded "{'hi world!': 'hello world!'}"
SUCCESS: Looking up "hi world!" gives "['hello world!']"

また、ルックアップを実行したときに返される出力を確認します。

print(dictionaryLookup(_dictionaryId, 'hi world'))
 
OUTPUT:
{'matches': [{'value': 'hello world!', 'sheet': 'sds-categories', 'key': 'hi world!'}]}

以降のセクションはPythonのコードを表示はしませんが、辞書とテストを更新してスクリプトを実行していきます。

辞書のプロパティ設定

このセクションでは、global-propertiesにプロパティを1つずつ追加し各設定がどのように影響するかを確認します。ここでは、このチュートリアルやその他多くの設定のリストについて説明します。

トークナイザ文書

トークナイザはデータを正しく処理するためにデータの言語を指定する必要があります。 このために、tokenizer-contextプロパティを設定します。 英語(en)がデフォルト言語です。

{
    "myMappings": {
        "hi world!": "hello world!"
    },
   "global-properties": {
		"group-prefix": "sds",
		"tokenizer-context": "en"
    }
}

このプロパティで使用可能なISO 639-1言語コード一覧はこちらにあります。

モード

おそらく最も重要なプロパティが(マッチング)modeです。 入力文字列と辞書に指定しているキーを照合する方法を定義します。

{
    "myMappings": {
        "Tokyo": "Japanese city",
		"Beijing": "Chinese city"
    },
   "global-properties": {
        "group-prefix": "sds",
		"mode": "complete"
    }
}

設定可能なモードの一覧です(デフォルトは'complete'です)。 一覧についてはこのページで掲載しています。

キー/入力、 入力された文字全てをマッチングに利用します。

  1. complete - 完全一致。入力テキストがキーと完全に一致する必要があります。

    • "Tokyo" の文字列だけ入力された場合、結果"Japanese city"  が返却されます。

    • didyoumean - 所謂「もしかして?」モードです。スペルチェックを行い範囲内の誤字であれば結果を提供します。

      • 現在まだ利用できません。

一致箇所指定モード

  1. head - 前方一致。検索語から始まる単語に一致します。

    • "Tokyo is a big city " と入力されると"Japanese city" が返却されます。

    • overlap - 部分一致。文書中の任意の場所に検索語が含まれる場合に一致します。

      • "I go to Tokyo to every year"  と入力されると"Japanese city" が返却されます

    • tail - 後方一致。検索語で終わる単語に一致します。

      • "I love Tokyo  と入力されると"Japanese city" が返却されます

キーポジションモード

  1. prefix - 入力中のキーからマッチングを行います。クエリコンプリション(サジェスト)に利用します。

    • 「T」「To」「Tok」  「Tokyo」の文字列を補完し、結果の "Japanese city" を返却します。

    • phrase - フレーズ一致。入力した文字の中からマッチングを行います。

      • 「oky」と入力されると、結果の "Japanese city" を返却します。

    • keystroke -CJK(中国語、日本語、韓国語)の入力中キーを元にしたクエリコンプリション(サジェスト)向けのマッチングです。  (「t」「と」「東k」 の3つの異なる入力ワードは日本語入力システムを使って「東京(Tokyo)」 を補完します。)
      マッピングに利用するキーは完全一致 (この場合は"東京") と キーが含まれるカテゴリ/グループです。(例えば"Japanese cities").

      • 入力キーが「t」「と」「東k」は、下記のモード設定ではいずれも"Japanese city"が返されます。 

{
    "myMappings": {
        "東京": "Japanese city",
        "北京市": "Chinese city",
    },
   "global-properties": {
        "group-prefix": "sds",
        "tokenizer-context": "ja",
		"mode": "keystroke"
    }
}

カウント、ユニークカウント、カウントフィールド

前のセクションで説明したmodeプロパティはどのように一致させるかを設定しました。 このセクションでは、返される空でない結果の一致の最小数(ユニークな数と合計数)を決定するプロパティcountとunique-countを見ていきます。

以下では、入力文字列のどこにでも用語を見つけることができるように、一致モードをオーバーラップするように設定しました。さらに、プロパティcountとunique-countを追加しました。 下記の構成で結果を生成する入力文字列の場合、文字列には少なくとも3つの日本の都市が含まれている必要がありますが、少なくとも2つは固有の都市である必要があります。

したがって、入力文字列である「東京 大阪 京都」と「東京 東京 大阪」は結果が返されますが、「東京 東京 東京(東京都のみ)」と「東京 大阪(合計2都市のみ)」は結果が失敗となります。

{
    "myMappings": {
		"Tokyo": "Japanse city",
		"Osaka": "Japanse city",
		"Kyoto": "Japanse city",
    },
   "global-properties": {
		"mode": "overlap",
        "group-prefix": "sds",
		"count": 3,
        "unique-count": 2,
        "count-field": "none"
    }
}

Property explanations:

  • count - キーマッチの最小数が設定値と同じ値(デフォルト値は1)にマッピングされます。

    • 入力文字列「Toyota Toyota Toyota」は最小カウント3以上なので、['car'、 'car'、 'car']が返されます。
      入力文字列「Toyota Toyota」は、カウントが2であるため、何も返されません。

  • unique-count - 一致するキーのユニーク数の最小数が同じ値にマッピングされます(デフォルト値は1)

    • 文字列「toyota ford」は、2つのユニークキーがあるので、['car'、 'car']のリターンを返します。「toyota toyota」 ユニークなキーが1なので何も返されません。

  • count-field - Search関連辞書には「none」、タクソノミ辞書には「value」を使用します(デフォルト値は「none」)

キー正規化フラグ、バリュー正規化フラグ

これまでマッチングには正確に一致するキーを利用して来ました。本項ではキー「toyota」に対して大文字の「Toyota」がマッチングしない事例を取り上げます。key-normalization-flagsを設定する事でこれを回避できます。スペースの除去 ("   Toyota" → "Toyota")、スペースが二つ以上の場合、一つに減らす("Japanses     car" → "Japanses car") 、ユニコードの正規化 (日本の半角と全角正規化を含む)の3パターンに対応します。以下では、9種類の正規化をサポートするためのフラグを示します。

// プロパティに数字を指定する事で対応する正規化の種類を指定できます。
// デフォルト                   =    0        正規化しません
// 大文字小文字の正規化          =    4        Madrid → madrid
// アクセントの正規化            =    8        München → Munchen
// URLをデコードする正規化       =   64        Rio%20de%20Janeiro → Rio de Janeiro
// URL正規化(URLパラメータ削除)  =  128        http://where.no/city.html#Oslo → where.no/city.html
// インド文字正規化              =  256        - → -
// スカンジナビア文字正規化       =  512        Måløy → Maloy
// アラビア文字正規化            = 1024       - → -
// トルコ文字正規化              = 2048        - → -
// カナ文字正規化                = 4096        ロンドン (London) → ろんどん (London)
 
{
    "myMappings": {
		"München": "German city"
    },
    "global-properties": {
        "group-prefix": "sds",
        "key-normalization-flags": 12,      // 大文字小文字+アクセント正規化,
        "value-normalization-flags": 4      // 大文字小文字正規化
    }
}

上記のサンプルでは大文字小文字とアクセントの正規化を行いました。Münichのいくつかのバージョン (Münich, münich, munich, MUNICH, etc) が入力されても結果が返されるようになります。 同様にvalue-normalization-flagsも同じフラグの数値を使用して値を正規化することができます。上記の例では出力結果に大文字小文字正規化を行いました。「Münich」に対して「City」ではなく「city」を返します。

leftmost-longest-match

これまでに紹介したモードではリクエストに対して、返される結果は確実なものだけでした。例えば"mode":"complete" の場合には完全一致のみが有効です。他のモードでは簡単に出来ない事もあります。以下サンプルでは"mode":"overlap"に設定され、York(イギリスの都市:'UK city')とNew York(アメリカの都市:'US city')、2つの都市のマッピングが設定された辞書が表示されています。"I love New York Yankees." の入力に対して返されるのは"US City"であることが明白ですが、エントリーが"Manchester" (イギリスの都市:''UK city') と "Manchester United" (イギリスのサッカークラブ:'UK soccer club')の場合はどうでしょうか。このような場合には、辞書に応じて両方とも返すことをお勧めします。 

{
    "myMappings": {
        "New York": "US city",
        "York": "UK city",
    },
    "global-properties": {
        "group-prefix": "sds",
        "mode": "overlap",
        "leftmost-longest-match": True
    }
}

leftmost--longest–match プロパティを"true"に設定すると辞書は条件に合うキーの長い方を受け入れます。上記ケースでは"false"に設定したので双方が返ります。 (New York → US city, York → UK city). デフォルト値はtrueです。

目的に応じた辞書の設定

これまでのセクションでプロパティ設定や API、スクリプトについて学習しました。
さまざまな辞書を実装した事でこれ以降のセクションで活用可能になりました。いくつかのユースケースでは追加のプロパティを以前に説明した内容に加えて解説します。

タクソノミ用辞書

Piano Insightは標準のタクソノミを有効化したり、カスタムタクソノミをアップロードする機能をサポートしています。 詳細はタクソノミ設定手順で説明しています。 以下は標準的なタクソノミ辞書の構成です。

{
    "location": {
		"Tokyo": "city/Japan",
		"Beijing": "city/China",
        "Mont Blanc": "mountain/France",
        "Nile": "river/Egypt"
    },
	"organization": {
		"FCC": "governmental/US",
		"Save the Children": "humanitarian/UK"
    },
    "global-properties": {
		"group-prefix": "sds",
        "tokenizer-context": "en",
        "mode": "overlap",
        "leftmost-longest-match": true,
        "key-normalization-flags": 4,
        "value-normalization-flags": 4,
        "count": 2,
        "unique-count": 2,
        "count-field": "value",
        "expand-paths": true,
        "annotate-paths": false
    }
}

タクソノミ辞書固有のプロパティ設定:

  • expand-paths - 全てのバリエーションでタクソノミのパスを拡張します。(デフォルト値はfalseです)

    • trueに設定すると入力文字列'Tokyo'に対して  ['city', 'city/Japan']を返し、falseの場合には  ['city/Japan']を返します。

  • annotate-paths - マッピングテーブルの名前をルートパスとして設定します。(デフォルト値はfalseです)

    • trueに設定すると入力文字列'Tokyo'に対して  ['location/city/Japan']を返し、falseの場合には  ['city/Japan']を返します。

同義語辞書

下記は典型的な同義語辞書の登録形式です。

{
    "synonyms": {
		"tv": "television",
        "television": "tv",
        "car": "automobile",
        "automobile": "car",
        "ipod": "mp3 player"
    },
    "global-properties": {
		"group-prefix": "sds",
		"tokenizer-context": "en",
		"leftmost-longest-match": true,    
        "allow-complete-match": true,
        "key-normalization-flags": 4,
        "value-normalization-flags": 12,
        "swap-fields": true,
        "count": 1,
        "unique-count": 1,
        "count-field": "none"
    }
}

クエリコンプリション(サジェスト)辞書

クエリコンプリーション(サジェストは)ホワイトリストとブラックリストの二つの辞書を利用して行います。ホワイトリストはサジェストさせたい言葉を登録し、ブラックリストにはサジェストに出したくない言葉(汚い言葉など)を登録します。ホワイトリストに用いるのに最適なソースは過去にヒットしたクエリ(as マッチングクエリ)つまり検索に用いられた事のある用語です。 マッチングクエリの情報を取得する方法については「クエリ使用情報の取得」を参照してください。長時間に渡り蓄積させた検索クエリ(レポートはUTC時間に基づいて24時間ごとに行われます)を利用するか、前日に新しい用語をすべて追加してから、更新されたバージョンの辞書をアップロードすることもできます。

以下はホワイトリスト辞書の例です。 辞書はユーザに対して現在の部分入力に基づいて完全な検索語を提供します。 例えばユーザが "tok"から始まる文字入力開始した場合、完全な "tokyo"という言葉をサジェストして提示することができます。

{
    "whitelist": {
		"new york": "new york",
        "new delhi": "new delhi",
		"tokyo": "tokyo",
    },
    "global-properties": {
		"group-prefix": "sds",
		"tokenizer-context": "en",
		"mode": "prefix",
        "key-normalization-flags": 4,
        "value-normalization-flags": 4, 
    }
}


Last updated: