クイック スタート:Personalizer クライアント ライブラリ

重要

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

Azure AI Personalizer クライアント ライブラリの使用を開始して、基本的な学習ループを設定します。 学習ループは、意思決定とフィードバックのシステムです。アプリケーションからサービスに意思決定のランク付けを要求し、その後、上位にランクされた選択肢を使用して、結果から報酬スコアを計算します。 報酬スコアはサービスに返されます。 Personalizer には AI アルゴリズムが使用されており、特定のコンテキストに対して時間の経過とともに、より適切な意思決定が行われるようになります。 サンプル アプリケーションを設定するには、次の手順に従います。

シナリオ例

このクイックスタートでは、食料品の電子小売業者が、Web サイトで各顧客に関連性の高いパーソナライズされた商品を表示することで、収益を増やしたいと考えています。 メイン ページには、見込み顧客に対して調理済みの食品を表示する [注目の商品] セクションがあります。 その電子小売業者は、購入の可能性を最大限に高めるために、適切な商品を適切な顧客に表示する方法を決定したいと考えています。

Personalizer サービスの強化学習を使用して、自動化されたスケーラブルで適応可能な方法で、この問題を解決します。 "アクション" とその特徴、"コンテキストの特徴"、"報酬スコア" を作成する方法について説明します。 Personalizer クライアント ライブラリを使って、Rank および Reward API を呼び出します。

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

前提条件

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

モデルの構成

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

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 Microsoft.Azure.CognitiveServices.Personalizer --version 1.0.0

ヒント

Visual Studio IDE を使用している場合、クライアント ライブラリは、ダウンロード可能な NuGet パッケージとして入手できます。

コード ブロック 1: サンプル データを生成する

Personalizer は、リアルタイム データを受信して解釈するアプリケーションで実行することを意図したものです。 このクイックスタートでは、サンプル コードを使用して、食料品の Web サイトでの架空の顧客アクションを生成します。 次のコード ブロックで、GetActionsGetContextGetRewardScore という 3 つの主要なメソッドを定義します。

  • GetActions は、食料品の Web サイトでランク付けする必要がある選択肢の一覧を返します。 この例では、アクションは食事商品です。 アクションの各選択肢には、後でユーザーの行動に影響を与える可能性がある詳細 (特徴) があります。 アクションは Rank API の入力として使用されます

  • GetContext は、シミュレートされた顧客による訪問を返します。 どの顧客が存在するか、訪問が行われている時刻などのランダム化された詳細 (コンテキストの特徴) を選択します。 一般に、コンテキストは、アプリケーション、システム、環境、またはユーザーの現在の状態を表します。 コンテキスト オブジェクトは、Rank API の入力として使用されます。

    このクイックスタートのコンテキストの特徴は簡単なものです。 ただし、実際の運用システムでは、特徴を設計し、その有効性を評価することが重要です。 ガイダンスについては、リンク先のドキュメントを参照してください。

  • GetRewardScore は、顧客との対話の成功を表す 0 から 1 までのスコアを返します。 シンプルなロジックを使用して、さまざまなアクションの選択肢に対して、異なるコンテキストでどのように応答するかを決定します。 たとえば、ある特定のユーザーは常に、ベジタリアンおよびビーガン製品に対して 1.0、その他の製品に対して 0.0 になります。 実際のシナリオでは、Personalizer は Rank および Reward API 呼び出しで送信されたデータからユーザーの好みを学習します。 これらは、コード例のように明示的に定義しません。

    実際の運用システムでは、事業目標と KPI に合致するように報酬スコアを設計する必要があります。 報酬メトリックを計算する方法を決定するには、多少実験が必要になる場合があります。

    次のコードでは、ユーザーの好みとアクションに対する応答が一連の条件付きステートメントとしてハードコーディングされており、説明テキストが情報提供のためにコードに含まれています。

  1. キーとエンドポイントを見つけます。

    重要

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

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

  2. Program.cs をテキスト エディターまたは IDE で開き、次のコードを貼り付けます。

    using Microsoft.Azure.CognitiveServices.Personalizer;
    using Microsoft.Azure.CognitiveServices.Personalizer.Models;
    
    class Program
    {
        private static readonly string ApiKey = "REPLACE_WITH_YOUR_PERSONALIZER_KEY";
        private static readonly string ServiceEndpoint = "REPLACE_WITH_YOUR_ENDPOINT_URL";
    
        static PersonalizerClient InitializePersonalizerClient(string url)
        {
            PersonalizerClient client = new PersonalizerClient(
                new ApiKeyServiceClientCredentials(ApiKey))
            { Endpoint = url };
    
            return client;
        }
    
        static Dictionary<string, ActionFeatures> actions = new Dictionary<string, ActionFeatures>
        {
        {"pasta", new ActionFeatures(
                        new BrandInfo(company: "pasta_inc"),
                        new ItemAttributes(
                            quantity: 1,
                            category: "Italian",
                            price: 12),
                        new DietaryAttributes(
                            vegan: false,
                            lowCarb: false,
                            highProtein: false,
                            vegetarian: false,
                            lowFat: true,
                            lowSodium: true))},
        {"bbq", new ActionFeatures(
                        new BrandInfo(company: "ambisco"),
                        new ItemAttributes(
                            quantity: 2,
                            category: "bbq",
                            price: 20),
                        new DietaryAttributes(
                            vegan: false,
                            lowCarb: true,
                            highProtein: true,
                            vegetarian: false,
                            lowFat: false,
                            lowSodium: false))},
        {"bao", new ActionFeatures(
                        new BrandInfo(company: "bao_and_co"),
                        new ItemAttributes(
                            quantity: 4,
                            category: "Chinese",
                            price: 8),
                        new DietaryAttributes(
                            vegan: false,
                            lowCarb: true,
                            highProtein: true,
                            vegetarian: false,
                            lowFat: true,
                            lowSodium: false))},
        {"hummus", new ActionFeatures(
                        new BrandInfo(company: "garbanzo_inc"),
                        new ItemAttributes(
                            quantity: 1,
                            category: "Breakfast",
                            price: 5),
                        new DietaryAttributes(
                            vegan: true,
                            lowCarb: false,
                            highProtein: true,
                            vegetarian: true,
                            lowFat: false,
                            lowSodium: false))},
        {"veg_platter", new ActionFeatures(
                        new BrandInfo(company: "farm_fresh"),
                        new ItemAttributes(
                            quantity: 1,
                            category: "produce",
                            price: 7),
                        new DietaryAttributes(
                            vegan: true,
                            lowCarb: true,
                            highProtein: false,
                            vegetarian: true,
                            lowFat: true,
                            lowSodium: true ))},
    };
    
        static IList<RankableAction> GetActions()
        {
            IList<RankableAction> rankableActions = new List<RankableAction>();
            foreach (var action in actions)
            {
                rankableActions.Add(new RankableAction
                {
                    Id = action.Key,
                    Features = new List<object>() { action.Value }
                });
            }
    
            return rankableActions;
        }
    
        public class BrandInfo
        {
            public string Company { get; set; }
            public BrandInfo(string company)
            {
                Company = company;
            }
        }
    
        public class ItemAttributes
        {
            public int Quantity { get; set; }
            public string Category { get; set; }
            public double Price { get; set; }
            public ItemAttributes(int quantity, string category, double price)
            {
                Quantity = quantity;
                Category = category;
                Price = price;
            }
        }
    
        public class DietaryAttributes
        {
            public bool Vegan { get; set; }
            public bool LowCarb { get; set; }
            public bool HighProtein { get; set; }
            public bool Vegetarian { get; set; }
            public bool LowFat { get; set; }
            public bool LowSodium { get; set; }
            public DietaryAttributes(bool vegan, bool lowCarb, bool highProtein, bool vegetarian, bool lowFat, bool lowSodium)
            {
                Vegan = vegan;
                LowCarb = lowCarb;
                HighProtein = highProtein;
                Vegetarian = vegetarian;
                LowFat = lowFat;
                LowSodium = lowSodium;
    
            }
        }
    
        public class ActionFeatures
        {
            public BrandInfo BrandInfo { get; set; }
            public ItemAttributes ItemAttributes { get; set; }
            public DietaryAttributes DietaryAttributes { get; set; }
            public ActionFeatures(BrandInfo brandInfo, ItemAttributes itemAttributes, DietaryAttributes dietaryAttributes)
            {
                BrandInfo = brandInfo;
                ItemAttributes = itemAttributes;
                DietaryAttributes = dietaryAttributes;
            }
        }
    
        public static Context GetContext()
        {
            return new Context(
                    user: GetRandomUser(),
                    timeOfDay: GetRandomTimeOfDay(),
                    location: GetRandomLocation(),
                    appType: GetRandomAppType());
        }
    
        static string[] timesOfDay = new string[] { "morning", "afternoon", "evening" };
    
        static string[] locations = new string[] { "west", "east", "midwest" };
    
        static string[] appTypes = new string[] { "edge", "safari", "edge_mobile", "mobile_app" };
    
        static IList<UserProfile> users = new List<UserProfile>
    {
        new UserProfile(
            name: "Bill",
            dietaryPreferences: new Dictionary<string, bool> { { "low_carb", true } },
            avgOrderPrice: "0-20"),
        new UserProfile(
            name: "Satya",
            dietaryPreferences: new Dictionary<string, bool> { { "low_sodium", true} },
            avgOrderPrice: "201+"),
        new UserProfile(
            name: "Amy",
            dietaryPreferences: new Dictionary<string, bool> { { "vegan", true }, { "vegetarian", true } },
            avgOrderPrice: "21-50")
    };
    
        static string GetRandomTimeOfDay()
        {
            var random = new Random();
            var timeOfDayIndex = random.Next(timesOfDay.Length);
            Console.WriteLine($"TimeOfDay: {timesOfDay[timeOfDayIndex]}");
            return timesOfDay[timeOfDayIndex];
        }
    
        static string GetRandomLocation()
        {
            var random = new Random();
            var locationIndex = random.Next(locations.Length);
            Console.WriteLine($"Location: {locations[locationIndex]}");
            return locations[locationIndex];
        }
    
        static string GetRandomAppType()
        {
            var random = new Random();
            var appIndex = random.Next(appTypes.Length);
            Console.WriteLine($"AppType: {appTypes[appIndex]}");
            return appTypes[appIndex];
        }
    
        static UserProfile GetRandomUser()
        {
            var random = new Random();
            var userIndex = random.Next(users.Count);
            Console.WriteLine($"\nUser: {users[userIndex].Name}");
            return users[userIndex];
        }
    
        public class UserProfile
        {
            // Mark name as non serializable so that it is not part of the context features
            [NonSerialized()]
            public string Name;
            public Dictionary<string, bool> DietaryPreferences { get; set; }
            public string AvgOrderPrice { get; set; }
    
            public UserProfile(string name, Dictionary<string, bool> dietaryPreferences, string avgOrderPrice)
            {
                Name = name;
                DietaryPreferences = dietaryPreferences;
                AvgOrderPrice = avgOrderPrice;
            }
        }
    
        public class Context
        {
            public UserProfile User { get; set; }
            public string TimeOfDay { get; set; }
            public string Location { get; set; }
            public string AppType { get; set; }
    
            public Context(UserProfile user, string timeOfDay, string location, string appType)
            {
                User = user;
                TimeOfDay = timeOfDay;
                Location = location;
                AppType = appType;
            }
        }
        public static float GetRewardScore(Context context, string actionId)
        {
            float rewardScore = 0.0f;
            string userName = context.User.Name;
            ActionFeatures actionFeatures = actions[actionId];
            if (userName.Equals("Bill"))
            {
                if (actionFeatures.ItemAttributes.Price < 10 && !context.Location.Equals("midwest"))
                {
                    rewardScore = 1.0f;
                    Console.WriteLine($"\nBill likes to be economical when he's not in the midwest visiting his friend Warren. He bought {actionId} because it was below a price of $10.");
                }
                else if (actionFeatures.DietaryAttributes.LowCarb && context.Location.Equals("midwest"))
                {
                    rewardScore = 1.0f;
                    Console.WriteLine($"\nBill is visiting his friend Warren in the midwest. There he's willing to spend more on food as long as it's low carb, so Bill bought {actionId}.");
                }
                else if (actionFeatures.ItemAttributes.Price >= 10 && !context.Location.Equals("midwest"))
                {
                    rewardScore = 1.0f;
                    Console.WriteLine($"\nBill didn't buy {actionId} because the price was too high when not visting his friend Warren in the midwest.");
                }
                else if (actionFeatures.DietaryAttributes.LowCarb && context.Location.Equals("midwest"))
                {
                    rewardScore = 1.0f;
                    Console.WriteLine($"\nBill didn't buy {actionId} because it's not low-carb, and he's in the midwest visitng his friend Warren.");
                }
            }
            else if (userName.Equals("Satya"))
            {
                if (actionFeatures.DietaryAttributes.LowSodium)
                {
                    rewardScore = 1.0f;
                    Console.WriteLine($"\nSatya is health conscious, so he bought {actionId} since it's low in sodium.");
                }
                else
                {
                    Console.WriteLine($"\nSatya did not buy {actionId} because it's not low sodium.");
                }
            }
            else if (userName.Equals("Amy"))
            {
                if (actionFeatures.DietaryAttributes.Vegan || actionFeatures.DietaryAttributes.Vegetarian)
                {
                    rewardScore = 1.0f;
                    Console.WriteLine($"\nAmy likes to eat plant-based foods, so she bought {actionId} because it's vegan or vegetarian friendly.");
                }
                else
                {
                    Console.WriteLine($"\nAmy did not buy {actionId} because it's not vegan or vegetarian.");
                }
            }
            return rewardScore;
        }
        // ...
    
  3. キーとエンドポイントを、コード内に示されている場所に貼り付けます。 エンドポイントの形式は https://<your_resource_name>.cognitiveservices.azure.com/ です。

    重要

    終わったらコードからキーを削除し、公開しないよう注意してください。 運用環境では、Azure Key Vault などの資格情報を格納してアクセスする安全な方法を使用します。 詳細については、Azure AI サービスのセキュリティに関する記事を参照してください。

コード ブロック 2: 学習ループを繰り返す

次のコード ブロックは、main メソッドを定義し、スクリプトを閉じます。 それにより、学習ループの繰り返しを実行し、コンテキスト (顧客を含む) を生成してから、Rank API を使用してそのコンテキストでのアクションのランク付けを要求し、報酬スコアを計算し、Reward API を使用して Personalizer サービスにそのスコアを返します。 各ステップで関連する情報をコンソールに出力します。

この例では、[注目の商品] セクションに表示する商品を決定するために Rank の各呼び出しが行われます。 その後、Reward の呼び出しにより、注目の商品がユーザーによって購入されたかどうかが示されます。 報酬は、共通の EventId 値を通じて意思決定に関連付けられます。

    static void Main(string[] args)
    {
        int iteration = 1;
        bool runLoop = true;

        // Get the actions list to choose from personalizer with their features.
        IList<RankableAction> actions = GetActions();

        // Initialize Personalizer client.
        PersonalizerClient client = InitializePersonalizerClient(ServiceEndpoint);

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

            // Get context information.
            Context context = GetContext();

            // Create current context from user specified data.
            IList<object> currentContext = new List<object>() {
            context
        };

            // Generate an ID to associate with the request.
            string eventId = Guid.NewGuid().ToString();

            // Rank the actions
            var request = new RankRequest(actions: actions, contextFeatures: currentContext, eventId: eventId);
            RankResponse response = client.Rank(request);

            Console.WriteLine($"\nPersonalizer service thinks {context.User.Name} would like to have: {response.RewardActionId}.");

            float reward = GetRewardScore(context, response.RewardActionId);

            // Send the reward for the action based on user response.
            client.Reward(response.EventId, new RewardRequest(reward));

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

        } while (runLoop);
    }

        private static string GetKey()
    {
        return Console.ReadKey().Key.ToString().Last().ToString().ToUpper();
    }

}

プログラムの実行

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

dotnet run

最初の繰り返しでは、Personalizer はトレーニングをまだ行っていないため、ランダムなアクションを推奨します。 必要に応じて、さらに多くの繰り返しを実行できます。 約 10 分後、サービスのレコメンデーションは改善を示し始めます。

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

分析のために多数のイベントを生成する (省略可能)

このクイックスタートのシナリオでは、たとえば 5,000 件のイベントを簡単に生成できます。これは、徒弟モードとオンライン モードの使用、オフライン評価の実行、特徴評価の作成の経験を得るには十分です。 上記の main メソッドを次のように置き換えます。

    static void Main(string[] args)
    {
    int iteration = 1;
    int runLoop = 0;

    // Get the actions list to choose from personalizer with their features.
    IList<RankableAction> actions = GetActions();

    // Initialize Personalizer client.
    PersonalizerClient client = InitializePersonalizerClient(ServiceEndpoint);

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

        // Get context information.
        Context context = GetContext();

        // Create current context from user specified data.
        IList<object> currentContext = new List<object>() {
            context
        };

        // Generate an ID to associate with the request.
        string eventId = Guid.NewGuid().ToString();

        // Rank the actions
        var request = new RankRequest(actions: actions, contextFeatures: currentContext, eventId: eventId);
        RankResponse response = client.Rank(request);

        Console.WriteLine($"\nPersonalizer service thinks {context.User.Name} would like to have: {response.RewardActionId}.");

        float reward = GetRewardScore(context, response.RewardActionId);

        // Send the reward for the action based on user response.
        client.Reward(response.EventId, new RewardRequest(reward));

        runLoop = runLoop + 1;

    } while (runLoop < 1000);
}

このクイックスタート用のコードは、GitHub で入手できます。

リファレンスのドキュメント |ライブラリのソース コード | パッケージ (npm) | クイックスタートのコード サンプル

前提条件

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

モデルの構成

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

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 で、personalizer-quickstart.js という名前の新しい Node.js スクリプトを作成し、リソースのエンドポイントとサブスクリプション キー用の変数を作成します。

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

次のコマンドを使用して、Node.js 用 Personalizer クライアント ライブラリをインストールします。

npm install @azure/cognitiveservices-personalizer --save

このクイックスタートの残りの npm パッケージをインストールします。

npm install @azure/ms-rest-azure-js @azure/ms-rest-js readline-sync uuid --save

コード ブロック 1: サンプル データを生成する

Personalizer は、リアルタイム データを受信して解釈するアプリケーションで実行することを意図したものです。 このクイックスタートでは、サンプル コードを使用して、食料品の Web サイトでの架空の顧客アクションを生成します。 次のコード ブロックでは、getActionsListgetContextFeaturesListgetReward という 3 つの主要なメソッドを定義します。

  • getActionsList は、食料品の Web サイトでランク付けする必要がある選択肢の一覧を返します。 この例では、アクションは食事商品です。 アクションの各選択肢には、後でユーザーの行動に影響を与える可能性がある詳細 (特徴) があります。 アクションは Rank API の入力として使用されます

  • getContextFeaturesList は、シミュレートされた顧客による訪問を返します。 どの顧客が存在するか、訪問が行われている時刻などのランダム化された詳細 (コンテキストの特徴) を選択します。 一般に、コンテキストは、アプリケーション、システム、環境、またはユーザーの現在の状態を表します。 コンテキスト オブジェクトは、Rank API の入力として使用されます。

    このクイックスタートのコンテキストの特徴は簡単なものです。 ただし、実際の運用システムでは、特徴を設計し、その有効性を評価することが重要です。 ガイダンスについては、リンク先のドキュメントを参照してください。

  • getReward で、サービスのレコメンデーションを成功または失敗としてスコア付けするようにユーザーに求めます。 これは、顧客との対話の成功を表す 0 から 1 までのスコアを返します。 実際のシナリオでは、Personalizer はリアルタイムの顧客との対話からユーザーの好みを学習します。

    実際の運用システムでは、事業目標と KPI に合致するように報酬スコアを設計する必要があります。 報酬メトリックを計算する方法を決定するには、多少実験が必要になる場合があります。

テキスト エディターまたは IDE で personalizer-quickstart.js を開き、次のコードを貼り付けます。

const uuidv1 = require('uuid/v1');
const Personalizer = require('@azure/cognitiveservices-personalizer');
const CognitiveServicesCredentials = require('@azure/ms-rest-azure-js').CognitiveServicesCredentials;
const readline = require('readline-sync');

function getReward() {
  const answer = readline.question("\nIs this correct (y/n)\n");
  if (answer.toLowerCase() === 'y') {
    console.log("\nGreat| Enjoy your food.");
    return 1;
  }
  console.log("\nYou didn't like the recommended food choice.");
  return 0;
}

function getContextFeaturesList() {
  const timeOfDayFeatures = ['morning', 'afternoon', 'evening', 'night'];
  const tasteFeatures = ['salty', 'sweet'];

  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 food would you prefer (enter number)? 1. salty 2. sweet\n");
  selection = parseInt(answer);
  const taste = selection >= 1 && selection <= 2 ? tasteFeatures[selection - 1] : tasteFeatures[0];

  console.log("Selected features:\n");
  console.log("Time of day: " + timeOfDay + "\n");
  console.log("Taste: " + taste + "\n");

  return [
    {
      "time": timeOfDay
    },
    {
      "taste": taste
    }
  ];
}

function getExcludedActionsList() {
  return [
    "juice"
  ];
}

function getActionsList() {
  return [
    {
      "id": "pasta",
      "features": [
        {
          "taste": "salty",
          "spiceLevel": "medium"
        },
        {
          "nutritionLevel": 5,
          "cuisine": "italian"
        }
      ]
    },
    {
      "id": "ice cream",
      "features": [
        {
          "taste": "sweet",
          "spiceLevel": "none"
        },
        {
          "nutritionalLevel": 2
        }
      ]
    },
    {
      "id": "juice",
      "features": [
        {
          "taste": "sweet",
          "spiceLevel": "none"
        },
        {
          "nutritionLevel": 5
        },
        {
          "drink": true
        }
      ]
    },
    {
      "id": "salad",
      "features": [
        {
          "taste": "salty",
          "spiceLevel": "low"
        },
        {
          "nutritionLevel": 8
        }
      ]
    }
  ];
}

コード ブロック 2: 学習ループを繰り返す

次のコード ブロックは、main メソッドを定義し、スクリプトを閉じます。 これにより学習ループの繰り返しを実行し、コマンド ラインでユーザーに好みを尋ねて、その情報を Personalizer に送信して最適なアクションを選択します。 選択したアクションは、コマンド ラインを使用して選択を行うユーザーに表示されます。 次に、Personalizer サービスに報酬スコアを送信して、サービスによる選択がどれだけうまくいったかを通知します。

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

  1. 以下のコードを personalizer-quickstart.js に追加します。

  2. キーとエンドポイントを見つけます。 エンドポイントの形式は https://<your_resource_name>.cognitiveservices.azure.com/ です。

    重要

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

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

  3. キーとエンドポイントを、コード内に示されている場所に貼り付けます。

    重要

    終わったらコードからキーを削除し、公開しないよう注意してください。 運用環境では、Azure Key Vault などの資格情報を格納してアクセスする安全な方法を使用します。 セキュリティの詳細については、Azure AI サービスのセキュリティに関する記事を参照してください。

    async function main() {
    
        // The key specific to your personalization service instance; e.g. "0123456789abcdef0123456789ABCDEF"
        const serviceKey = "PASTE_YOUR_PERSONALIZER_SUBSCRIPTION_KEY_HERE";
      
        // The endpoint specific to your personalization service instance; 
        // e.g. https://<your-resource-name>.cognitiveservices.azure.com
        const baseUri = "PASTE_YOUR_PERSONALIZER_ENDPOINT_HERE";
      
        const credentials = new CognitiveServicesCredentials(serviceKey);
      
        // Initialize Personalization client.
        const personalizerClient = new Personalizer.PersonalizerClient(credentials, baseUri);
      
      
        let runLoop = true;
      
        do {
      
          let rankRequest = {}
      
          // Generate an ID to associate with the request.
          rankRequest.eventId = uuidv1();
      
          // Get context information from the user.
          rankRequest.contextFeatures = getContextFeaturesList();
      
          // Get the actions list to choose from personalization with their features.
          rankRequest.actions = getActionsList();
      
          // Exclude an action for personalization ranking. This action will be held at its current position.
          rankRequest.excludedActions = getExcludedActionsList();
      
          rankRequest.deferActivation = false;
      
          // Rank the actions
          const rankResponse = await personalizerClient.rank(rankRequest);
      
          console.log("\nPersonalization service thinks you would like to have:\n")
          console.log(rankResponse.rewardActionId);
      
          // Display top choice to user, user agrees or disagrees with top choice
          const reward = getReward();
      
          console.log("\nPersonalization service ranked the actions with the probabilities as below:\n");
          for (let i = 0; i < rankResponse.ranking.length; i++) {
            console.log(JSON.stringify(rankResponse.ranking[i]) + "\n");
          }
      
          // Send the reward for the action based on user response.
      
          const rewardRequest = {
            value: reward
          }
      
          await personalizerClient.events.reward(rankRequest.eventId, rewardRequest);
      
          runLoop = continueLoop();
      
        } while (runLoop);
      }
      
      function continueLoop() {
        const answer = readline.question("\nPress q to break, any other key to continue.\n")
        if (answer.toLowerCase() === 'q') {
          return false;
        }
        return true;
      }
    
    main()
    .then(result => console.log("done"))
    .catch(err=> console.log(err));
    

プログラムの実行

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

node personalizer-quickstart.js

いくつかの学習ループを繰り返します。 約 10 分後、サービスのレコメンデーションは改善を示し始めます。

このクイックスタート用のコードは、GitHub で入手できます。

リファレンスのドキュメント | ライブラリのソース コード | パッケージ (pypi) | クイックスタートのコード サンプル

前提条件

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

モデルの構成

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

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

Change model update frequency

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

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

Change reward wait time

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

personalizer-quickstart.py という名前の新しい Python ファイルを作成します。

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

pip を使用して Personalizer クライアント ライブラリをインストールします。

pip install azure-cognitiveservices-personalizer

コード ブロック 1: サンプル データを生成する

Personalizer は、リアルタイム データを受信して解釈するアプリケーションで実行することを意図したものです。 このクイックスタートの目的として、サンプル コードを使用して、食料品の Web サイトでの架空の顧客アクションを生成します。 次のコード ブロックで、get_actionsget_contextget_reward_score という 3 つの主要な関数を定義します。

  • get_actions は、食料品の Web サイトでランク付けする必要がある選択肢の一覧を返します。 この例では、アクションは食事商品です。 アクションの各選択肢には、後でユーザーの行動に影響を与える可能性がある詳細 (特徴) があります。 アクションは Rank API の入力として使用されます

  • get_context は、シミュレートされた顧客による訪問を返します。 どの顧客が存在するか、訪問が行われている時刻などのランダム化された詳細 (コンテキストの特徴) を選択します。 一般に、コンテキストは、アプリケーション、システム、環境、またはユーザーの現在の状態を表します。 コンテキスト オブジェクトは、Rank API の入力として使用されます。

    このクイックスタートのコンテキストの特徴は簡単なものです。 ただし、実際の運用システムでは、特徴を設計し、その有効性を評価することが非常に重要です。 ガイダンスについては、リンク先のドキュメントを参照してください。

  • get_reward_score は、顧客との対話の成功を表す 0 から 1 までのスコアを返します。 シンプルなロジックを使用して、さまざまなアクションの選択肢に対して、異なるコンテキストでどのように応答するかを決定します。 たとえば、ある特定のユーザーは常に、ベジタリアンおよびビーガン製品に対して 1.0、その他の製品に対して 0.0 になります。 実際のシナリオでは、Personalizer は Rank および Reward API 呼び出しで送信されたデータからユーザーの好みを学習します。 これらは、コード例のように明示的に定義しません。

    実際の運用システムでは、事業目標と KPI に合致するように報酬スコアを設計する必要があります。 報酬メトリックを計算する方法を決定するには、多少実験が必要になる場合があります。

    次のコードでは、ユーザーの好みとアクションに対する応答が一連の条件付きステートメントとしてハードコーディングされており、説明テキストが情報提供のためにコードに含まれています。

次の手順に従って、Personalizer スクリプトを設定します。

  1. キーとエンドポイントを見つけます。

    重要

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

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

  2. テキスト エディターまたは IDE で personalizer-quickstart.py を開き、次のコードを貼り付けます。

  3. キーとエンドポイントを、コード内に示されている場所に貼り付けます。 エンドポイントの形式は https://<your_resource_name>.cognitiveservices.azure.com/ です。

    重要

    終わったらコードからキーを削除し、公開しないよう注意してください。 運用環境では、Azure Key Vault など、資格情報を保存してアクセスするための安全な方法を使用してください。 詳細については、Azure AI サービスのセキュリティを参照してください。

from azure.cognitiveservices.personalizer import PersonalizerClient
from azure.cognitiveservices.personalizer.models import RankableAction, RewardRequest, RankRequest
from msrest.authentication import CognitiveServicesCredentials

import datetime, json, os, time, uuid, random

key = "paste_your_personalizer_key_here"
endpoint = "paste_your_personalizer_endpoint_here"

# Instantiate a Personalizer client
client = PersonalizerClient(endpoint, CognitiveServicesCredentials(key))

actions_and_features = {
    'pasta': {
        'brand_info': {
            'company':'pasta_inc'
        }, 
        'attributes': {
            'qty':1, 'cuisine':'italian',
            'price':12
        },
        'dietary_attributes': {
            'vegan': False,
            'low_carb': False,
            'high_protein': False,
            'vegetarian': False,
            'low_fat': True,
            'low_sodium': True
        }
    },
    'bbq': {
        'brand_info' : {
            'company': 'ambisco'
        },
        'attributes': {
            'qty': 2,
            'category': 'bbq',
            'price': 20
        }, 
        'dietary_attributes': {
            'vegan': False,
            'low_carb': True,
            'high_protein': True,
            'vegetarian': False,
            'low_fat': False,
            'low_sodium': False
        }
    },
    'bao': {
        'brand_info': {
            'company': 'bao_and_co'
        },
        'attributes': {
            'qty': 4,
            'category': 'chinese',
            'price': 8
        }, 
        'dietary_attributes': {
            'vegan': False,
            'low_carb': True,
            'high_protein': True,
            'vegetarian': False,
            'low_fat': True,
            'low_sodium': False
        }
    },
    'hummus': {
        'brand_info' : { 
            'company': 'garbanzo_inc'
        },
        'attributes' : {
            'qty': 1,
            'category': 'breakfast',
            'price': 5
        }, 
        'dietary_attributes': {
            'vegan': True, 
            'low_carb': False,
            'high_protein': True,
            'vegetarian': True,
            'low_fat': False, 
            'low_sodium': False
        }
    },
    'veg_platter': {
        'brand_info': {
            'company': 'farm_fresh'
        }, 
        'attributes': {
            'qty': 1,
            'category': 'produce', 
            'price': 7
        },
        'dietary_attributes': {
            'vegan': True,
            'low_carb': True,
            'high_protein': False,
            'vegetarian': True,
            'low_fat': True,
            'low_sodium': True
        }
    }
}

def get_actions():
    res = []
    for action_id, feat in actions_and_features.items():
        action = RankableAction(id=action_id, features=[feat])
        res.append(action)
    return res

user_profiles = {
    'Bill': {
        'dietary_preferences': 'low_carb', 
        'avg_order_price': '0-20',
        'browser_type': 'edge'
    },
    'Satya': {
        'dietary_preferences': 'low_sodium',
        'avg_order_price': '201+',
        'browser_type': 'safari'
    },
    'Amy': {
        'dietary_preferences': {
            'vegan', 'vegetarian'
        },
        'avg_order_price': '21-50',
        'browser_type': 'edge'},
    }

def get_context(user):
    location_context = {'location': random.choice(['west', 'east', 'midwest'])}
    time_of_day = {'time_of_day': random.choice(['morning', 'afternoon', 'evening'])}
    app_type = {'application_type': random.choice(['edge', 'safari', 'edge_mobile', 'mobile_app'])}
    res = [user_profiles[user], location_context, time_of_day, app_type]
    return res

def get_random_users(k = 5):
    return random.choices(list(user_profiles.keys()), k=k)


def get_reward_score(user, actionid, context):
    reward_score = 0.0
    action = actions_and_features[actionid]
    
    if user == 'Bill':
        if action['attributes']['price'] < 10 and (context[1]['location'] !=  "midwest"):
            reward_score = 1.0
            print("Bill likes to be economical when he's not in the midwest visiting his friend Warren. He bought", actionid, "because it was below a price of $10.")
        elif (action['dietary_attributes']['low_carb'] == True) and (context[1]['location'] ==  "midwest"):
            reward_score = 1.0
            print("Bill is visiting his friend Warren in the midwest. There he's willing to spend more on food as long as it's low carb, so Bill bought" + actionid + ".")
            
        elif (action['attributes']['price'] >= 10) and (context[1]['location'] != "midwest"):
            print("Bill didn't buy", actionid, "because the price was too high when not visting his friend Warren in the midwest.")
            
        elif (action['dietary_attributes']['low_carb'] == False) and (context[1]['location'] ==  "midwest"):
            print("Bill didn't buy", actionid, "because it's not low-carb, and he's in the midwest visitng his friend Warren.")
             
    elif user == 'Satya':
        if action['dietary_attributes']['low_sodium'] == True:
            reward_score = 1.0
            print("Satya is health conscious, so he bought", actionid,"since it's low in sodium.")
        else:
            print("Satya did not buy", actionid, "because it's not low sodium.")   
            
    elif user == 'Amy':
        if (action['dietary_attributes']['vegan'] == True) or (action['dietary_attributes']['vegetarian'] == True):
            reward_score = 1.0
            print("Amy likes to eat plant-based foods, so she bought", actionid, "because it's vegan or vegetarian friendly.")       
        else:
            print("Amy did not buy", actionid, "because it's not vegan or vegetarian.")
                
    return reward_score
    # ...

コード ブロック 2: 学習ループを繰り返す

次のコード ブロックで、、run_personalizer_cycle 関数を定義し、シンプルなユーザー フィードバック ループでそれを呼び出します。 それにより、学習ループの繰り返しを実行し、コンテキスト (顧客を含む) を生成してから、Rank API を使用してそのコンテキストでのアクションのランク付けを要求し、報酬スコアを計算し、Reward API を使用して Personalizer サービスにそのスコアを返します。 各ステップで関連する情報をコンソールに出力します。

この例では、[注目の商品] セクションに表示する商品を決定するために Rank の各呼び出しが行われます。 その後、Reward の呼び出しにより、注目の商品がユーザーによって購入されたかどうかが示されます。 報酬は、共通の EventId 値を通じて意思決定に関連付けられます。

def run_personalizer_cycle():
    actions = get_actions()
    user_list = get_random_users()
    for user in user_list:
        print("------------")
        print("User:", user, "\n")
        context = get_context(user)
        print("Context:", context, "\n")
        
        rank_request = RankRequest(actions=actions, context_features=context)
        response = client.rank(rank_request=rank_request)
        print("Rank API response:", response, "\n")
        
        eventid = response.event_id
        actionid = response.reward_action_id
        print("Personalizer recommended action", actionid, "and it was shown as the featured product.\n")
        
        reward_score = get_reward_score(user, actionid, context)
        client.events.reward(event_id=eventid, value=reward_score)     
        print("\nA reward score of", reward_score , "was sent to Personalizer.")
        print("------------\n")

continue_loop = True
while continue_loop:
    run_personalizer_cycle()
    
    br = input("Press Q to exit, or any other key to run another loop: ")
    if(br.lower()=='q'):
        continue_loop = False

プログラムの実行

上記のすべてのコードが Python ファイルに含まれると、アプリケーション ディレクトリから実行できます。

python personalizer-quickstart.py

最初の繰り返しでは、Personalizer はトレーニングをまだ行っていないため、ランダムなアクションを推奨します。 必要に応じて、さらに多くの繰り返しを実行できます。 約 10 分後、サービスのレコメンデーションは改善を示し始めます。

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

分析のために多数のイベントを生成する (省略可能)

このクイックスタートのシナリオでは、たとえば 5,000 件のイベントを簡単に生成できます。これは、徒弟モードの使用、オンライン モード、オフライン評価の実行、特徴評価の作成の経験を得るには十分です。 上記のコード ブロックの while ループを次のコードに置き換えます。

for i in range(0,1000):
    run_personalizer_cycle()

このクイックスタート用のコードは、GitHub で入手できます。

トレーニング済みのモデルをダウンロードする

上記の例の 5,000 個のイベントでトレーニングされた Personalizer モデルをダウンロードする場合は、Personalizer Samples リポジトリにアクセスし、Personalizer_QuickStart_Model.zip ファイルをダウンロードします。 次に、Azure portal で Personalizer リソースに移動し、[セットアップ] ページの [インポート/エクスポート] タブに移動して、ファイルをインポートします。

リソースをクリーンアップする

Azure AI サービス サブスクリプションをクリーンアップするには、リソースを削除するか、リソース グループを削除できます。これにより、関連付けられているリソースが削除されます。

次のステップ