你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

快速入门:个性化体验创建服务客户端库

重要

从 2023 年 9 月 20 日开始,将无法创建新的个性化体验创建服务资源。 个性化体验创建服务将于 2026 年 10 月 1 日停用。

开始使用 Azure AI 个性化体验创建服务客户端库来设置基础学习循环。 学习循环是一个决策和反馈系统:应用程序从服务处请求决策排名,然后使用排名靠前的选择,并从结果中计算奖励分数。 它会将奖励分数返回给服务。 随着时间的推移,个性化体验创建服务会使用 AI 算法针对任何给定上下文做出更好的决策。 按照以下步骤设置示例应用程序。

示例方案

在此快速入门中,一家杂货电子零售商希望通过在其网站上向每位客户展示相关和个性化的产品来增加收入。 主页上有一个“特色产品”部分向潜在客户显示预制食品。 该电商希望确定如何向正确的客户展示正确的产品,以最大限度地提高购买可能性。

个性化体验创建服务会使用强化学习以自动化、可缩放且适应性强的方式解决此问题。 你将了解如何创建操作及其特征、上下文特征奖励分数。 你将使用个性化体验创建服务客户端库来调用排名和奖励 API

参考文档 | 库源代码 | 包 (NuGet) | .NET 代码示例

先决条件

  • Azure 订阅 - 免费创建订阅
  • .NET Core 的当前版本。
  • 拥有 Azure 订阅后,在 Azure 门户中创建个性化体验创建服务资源,获取密钥和终结点。 部署后,选择”转到资源”。
    • 将需要从创建的资源获取密钥和终结点,以便将应用程序连接到个性化体验创建服务 API。 你稍后会在快速入门中将密钥和终结点粘贴到下方的代码中。
    • 可以使用免费定价层 (F0) 试用该服务,然后再升级到付费层进行生产。

模型配置

更改模型更新频率

在 Azure 门户中,转到个性化体验创建服务资源的“配置”页,并将“模型更新频率”更改为 30 秒。 此短暂持续时间可快速训练模型,使你可以看到建议的操作如何针对每次迭代而变化。

更改模型更新频率

更改奖励等待时间

在 Azure 门户中,转到个性化体验创建服务资源的“配置”页,并将“奖励等待时间”更改为 10 分钟。 这决定了在发送建议后模型将等待多长时间来接收来自该建议的奖励反馈。 在奖励等待时间过去之前,不会进行训练。

更改奖励等待时间

新建 C# 应用程序

在首选编辑器或 IDE 中创建新的 .NET Core 应用程序。

在控制台窗口(例如 cmd、PowerShell 或 Bash)中,使用 dotnet new 命令创建名为 personalizer-quickstart 的新控制台应用。 此命令将创建包含单个源文件的简单“Hello World”C# 项目:Program.cs

dotnet new console -n personalizer-quickstart

将目录更改为新创建的应用文件夹。 然后使用以下代码生成应用程序:

dotnet build

生成输出不应包含警告或错误。

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

安装客户端库

在应用程序目录中,使用以下命令安装适用于 .NET 的个性化体验创建服务客户端库:

dotnet add package Microsoft.Azure.CognitiveServices.Personalizer --version 1.0.0

提示

如果你使用的是 Visual Studio IDE,客户端库可用作可下载的 NuGet 包。

代码块 1:生成示例数据

个性化体验创建服务用于在接收和解释实时数据的应用程序上运行。 在此快速入门中,你将使用示例代码在杂货店网站上生成虚构的客户操作。 以下代码块定义了三个关键方法:GetActionsGetContextGetRewardScore

  • GetActions 会返回杂货网站需要排名的选项列表。 在此示例中,操作是膳食产品。 每个操作选项都有可能影响用户行为的详细信息(特征)。 操作用作排名 API 的输入

  • GetContext 会返回模拟的客户访问。 它会选择随机的详细信息(上下文特征),如哪些客户在线以及一天中的访问时间。 通常,上下文表示你的应用程序、系统、环境或用户的当前状态。 上下文对象用作排名 API 的输入。

    此快速入门中的上下文特征是简单的。 但是,在实际生产系统中,设计你的特征评估其有效性非常重要。 有关指导,请参阅链接的文档。

  • GetRewardScore 会返回一个介于 0 和 1 之间的分数,表示客户交互的成功程度。 它使用简单的逻辑来确定不同上下文如何响应不同的操作选项。 例如,某个用户始终会在素食和纯素产品上返回 1.0,在其他产品上返回 0.0。 在实际场景中,个性化体验创建服务将根据排名和奖励 API 调用中发送的数据了解用户偏好。 你不用像示例代码中那样显式定义这些内容。

    在实际的生产系统中,奖励分数的设计应与你的业务目标和 KPI 保持一致。 确定如何计算奖励指标可能需要一些试验。

    在下面的代码中,用户的偏好和对操作的响应被硬编码为一系列条件语句。为便于演示,代码中包含了解释性文本。

  1. 查找你的密钥和终结点。

    重要

    转到 Azure 门户。 如果在“先决条件”部分中创建的个性化体验创建服务资源已成功部署,请单击“后续步骤”下的“转到资源”按钮 。 在资源的“密钥和终结点”页的“资源管理”下可以找到密钥和终结点 。

    完成后,请记住将密钥从代码中删除,并且永远不要公开发布该密钥。 对于生产环境,请考虑使用安全的方法来存储和访问凭据。 例如,Azure 密钥保管库

  2. 在文本编辑器或 IDE 中打开 Program.cs,然后粘贴在下面的代码中。

    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 方法并关闭脚本。 它会运行学习循环迭代,在其中生成上下文(包括客户),使用排名 API 请求该上下文中的操作排名,计算奖励分数,并使用奖励 API 将该分数传递回个性化体验创建服务。 它会在每个步骤中将相关信息打印到控制台。

在此示例中,每次排名调用都是为了确定应在“特色产品”部分显示哪种产品。 然后,奖励调用会指示用户是否购买了特色产品。 奖励通过共同的 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

在第一次迭代中,个性化体验创建服务会推荐随机操作,因为它尚未执行任何训练。 可以选择运行更多迭代。 大约 10 分钟后,该服务会开始在建议中显示改进。

快速入门程序会提出一些问题来收集用户的偏好(称为“特征”),然后提供排名最高的操作。

生成多个事件以供分析(可选)

比如,可以从此快速入门方案中轻松生成 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 v14.16.0 和 npm 6.14.11 验证)。
  • 拥有 Azure 订阅后,在 Azure 门户中创建个性化体验创建服务资源,获取密钥和终结点。 部署后,选择”转到资源”。
    • 将需要从创建的资源获取密钥和终结点,以便将应用程序连接到个性化体验创建服务 API。 你稍后会在快速入门中将密钥和终结点粘贴到下方的代码中。
    • 可以使用免费定价层 (F0) 试用该服务,然后再升级到付费层进行生产。

模型配置

更改模型更新频率

在 Azure 门户中,转到个性化体验创建服务资源的“配置”页,并将“模型更新频率”更改为 30 秒。 此短暂持续时间可快速训练模型,使你可以看到建议的操作如何针对每次迭代而变化。

更改模型更新频率

更改奖励等待时间

在 Azure 门户中,转到个性化体验创建服务资源的“配置”页,并将“奖励等待时间”更改为 10 分钟。 这决定了在发送建议后模型将等待多长时间来接收来自该建议的奖励反馈。 在奖励等待时间过去之前,不会进行训练。

更改奖励等待时间

创建新的 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 的个性化体验创建服务客户端库:

npm install @azure/cognitiveservices-personalizer --save

安装此快速入门的其余 npm 包:

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

代码块 1:生成示例数据

个性化体验创建服务用于在接收和解释实时数据的应用程序上运行。 在此快速入门中,你将使用示例代码在杂货店网站上生成虚构的客户操作。 以下代码块定义了三个关键方法:GetActionsGetContextGetRewardScore

  • getActionsList 会返回杂货网站需要排名的选项列表。 在此示例中,操作是膳食产品。 每个操作选项都有可能影响用户行为的详细信息(特征)。 操作用作排名 API 的输入

  • getContextFeaturesList 会返回模拟的客户访问。 它会选择随机的详细信息(上下文特征),如哪些客户在线以及一天中的访问时间。 通常,上下文表示你的应用程序、系统、环境或用户的当前状态。 上下文对象用作排名 API 的输入。

    此快速入门中的上下文特征是简单的。 但是,在实际生产系统中,设计你的特征评估其有效性非常重要。 有关指导,请参阅链接的文档。

  • getReward 提示用户按成功或失败为服务建议评级。 它会返回一个介于 0 和 1 之间的分数,表示客户交互的成功程度。 在实际场景中,个性化体验创建服务将从实时客户交互中了解用户偏好。

    在实际的生产系统中,奖励分数的设计应与你的业务目标和 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 方法并关闭脚本。 它会运行一个学习循环迭代,在该迭代中,它会在命令行询问用户的偏好,并将该信息发送到个性化体验创建服务以选择最佳操作。 它将所选操作呈现给使用命令行做出选择的用户。 然后,它会向个性化体验创建服务发送奖励分数,以指示服务在选择方面的表现。

个性化体验创建服务学习循环是一个排名奖励调用周期。 在本快速入门中,用于个性化内容的每个排名调用都后接一个奖励调用,该奖励调用让个性化体验创建服务知道该服务的表现如何。

  1. 将下面的代码添加到 personalizer-quickstart.js

  2. 查找你的密钥和终结点。 你的终结点的形式为 https://<your_resource_name>.cognitiveservices.azure.com/

    重要

    转到 Azure 门户。 如果在“先决条件”部分中创建的个性化体验创建服务资源已成功部署,请单击“后续步骤”下的“转到资源”按钮 。 在资源的“密钥和终结点”页的“资源管理”下可以找到密钥和终结点 。

    完成后,请记住将密钥从代码中删除,并且永远不要公开发布该密钥。 对于生产环境,请考虑使用安全的方法来存储和访问凭据。 例如,Azure 密钥保管库

  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 门户中创建个性化体验创建服务资源,获取密钥和终结点。 部署后,选择”转到资源”。
    • 需要从创建的资源中获取密钥和终结点才能将应用程序连接到个性化体验创建服务 API,密钥和终结点将粘贴到下面的快速入门代码中。
    • 可以使用免费定价层 (F0) 试用该服务,然后在以后升级到付费层进行生产。

模型配置

更改模型更新频率

在 Azure 门户中,转到个性化体验创建服务资源的“配置”页,并将“模型更新频率”更改为 30 秒。 此短暂持续时间可快速训练模型,使你可以看到建议的操作如何针对每次迭代而变化。

更改模型更新频率

更改奖励等待时间

在 Azure 门户中,转到个性化体验创建服务资源的“配置”页,并将“奖励等待时间”更改为 10 分钟。 这决定了在发送建议后模型将等待多长时间来接收来自该建议的奖励反馈。 在奖励等待时间过去之前,不会进行训练。

更改奖励等待时间

创建新的 Python 应用程序

新建名为 personalizer-quickstart.py 的 Python 文件。

安装客户端库

使用 pip 安装个性化体验创建服务客户端库:

pip install azure-cognitiveservices-personalizer

代码块 1:生成示例数据

个性化体验创建服务用于在接收和解释实时数据的应用程序上运行。 为了实现此快速入门的目的,你将使用示例代码在杂货网站上生成虚构的客户操作。 以下代码块定义了三个关键函数:get_actionsget_contextget_reward_score

  • get_actions 会返回杂货网站需要排名的选项列表。 在此示例中,操作是膳食产品。 每个操作选项都有可能影响用户行为的详细信息(特征)。 操作用作排名 API 的输入

  • get_context 会返回模拟的客户访问。 它会选择随机的详细信息(上下文特征),如哪些客户在线以及一天中的访问时间。 通常,上下文表示你的应用程序、系统、环境或用户的当前状态。 上下文对象用作排名 API 的输入。

    此快速入门中的上下文特征是简单的。 但是,在实际生产系统中,设计你的特征评估其有效性非常重要。 有关指导,请参阅链接的文档。

  • get_reward_score 会返回一个介于 0 和 1 之间的分数,表示客户交互的成功程度。 它使用简单的逻辑来确定不同上下文对不同操作选项的响应方式。 例如,某个用户始终会在素食和纯素产品上返回 1.0,在其他产品上返回 0.0。 在实际场景中,个性化体验创建服务将根据排名和奖励 API 调用中发送的数据了解用户偏好。 你不用像示例代码中那样显式定义这些内容。

    在实际的生产系统中,奖励分数的设计应与你的业务目标和 KPI 保持一致。 确定如何计算奖励指标可能需要一些试验。

    在下面的代码中,用户的偏好和对操作的响应被硬编码为一系列条件语句。为便于演示,代码中包含了解释性文本。

请按照以下步骤设置个性化体验创建服务脚本。

  1. 查找你的密钥和终结点。

    重要

    转到 Azure 门户。 如果在“先决条件”部分中创建的个性化体验创建服务资源已成功部署,请单击“后续步骤”下的“转到资源”按钮 。 在资源的“密钥和终结点”页的“资源管理”下可以找到密钥和终结点 。

    完成后,请记住将密钥从代码中删除,并且永远不要公开发布该密钥。 对于生产环境,请考虑使用安全的方法来存储和访问凭据。 例如,Azure 密钥保管库

  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 函数,并在简单的用户反馈循环中调用它。 它会运行学习循环迭代,在其中生成上下文(包括客户),使用排名 API 请求该上下文中的操作排名,计算奖励分数,并使用奖励 API 将该分数传递回个性化体验创建服务。 它会在每个步骤中将相关信息打印到控制台。

在此示例中,每次排名调用都是为了确定应在“特色产品”部分显示哪种产品。 然后,奖励调用会指示用户是否购买了特色产品。 奖励通过共同的 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

在第一次迭代中,个性化体验创建服务会推荐随机操作,因为它尚未执行任何训练。 可以选择运行更多迭代。 大约 10 分钟后,该服务会开始在建议中显示改进。

快速入门程序会提出一些问题来收集用户的偏好(称为“特征”),然后提供排名最高的操作。

生成多个事件以供分析(可选)

比如,可以从此快速入门方案中轻松生成 5,000 个事件,这足以获得使用学徒模式、联机模式、运行脱机评估和创建特征评估的经验。 将上面代码块中的 while 循环替换为以下代码。

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

GitHub 上提供了此快速入门的源代码。

下载训练的模型

如果要下载基于上述示例中的 5,000 个事件训练的个性化体验创建服务模型,请访问个性化体验创建服务示例存储库并下载 Personalizer_QuickStart_Model.zip 文件。 然后转到 Azure 门户中的个性化体验创建服务资源,转到“设置”页和“导入/导出”选项卡,然后导入文件。

清理资源

若要清理 Azure AI 服务订阅,可以删除资源或资源组,这会删除所有关联的资源。

后续步骤