Montera och aktivera en godkänd modell

Använd uppmätta resultat för att hitta bättre modellkoefficienter. I detta exempel anpassar en Python-applikation en viktprediktionsformel, kontrollerar den mot separata valideringsposter och godkänner den om förutsägelserna förbättras.

Applikationen (kallad supervisor) lagrar godkända koefficienter för senare prognoser. Fitting körs i NumPy, utanför dataflödesgrafen. En inbyggd mappningstransformering utvärderar sedan den godkända formeln.

Diagram över matchade objekt och mätta vikter skickade till en extern applikation för anpassning och godkännande. Den lagrar de godkända koefficienterna. En separat prediktionsgraf läser dessa koefficienter och utvärderar formeln för varje ny avläsning.

Hur delarna ansluter

  1. Exportera matcher. Exportgrafen vidarebefordrar slutförda matcher till programmet.
  2. Passa in och godkänn externt. Python-exemplet beräknar en kandidatpost och ett granskningsbeslut. Din chef ansvarar för godkännande och beständighet.
  3. Lagra godkända koefficienter. Övervakaren skriver hela den aktiva modellposten till tillståndslagret.

För att läsa den posten, se Läs koefficienter från state store.

För leverans via MQTT istället för state-store-lookup, se Leverera godkända modelluppdateringar via MQTT.

Förutsättningar

Fyll i förutsättningarna för det delade exemplet, inklusive default-profilen och factory-mqtt-slutpunkten. Använd YAML för felsökning och Azure CLI eller Bicep för produktionshantering.

För CLI-distributioner anger du AIO_INSTANCE_NAME och RESOURCE_GROUP för din befintliga instans och resursgrupp. För Bicep-utrullningar, sätt CUSTOM_LOCATION_NAME även till instansens befintliga anpassade plats. distributionsflikarna refererar till den befintliga instansen, profilen och endpointen; de skapar dem inte. Exportgrafen kräver ingen register-endpoint eftersom den inte har några transformeringar.

Fitting-skriptet kräver också Python 3 och NumPy. Exemplet för state-store-skrivning använder mosquitto_pub- och mosquitto_sub-MQTT-klienterna.

Förbered matchade poster

Förbered färdiga matcher med de egenskapsvärden som används av varje prediktion. Inriktnings- och korrelationsmönstret visar ett sätt att producera dem.

Anpassa modellen till en kandidat från syntetiskt matchade register

Börja med modell v7: weight = 2 * speed + 0.5 * temperature + 175. Varje matchande post måste innehålla egenskapsvärdena som användes för den posten, dess uppmätta vikt och modellversionen som gav förutsägelsen. Till exempel är träningsindata till övervakaren:

{"assetId":"line-1","pieceId":"part-1","modelVersion":7,"speed":8,"temperature":18,"predictedWeight":200,"measuredWeight":198,"residual":-2}

Använd dessa syntetiska matchningar: fyra för anpassning och två för validering. Håll setens del-ID:n åtskilda.

Använd Bit Hastighet Temperatur Uppmätt vikt Prediktion för modell v7
Utbildning part-1 8 18 198 200
Utbildning part-2 10 18 202 204
Utbildning part-3 8 22 200 202
Utbildning part-4 10 22 204 206
Validation part-5 9 20 201 203
Validation part-6 11 24 207 209

Anpassningsoperationen minimerar summan av kvadrerade fel för b0 + b1 * speed + b2 * temperature. Dessa utbildningsvärden ger:

  • Hastighetskoefficient: (202 - 198) / (10 - 8) = 2.
  • Temperaturkoefficient: (200 - 198) / (22 - 18) = 0.5.
  • Intercept: 198 - 2 * 8 - 0.5 * 18 = 173.

Kandidaten förutspår 201 och 207 på de två valideringsobjekten. Rotmedelkvadratfelet (RMSE) är 0; modell v7:s RMSE är 2. Välj en lämplig urvalsstorlek för validering för din applikation.

För live-inmatning, driftsätt exportgrafen genom att använda en av följande metoder. Applikationen prenumererar på factory/supervisor-input; publicerar färdiga matcher till factory/matches. Den fristående Python-demon nedan använder tabellen direkt och kräver inte MQTT.

  1. I vyn Operations öppnar du din instans och väljer Dataflödesgraf>Skapa dataflödesgraf.
  2. Ge grafen namnet export-training-matches och välj profilen default.
  3. Lägg till källa source med endpoint factory-mqtt och topic factory/matches.
  4. Lägg till destination destination med endpoint factory-mqtt och topic factory/supervisor-input.
  5. Koppla source direkt till destination, utan att lägga till en transform, och välj Spara.

Grafen vidarebefordrar varje avslutad match oförändrad. Applikationen samlar in träffar, håller tränings- och valideringsposter åtskilda och anpassar en kandidatmodell.

Ett exempel på kandidat som genereras av den applikationen på factory/model-proposals:

{
  "kind": "modelProposal",
  "assetId": "line-1",
  "updateId": "update-42",
  "expectedModelVersion": 7,
  "modelVersion": 8,
  "coefficients": {
    "speedCoefficient": 2,
    "temperatureCoefficient": 0.5,
    "intercept": 173
  },
  "validation": {
    "trainingPieceIds": ["part-1", "part-2", "part-3", "part-4"],
    "validationPieceIds": ["part-5", "part-6"],
    "activeRmse": 2,
    "candidateRmse": 0
  }
}

För detta exempel kräver godkännande den konfigurerade tillgången line-1, aktiv version 7, kandidatversion 8, ändliga koefficienter, disjunkta stickprovsmängder, lägre validerings-RMSE och en interceptändring som inte är större än 3. interceptet ändrades med -2, så denna kandidat passerar. En kandidat med intercept 180 misslyckas med sista kontrollen. Neka ett återförsök när v8 har aktiverats. Dessa kontroller är exempel på applikationspolicys.

Spara detta exempel som fit-model.py och kör python3 fit-model.py. Den använder de sex posterna ovan och skriver ut ett kandidat- och granskningsbeslut. Din övervakare hanterar MQTT och skrivningar av aktivt tillstånd; skriptet anpassar bara och granskar.

import json
import numpy as np

# Each row is (piece ID, speed, temperature, measured weight).
training = [
    ("part-1", 8, 18, 198),
    ("part-2", 10, 18, 202),
    ("part-3", 8, 22, 200),
    ("part-4", 10, 22, 204),
]
validation = [("part-5", 9, 20, 201), ("part-6", 11, 24, 207)]
if {r[0] for r in training} & {r[0] for r in validation}:
    raise ValueError("Training and validation pieces must be distinct.")

x = np.array([[1, speed, temperature] for _, speed, temperature, _ in training], dtype=float)
y = np.array([weight for _, _, _, weight in training], dtype=float)
candidate, _, rank, _ = np.linalg.lstsq(x, y, rcond=None)
if rank != 3 or not np.isfinite(candidate).all():
    raise ValueError("The fit needs three independent, finite parameters.")

vx = np.array([[1, speed, temperature] for _, speed, temperature, _ in validation], dtype=float)
vy = np.array([weight for _, _, _, weight in validation], dtype=float)
active = np.array([175, 2, 0.5])  # Intercept, speed, temperature for v7.
active_rmse = float(np.sqrt(np.mean((vx @ active - vy) ** 2)))
candidate_rmse = float(np.sqrt(np.mean((vx @ candidate - vy) ** 2)))
proposal = {
    "kind": "modelProposal",
    "assetId": "line-1",
    "updateId": "update-42",
    "expectedModelVersion": 7,
    "modelVersion": 8,
    "coefficients": {
        "intercept": float(candidate[0]),
        "speedCoefficient": float(candidate[1]),
        "temperatureCoefficient": float(candidate[2]),
    },
    "validation": {
        "trainingPieceIds": [row[0] for row in training],
        "validationPieceIds": [row[0] for row in validation],
        "activeRmse": active_rmse,
        "candidateRmse": candidate_rmse,
    },
}


def review(update, active_model, committed_update_ids):
    """Example policy for candidates produced by this trusted fitting process."""
    coefficients = update["coefficients"]
    metrics = update["validation"]
    if update["updateId"] in committed_update_ids:
        return {"approved": False, "reason": "duplicate update"}
    if update["assetId"] != active_model["assetId"]:
        return {"approved": False, "reason": "wrong asset"}
    if (update["expectedModelVersion"] != active_model["modelVersion"]
            or update["modelVersion"] != active_model["modelVersion"] + 1):
        return {"approved": False, "reason": "stale or unexpected version"}
    if (set(metrics["trainingPieceIds"]) & set(metrics["validationPieceIds"])
            or not metrics["trainingPieceIds"] or not metrics["validationPieceIds"]):
        return {"approved": False, "reason": "invalid validation split"}
    numbers = [*coefficients.values(), metrics["activeRmse"], metrics["candidateRmse"]]
    if not np.isfinite(numbers).all():
        return {"approved": False, "reason": "non-finite coefficient or metric"}
    if metrics["candidateRmse"] < 0 or metrics["activeRmse"] < 0:
        return {"approved": False, "reason": "invalid RMSE"}
    if metrics["candidateRmse"] >= metrics["activeRmse"]:
        return {"approved": False, "reason": "no validation improvement"}
    if abs(coefficients["intercept"] - active_model["intercept"]) > 3:
        return {"approved": False, "reason": "intercept change exceeds policy"}
    return {"approved": True, "reason": "candidate passes example policy"}


active_record = {"assetId": "line-1", "modelVersion": 7, "intercept": 175}
decision = review(proposal, active_record, set())
print(json.dumps({"candidate": proposal, "review": decision}, allow_nan=False))

Anpassning av flyttal kan skriva ut värden nära 173, 2, 0.5 och 0. Använd numeriska toleranser och spara de exakta godkända koefficienterna. Review förväntar sig välformulerade kandidater och mätvärden från den betrodda anpassningsprocessen. Innan den godkända posten skrivs kontrollerar övervakaren den aktiva versionen igen och registrerar godkännande- och uppdaterings-ID i sin egen persistenta logg. Serialisera granskningar och skrivningar per tillgång så att konkurrerande uppdateringar inte båda kan klara samma versionskontroll.

Lagra godkända koefficienter

Efter godkännande skriver handledaren en komplett aktiv modellpost under nyckeln factory-active-models. Inledningsvis innehåller den:

{"assetId":"line-1","modelVersion":7,"speedCoefficient":2,"temperatureCoefficient":0.5,"intercept":175}

Efter att ha accepterat update-42, ersätt det med:

{"assetId":"line-1","modelVersion":8,"speedCoefficient":2,"temperatureCoefficient":0.5,"intercept":173}

För en fristående uppslagsdemo, skriv den syntetiska v8-posten direkt. För en anpassad till data-modell, ersätt alla tre koefficienter nedan med de exakt godkända värdena. För att jämföra v7 och v8, kör först skriptet med modelVersion: 7 och intercept: 175, återställ sedan v8-värdena och kör det igen.

Använd state-store-protokollet SET . Spara det här Python-skriptet som write-model.py; den skapar begäran-filen med de nödvändiga byte-längderna.

import json
from pathlib import Path

record = {
    "assetId": "line-1",
    "modelVersion": 8,
    "speedCoefficient": 2,
    "temperatureCoefficient": 0.5,
    "intercept": 173,
}
parts = [b"SET", b"factory-active-models", json.dumps(record, separators=(",", ":")).encode("utf-8")]
request = b"*3\r\n" + b"".join(
    b"$" + str(len(part)).encode("ascii") + b"\r\n" + part + b"\r\n"
    for part in parts
)
Path("set-model.resp").write_bytes(request)

Vid en debug-skrivning, generera filen och skicka den sedan med de nödvändiga MQTT v5-egenskaperna. Kör abonnenten först i en annan terminal; lägg till din brokers autentisering och TLS-alternativ i båda MQTT-kommandona.

Håll writerns klocka inom en minut från brokerns klocka. Egenskapen __ts innehåller tidsstämpeln för begäran. Om svaret rapporterar att tidsstämpeln ligger för långt fram i tiden, rätta klockavvikelsen innan du försöker igen.

mosquitto_sub -h <BROKER_HOST> -p <BROKER_PORT> -V mqttv5 -q 1 \
  -F '%D %p' \
  -t 'clients/model-writer/services/statestore/_any_/command/invoke/response'
python3 write-model.py
mosquitto_pub -h <BROKER_HOST> -p <BROKER_PORT> -V mqttv5 -q 1 \
  -t 'statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invoke' \
  -D publish response-topic 'clients/model-writer/services/statestore/_any_/command/invoke/response' \
  -D publish correlation-data 'update-42' \
  -D publish user-property __ts "$(date +%s%3N):0:model-writer" \
  -f set-model.resp

En lyckad skrivning visar update-42 +OK: begärandets korrelationsdata följt av svarspayloaden. För transport- och auktorisationskrav, se state store-protokollreferensen. Manualen SET ersätter posten utan en kontroll av förväntad modellversion. Använd en enda auktoriserad skrivare för denna demo. I din applikation, behåll den godkända historiken och uppdatera ID:n utanför prediktorn för återställning och dubblettkontroller. För att rulla tillbaka formeln, godkänn de gamla koefficienterna som en ny version.

För grafen som läser den lagrade modellen och beräknar prediktioner, se Läs koefficienter från tillståndslagringen.