1. לפני שמתחילים
בשיעור ה-Lab הזה תלמדו איך להריץ מסקנה של סיווג תמונות מאתר באמצעות TensorFlow Serving עם REST ו-gRPC.
דרישות מוקדמות
- ידע בסיסי בפיתוח אתרים, כמו HTML ו-JavaScript
- ידע בסיסי בלמידת מכונה עם TensorFlow, כמו אימון ופריסה
- ידע בסיסי בטרמינלים וב-Docker
מה תלמדו
- איך למצוא מודלים שעברו אימון מראש לסיווג תמונות ב-TensorFlow Hub.
- איך לבנות אתר פשוט ולבצע חיזויים באמצעות מודל סיווג התמונות שהורד דרך TensorFlow Serving (REST ו-gRPC).
- איך להציג את תוצאת הזיהוי בממשק המשתמש.
מה נדרש
- Docker
- Google Chrome
- Web Server for Chrome
- Node.js ו-NPM
- Bash
- קומפיילר של מאגר פרוטוקולים (נדרש רק אם רוצים ליצור מחדש את ה-stub של gRPC באופן עצמאי)
- פלאגין ליצירת קוד gRPC-web (נדרש רק אם רוצים ליצור מחדש את ה-stub של gRPC באופן עצמאי)
2. להגדרה
כדי להוריד את הקוד של ה-Codelab הזה:
- עוברים אל מאגר ה-GitHub הזה.
- לוחצים על Code > Download zip (קוד > הורדת קובץ zip) כדי להוריד את כל הקוד של ה-codelab הזה.
- מבטלים את הדחיסה של קובץ ה-ZIP שהורדתם כדי לחלץ תיקיית בסיס
codelabs
עם כל המשאבים שאתם צריכים.
ב-codelab הזה, צריך רק את הקבצים בספריית המשנה TFServing/ImageClassificationWeb
במאגר, שמכילה שתי תיקיות:
- התיקייה
starter
מכילה את קוד ההתחלה שעליו תבנו את הפתרון במעבדת הקוד הזו. - התיקייה
finished
מכילה את הקוד המלא של האפליקציה לדוגמה.
3. התקנת יחסי התלות
כדי להתקין את יחסי התלות:
- בטרמינל, עוברים לתיקייה
starter
ומתקינים את חבילות ה-NPM הנדרשות:
npm install
4. הפעלת אתר ההתחלה
משתמשים ב- Web Server for Chrome כדי לטעון את הקובץ TFServing/ImageClassificationWeb/starter/dist/index.html
:
- מזינים
Chrome://apps/
בסרגל הכתובות של Chrome ואז מחפשים את Web Server for Chrome ברשימת האפליקציות. - מפעילים את Web Server for Chrome ואז בוחרים את התיקייה
TFServing/ImageClassificationWeb/starter/dist/
. - לוחצים על לחצן החלפת המצב של Web Server כדי להפעיל אותו, ואז עוברים לכתובת http://localhost:8887/ בדפדפן.
הפעלת האתר ובדיקה שלו
עכשיו האתר אמור להופיע. ממשק המשתמש די פשוט: יש תמונה של חתול שרוצים לסווג, והמשתמש יכול לשלוח את הנתונים אל ה-Backend באמצעות REST או gRPC. הקצה העורפי מבצע סיווג של התמונה ומחזיר את תוצאת הסיווג לאתר, שבו התוצאה מוצגת.
אם לוחצים על סיווג, לא קורה כלום כי עדיין אין תקשורת עם העורף.
5. פריסת מודל לסיווג תמונות באמצעות TensorFlow Serving
סיווג תמונות הוא משימה נפוצה מאוד של למידת מכונה (ML), שבה מסווגים תמונה לקטגוריות מוגדרות מראש על סמך התוכן העיקרי של התמונה. דוגמה לסיווג פרחים:
יש מספר מודלים של סיווג תמונות שעברו אימון מראש ב-TensorFlow Hub. ב-Codelab הזה משתמשים במודל פופולרי של Inception v3.
כדי לפרוס את המודל לסיווג תמונות באמצעות TensorFlow Serving:
- מורידים את קובץ המודל Inception v3.
- מבטלים את הדחיסה של קובץ
.tar.gz
שהורדתם באמצעות כלי לביטול דחיסה, כמו 7-Zip. - יוצרים תיקייה בשם
inception_v3
ואז יוצרים בתוכה תיקיית משנה בשם123
. - מעבירים את התיקייה
variables
ואת הקובץsaved_model.pb
שחולצו לתיקיית המשנה123
.
אפשר להתייחס לתיקייה inception_v3
כאל התיקייה SavedModel
. 123
היא דוגמה למספר גרסה. אם רוצים, אפשר לבחור מספר אחר.
מבנה התיקיות צריך להיראות כמו בתמונה הזו:
הפעלת 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:
- יוצרים את בקשת ה-REST.
- שולחים את בקשת ה-REST אל TensorFlow Serving.
- מחפשים את התוצאה החזויה בתגובת ה-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;
עכשיו המאזין מחלץ מהתגובה את ההסתברויות החזויות, מזהה את הקטגוריה הסבירה ביותר של האובייקט ומציג את התוצאה בממשק המשתמש.
הפעלה
- בטרמינל, עוברים לתיקייה
starter
ומשתמשים ב-webpack כדי לארוז את כל קובצי ה-JavaScript לקובץ אחד שאפשר להטמיע בקובץdist/index.html
:
npm install -g npx npm install --save-dev webpack npx webpack
- מרעננים את הדף http://localhost:8887/ בדפדפן ולוחצים על REST > Classify.
באתר מוצגת הקטגוריה החזויה 286
, שמשויכת לתווית Egyptian Cat
במערך הנתונים ImageNet.
8. חיבור האתר ל-TensorFlow Serving דרך gRPC
בנוסף ל-REST, TensorFlow Serving תומך גם ב-gRPC.
gRPC היא מסגרת מודרנית בקוד פתוח לקריאה לשירות מרוחק (RPC) עם ביצועים גבוהים, שיכולה לפעול בכל סביבה. הוא יכול לחבר ביעילות שירותים במרכזי נתונים שונים, עם תמיכה בחיבורים לטעינת איזון, מעקב, בדיקת תקינות ואימות. בפועל, נראה ש-gRPC מניב ביצועים טובים יותר מ-REST.
שליחת בקשות וקבלת תגובות באמצעות gRPC
יש ארבע פעולות פשוטות:
- אופציונלי: יוצרים את קוד ה-stub של לקוח gRPC.
- יוצרים את בקשת ה-gRPC.
- שולחים את בקשת gRPC אל TensorFlow Serving.
- לחלץ את התוצאה החזויה מהתגובה של gRPC ולהציג אותה בממשק המשתמש.
את השלבים האלה מבצעים בקובץ src/index.js
.
אופציונלי: יצירת קוד stub של לקוח gRPC
כדי להשתמש ב-gRPC עם TensorFlow Serving, צריך לפעול לפי תהליך העבודה של gRPC. פרטים נוספים זמינים במסמכי התיעוד של gRPC.
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;
}
עכשיו המאזין מחלץ מהתגובה את ההסתברויות החזויות, מזהה את הקטגוריה הסבירה ביותר של האובייקט ומציג את התוצאה בממשק המשתמש.
הפעלה
- במסוף, משתמשים ב-webpack כדי לארוז את כל קובצי ה-JavaScript לקובץ אחד שאפשר להטמיע בקובץ
index.html
:
npx webpack
- מרעננים את הכתובת http://localhost:8887/ בדפדפן.
- לוחצים על gRPC > Classify (סיווג).
באתר מוצגת הקטגוריה החזויה של 286
, שמשויכת לתווית Egyptian Cat
במערך הנתונים ImageNet.
9. מזל טוב
השתמשתם ב-TensorFlow Serving כדי להוסיף לאתר שלכם יכולות של סיווג תמונות.