Share via


Azure AI Personalizer のマルチスロットの概要

重要

2023 年 9 月 20 日以降は、新しい Personalizer リソースを作成できなくなります。 Personalizer サービスは、2026 年 10 月 1 日に廃止されます。

マルチスロット パーソナル化 (プレビュー) を使用すると、複数のアクション (製品やコンテンツなど) がユーザーに示される Web レイアウト、カルーセル、リストで、コンテンツを対象にすることができます。 Personalizer のマルチスロット API を使用すると、Personalizer 内の AI モデルで、ユーザー インターフェイス内の配置を考慮し、それから学習して、特定の動作をもたらすユーザー コンテキストや製品を学習できます。 たとえば、特定の製品やコンテンツでは、ページのメインの強調表示にするより、サイド バーやフッターにする方が、クリック数が増えることを、Personalizer で学習できます。

このガイドでは、Personalizer のマルチスロット API を使用する方法について説明します。

リファレンス ドキュメント | ライブラリのソース コード | パッケージ (NuGet) | マルチスロットの概念 | サンプル

前提条件

  • Azure サブスクリプション - 無料アカウントを作成します
  • 最新バージョンの .NET Core
  • Azure サブスクリプションを用意できたら、Azure portal で Personalizer リソースを作成し、自分のキーとエンドポイントを取得します。 デプロイされたら、 [リソースに移動] を選択します。
    • アプリケーションを Personalizer API に接続するには、作成したリソースのキーとエンドポイントが必要です。 このクイックスタートで後に示すコードに、自分のキーとエンドポイントを貼り付けます。
    • Free 価格レベル (F0) を使用してサービスを試用し、後から運用環境用の有料レベルにアップグレードすることができます。

設定

Personalizer インスタンスをマルチスロットにアップグレードする

Note

マルチスロット パーソナル化 (プレビュー) は、Personalizer サービスの他の機能に影響します。 この変更を元に戻すことはできません。 マルチスロット パーソナル化を有効にする前に、「マルチスロット パーソナル化 (プレビュー)」を参照してください。

  1. Azure portal の [リソース管理] の [Personalizer リソース] で自動最適化を無効化し、 [モデルと学習設定] ページで自動最適化をオフにして保存します。

Note

マルチスロット パーソナル化は、自動最適化を無効にしない限り機能しません。 マルチスロット パーソナル化の自動最適化は、今後サポートされる予定です。

  1. Azure portal の [リソース管理] の [Personalizer リソース] で Personalizer をマルチスロットに更新し、[モデルと学習設定] ページで、[学習設定のエクスポート] を選択します。 ダウンロードした JSON ファイルの arguments フィールドは、 --cb_explore_adf で始まっています。 これを --ccb_explore_adf に変更し、ファイルを保存します。 CB (Contextual Bandit) と CCB (条件付き Contextual Bandit) は、それぞれ単一スロットと複数スロットのパーソナル化に使用されるアルゴリズムです。 ADF (Action Dependent Features) は、アクションが機能によって表現または識別されることを意味します。

Learning settings before change

Learning settings after change

ポータルの同じタブの [学習設定のインポート] で、最近変更した JSON ファイルを参照して選択し、アップロードします。 これにより、Personalizer インスタンスが "マルチスロット" Personalizer に更新され、マルチスロットの Rank と Reward の呼び出しがサポートされるようになります。

モデルの更新頻度を変更する

Azure portal で、Personalizer リソースの [構成] ページに進み、[モデルの更新頻度] を 30 秒に変更します。 このような短い時間を設定することでモデルは迅速にトレーニングされ、繰り返しのたびに推奨されるアクションが変化する様子を確認できます。

Change model update frequency

報酬の待機時間を変更する

Azure portal の Personalizer リソースの [構成] ページに進み、[報酬の待機時間] を 10 分に変更します。 これにより、レコメンデーションの送信後、そのモデルがそのレコメンデーションからの報酬のフィードバックを受け取る待機時間が決まります。 トレーニングは、報酬の待機時間が経過するまで行われません。

Change reward wait time

新しい C# アプリケーションを作成する

好みのエディターまたは IDE で、新しい .NET Core アプリケーションを作成します。

コンソール ウィンドウ (cmd、PowerShell、Bash など) で、dotnet new コマンドを使用し、personalizer-quickstart という名前で新しいコンソール アプリを作成します。 このコマンドにより、1 つのソース ファイル (Program.cs) を使用する単純な "Hello World" C# プロジェクトが作成されます。

dotnet new console -n personalizer-quickstart

新しく作成されたアプリ フォルダーにディレクトリを変更します。 次を使用してアプリケーションをビルドできます。

dotnet build

ビルドの出力に警告やエラーが含まれないようにする必要があります。

...
Build succeeded.
 0 Warning(s)
 0 Error(s)
...

クライアント ライブラリをインストールする

次のコマンドを使用して、アプリケーション ディレクトリ内に .NET 用 Personalizer クライアント ライブラリをインストールします。

dotnet add package Azure.AI.Personalizer --version 2.0.0-beta.2

プロジェクト ディレクトリから、好みのエディターまたは IDE で Program.cs ファイルを開きます。 ディレクティブを使用して以下を追加します。

using System;
using Azure;
using Azure.AI.Personalizer;
using System.Collections.Generic;
using System.Linq;

オブジェクト モデル

Personalizer クライアントは、自分のキーが含まれている Azure.AzureKeyCredential を使用して Azure に対する認証を行う PersonalizerClient オブジェクトです。

各スロットのコンテンツの最適な項目を 1 つ要求するには、PersonalizerRankMultiSlotOptions オブジェクトを作成し、それを PersonalizerClient.RankMultiSlot に渡します。 RankMultiSlot メソッドは PersonalizerMultiSlotRankResult を返します。

Personalizer にリワード スコアを送信するには、PersonalizerRewardMultiSlotOptions を作成し、それを対応するイベント ID と共に PersonalizerClient.RewardMultiSlot メソッドに渡します。

このクイックスタートでは、報酬スコアは重要ではありません。 実稼働システムでは、何がどの程度まで報酬スコアに影響を及ぼすかを特定するのは複雑なプロセスとなる場合があり、そのプロセスはやがて変更することになる場合もあります。 実際の Personalizer アーキテクチャでは、この設計上の意思決定を主要な意思決定に含めるようにしてください。

コード例

以下のコード スニペットは、.NET 用 Personalizer クライアント ライブラリを使用して次のタスクを実行する方法を示します。

クライアントを認証する

このセクションでは、次の 2 つのことを行います。

  • キーとエンドポイントを指定する
  • Personalizer クライアントを作成する

まず、Program クラスに以下の行を追加します。 必ず Personalizer リソースから取得したキーとエンドポイントを追加してください。

重要

Azure Portal にアクセスします。 「前提条件」セクションで作成した Personalizer リソースが正常にデプロイされた場合、 [次の手順] の下にある [リソースに移動] ボタンをクリックします。 キーとエンドポイントは、リソースの [key and endpoint](キーとエンドポイント) ページの [リソース管理] にあります。

終わったらコードからキーを削除し、公開しないよう注意してください。 運用環境では、資格情報を安全に格納して利用するための方法を用いることを検討してください。 たとえば、Azure Key Vault が考えられます。

private const string ServiceEndpoint  = "https://REPLACE-WITH-YOUR-PERSONALIZER-RESOURCE-NAME.cognitiveservices.azure.com";
private const string ResourceKey = "<REPLACE-WITH-YOUR-PERSONALIZER-KEY>";

次に、ランクと報酬の URL を作成します。

static PersonalizerClient InitializePersonalizerClient(Uri url)
{
    return new PersonalizerClient(url, new AzureKeyCredential(ResourceKey));
}

アクションとして表されるコンテンツの選択肢を取得する

アクションはコンテンツの選択肢を表します。Personalizer を使用して、この中から最適なコンテンツ項目を選択します。 一連のアクションとそのフィーチャーを表す次のメソッドを Program クラスに追加します。

private static IList<PersonalizerRankableAction> GetActions()
{
    IList<PersonalizerRankableAction> actions = new List<PersonalizerRankableAction>
    {
        new PersonalizerRankableAction(
            id: "Red-Polo-Shirt-432",
            features:
            new List<object>() { new { onSale = "true", price = "20", category = "Clothing" } }
        ),

        new PersonalizerRankableAction(
            id: "Tennis-Racket-133",
            features:
            new List<object>() { new { onSale = "false", price = "70", category = "Sports" } }
        ),

        new PersonalizerRankableAction(
            id: "31-Inch-Monitor-771",
            features:
            new List<object>() { new { onSale = "true", price = "200", category = "Electronics" } }
        ),

        new PersonalizerRankableAction(
            id: "XBox-Series X-117",
            features:
            new List<object>() { new { onSale = "false", price = "499", category = "Electronics" } }
        )
    };

    return actions;
}

スロットを取得する

スロットにより、ユーザーが対話するページが構成されます。 Personalizer により、定義されている各スロットに表示するアクションが決定されます。 アクションは、ExcludeActions として示されている特定のスロットから除外できます。 BaselineAction はスロットの既定のアクションであり、Personalizer を使用しない場合に表示されていたものです。

このクイックスタートには、単純なスロット機能があります。 運用システムでは、フィーチャーを決定して評価することが、簡単ではない場合があります。

private static IList<PersonalizerSlotOptions> GetSlots()
{
    IList<PersonalizerSlotOptions> slots = new List<PersonalizerSlotOptions>
    {
        new PersonalizerSlotOptions(
            id: "BigHeroPosition",
            features: new List<object>() { new { size = "large", position = "left" } },
            excludedActions: new List<string>() { "31-Inch-Monitor-771" },
            baselineAction: "Red-Polo-Shirt-432"

        ),

        new PersonalizerSlotOptions(
            id: "SmallSidebar",
            features: new List<object>() { new { size = "small", position = "right" } },
            excludedActions: new List<string>() { "Tennis-Racket-133" },
            baselineAction: "XBox-Series X-117"
        ),
    };

    return slots;
}

コンテキストに対するユーザーの好みを取得する

Program クラスに次のメソッドを追加し、時間帯とユーザーが使用しているデバイスの種類に関するユーザーの入力をコマンド ラインから取得します。 これらのメソッドはコンテキストのフィーチャーとして使用されます。

static string GetTimeOfDayForContext()
{
    string[] timeOfDayFeatures = new string[] { "morning", "afternoon", "evening", "night" };

    Console.WriteLine("\nWhat time of day is it (enter number)? 1. morning 2. afternoon 3. evening 4. night");
    if (!int.TryParse(GetKey(), out int timeIndex) || timeIndex < 1 || timeIndex > timeOfDayFeatures.Length)
    {
        Console.WriteLine("\nEntered value is invalid. Setting feature value to " + timeOfDayFeatures[0] + ".");
        timeIndex = 1;
    }

    return timeOfDayFeatures[timeIndex - 1];
}
static string GetDeviceForContext()
{
    string[] deviceFeatures = new string[] { "mobile", "tablet", "desktop" };

    Console.WriteLine("\nWhat is the device type (enter number)? 1. Mobile 2. Tablet 3. Desktop");
    if (!int.TryParse(GetKey(), out int deviceIndex) || deviceIndex < 1 || deviceIndex > deviceFeatures.Length)
    {
        Console.WriteLine("\nEntered value is invalid. Setting feature value to " + deviceFeatures[0] + ".");
        deviceIndex = 1;
    }

    return deviceFeatures[deviceIndex - 1];
}

どちらのメソッドも、GetKey メソッドを使用して、ユーザーの選択内容をコマンド ラインから読み取ります。

private static string GetKey()
{
    return Console.ReadKey().Key.ToString().Last().ToString().ToUpper();
}
private static IList<object> GetContext(string time, string device)
{
    return new List<object>()
    {
        new { time = time },
        new { device = device }
    };
}

学習ループを作成する

Personalizer の学習ループとは、RankMultiSlot および RewardMultiSlot 呼び出しのサイクルです。 このクイックスタートでは、コンテンツをパーソナライズするための各 Rank 呼び出しの後に Reward 呼び出しを行って、サービスがどの程度適切に実行されたかを Personalizer に伝えます。

次のコードでは、コマンド ラインでユーザーに好みをたずね、その情報を Personalizer に送信して各スロットに最適なアクションを選択し、その選択をリストから選択できるようユーザーに提示した後、その選択にサービスがどの程度寄与したかを示す報酬スコアを Personalizer に送る形でサイクルをループで処理しています。

static void Main(string[] args)
{
    Console.WriteLine($"Welcome to this Personalizer Quickstart!\n" +
    $"This code will help you understand how to use the Personalizer APIs (multislot rank and multislot reward).\n" +
    $"Each iteration represents a user interaction and will demonstrate how context, actions, slots, and rewards work.\n" +
    $"Note: Personalizer AI models learn from a large number of user interactions:\n" +
    $"You won't be able to tell the difference in what Personalizer returns by simulating a few events by hand.\n" +
    $"If you want a sample that focuses on seeing how Personalizer learns, see the Python Notebook sample.");

    int iteration = 1;
    bool runLoop = true;

    IList<PersonalizerRankableAction> actions = GetActions();
    IList<PersonalizerSlotOptions> slots = GetSlots();
    PersonalizerClient client = InitializePersonalizerClient(new Uri(ServiceEndpoint));

    do
    {
        Console.WriteLine("\nIteration: " + iteration++);

        string timeOfDayFeature = GetTimeOfDayForContext();
        string deviceFeature = GetDeviceForContext();

        IList<object> currentContext = GetContext(timeOfDayFeature, deviceFeature);

        string eventId = Guid.NewGuid().ToString();

        var multiSlotRankOptions = new PersonalizerRankMultiSlotOptions(actions, slots, currentContext, eventId);
        PersonalizerMultiSlotRankResult multiSlotRankResult = client.RankMultiSlot(multiSlotRankOptions);

        for (int i = 0; i < multiSlotRankResult.Slots.Count(); ++i)
        {
            string slotId = multiSlotRankResult.Slots[i].SlotId;
            Console.WriteLine($"\nPersonalizer service decided you should display: { multiSlotRankResult.Slots[i].RewardActionId} in slot {slotId}. Is this correct? (y/n)");

            string answer = GetKey();

            if (answer == "Y")
            {
                client.RewardMultiSlot(eventId, slotId, 1f);
                Console.WriteLine("\nGreat! The application will send Personalizer a reward of 1 so it learns from this choice of action for this slot.");
            }
            else if (answer == "N")
            {
                client.RewardMultiSlot(eventId, slotId, 0f);
                Console.WriteLine("\nYou didn't like the recommended item. The application will send Personalizer a reward of 0 for this choice of action for this slot.");
            }
            else
            {
                client.RewardMultiSlot(eventId, slotId, 0f);
                Console.WriteLine("\nEntered choice is invalid. Service assumes that you didn't like the recommended item.");
            }
        }

        Console.WriteLine("\nPress q to break, any other key to continue:");
        runLoop = !(GetKey() == "Q");

    } while (runLoop);
}

rank 呼び出しと reward 呼び出しについて、以降の各セクションでさらに詳しく見ていきましょう。 コード ファイルを実行する前にコンテンツの選択肢を取得し、スロットを取得して、マルチスロットのランクと報酬の要求を送信する、次のメソッドを追加します。

  • GetActions
  • GetSlots
  • GetTimeOfDayForContext
  • GetDeviceForContext
  • GetKey
  • GetContext

最適なアクションを要求する

Rank 要求を実行するために、このプログラムは、ユーザーの好みをたずねてコンテンツの選択肢の Context を作成します。 要求には、応答を受け取るためのコンテキスト、アクション、スロットとそれぞれのフィーチャーおよび一意のイベント ID が含まれます。

このクイックスタートのコンテキストのフィーチャーは、時間帯とユーザーのデバイスという単純なものです。 実稼働システムでは、アクションとフィーチャーを決定し、評価することが、決して簡単ではない場合もあります。

string timeOfDayFeature = GetTimeOfDayForContext();
string deviceFeature = GetDeviceForContext();

IList<object> currentContext = GetContext(timeOfDayFeature, deviceFeature);

string eventId = Guid.NewGuid().ToString();

var multiSlotRankOptions = new PersonalizerRankMultiSlotOptions(actions, slots, currentContext, eventId);
PersonalizerMultiSlotRankResult multiSlotRankResult = client.RankMultiSlot(multiSlotRankOptions);

報酬を送信する

Reward 要求の報酬スコアを取得するめに、このプログラムでは、各スロットのユーザーの選択をコマンド ラインから取得し、選択に数値を割り当てた後、一意のイベント ID、スロット ID、各スロットの数値 (報酬スコア) を Reward API に送信します。 スロットごとに報酬を定義する必要はありません。

このクイックスタートでは、0 または 1 という単純な数値を報酬スコアとして割り当てます。 実際のニーズにもよりますが、実稼働システムでは、いつ何を Reward 呼び出しに送信するかが決して簡単な決定事項ではない場合もあります。

for (int i = 0; i < multiSlotRankResult.Slots.Count(); ++i)
{
    string slotId = multiSlotRankResult.Slots[i].SlotId;
    Console.WriteLine($"\nPersonalizer service decided you should display: { multiSlotRankResult.Slots[i].RewardActionId} in slot {slotId}. Is this correct? (y/n)");

    string answer = GetKey();

    if (answer == "Y")
    {
        client.RewardMultiSlot(eventId, slotId, 1f);
        Console.WriteLine("\nGreat! The application will send Personalizer a reward of 1 so it learns from this choice of action for this slot.");
    }
    else if (answer == "N")
    {
        client.RewardMultiSlot(eventId, slotId, 0f);
        Console.WriteLine("\nYou didn't like the recommended item. The application will send Personalizer a reward of 0 for this choice of action for this slot.");
    }
    else
    {
        client.RewardMultiSlot(eventId, slotId, 0f);
        Console.WriteLine("\nEntered choice is invalid. Service assumes that you didn't like the recommended item.");
    }
}

プログラムの実行

アプリケーション ディレクトリから、dotnet run コマンドを使用してアプリケーションを実行します。

dotnet run

The quickstart program asks a couple of questions to gather user preferences, known as features, then provides the top action.

こちらでこのクイックスタート用のソース コードを入手できます。

リファレンス ドキュメント | マルチスロットの概念 | サンプル

前提条件

  • Azure サブスクリプション - 無料アカウントを作成します
  • Node.js と NPM をインストールします (Node.js v 14.16.0 と NPM 6.14.11 で検証されています)。
  • Azure サブスクリプションを用意できたら、Azure portal で Personalizer リソースを作成し、自分のキーとエンドポイントを取得します。 デプロイされたら、 [リソースに移動] を選択します。
    • アプリケーションを Personalizer API に接続するには、作成したリソースのキーとエンドポイントが必要です。 このクイックスタートで後に示すコードに、自分のキーとエンドポイントを貼り付けます。
    • Free 価格レベル (F0) を使用してサービスを試用し、後から運用環境用の有料レベルにアップグレードすることができます。

設定

Personalizer インスタンスをマルチスロットにアップグレードする

Note

マルチスロット パーソナル化 (プレビュー) は、Personalizer サービスの他の機能に影響します。 この変更を元に戻すことはできません。 マルチスロット パーソナル化を有効にする前に、「マルチスロット パーソナル化 (プレビュー)」を参照してください。

  1. Azure portal の [リソース管理] の [Personalizer リソース] で自動最適化を無効化し、 [モデルと学習設定] ページで自動最適化をオフにして保存します。

Note

マルチスロット パーソナル化は、自動最適化を無効にしない限り機能しません。 マルチスロット パーソナル化の自動最適化は、今後サポートされる予定です。

  1. Azure portal の [リソース管理] の [Personalizer リソース] で Personalizer をマルチスロットに更新し、[モデルと学習設定] ページで、[学習設定のエクスポート] を選択します。 ダウンロードした JSON ファイルの arguments フィールドは、 --cb_explore_adf で始まっています。 これを --ccb_explore_adf に変更し、ファイルを保存します。 CB (Contextual Bandit) と CCB (条件付き Contextual Bandit) は、それぞれ単一スロットと複数スロットのパーソナル化に使用されるアルゴリズムです。 ADF (Action Dependent Features) は、アクションが機能によって表現または識別されることを意味します。

Learning settings before change

Learning settings after change

ポータルの同じタブの [学習設定のインポート] で、最近変更した JSON ファイルを参照して選択し、アップロードします。 これにより、Personalizer インスタンスが "マルチスロット" Personalizer に更新され、マルチスロットの Rank と Reward の呼び出しがサポートされるようになります。

モデルの更新頻度を変更する

Azure portal で、Personalizer リソースの [構成] ページに進み、[モデルの更新頻度] を 30 秒に変更します。 このような短い時間を設定することでモデルは迅速にトレーニングされ、繰り返しのたびに推奨されるアクションが変化する様子を確認できます。

Change model update frequency

報酬の待機時間を変更する

Azure portal の Personalizer リソースの [構成] ページに進み、[報酬の待機時間] を 10 分に変更します。 これにより、レコメンデーションの送信後、そのモデルがそのレコメンデーションからの報酬のフィードバックを受け取る待機時間が決まります。 トレーニングは、報酬の待機時間が経過するまで行われません。

Change reward wait time

新しい Node.js アプリケーションを作成する

コンソール ウィンドウ (cmd、PowerShell、Bash など) で、ご利用のアプリ用に新しいディレクトリを作成し、そこに移動します。

mkdir myapp && cd myapp

npm init -y コマンドを実行して、package.json ファイルを作成します。

npm init -y

任意のエディターまたは IDE で、sample.js という名前の新しい Node.js アプリケーションを作成し、リソースのエンドポイントとサブスクリプション キー用の変数を作成します。

重要

Azure Portal にアクセスします。 「前提条件」セクションで作成した Personalizer リソースが正常にデプロイされた場合、 [次の手順] の下にある [リソースに移動] ボタンをクリックします。 キーとエンドポイントは、リソースの [key and endpoint](キーとエンドポイント) ページの [リソース管理] にあります。

終わったらコードからキーを削除し、公開しないよう注意してください。 運用環境では、資格情報を安全に格納して利用するための方法を用いることを検討してください。 たとえば、Azure Key Vault が考えられます。

const axios = require('axios');
const { v4: uuidv4 } = require('uuid');
const readline = require('readline-sync');
// The endpoint specific to your personalization service instance; 
// e.g. https://<your-resource-name>.cognitiveservices.azure.com
const PersonalizationBaseUrl = '<REPLACE-WITH-YOUR-PERSONALIZER-ENDPOINT>';
// The key specific to your personalization service instance; e.g. "0123456789abcdef0123456789ABCDEF"
const ResourceKey = '<REPLACE-WITH-YOUR-PERSONALIZER-KEY>';

クイックスタート用の NPM パッケージをインストールする

npm install readline-sync uuid axios --save

オブジェクト モデル

各スロットに対してコンテンツの 1 つの最適な項目を要求するには、 [rankRequest] を作成してから、POST 要求を multislot/rank に送信します。 その後、応答は [rankResponse] に解析されます。

報酬スコアを Personalizer に送信するには、 [rewards] を作成してから、POST 要求を multislot/events/{eventId}/reward に送信します。

このクイックスタートでは、報酬スコアの決定は重要ではありません。 実稼働システムでは、何がどの程度まで報酬スコアに影響を及ぼすかを特定するのは複雑なプロセスとなる場合があり、そのプロセスはやがて変更することになる場合もあります。 実際の Personalizer アーキテクチャでは、この設計上の意思決定を主要な意思決定に含めるようにしてください。

コード例

これらのコード スニペットでは、NodeJS に HTTP 要求を送信することによって以下のタスクを実行する方法が示されています。

ベース URL を作成する

このセクションでは、ベース URL を使用してランクと報酬の URL を作成し、リソース キーを使用して要求ヘッダーを作成します。

const MultiSlotRankUrl = PersonalizationBaseUrl.concat('personalizer/v1.1-preview.1/multislot/rank');
const MultiSlotRewardUrlBase = PersonalizationBaseUrl.concat('personalizer/v1.1-preview.1/multislot/events/');
const Headers = {
    'ocp-apim-subscription-key': ResourceKey,
    'Content-Type': 'application/json'
};

アクションとして表されるコンテンツの選択肢を取得する

アクションはコンテンツの選択肢を表します。Personalizer を使用して、この中から最適なコンテンツ項目を選択します。 一連のアクションとそのフィーチャーを表す次のメソッドをスクリプトに追加します。

function getActions() {
    return [
        {
            'id': 'Red-Polo-Shirt-432',
            'features': [
                {
                    'onSale': 'true',
                    'price': 20,
                    'category': 'Clothing'
                }
            ]
        },
        {
            'id': 'Tennis-Racket-133',
            'features': [
                {
                    'onSale': 'false',
                    'price': 70,
                    'category': 'Sports'
                }
            ]
        },
        {
            'id': '31-Inch-Monitor-771',
            'features': [
                {
                    'onSale': 'true',
                    'price': 200,
                    'category': 'Electronics'
                }
            ]
        },
        {
            'id': 'XBox-Series X-117',
            'features': [
                {
                    'onSale': 'false',
                    'price': 499,
                    'category': 'Electronics'
                }
            ]
        }
    ];
}

コンテキストに対するユーザーの好みを取得する

スクリプトに次のメソッドを追加し、時間帯とユーザーが使用しているデバイスの種類に関するユーザーの入力をコマンド ラインから取得します。 これらはコンテキストのフィーチャーとして使用されます。

function getContextFeatures() {
    const timeOfDayFeatures = ['morning', 'afternoon', 'evening', 'night'];
    const deviceFeatures = ['mobile', 'tablet', 'desktop'];

    let answer = readline.question('\nWhat time of day is it (enter number)? 1. morning 2. afternoon 3. evening 4. night\n');
    let selection = parseInt(answer);
    const timeOfDay = selection >= 1 && selection <= 4 ? timeOfDayFeatures[selection - 1] : timeOfDayFeatures[0];

    answer = readline.question('\nWhat type of device is the user on (enter number)? 1. mobile 2. tablet 3. desktop\n');
    selection = parseInt(answer);
    const device = selection >= 1 && selection <= 3 ? deviceFeatures[selection - 1] : deviceFeatures[0];

    console.log('Selected features:\n');
    console.log('Time of day: ' + timeOfDay + '\n');
    console.log('Device: ' + device + '\n');

    return [
        {
            'time': timeOfDay
        },
        {
            'device': device
        }
    ];
}

スロットを取得する

スロットにより、ユーザーが対話するページが構成されます。 Personalizer により、定義されている各スロットに表示するアクションが決定されます。 アクションは、ExcludeActions として示されている特定のスロットから除外できます。 BaselineAction はスロットの既定のアクションであり、Personalizer を使用しない場合に表示されていたものです。

このクイックスタートには、単純なスロット機能があります。 運用システムでは、フィーチャーを決定して評価することが、簡単ではない場合があります。

function getSlots() {
    return [
        {
            'id': 'BigHeroPosition',
            'features': [
                {
                    'size': 'large',
                    'position': 'left',
                }
            ],
            'excludedActions': ['31-Inch-Monitor-771'],
            'baselineAction': 'Red-Polo-Shirt-432'
        },
        {
            'id': 'SmallSidebar',
            'features': [
                {
                    'size': 'small',
                    'position': 'right',
                }
            ],
            'excludedActions': ['Tennis-Racket-133'],
            'baselineAction': 'XBox-Series X-117'
        }
    ];
}

HTTP 要求を行う

マルチスロットの Rank 呼び出しと Reward 呼び出しの場合は、これらの関数を追加して POST 要求を Personalizer のエンドポイントに送信します。

async function sendMultiSlotRank(rankRequest) {
    try {
        let response = await axios.post(MultiSlotRankUrl, rankRequest, { headers: Headers })
        return response.data;
    }
    catch (err) {
        if(err.response)
        {
            throw err.response.data
        }
        console.log(err)
        throw err;
    }
}
async function sendMultiSlotReward(rewardRequest, eventId) {
    try {
        let rewardUrl = MultiSlotRewardUrlBase.concat(eventId, '/reward');
        let response = await axios.post(rewardUrl, rewardRequest, { headers: Headers })
    }
    catch (err) {
        console.log(err);
        throw err;
    }
}

Personalizer の決定に関するフィードバックを取得する

次のメソッドをスクリプトに追加します。 Personalizer によりコマンド ライン プロンプトを使用して各スロットの適切な決定が行われたかどうかを通知します。

function getRewardForSlot() {
    let answer = readline.question('\nIs this correct? (y/n)\n').toUpperCase();
    if (answer === 'Y') {
        console.log('\nGreat! The application will send Personalizer a reward of 1 so it learns from this choice of action for this slot.\n');
        return 1;
    }
    else if (answer === 'N') {
        console.log('\nYou didn\'t like the recommended item.The application will send Personalizer a reward of 0 for this choice of action for this slot.\n');
        return 0;
    }
    console.log('\nEntered choice is invalid. Service assumes that you didn\'t like the recommended item.\n');
    return 0;
}

学習ループを作成する

Personalizer の学習ループとは、Rank 呼び出しと Reward 呼び出しのサイクルです。 このクイックスタートでは、コンテンツをパーソナライズするための各 Rank 呼び出しの後に Reward 呼び出しを行って、サービスがどの程度適切に実行されたかを Personalizer に伝えます。

次のコードでは、コマンド ラインでユーザーに好みをたずね、その情報を Personalizer に送信して各スロットに最適なアクションを選択し、その選択をリストから選択できるようユーザーに提示した後、その選択にサービスがどの程度寄与したかを示す報酬スコアを Personalizer に送る形でサイクルをループで処理しています。

let runLoop = true;

(async () => {
    do {

        let multiSlotRankRequest = {};

        // Generate an ID to associate with the request.
        multiSlotRankRequest.eventId = uuidv4();

        // Get context information from the user.
        multiSlotRankRequest.contextFeatures = getContextFeatures();

        // Get the actions list to choose from personalization with their features.
        multiSlotRankRequest.actions = getActions();

        // Get the list of slots for which Personalizer will pick the best action.
        multiSlotRankRequest.slots = getSlots();

        multiSlotRankRequest.deferActivation = false;

        try {
            //Rank the actions for each slot
            let multiSlotRankResponse = await sendMultiSlotRank(multiSlotRankRequest);
            let multiSlotrewards = {};
            multiSlotrewards.reward = [];
    
            for (let i = 0; i < multiSlotRankResponse.slots.length; i++) {
                console.log('\nPersonalizer service decided you should display: '.concat(multiSlotRankResponse.slots[i].rewardActionId, ' in slot ', multiSlotRankResponse.slots[i].id, '\n'));
    
                let slotReward = {};
                slotReward.slotId = multiSlotRankResponse.slots[i].id;
                // User agrees or disagrees with Personalizer decision for slot
                slotReward.value = getRewardForSlot();
                multiSlotrewards.reward.push(slotReward);
            }
    
            // Send the rewards for the event
            await sendMultiSlotReward(multiSlotrewards, multiSlotRankResponse.eventId);
    
            let answer = readline.question('\nPress q to break, any other key to continue:\n').toUpperCase();
            if (answer === 'Q') {
                runLoop = false;
            }
        }
        catch (err) {
            console.log(err);
            throw err;
        }



    } while (runLoop);
})()

rank 呼び出しと reward 呼び出しについて、以降の各セクションでさらに詳しく見ていきましょう。

次のメソッドを追加します。これにより、コード ファイルを実行する前に、コンテンツの選択肢が取得され、コンテキストのユーザー設定が取得され、スロットが取得され、HTTP 要求が行われて、各スロットの報酬が取得されます。

  • getActions
  • getContextFeatures
  • getSlots
  • sendRank
  • sendReward
  • getRewardForSlot

最適なアクションを要求する

Rank 要求を実行するために、このプログラムは、ユーザーの好みをたずねてコンテンツの選択肢を作成します。 要求本文には、コンテキスト、アクション、スロットと、それぞれの機能が含まれています。 sendMultiSlotRank メソッドは、rankRequest を受け取り、マルチスロットのランク要求を実行します。

このクイックスタートのコンテキストのフィーチャーは、時間帯とユーザーのデバイスという単純なものです。 実稼働システムでは、アクションとフィーチャーを決定し、評価することが、決して簡単ではない場合もあります。

let multiSlotRankRequest = {};

// Generate an ID to associate with the request.
multiSlotRankRequest.eventId = uuidv4();

// Get context information from the user.
multiSlotRankRequest.contextFeatures = getContextFeatures();

// Get the actions list to choose from personalization with their features.
multiSlotRankRequest.actions = getActions();

// Get the list of slots for which Personalizer will pick the best action.
multiSlotRankRequest.slots = getSlots();

multiSlotRankRequest.deferActivation = false;

//Rank the actions for each slot
try {
    let multiSlotRankResponse = await sendMultiSlotRank(multiSlotRankRequest);
}
catch (err) {
    console.log(err);
    throw err;
}

報酬を送信する

Reward 要求用の報酬スコアを取得するために、プログラムでは各スロットのユーザーの選択をコマンド ラインから取得し、選択に数値 (報酬スコア) を割り当てた後、一意のイベント ID、スロット ID、各スロットの報酬スコアを sendMultiSlotReward メソッドに送信します。 スロットごとに報酬を定義する必要はありません。

このクイックスタートでは、0 または 1 という単純な数値を報酬スコアとして割り当てます。 実際のニーズにもよりますが、実稼働システムでは、いつ何を Reward 呼び出しに送信するかが決して簡単な決定事項ではない場合もあります。

let multiSlotrewards = {};
multiSlotrewards.reward = [];

for (i = 0; i < multiSlotRankResponse.slots.length; i++) {
    console.log('\nPersonalizer service decided you should display: '.concat(multiSlotRankResponse.slots[i].rewardActionId, ' in slot ', multiSlotRankResponse.slots[i].id, '\n'));

    let slotReward = {};
    slotReward.slotId = multiSlotRankResponse.slots[i].id;
    // User agrees or disagrees with Personalizer decision for slot
    slotReward.value = getRewardForSlot();
    multiSlotrewards.reward.push(slotReward);
}

// Send the rewards for the event
await sendMultiSlotReward(multiSlotrewards, multiSlotRankResponse.eventId);

プログラムの実行

アプリケーション ディレクトリから Node.js を使用して、アプリケーションを実行します。

node sample.js

The quickstart program asks a couple of questions to gather user preferences, known as features, then provides the top action.

こちらでこのクイックスタート用のソース コードを入手できます。

マルチスロットの概念 | サンプル

前提条件

  • Azure サブスクリプション - 無料アカウントを作成します
  • Python 3.x
  • Azure サブスクリプションを用意できたら、Azure portal で Personalizer リソースを作成し、自分のキーとエンドポイントを取得します。 デプロイされたら、 [リソースに移動] を選択します。
    • アプリケーションを Personalizer API に接続するには、作成したリソースのキーとエンドポイントが必要です。 このクイックスタートで後に示すコードに、自分のキーとエンドポイントを貼り付けます。
    • Free 価格レベル (F0) を使用してサービスを試用し、後から運用環境用の有料レベルにアップグレードすることができます。

設定

Personalizer インスタンスをマルチスロットにアップグレードする

Note

マルチスロット パーソナル化 (プレビュー) は、Personalizer サービスの他の機能に影響します。 この変更を元に戻すことはできません。 マルチスロット パーソナル化を有効にする前に、「マルチスロット パーソナル化 (プレビュー)」を参照してください。

  1. Azure portal の [リソース管理] の [Personalizer リソース] で自動最適化を無効化し、 [モデルと学習設定] ページで自動最適化をオフにして保存します。

Note

マルチスロット パーソナル化は、自動最適化を無効にしない限り機能しません。 マルチスロット パーソナル化の自動最適化は、今後サポートされる予定です。

  1. Azure portal の [リソース管理] の [Personalizer リソース] で Personalizer をマルチスロットに更新し、[モデルと学習設定] ページで、[学習設定のエクスポート] を選択します。 ダウンロードした JSON ファイルの arguments フィールドは、 --cb_explore_adf で始まっています。 これを --ccb_explore_adf に変更し、ファイルを保存します。 CB (Contextual Bandit) と CCB (条件付き Contextual Bandit) は、それぞれ単一スロットと複数スロットのパーソナル化に使用されるアルゴリズムです。 ADF (Action Dependent Features) は、アクションが機能によって表現または識別されることを意味します。

Learning settings before change

Learning settings after change

ポータルの同じタブの [学習設定のインポート] で、最近変更した JSON ファイルを参照して選択し、アップロードします。 これにより、Personalizer インスタンスが "マルチスロット" Personalizer に更新され、マルチスロットの Rank と Reward の呼び出しがサポートされるようになります。

モデルの更新頻度を変更する

Azure portal で、Personalizer リソースの [構成] ページに進み、[モデルの更新頻度] を 30 秒に変更します。 このような短い時間を設定することでモデルは迅速にトレーニングされ、繰り返しのたびに推奨されるアクションが変化する様子を確認できます。

Change model update frequency

報酬の待機時間を変更する

Azure portal の Personalizer リソースの [構成] ページに進み、[報酬の待機時間] を 10 分に変更します。 これにより、レコメンデーションの送信後、そのモデルがそのレコメンデーションからの報酬のフィードバックを受け取る待機時間が決まります。 トレーニングは、報酬の待機時間が経過するまで行われません。

Change reward wait time

新しい Python アプリケーションを作成する

新しい Python ファイルを作成し、リソースのエンドポイントとサブスクリプション キー用の変数を作成します。

重要

Azure Portal にアクセスします。 「前提条件」セクションで作成した Personalizer リソースが正常にデプロイされた場合、 [次の手順] の下にある [リソースに移動] ボタンをクリックします。 キーとエンドポイントは、リソースの [key and endpoint](キーとエンドポイント) ページの [リソース管理] にあります。

終わったらコードからキーを削除し、公開しないよう注意してください。 運用環境では、資格情報を安全に格納して利用するための方法を用いることを検討してください。 たとえば、Azure Key Vault が考えられます。

import json, uuid, requests

# The endpoint specific to your personalization service instance; 
# e.g. https://<your-resource-name>.cognitiveservices.azure.com
PERSONALIZATION_BASE_URL = "<REPLACE-WITH-YOUR-PERSONALIZER-ENDPOINT>"
# The key specific to your personalization service instance; e.g. "0123456789abcdef0123456789ABCDEF"
RESOURCE_KEY = "<REPLACE-WITH-YOUR-PERSONALIZER-KEY>"

オブジェクト モデル

各スロットに対してコンテンツの 1 つの最適な項目を要求するには、rankRequest を作成してから、POST 要求を multislot/rank に送信します。 その後、応答が rank_response に解析されます。

報酬スコアを Personalizer に送信するには、 [rewards] を作成してから、POST 要求を multislot/events/{eventId}/reward に送信します。

このクイックスタートでは、報酬スコアの決定は重要ではありません。 実稼働システムでは、何がどの程度まで報酬スコアに影響を及ぼすかを特定するのは複雑なプロセスとなる場合があり、そのプロセスはやがて変更することになる場合もあります。 実際の Personalizer アーキテクチャでは、この設計上の意思決定を主要な意思決定に含めるようにしてください。

コード例

これらのコード スニペットでは、Python に HTTP 要求を送信することによって以下のタスクを実行する方法が示されています。

ベース URL を作成する

このセクションでは、ベース URL を使用してランクと報酬の URL を作成し、リソース キーを使用して要求ヘッダーを作成します。

MULTI_SLOT_RANK_URL = '{0}personalizer/v1.1-preview.1/multislot/rank'.format(PERSONALIZATION_BASE_URL)
MULTI_SLOT_REWARD_URL_BASE = '{0}personalizer/v1.1-preview.1/multislot/events/'.format(PERSONALIZATION_BASE_URL)
HEADERS = {
    'ocp-apim-subscription-key': RESOURCE_KEY,
    'Content-Type': 'application/json'
}

アクションとして表されるコンテンツの選択肢を取得する

アクションはコンテンツの選択肢を表します。Personalizer を使用して、この中から最適なコンテンツ項目を選択します。 一連のアクションとそのフィーチャーを表す次のメソッドをスクリプトに追加します。

def get_actions():
    return [
        {
            "id": "Red-Polo-Shirt-432",
            "features": [
                {
                    "onSale": "true",
                    "price": 20,
                    "category": "Clothing"
                }
            ]
        },
        {
            "id": "Tennis-Racket-133",
            "features": [
                {
                    "onSale": "false",
                    "price": 70,
                    "category": "Sports"
                }
            ]
        },
        {
            "id": "31-Inch-Monitor-771",
            "features": [
                {
                    "onSale": "true",
                    "price": 200,
                    "category": "Electronics"
                }
            ]
        },
        {
            "id": "XBox-Series X-117",
            "features": [
                {
                    "onSale": "false",
                    "price": 499,
                    "category": "Electronics"
                }
            ]
        }
    ]

コンテキストに対するユーザーの好みを取得する

スクリプトに次のメソッドを追加し、時間帯とユーザーが使用しているデバイスの種類に関するユーザーの入力をコマンド ラインから取得します。 これらはコンテキストのフィーチャーとして使用されます。

def get_context_features():
    time_features = ["morning", "afternoon", "evening", "night"]
    time_pref = input("What time of day is it (enter number)? 1. morning 2. afternoon 3. evening 4. night\n")
    try:
        parsed_time = int(time_pref)
        if(parsed_time <=0 or parsed_time > len(time_features)):
            raise IndexError
        time_of_day = time_features[parsed_time-1]
    except (ValueError, IndexError):
        print("Entered value is invalid. Setting feature value to", time_features[0] + ".")
        time_of_day = time_features[0]

    device_features = ['mobile', 'tablet', 'desktop']
    device_pref = input("What type of device is the user on (enter number)? 1. mobile 2. tablet 3. desktop\n")
    try:
        parsed_device = int(device_pref)
        if(parsed_device <=0 or parsed_device > len(device_features)):
            raise IndexError
        device = device_features[parsed_device-1]
    except (ValueError, IndexError):
        print("Entered value is invalid. Setting feature value to", device_features[0]+ ".")
        device = device_features[0]

    return [
        {'time': time_of_day},
        {'device': device}
        ]

スロットを取得する

スロットにより、ユーザーが対話するページが構成されます。 Personalizer により、定義されている各スロットに表示するアクションが決定されます。 アクションは、ExcludeActions として示されている特定のスロットから除外できます。 BaselineAction はスロットの既定のアクションであり、Personalizer を使用しない場合に表示されていたものです。

このクイックスタートには、単純なスロット機能があります。 運用システムでは、フィーチャーを決定して評価することが、簡単ではない場合があります。

def get_slots():
    return [
        {
            "id": "BigHeroPosition",
            "features": [
                {
                    "size": "large",
                    "position": "left",
                }
            ],
            "excludedActions": ["31-Inch-Monitor-771"],
            "baselineAction": "Red-Polo-Shirt-432"
        },
        {
            "id": "SmallSidebar",
            "features": [
                {
                    "size": "small",
                    "position": "right",
                }
            ],
            "excludedActions": ["Tennis-Racket-133"],
            "baselineAction": "XBox-Series X-117"
        }
    ]

HTTP 要求を行う

マルチスロットの Rank 呼び出しと Reward 呼び出しの場合は、これらの関数を追加して POST 要求を Personalizer のエンドポイントに送信します。

def send_multi_slot_rank(rank_request):
multi_slot_response = requests.post(MULTI_SLOT_RANK_URL, data=json.dumps(rank_request), headers=HEADERS)
if multi_slot_response.status_code != 201:
    raise Exception(multi_slot_response.text)
return json.loads(multi_slot_response.text)
def send_multi_slot_reward(reward_request, event_id):
    reward_url = '{0}{1}/reward'.format(MULTI_SLOT_REWARD_URL_BASE, event_id)
    requests.post(reward_url, data=json.dumps(reward_request), headers=HEADERS)

Personalizer の決定に関するフィードバックを取得する

次のメソッドをスクリプトに追加します。 Personalizer によりコマンド ライン プロンプトを使用して各スロットの適切な決定が行われたかどうかを通知します。

def get_reward_for_slot():
    answer = input('\nIs this correct? (y/n)\n').upper()
    if (answer == 'Y'):
        print('\nGreat! The application will send Personalizer a reward of 1 so it learns from this choice of action for this slot.\n')
        return 1
    elif (answer == 'N'):
        print('\nYou didn\'t like the recommended item.The application will send Personalizer a reward of 0 for this choice of action for this slot.\n')
        return 0
    print('\nEntered choice is invalid. Service assumes that you didn\'t like the recommended item.\n')
    return 0

学習ループを作成する

Personalizer の学習ループとは、Rank 呼び出しと Reward 呼び出しのサイクルです。 このクイックスタートでは、コンテンツをパーソナライズするための各 Rank 呼び出しの後に Reward 呼び出しを行って、サービスがどの程度適切に実行されたかを Personalizer に伝えます。

次のコードでは、コマンド ラインでユーザーに好みをたずね、その情報を Personalizer に送信して各スロットに最適なアクションを選択し、その選択をリストから選択できるようユーザーに提示した後、その選択にサービスがどの程度寄与したかを示す報酬スコアを Personalizer に送る形でサイクルをループで処理しています。

run_loop = True

while run_loop:

    eventId = str(uuid.uuid4())
    context = get_context_features()
    actions = get_actions()
    slots = get_slots()

    rank_request = {
        "eventId": eventId,
        "contextFeatures": context,
        "actions": actions,
        "slots": slots,
        "deferActivation": False
      }

    #Rank the actions for each slot
    multi_slot_rank_response = send_multi_slot_rank(rank_request)
    multi_slot_rewards = {"reward": []}

    for i in range(len(multi_slot_rank_response['slots'])):
        print('\nPersonalizer service decided you should display: {0} in slot {1}\n'.format(multi_slot_rank_response['slots'][i]['rewardActionId'], multi_slot_rank_response['slots'][i]['id']))

        slot_reward = {'slotId': multi_slot_rank_response['slots'][i]['id']}
        # User agrees or disagrees with Personalizer decision for slot
        slot_reward['value'] = get_reward_for_slot()
        multi_slot_rewards['reward'].append(slot_reward)

    # Send the rewards for the event
    send_multi_slot_reward(multi_slot_rewards, multi_slot_rank_response['eventId'])

    answer = input('\nPress q to break, any other key to continue:\n').upper()
    if (answer == 'Q'):
        run_loop = False

rank 呼び出しと reward 呼び出しについて、以降の各セクションでさらに詳しく見ていきましょう。

次のメソッドを追加します。これにより、コード ファイルを実行する前に、コンテンツの選択肢が取得され、コンテキストのユーザー設定が取得され、スロットが取得され、HTTP 要求が行われて、各スロットの報酬が取得されます。

  • get_actions
  • get_context_features
  • get_slots
  • send_rank
  • send_reward
  • get_reward_for_dsot

最適なアクションを要求する

Rank 要求を実行するために、このプログラムは、ユーザーの好みをたずねてコンテンツの選択肢を作成します。 要求本文には、コンテキスト、アクション、スロットと、それぞれの機能が含まれています。 send_multi_slot_rank メソッドは、rankRequest を受け取り、マルチスロットのランク要求を実行します。

このクイックスタートのコンテキストのフィーチャーは、時間帯とユーザーのデバイスという単純なものです。 実稼働システムでは、アクションとフィーチャーを決定し、評価することが、決して簡単ではない場合もあります。

eventId = str(uuid.uuid4())
context = get_context_features()
actions = get_actions()
slots = get_slots()

rank_request = {
    "eventId": eventId,
    "contextFeatures": context,
    "actions": actions,
    "slots": slots,
    "deferActivation": False
    }

#Rank the actions for each slot
multi_slot_rank_response = send_multi_slot_rank(rank_request)

報酬を送信する

Reward 要求用の報酬スコアを取得するために、プログラムでは各スロットのユーザーの選択をコマンド ラインから取得し、選択に数値 (報酬スコア) を割り当てた後、一意のイベント ID、スロット ID、各スロットの報酬スコアを send_multi_slot_reward メソッドに送信します。 スロットごとに報酬を定義する必要はありません。

このクイックスタートでは、0 または 1 という単純な数値を報酬スコアとして割り当てます。 実際のニーズにもよりますが、実稼働システムでは、いつ何を Reward 呼び出しに送信するかが決して簡単な決定事項ではない場合もあります。

multi_slot_rewards = {"reward": []}

for i in range(len(multi_slot_rank_response['slots'])):
    print('\nPersonalizer service decided you should display: {0} in slot {1}\n'.format(multi_slot_rank_response['slots'][i]['rewardActionId'], multi_slot_rank_response['slots'][i]['id']))

    slot_reward = {'slotId': multi_slot_rank_response['slots'][i]['id']}
    # User agrees or disagrees with Personalizer decision for slot
    slot_reward['value'] = get_reward_for_slot()
    multi_slot_rewards['reward'].append(slot_reward)

# Send the rewards for the event
send_multi_slot_reward(multi_slot_rewards, multi_slot_rank_response['eventId'])

プログラムの実行

アプリケーション ディレクトリから Python を使用して、アプリケーションを実行します。

python sample.py

The quickstart program asks a couple of questions to gather user preferences, known as features, then provides the top action.

こちらでこのクイックスタート用のソース コードを入手できます。

次の手順