תבנית ליצירת אתר פשוט שמסווג תמונות

1. לפני שמתחילים

בשיעור ה-Lab הזה תלמדו איך להריץ מסקנה של סיווג תמונות מאתר באמצעות TensorFlow Serving עם REST ו-gRPC.

דרישות מוקדמות

  • ידע בסיסי בפיתוח אתרים, כמו HTML ו-JavaScript
  • ידע בסיסי בלמידת מכונה עם TensorFlow, כמו אימון ופריסה
  • ידע בסיסי בטרמינלים וב-Docker

מה תלמדו

  • איך למצוא מודלים שעברו אימון מראש לסיווג תמונות ב-TensorFlow Hub.
  • איך לבנות אתר פשוט ולבצע חיזויים באמצעות מודל סיווג התמונות שהורד דרך TensorFlow Serving (REST ו-gRPC).
  • איך להציג את תוצאת הזיהוי בממשק המשתמש.

מה נדרש

2. להגדרה

כדי להוריד את הקוד של ה-Codelab הזה:

  1. עוברים אל מאגר ה-GitHub הזה.
  2. לוחצים על Code > Download zip (קוד > הורדת קובץ zip) כדי להוריד את כל הקוד של ה-codelab הזה.

a72f2bb4caa9a96.png

  1. מבטלים את הדחיסה של קובץ ה-ZIP שהורדתם כדי לחלץ תיקיית בסיס codelabs עם כל המשאבים שאתם צריכים.

ב-codelab הזה, צריך רק את הקבצים בספריית המשנה TFServing/ImageClassificationWeb במאגר, שמכילה שתי תיקיות:

  • התיקייה starter מכילה את קוד ההתחלה שעליו תבנו את הפתרון במעבדת הקוד הזו.
  • התיקייה finished מכילה את הקוד המלא של האפליקציה לדוגמה.

3. התקנת יחסי התלות

כדי להתקין את יחסי התלות:

  • בטרמינל, עוברים לתיקייה starter ומתקינים את חבילות ה-NPM הנדרשות:
npm install

4. הפעלת אתר ההתחלה

משתמשים ב- Web Server for Chrome כדי לטעון את הקובץ TFServing/ImageClassificationWeb/starter/dist/index.html:

  1. מזינים Chrome://apps/ בסרגל הכתובות של Chrome ואז מחפשים את Web Server for Chrome ברשימת האפליקציות.
  2. מפעילים את Web Server for Chrome ואז בוחרים את התיקייה TFServing/ImageClassificationWeb/starter/dist/.
  3. לוחצים על לחצן החלפת המצב של Web Server כדי להפעיל אותו, ואז עוברים לכתובת http://localhost:8887/ בדפדפן.

f7b43cd44ebf1f1b.png

הפעלת האתר ובדיקה שלו

עכשיו האתר אמור להופיע. ממשק המשתמש די פשוט: יש תמונה של חתול שרוצים לסווג, והמשתמש יכול לשלוח את הנתונים אל ה-Backend באמצעות REST או gRPC. הקצה העורפי מבצע סיווג של התמונה ומחזיר את תוצאת הסיווג לאתר, שבו התוצאה מוצגת.

837d97a27c59a0b3.png

אם לוחצים על סיווג, לא קורה כלום כי עדיין אין תקשורת עם העורף.

5. פריסת מודל לסיווג תמונות באמצעות TensorFlow Serving

סיווג תמונות הוא משימה נפוצה מאוד של למידת מכונה (ML), שבה מסווגים תמונה לקטגוריות מוגדרות מראש על סמך התוכן העיקרי של התמונה. דוגמה לסיווג פרחים:

a6da16b4a7665db0.png

יש מספר מודלים של סיווג תמונות שעברו אימון מראש ב-TensorFlow Hub. ב-Codelab הזה משתמשים במודל פופולרי של Inception v3.

כדי לפרוס את המודל לסיווג תמונות באמצעות TensorFlow Serving:

  1. מורידים את קובץ המודל Inception v3.
  2. מבטלים את הדחיסה של קובץ .tar.gz שהורדתם באמצעות כלי לביטול דחיסה, כמו 7-Zip.
  3. יוצרים תיקייה בשם inception_v3 ואז יוצרים בתוכה תיקיית משנה בשם 123.
  4. מעבירים את התיקייה variables ואת הקובץ saved_model.pb שחולצו לתיקיית המשנה 123.

אפשר להתייחס לתיקייה inception_v3 כאל התיקייה SavedModel. ‫123 היא דוגמה למספר גרסה. אם רוצים, אפשר לבחור מספר אחר.

מבנה התיקיות צריך להיראות כמו בתמונה הזו:

21a8675ac8d31907.png

הפעלת TensorFlow Serving

  • בטרמינל, מפעילים את TensorFlow Serving עם Docker, אבל מחליפים את PATH/TO/SAVEDMODEL בנתיב המוחלט של התיקייה inception_v3 במחשב.
docker pull tensorflow/serving

docker run -it --rm -p 8500:8500 -p 8501:8501 -v "PATH/TO/SAVEDMODEL:/models/inception" -e MODEL_NAME=inception tensorflow/serving

מערכת Docker מורידה אוטומטית את תמונת TensorFlow Serving קודם, וזה לוקח דקה. לאחר מכן, TensorFlow Serving אמור להתחיל לפעול. היומן צריך להיראות כמו קטע הקוד הבא:

2022-02-25 06:01:12.513231: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:206] Restoring SavedModel bundle.
2022-02-25 06:01:12.585012: I external/org_tensorflow/tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 3000000000 Hz
2022-02-25 06:01:13.395083: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running initialization op on SavedModel bundle at path: /models/inception/123
2022-02-25 06:01:13.837562: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:277] SavedModel load for tags { serve }; Status: success: OK. Took 1928700 microseconds.
2022-02-25 06:01:13.877848: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/inception/123/assets.extra/tf_serving_warmup_requests
2022-02-25 06:01:13.929844: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: inception version: 123}
2022-02-25 06:01:13.985848: I tensorflow_serving/model_servers/server_core.cc:486] Finished adding/updating models
2022-02-25 06:01:13.985987: I tensorflow_serving/model_servers/server.cc:367] Profiler service is enabled
2022-02-25 06:01:13.988994: I tensorflow_serving/model_servers/server.cc:393] Running gRPC ModelServer at 0.0.0.0:8500 ...
[warn] getaddrinfo: address family for nodename not supported
2022-02-25 06:01:14.033872: I tensorflow_serving/model_servers/server.cc:414] Exporting HTTP/REST API at:localhost:8501 ...
[evhttp_server.cc : 245] NET_LOG: Entering the event loop ...

6. הגדרת שרת Proxy של Envoy

נכון לעכשיו, TensorFlow Serving לא מגדיר את הכותרת Access-Control-Allow-Origin, ולכן הדפדפן חוסם את הבקשה מ-JavaScript של חזית האתר אל TensorFlow Serving מטעמי אבטחה. כדי לעקוף את הבעיה הזו, צריך להשתמש בשרת proxy, כמו Envoy, כדי להעביר את הבקשה מ-JavaScript לקצה העורפי של TensorFlow Serving.

הפעלת Envoy

  • בטרמינל, מורידים את תמונת Envoy ומפעילים את Envoy עם Docker, אבל מחליפים את placeholder PATH/TO/ENVOY-CUSTOM.YAML בנתיב המוחלט של הקובץ envoy-custom.yaml בתיקייה starter.
docker pull envoyproxy/envoy-dev:fd3e8370ddb7a96634c192d1461516e6de1d1797

docker run --add-host host.docker.internal:host-gateway --rm -it -p 9901:9901 -p 8000:8000 -p 8080:8080 -v PATH/TO/ENVOY-CUSTOM.YAML:/envoy-custom.yaml envoyproxy/envoy-dev:fd3e8370ddb7a96634c192d1461516e6de1d1797 -c /envoy-custom.yaml

מערכת Docker מורידה קודם את תמונת Envoy באופן אוטומטי. לאחר מכן, Envoy אמור להתחיל לפעול. היומן צריך להיראות כמו קטע הקוד הבא:

[2022-03-02 07:51:48.563][1][info][main] [source/server/server.cc:436]   response trailer map: 152 bytes: grpc-message,grpc-status
[2022-03-02 07:51:48.681][1][info][main] [source/server/server.cc:772] runtime: {}
[2022-03-02 07:51:48.682][1][info][admin] [source/server/admin/admin.cc:134] admin address: 0.0.0.0:9901
[2022-03-02 07:51:48.683][1][info][config] [source/server/configuration_impl.cc:127] loading tracing configuration
[2022-03-02 07:51:48.683][1][info][config] [source/server/configuration_impl.cc:87] loading 0 static secret(s)
[2022-03-02 07:51:48.683][1][info][config] [source/server/configuration_impl.cc:93] loading 2 cluster(s)
[2022-03-02 07:51:48.687][1][info][config] [source/server/configuration_impl.cc:97] loading 2 listener(s)
[2022-03-02 07:51:48.694][1][info][config] [source/server/configuration_impl.cc:109] loading stats configuration
[2022-03-02 07:51:48.696][1][info][main] [source/server/server.cc:868] starting main dispatch loop
[2022-03-02 07:51:48.881][1][info][runtime] [source/common/runtime/runtime_impl.cc:446] RTDS has finished initialization
[2022-03-02 07:51:48.881][1][info][upstream] [source/common/upstream/cluster_manager_impl.cc:207] cm init: all clusters initialized
[2022-03-02 07:51:48.881][1][info][main] [source/server/server.cc:849] all clusters initialized. initializing init manager
[2022-03-02 07:51:48.881][1][info][config] [source/server/listener_manager_impl.cc:784] all dependencies initialized. starting workers
[2022-03-02 07:51:48.902][1][warning][main] [source/server/server.cc:747] there is no configured limit to the number of allowed active connections. Set a limit via the runtime key overload.global_downstream_max_connections

7. קישור האתר ל-TensorFlow באמצעות REST

הקצה העורפי מוכן עכשיו, כך שאפשר לשלוח בקשות מהלקוח אל TensorFlow Serving כדי לסווג תמונות. יש שתי דרכים לשלוח בקשות ל-TensorFlow Serving:

  • REST
  • gRPC

שליחת בקשות וקבלת תשובות באמצעות REST

יש שלושה שלבים פשוטים לשליחה ולקבלה של בקשות באמצעות REST:

  1. יוצרים את בקשת ה-REST.
  2. שולחים את בקשת ה-REST אל TensorFlow Serving.
  3. מחפשים את התוצאה החזויה בתגובת ה-REST ומציגים אותה.

השלבים האלה מתבצעים בקובץ src/index.js.

יצירת בקשת REST

בשלב הזה, הפונקציה classify_img() לא שולחת את בקשת ה-REST ל-TensorFlow Serving. כדי ליצור בקשת REST, צריך להטמיע קודם את הענף הזה של REST:

if (radioButtons[0].checked) {
    console.log('Using REST');
    // TODO: Add code to send a REST request to TensorFlow Serving.

} 

‫TensorFlow Serving מצפה לבקשת POST שמכילה את טנסור התמונה של מודל Inception v3 שבו אתם משתמשים, ולכן אתם צריכים לחלץ את ערכי ה-RGB מכל פיקסל בתמונה למערך, ואז לעטוף את המערך ב-JSON, שהוא המטען הייעודי (payload) של הבקשה.

  • מוסיפים את הקוד הבא לענף REST:
//Create the REST request.
let imgTensor = new Array();
let pixelArray = new Array();
context.drawImage(img, 0, 0);
for(let i=0; i<inputImgHeight; i++) {
    pixelArray[i] = new Array();
    for (let j=0; j<inputImgWidth; j++) {
        pixelArray[i][j] = new Array();
        pixelArray[i][j].push(context.getImageData(i, j, 1, 1).data[0]/255); 
        pixelArray[i][j].push(context.getImageData(i, j, 1, 1).data[1]/255); 
        pixelArray[i][j].push(context.getImageData(i, j, 1, 1).data[2]/255); 
    }
}
imgTensor.push(pixelArray);

const RESTURL = 'http://localhost:8000/v1/models/inception:predict';        
let xhr = new XMLHttpRequest();
xhr.open('POST', RESTURL);
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8;');
let data = JSON.stringify({
    instances: imgTensor
});    
xhr.onload = () => {

}
xhr.onerror = () => {
    console.log('REST request error');
}

שליחת בקשת REST אל TensorFlow Serving

עכשיו אפשר לשלוח את הבקשה.

  • מוסיפים את הקוד הזה מיד אחרי הקוד שלמעלה בענף REST:
// Send the REST request.
xhr.send(data);

עיבוד התגובה של REST מ-TensorFlow Serving

מודל Inception v3 מחזיר מערך של הסתברויות שהתמונה שייכת לקטגוריות מוגדרות מראש. אם התחזית מצליחה, צריך להציג את הקטגוריה הסבירה ביותר בממשק המשתמש.

מטמיעים את ה-onload() listener כדי לטפל בתגובה.

xhr.onload = () => {

}
  • מוסיפים את הקוד הזה לרכיב listener‏ onload():
// Process the REST response.
const response = JSON.parse(xhr.responseText);
const maxIndex = argmax(response['predictions'][0])
document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;

עכשיו המאזין מחלץ מהתגובה את ההסתברויות החזויות, מזהה את הקטגוריה הסבירה ביותר של האובייקט ומציג את התוצאה בממשק המשתמש.

הפעלה

  1. בטרמינל, עוברים לתיקייה starter ומשתמשים ב-webpack כדי לארוז את כל קובצי ה-JavaScript לקובץ אחד שאפשר להטמיע בקובץ dist/index.html:
npm install -g npx
npm install --save-dev webpack
npx webpack
  1. מרעננים את הדף http://localhost:8887/ בדפדפן ולוחצים על REST > Classify.

באתר מוצגת הקטגוריה החזויה 286, שמשויכת לתווית Egyptian Cat במערך הנתונים ImageNet.

c865a93b9b58335d.png

8. חיבור האתר ל-TensorFlow Serving דרך gRPC

בנוסף ל-REST, ‏ TensorFlow Serving תומך גם ב-gRPC.

b6f4449c2c850b0e.png

‫gRPC היא מסגרת מודרנית בקוד פתוח לקריאה לשירות מרוחק (RPC) עם ביצועים גבוהים, שיכולה לפעול בכל סביבה. הוא יכול לחבר ביעילות שירותים במרכזי נתונים שונים, עם תמיכה בחיבורים לטעינת איזון, מעקב, בדיקת תקינות ואימות. בפועל, נראה ש-gRPC מניב ביצועים טובים יותר מ-REST.

שליחת בקשות וקבלת תגובות באמצעות gRPC

יש ארבע פעולות פשוטות:

  1. אופציונלי: יוצרים את קוד ה-stub של לקוח gRPC.
  2. יוצרים את בקשת ה-gRPC.
  3. שולחים את בקשת gRPC אל TensorFlow Serving.
  4. לחלץ את התוצאה החזויה מהתגובה של gRPC ולהציג אותה בממשק המשתמש.

את השלבים האלה מבצעים בקובץ src/index.js.

אופציונלי: יצירת קוד stub של לקוח gRPC

כדי להשתמש ב-gRPC עם TensorFlow Serving, צריך לפעול לפי תהליך העבודה של gRPC. פרטים נוספים זמינים במסמכי התיעוד של gRPC.

a9d0e5cb543467b4.png

‫TensorFlow Serving ו-TensorFlow מגדירים את קובצי ה-.proto בשבילכם. החל מגרסה 2.8 של TensorFlow ו-TensorFlow Serving, אלה הקבצים שנדרשים:.proto

tensorflow/core/example/example.proto
tensorflow/core/example/feature.proto
tensorflow/core/protobuf/struct.proto
tensorflow/core/protobuf/saved_object_graph.proto
tensorflow/core/protobuf/saver.proto
tensorflow/core/protobuf/trackable_object_graph.proto
tensorflow/core/protobuf/meta_graph.proto
tensorflow/core/framework/node_def.proto
tensorflow/core/framework/attr_value.proto
tensorflow/core/framework/function.proto
tensorflow/core/framework/types.proto
tensorflow/core/framework/tensor_shape.proto
tensorflow/core/framework/full_type.proto
tensorflow/core/framework/versions.proto
tensorflow/core/framework/op_def.proto
tensorflow/core/framework/graph.proto
tensorflow/core/framework/tensor.proto
tensorflow/core/framework/resource_handle.proto
tensorflow/core/framework/variable.proto

tensorflow_serving/apis/inference.proto
tensorflow_serving/apis/classification.proto
tensorflow_serving/apis/predict.proto
tensorflow_serving/apis/regression.proto
tensorflow_serving/apis/get_model_metadata.proto
tensorflow_serving/apis/input.proto
tensorflow_serving/apis/prediction_service.proto
tensorflow_serving/apis/model.proto
  • בטרמינל, עוברים לתיקייה starter/src/proto/ ומייצרים את ה-stub:
bash generate_grpc_stub_js.sh

יצירת בקשת gRPC

בדומה לבקשת REST, יוצרים את בקשת gRPC בענף gRPC.

if (connectionMode[picker.selectedRow(inComponent: 0)] == "REST") {

}
else {
    print("Using gRPC")
    // TODO: Add code to send a gRPC request to TensorFlow Serving.
    
}
  • מוסיפים את הקוד הזה לענף gRPC:
// Create the gRPC request.
const PredictModule = require('./proto/generated/tensorflow_serving/apis/predict_pb.js');
const PredictionServiceClientModule = require('./proto/generated/tensorflow_serving/apis/prediction_service_grpc_web_pb.js');
const ModelModule = require('./proto/generated/tensorflow_serving/apis/model_pb.js');
const TensorModule = require('./proto/generated/tensorflow/core/framework/tensor_pb.js');

const GPRCURL = 'http://localhost:8080';
const stub = new PredictionServiceClientModule.PredictionServiceClient(GPRCURL);

const modelSpec = new ModelModule.ModelSpec();
modelSpec.setName('inception');

const tensorProto = new TensorModule.TensorProto();
const tensorShapeProto = new TensorModule.TensorShapeProto();

const batchDim = (new TensorModule.TensorShapeProto.Dim()).setSize(1);
const heightDim = (new TensorModule.TensorShapeProto.Dim()).setSize(inputImgHeight);
const widthDim = (new TensorModule.TensorShapeProto.Dim()).setSize(inputImgWidth);
const channelDim = (new TensorModule.TensorShapeProto.Dim()).setSize(3);

tensorShapeProto.setDimList([batchDim, heightDim, widthDim, channelDim]);

tensorProto.setDtype(proto.tensorflow.DataType.DT_FLOAT);
tensorProto.setTensorShape(tensorShapeProto);
context.drawImage(img, 0, 0);
for(let i=0; i<inputImgHeight; i++) {
    for (let j=0; j<inputImgWidth; j++) {
        tensorProto.addFloatVal(context.getImageData(i, j, 1, 1).data[0]/255); 
        tensorProto.addFloatVal(context.getImageData(i, j, 1, 1).data[1]/255); 
        tensorProto.addFloatVal(context.getImageData(i, j, 1, 1).data[2]/255); 
    }
}

const predictionServiceRequest = new PredictModule.PredictRequest();
predictionServiceRequest.setModelSpec(modelSpec);
predictionServiceRequest.getInputsMap().set('inputs', tensorProto);

שליחת בקשת gRPC אל TensorFlow Serving

עכשיו אפשר לשלוח את הבקשה.

  • מוסיפים את הקוד הזה מיד אחרי הקוד בענף gRPC בקטע הקוד הקודם:
// Send the gRPC request.
stub.predict(predictionServiceRequest, {}, function(err, response) {
    // TODO: Add code to process the response.
});

עיבוד התגובה של gRPC מ-TensorFlow Serving

לבסוף, מטמיעים את פונקציית הקריאה החוזרת שמופיעה למעלה כדי לטפל בתגובה.

  • מוסיפים את הקוד הזה לגוף הפונקציה בקטע הקוד הקודם:
// Process the gRPC response.
if (err) {
    console.log(err.code);
    console.log(err.message);
} 
else {
    const maxIndex = argmax(response.getOutputsMap().get('logits').getFloatValList());
    document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;
}

עכשיו המאזין מחלץ מהתגובה את ההסתברויות החזויות, מזהה את הקטגוריה הסבירה ביותר של האובייקט ומציג את התוצאה בממשק המשתמש.

הפעלה

  1. במסוף, משתמשים ב-webpack כדי לארוז את כל קובצי ה-JavaScript לקובץ אחד שאפשר להטמיע בקובץ index.html:
npx webpack
  1. מרעננים את הכתובת http://localhost:8887/ בדפדפן.
  2. לוחצים על gRPC > Classify (סיווג).

באתר מוצגת הקטגוריה החזויה של 286, שמשויכת לתווית Egyptian Cat במערך הנתונים ImageNet.

9. מזל טוב

השתמשתם ב-TensorFlow Serving כדי להוסיף לאתר שלכם יכולות של סיווג תמונות.

מידע נוסף