教學課程:在 Azure Notebook 中使用個人化工具

重要

從 2023 年 9 月 20 日起,您將無法建立新的個人化工具資源。 個人化工具服務將于 2026 年 10 月 1 日淘汰。

本教學課程會在 Azure Notebook 中執行個人化工具迴圈,示範個人化工具迴圈的端對端生命週期。

迴圈會建議客戶應訂購的咖啡類型。 使用者及其喜好設定會儲存在使用者資料集中。 咖啡的相關資訊會儲存在咖啡資料集中。

使用者和咖啡

筆記本會模擬使用者與網站的互動,從資料集選取隨機使用者、一天中的時間和天氣類型。 使用者資訊的摘要如下:

客戶 - 內容功能 一天中的時間 天氣類型
Alice
Bob
凱茜
Dave
上午
下午
晚上
陽光
下雨
下雪

為了協助個人化工具學習,隨著時間推移, 系統 也會知道每個人咖啡選擇的詳細資料。

咖啡 - 動作功能 溫度類型 原點位置 烤肉類型 有機
Cappacino 經常性 肯亞 深色 有機
冷釀造 沒有興趣 巴西 淺色 有機
Iced mocha 沒有興趣 衣索比亞 淺色 非有機
Latte (拿鐵) 經常性 巴西 深色 非有機

個人化工具迴圈的目的是 要盡可能多地尋找使用者與咖啡之間的最佳比對。

本教學課程的程式碼可在個人化工具範例 GitHub 存放庫中 取得

模擬的運作方式

在執行系統開始時,個人化工具的建議只會在 20% 到 30% 之間成功。 此成功會以回報給個人化工具獎勵 API 的獎勵來表示,分數為 1。 在一些排名和獎勵呼叫之後,系統會改善。

初始要求之後,請執行離線評估。 這可讓個人化工具檢閱資料,並建議更好的學習原則。 套用新的學習原則,並使用先前要求計數的 20% 再次執行筆記本。 迴圈將會使用新的學習原則來執行得更好。

排名和獎勵通話

針對對個人化工具服務的每個數千個呼叫,Azure Notebook 會將排名 要求傳送 至 REST API:

  • 排名/要求事件的唯一識別碼
  • 內容功能 - 隨機播放使用者、天氣和一天中的時間 - 模擬網站或行動裝置上的使用者
  • 具有功能的動作 - 所有 咖啡資料 - 個人化工具會從中提出建議

系統會接收要求,然後比較該預測與使用者當天和天氣的已知選擇。 如果已知選擇與預測的選擇相同, 則會將獎勵 1 傳回給個人化工具。 否則傳回的獎勵為 0。

注意

這是模擬,因此獎勵的演算法很簡單。 在真實世界的案例中,演算法應該使用商務邏輯,可能具有客戶體驗各個層面的權數,以判斷獎勵分數。

必要條件

檔案描述:

設定個人化工具資源

在Azure 入口網站中, 將個人化工具資源 設定為更新模型頻率 設定為 15 秒,獎勵 等候時間為 10 分鐘。 這些值位於 [ 組態 ] 頁面上。

設定
更新模型頻率 15 秒
獎勵等候時間 10 分鐘

這些值有非常短的持續時間,以便顯示本教學課程中的變更。 這些值不應該在生產案例中使用,而不需要驗證它們以您的個人化工具迴圈達成您的目標。

設定 Azure Notebook

  1. 將核心變更為 Python 3.6
  2. 開啟 Personalizer.ipynb 檔案。

執行筆記本資料格

執行每個可執行檔資料格,並等候它傳回。 您知道當儲存格旁的方括弧顯示數位而非 * 時,就會完成。 下列各節說明每個資料格以程式設計方式執行哪些動作,以及輸出的預期。

包含 Python 模組

包含必要的 Python 模組。 儲存格沒有輸出。

import json
import matplotlib.pyplot as plt
import random
import requests
import time
import uuid

設定個人化工具資源金鑰和名稱

從Azure 入口網站,在 個人化工具資源的 [快速入門] 頁面上尋找您的金鑰和端點。 將 的值 <your-resource-name> 變更為個人化工具資源的名稱。 將 的值 <your-resource-key> 變更為個人化工具金鑰。

# Replace 'personalization_base_url' and 'resource_key' with your valid endpoint values.
personalization_base_url = "https://<your-resource-name>.cognitiveservices.azure.com/"
resource_key = "<your-resource-key>"

使用此函式來記下反復函式反復專案的開始和結束時間。

這些儲存格沒有輸出。 函式會輸出呼叫時的目前日期和時間。

# Print out current datetime
def currentDateTime():
    currentDT = datetime.datetime.now()
    print (str(currentDT))

取得上次模型更新時間

呼叫 函式 get_last_updated 時,函式會列印模型更新的最後一個修改日期和時間。

這些儲存格沒有輸出。 函式會在呼叫時輸出最後一個模型定型日期。

函式會使用 GET REST API 來 取得模型屬性

# ititialize variable for model's last modified date
modelLastModified = ""
def get_last_updated(currentModifiedDate):

    print('-----checking model')

    # get model properties
    response = requests.get(personalization_model_properties_url, headers = headers, params = None)

    print(response)
    print(response.json())

    # get lastModifiedTime
    lastModifiedTime = json.dumps(response.json()["lastModifiedTime"])

    if (currentModifiedDate != lastModifiedTime):
        currentModifiedDate = lastModifiedTime
        print(f'-----model updated: {lastModifiedTime}')

取得原則和服務設定

使用這兩個 REST 呼叫來驗證服務的狀態。

這些儲存格沒有輸出。 函式會在呼叫時輸出服務值。

def get_service_settings():

    print('-----checking service settings')

    # get learning policy
    response = requests.get(personalization_model_policy_url, headers = headers, params = None)

    print(response)
    print(response.json())

    # get service settings
    response = requests.get(personalization_service_configuration_url, headers = headers, params = None)

    print(response)
    print(response.json())

建構 URL 並讀取 JSON 資料檔案

此儲存格

  • 建置 REST 呼叫中使用的 URL
  • 使用個人化工具資源金鑰設定安全性標頭
  • 設定 Rank 事件識別碼的隨機種子
  • 讀取 JSON 資料檔案
  • calls get_last_updated 方法 - 範例輸出中已移除學習原則
  • calls get_service_settings 方法

儲存格具有 對 和 get_service_settings 函式呼叫 get_last_updated 的輸出。

# build URLs
personalization_rank_url = personalization_base_url + "personalizer/v1.0/rank"
personalization_reward_url = personalization_base_url + "personalizer/v1.0/events/" #add "{eventId}/reward"
personalization_model_properties_url = personalization_base_url + "personalizer/v1.0/model/properties"
personalization_model_policy_url = personalization_base_url + "personalizer/v1.0/configurations/policy"
personalization_service_configuration_url = personalization_base_url + "personalizer/v1.0/configurations/service"

headers = {'Ocp-Apim-Subscription-Key' : resource_key, 'Content-Type': 'application/json'}

# context
users = "users.json"

# action features
coffee = "coffee.json"

# empty JSON for Rank request
requestpath = "example-rankrequest.json"

# initialize random
random.seed(time.time())

userpref = None
rankactionsjsonobj = None
actionfeaturesobj = None

with open(users) as handle:
    userpref = json.loads(handle.read())

with open(coffee) as handle:
    actionfeaturesobj = json.loads(handle.read())

with open(requestpath) as handle:
    rankactionsjsonobj = json.loads(handle.read())

get_last_updated(modelLastModified)
get_service_settings()

print(f'User count {len(userpref)}')
print(f'Coffee count {len(actionfeaturesobj)}')

確認輸出的 rewardWaitTime 設定為 10 分鐘,且 modelExportFrequency 設定為 15 秒。

-----checking model
<Response [200]>
{'creationTime': '0001-01-01T00:00:00+00:00', 'lastModifiedTime': '0001-01-01T00:00:00+00:00'}
-----model updated: "0001-01-01T00:00:00+00:00"
-----checking service settings
<Response [200]>
{...learning policy...}
<Response [200]>
{'rewardWaitTime': '00:10:00', 'defaultReward': 0.0, 'rewardAggregation': 'earliest', 'explorationPercentage': 0.2, 'modelExportFrequency': '00:00:15', 'logRetentionDays': -1}
User count 4
Coffee count 4

針對第一個 REST 呼叫進行疑難排解

上一個儲存格是呼叫個人化工具的第一個儲存格。 請確定輸出中的 REST 狀態碼為 <Response [200]> 。 如果您收到錯誤,例如 404,但您確定您的資源金鑰和名稱正確,請重載筆記本。

請確定咖啡計數和使用者都是 4。 如果您收到錯誤,請檢查您是否已上傳所有 3 個 JSON 檔案。

在 Azure 入口網站 中設定計量圖表

在本教學課程稍後,瀏覽器會顯示 10,000 個要求的長時間執行程式,其中包含更新文字方塊。 當長時間執行的進程結束時,在圖表中或總和中可能會比較容易看到。 若要檢視這項資訊,請使用資源所提供的計量。 您現在可以建立圖表,現在您已經完成對服務的要求,然後在長時間執行的程式進行時定期重新整理圖表。

  1. 在Azure 入口網站中,選取您的個人化工具資源。

  2. 在資源導覽中,選取 [監視] 下方的 [計量]。

  3. 在圖表中,選取 [ 新增計量 ]。

  4. 已設定資源和計量命名空間。 您只需要選取成功呼叫的 計量和總 和匯總

  5. 將時間篩選準則變更為過去 4 小時。

    Set up metric chart in Azure portal, adding metric for successful calls for the last 4 hours.

    您應該會在圖表中看到三個成功的呼叫。

產生唯一的事件識別碼

此函式會為每個排名呼叫產生唯一識別碼。 識別碼可用來識別排名和獎勵通話資訊。 此值可能來自商務程式,例如 Web 檢視識別碼或交易識別碼。

儲存格沒有輸出。 呼叫 時,函式會輸出唯一識別碼。

def add_event_id(rankjsonobj):
    eventid = uuid.uuid4().hex
    rankjsonobj["eventId"] = eventid
    return eventid

取得隨機使用者、天氣和一天中的時間

此函式會選取唯一的使用者、天氣和一天中的時間,然後將這些專案新增至 JSON 物件,以傳送至 Rank 要求。

儲存格沒有輸出。 呼叫 函式時,它會傳回隨機使用者的名稱、隨機天氣和一天中的隨機時間。

4 個使用者及其喜好設定的清單 - 僅顯示一些喜好設定,以求簡潔:

{
  "Alice": {
    "Sunny": {
      "Morning": "Cold brew",
      "Afternoon": "Iced mocha",
      "Evening": "Cold brew"
    }...
  },
  "Bob": {
    "Sunny": {
      "Morning": "Cappucino",
      "Afternoon": "Iced mocha",
      "Evening": "Cold brew"
    }...
  },
  "Cathy": {
    "Sunny": {
      "Morning": "Latte",
      "Afternoon": "Cold brew",
      "Evening": "Cappucino"
    }...
  },
  "Dave": {
    "Sunny": {
      "Morning": "Iced mocha",
      "Afternoon": "Iced mocha",
      "Evening": "Iced mocha"
    }...
  }
}
def add_random_user_and_contextfeatures(namesoption, weatheropt, timeofdayopt, rankjsonobj):
    name = namesoption[random.randint(0,3)]
    weather = weatheropt[random.randint(0,2)]
    timeofday = timeofdayopt[random.randint(0,2)]
    rankjsonobj['contextFeatures'] = [{'timeofday': timeofday, 'weather': weather, 'name': name}]
    return [name, weather, timeofday]

新增所有咖啡資料

此函式會將整個咖啡清單新增至 JSON 物件,以傳送至 Rank 要求。

儲存格沒有輸出。 函式會在呼叫 時變更 rankjsonobj

單一咖啡功能的範例如下:

{
    "id": "Cappucino",
    "features": [
    {
        "type": "hot",
        "origin": "kenya",
        "organic": "yes",
        "roast": "dark"

    }
}
def add_action_features(rankjsonobj):
    rankjsonobj["actions"] = actionfeaturesobj

比較預測與已知的使用者喜好設定

呼叫排名 API 之後,會針對每個反復專案呼叫此函式。

此函式會根據天氣和一天中的時間,比較使用者的咖啡喜好設定,以及個人化工具針對這些篩選使用者的建議。 如果建議相符,則會傳回 1 的分數,否則分數為 0。 儲存格沒有輸出。 函式會在呼叫時輸出分數。

def get_reward_from_simulated_data(name, weather, timeofday, prediction):
    if(userpref[name][weather][timeofday] == str(prediction)):
        return 1
    return 0

迴圈呼叫排名和獎勵

下一個資料格是 Notebook 的主要 工作,取得隨機使用者、取得咖啡清單、將兩者傳送至排名 API。 比較預測與使用者的已知喜好設定,然後將獎勵傳回個人化工具服務。

迴圈會執行一段時間 num_requests 。 個人化工具需要數千個呼叫來排名和獎勵,才能建立模型。

傳送至排名 API 的 JSON 範例如下。 為了簡潔起見,咖啡清單不完整。 您可以在 中看到 coffee.json 用於咖啡的完整 JSON。

傳送至排名 API 的 JSON:

{
   'contextFeatures':[
      {
         'timeofday':'Evening',
         'weather':'Snowy',
         'name':'Alice'
      }
   ],
   'actions':[
      {
         'id':'Cappucino',
         'features':[
            {
               'type':'hot',
               'origin':'kenya',
               'organic':'yes',
               'roast':'dark'
            }
         ]
      }
        ...rest of coffee list
   ],
   'excludedActions':[

   ],
   'eventId':'b5c4ef3e8c434f358382b04be8963f62',
   'deferActivation':False
}

排名 API 的 JSON 回應:

{
    'ranking': [
        {'id': 'Latte', 'probability': 0.85 },
        {'id': 'Iced mocha', 'probability': 0.05 },
        {'id': 'Cappucino', 'probability': 0.05 },
        {'id': 'Cold brew', 'probability': 0.05 }
    ],
    'eventId': '5001bcfe3bb542a1a238e6d18d57f2d2',
    'rewardActionId': 'Latte'
}

最後,每個迴圈都會顯示隨機選取使用者、天氣、一天中的時間,以及決定獎勵。 獎勵 1 表示個人化工具資源為指定的使用者、天氣和一天中的時間選取了正確的咖啡類型。

1 Alice Rainy Morning Latte 1

函式使用:

  • 排名:要取得排名 POST REST API。
  • 獎勵:回報獎勵 POST REST API。
def iterations(n, modelCheck, jsonFormat):

    i = 1

    # default reward value - assumes failed prediction
    reward = 0

    # Print out dateTime
    currentDateTime()

    # collect results to aggregate in graph
    total = 0
    rewards = []
    count = []

    # default list of user, weather, time of day
    namesopt = ['Alice', 'Bob', 'Cathy', 'Dave']
    weatheropt = ['Sunny', 'Rainy', 'Snowy']
    timeofdayopt = ['Morning', 'Afternoon', 'Evening']


    while(i <= n):

        # create unique id to associate with an event
        eventid = add_event_id(jsonFormat)

        # generate a random sample
        [name, weather, timeofday] = add_random_user_and_contextfeatures(namesopt, weatheropt, timeofdayopt, jsonFormat)

        # add action features to rank
        add_action_features(jsonFormat)

        # show JSON to send to Rank
        print('To: ', jsonFormat)

        # choose an action - get prediction from Personalizer
        response = requests.post(personalization_rank_url, headers = headers, params = None, json = jsonFormat)

        # show Rank prediction
        print ('From: ',response.json())

        # compare personalization service recommendation with the simulated data to generate a reward value
        prediction = json.dumps(response.json()["rewardActionId"]).replace('"','')
        reward = get_reward_from_simulated_data(name, weather, timeofday, prediction)

        # show result for iteration
        print(f'   {i} {currentDateTime()} {name} {weather} {timeofday} {prediction} {reward}')

        # send the reward to the service
        response = requests.post(personalization_reward_url + eventid + "/reward", headers = headers, params= None, json = { "value" : reward })

        # for every N rank requests, compute total correct  total
         total =  total + reward

        # every N iteration, get last updated model date and time
        if(i % modelCheck == 0):

            print("**** 10% of loop found")

            get_last_updated(modelLastModified)

        # aggregate so chart is easier to read
        if(i % 10 == 0):
            rewards.append( total)
            count.append(i)
             total = 0

        i = i + 1

    # Print out dateTime
    currentDateTime()

    return [count, rewards]

執行 10,000 個反復專案

針對 10,000 個反復專案執行個人化工具迴圈。 這是長時間執行的事件。 請勿關閉執行筆記本的瀏覽器。 定期重新整理Azure 入口網站中的計量圖表,以查看服務的總呼叫數。 當您有大約 20,000 個呼叫時,迴圈的每個反復專案的排名和獎勵呼叫都會完成反復專案。

# max iterations
num_requests = 200

# check last mod date N% of time - currently 10%
lastModCheck = int(num_requests * .10)

jsonTemplate = rankactionsjsonobj

# main iterations
[count, rewards] = iterations(num_requests, lastModCheck, jsonTemplate)

查看改善的圖表結果

countrewards 建立圖表。

def createChart(x, y):
    plt.plot(x, y)
    plt.xlabel("Batch of rank events")
    plt.ylabel("Correct recommendations per batch")
    plt.show()

10,000 個排名要求的執行圖表

執行 函 createChart 式。

createChart(count,rewards)

讀取圖表

此圖表顯示目前預設學習原則的模型成功。

This chart shows the success of the current learning policy for the duration of the test.

在測試結束時的理想目標,迴圈平均成功率接近 100%, 減去探索。 探索的預設值為 20%。

100-20=80

此探索值位於 [組態 ] 頁面上的個人化工具資源 Azure 入口網站。

若要根據資料到排名 API 尋找更好的學習原則,請在入口網站中針對個人化工具迴圈執行 離線評估

執行離線評估

  1. 在Azure 入口網站中,開啟個人化工具資源的 [評估 ] 頁面。

  2. 選取 [ 建立評估 ]。

  3. 輸入評估名稱的必要資料,以及迴圈評估的日期範圍。 日期範圍應該只包含您專注于評估的天數。 In the Azure portal, open the Personalizer resource's Evaluations page. Select Create Evaluation. Enter the evaluation name and date range.

    執行此離線評估的目的是要判斷此迴圈中所使用的功能和動作是否有更好的學習原則。 若要尋找更好的學習原則,請確定 優化探索 已開啟。

  4. 選取 [ 確定 ] 開始評估。

  5. 評估 頁面會列出新的評估及其目前狀態。 視您擁有的資料量而定,此評估可能需要一些時間。 您可以在幾分鐘後返回此頁面,以查看結果。

  6. 評估完成時,請選取評估,然後選取 [比較不同的學習原則 ]。 這會顯示可用的學習原則,以及它們與資料的行為方式。

  7. 選取資料表中最上層的學習原則,然後選取 [ 套用 ]。 這會將 最佳 學習原則套用至您的模型和重新定型。

將更新模型頻率變更為 5 分鐘

  1. 在Azure 入口網站中,仍在個人化工具資源上,選取 [ 設定 ] 頁面。
  2. 模型更新頻率 獎勵等候時間 變更為 5 分鐘,然後選取 [ 儲存 ]。

深入瞭解 獎勵等候時間和 模型更新頻率

#Verify new learning policy and times
get_service_settings()

確認輸出的 rewardWaitTimemodelExportFrequency 都設定為 5 分鐘。

-----checking model
<Response [200]>
{'creationTime': '0001-01-01T00:00:00+00:00', 'lastModifiedTime': '0001-01-01T00:00:00+00:00'}
-----model updated: "0001-01-01T00:00:00+00:00"
-----checking service settings
<Response [200]>
{...learning policy...}
<Response [200]>
{'rewardWaitTime': '00:05:00', 'defaultReward': 0.0, 'rewardAggregation': 'earliest', 'explorationPercentage': 0.2, 'modelExportFrequency': '00:05:00', 'logRetentionDays': -1}
User count 4
Coffee count 4

驗證新的學習原則

返回 Azure Notebooks 檔案並繼續執行相同的迴圈,但只執行 2,000 個反復專案。 定期重新整理Azure 入口網站中的計量圖表,以查看服務的總呼叫數。 當您有大約 4,000 個呼叫時,迴圈的每個反復專案的排名和獎勵呼叫都會完成反復專案。

# max iterations
num_requests = 2000

# check last mod date N% of time - currently 10%
lastModCheck2 = int(num_requests * .10)

jsonTemplate2 = rankactionsjsonobj

# main iterations
[count2, rewards2] = iterations(num_requests, lastModCheck2, jsonTemplate)

2,000 個排名要求的執行圖表

執行 函 createChart 式。

createChart(count2,rewards2)

檢閱第二張圖表

第二個圖表應該會顯示與使用者喜好設定一致的排名預測可見增加。

The second chart should show a visible increase in Rank predictions aligning with user preferences.

清除資源

如果您不打算繼續進行教學課程系列,請清除下列資源:

  • 刪除您的 Azure Notebook 專案。
  • 刪除個人化工具資源。

下一步

此範例中使用的 Jupyter Notebook 和資料檔案 可在個人化工具的 GitHub 存放庫上取得。