استخدام نموذج TensorFlow Lite للاستنتاج باستخدام "مجموعة أدوات تعلُّم الآلة" على نظام التشغيل iOS

يمكنك استخدام أدوات تعلُّم الآلة لإجراء استنتاج على الجهاز باستخدام TensorFlow Lite.

يمكن أن تستخدم حزمة تعلّم الآلة نماذج TensorFlow Lite فقط على الأجهزة التي تعمل بنظام التشغيل iOS 9 الأحدث.

قبل البدء

  1. إذا لم يسبق لك إضافة Firebase إلى تطبيقك، يمكنك إجراء ذلك من خلال اتّباع الخطوات الأولى في دليل البدء.
  2. تضمين مكتبات ML Kit في Podfile:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    بعد تثبيت مجموعات مشروعك الصغيرة أو تحديثها، احرص على فتح ملف Xcode باستخدام .xcworkspace.
  3. في تطبيقك، استورد Firebase:

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  4. حوِّل نموذج TensorFlow الذي تريد استخدامه إلى تنسيق TensorFlow Lite. عرض TOCO: TensorFlow Lite Optimization Converter.

استضافة النموذج أو تجميعه

قبل أن تتمكّن من استخدام نموذج TensorFlow Lite للاستنتاج في تطبيقك، عليك إتاحة النموذج لأداة تعلّم الآلة. يمكن لـ ML Kit استخدام TensorFlow Lite نماذج مستضافة عن بُعد باستخدام Firebase، أو مرفقة مع البرنامج الثنائي للتطبيق، أو كليهما.

من خلال استضافة نموذج على Firebase، يمكنك تعديل النموذج بدون إطلاق إصدار جديد من التطبيق، ويمكنك استخدام Remote Config وA/B Testing لإجراء ما يلي: تعرض نماذج مختلفة ديناميكيًا لمجموعات مختلفة من المستخدمين.

إذا اخترت توفير النموذج من خلال استضافته باستخدام Firebase فقط، وليس حزمة التطبيق مع التطبيق، يمكنك تقليل حجم التنزيل الأولي لتطبيقك. ومع ذلك، يُرجى الأخذ في الاعتبار أنه إذا لم يتم إدراج النموذج مع تطبيقك، لن تتوفّر الوظائف المتعلّقة بالطراز إلى أن ينزِّل تطبيقك لأول مرة.

يمكنك ضمان ميزات تعلُّم الآلة في تطبيقك من خلال دمج نموذجك مع تطبيقك. لا يزال يعمل في حال عدم توفُّر النموذج المستضاف على Firebase.

نماذج المضيف على Firebase

لاستضافة نموذج TensorFlow Lite على Firebase، اتّبِع الخطوات التالية:

  1. في قسم حزمة تعلّم الآلة في وحدة تحكّم Firebase، انقر على علامة التبويب مخصّص.
  2. انقر على إضافة نموذج مخصّص (أو إضافة نموذج آخر).
  3. حدِّد اسمًا سيتم استخدامه لتحديد نموذجك في Firebase. ثم تحميل ملف نموذج TensorFlow Lite (الذي ينتهي عادةً بـ .tflite أو .lite).

بعد إضافة نموذج مخصّص إلى مشروعك على Firebase، يمكنك الرجوع إلى النموذج في تطبيقاتك باستخدام الاسم الذي حددته. يمكنك تحميل المحتوى في أي وقت نموذج TensorFlow Lite الجديد، وسينزِّل تطبيقك النموذج الجديد بدء استخدامه عند إعادة تشغيل التطبيق في المرة التالية. يمكنك تحديد نوع الجهاز الشروط المطلوبة لكي يحاول تطبيقك تحديث النموذج (انظر أدناه).

تجميع طُرز مع تطبيق

لتجميع نموذج TensorFlow Lite مع تطبيقك، أضِف ملف النموذج (عادةً ما يكون تنتهي بالأرقام .tflite أو .lite) إلى مشروع Xcode، مع الحرص على اختيار انسخ موارد الحزمة عند إجراء ذلك. سيتم تضمين ملف النموذج في حزمة التطبيق ومتاحة للاستخدام مع ML Kit.

تحميل النموذج

لاستخدام نموذج TensorFlow Lite في تطبيقك، عليك أولاً ضبط حزمة تعلّم الآلة باستخدام المواقع التي يتوفر فيها نموذجك: استخدام Firebase عن بُعد، أو كليهما معًا. في حال تحديد نموذج محلي وبعيد، يمكنك واستخدام النموذج عن بُعد إذا كان متاحًا، والعودة إلى نموذج مُخزَّن محليًا في حال عدم توفّر النموذج عن بُعد.

ضبط نموذج مستضاف على Firebase

إذا كنت قد استضافت النموذج باستخدام Firebase، أنشِئ كائن CustomRemoteModel. لتحديد الاسم الذي عينته للنموذج عند نشره:

Swift

let remoteModel = CustomRemoteModel(
  name: "your_remote_model"  // The name you assigned in the Firebase console.
)

Objective-C

// Initialize using the name you assigned in the Firebase console.
FIRCustomRemoteModel *remoteModel =
    [[FIRCustomRemoteModel alloc] initWithName:@"your_remote_model"];

بعد ذلك، ابدأ مهمة تنزيل النموذج، مع تحديد الشروط التي يريدون السماح بالتنزيل. إذا لم يكن الطراز موجودًا على الجهاز، أو إذا كان طرازًا أحدث إتاحة إصدار معين من النموذج، فإن المهمة ستنزّل بشكل غير متزامن النموذج من Firebase:

Swift

let downloadConditions = ModelDownloadConditions(
  allowsCellularAccess: true,
  allowsBackgroundDownloading: true
)

let downloadProgress = ModelManager.modelManager().download(
  remoteModel,
  conditions: downloadConditions
)

Objective-C

FIRModelDownloadConditions *downloadConditions =
    [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                         allowsBackgroundDownloading:YES];

NSProgress *downloadProgress =
    [[FIRModelManager modelManager] downloadRemoteModel:remoteModel
                                             conditions:downloadConditions];

تبدأ العديد من التطبيقات مهمة التنزيل من خلال رمز الإعداد الخاص بها، ولكن يمكنك إجراء ذلك لذا في أي وقت قبل أن تحتاج إلى استخدام النموذج.

ضبط نموذج على الجهاز

إذا جمعت النموذج مع تطبيقك، أنشِئ عنصر CustomLocalModel، تحديد اسم ملف نموذج TensorFlow Lite:

Swift

guard let modelPath = Bundle.main.path(
  forResource: "your_model",
  ofType: "tflite",
  inDirectory: "your_model_directory"
) else { /* Handle error. */ }
let localModel = CustomLocalModel(modelPath: modelPath)

Objective-C

NSString *modelPath = [NSBundle.mainBundle pathForResource:@"your_model"
                                                    ofType:@"tflite"
                                               inDirectory:@"your_model_directory"];
FIRCustomLocalModel *localModel =
    [[FIRCustomLocalModel alloc] initWithModelPath:modelPath];

إنشاء مترجم فوري من نموذجك

بعد ضبط مصادر النماذج، يمكنك إنشاء الكائن ModelInterpreter من إحداهما.

إذا كان لديك نموذج مجمّع محليًا فقط، ما عليك سوى ضبط CustomLocalModel. كائن إلى modelInterpreter(localModel:):

Swift

let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)

Objective-C

FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];

فإذا كان لديك نموذج مستضاف عن بُعد، فعليك التحقق من أنه تم تنزيله قبل تشغيله. يمكنك التحقّق من حالة تنزيل النموذج باستخدام طريقة isModelDownloaded(remoteModel:) لمدير النموذج.

على الرغم من أنه ما عليك سوى تأكيد ذلك قبل تشغيل ميزة الترجمة الفورية، إذا لكل من نموذج مُستضاف عن بُعد ونموذج مُجمع محليًا، فقد تجعل إجراء عملية الفحص هذه عند إنشاء مثيل ModelInterpreter: إنشاء من النموذج البعيد إذا تم تنزيله، ومن الجهاز نموذج بخلاف ذلك.

Swift

var interpreter: ModelInterpreter
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
  interpreter = ModelInterpreter.modelInterpreter(remoteModel: remoteModel)
} else {
  interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
}

Objective-C

FIRModelInterpreter *interpreter;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
  interpreter = [FIRModelInterpreter modelInterpreterForRemoteModel:remoteModel];
} else {
  interpreter = [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
}

وإذا كان لديك نموذج مستضاف عن بُعد فقط، يجب إيقاف النموذج المرتبط بالنموذج الوظائف - على سبيل المثال، إخفاء جزء من واجهة المستخدم أو جعلها رمادية حتى التأكد من تنزيل النموذج.

يمكنك الحصول على حالة تنزيل النموذج من خلال إرفاق عناصر المراقبين بالإعداد التلقائي. مركز الإشعارات. تأكد من استخدام إشارة ضعيفة إلى self في المراقب حظر، نظرًا لأن عمليات التنزيل قد تستغرق بعض الوقت وقد يتم حذف الكائن الأصلي ويتم تحريره عند انتهاء التنزيل. على سبيل المثال:

Swift

NotificationCenter.default.addObserver(
    forName: .firebaseMLModelDownloadDidSucceed,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel,
        model.name == "your_remote_model"
        else { return }
    // The model was downloaded and is available on the device
}

NotificationCenter.default.addObserver(
    forName: .firebaseMLModelDownloadDidFail,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel
        else { return }
    let error = userInfo[ModelDownloadUserInfoKey.error.rawValue]
    // ...
}

Objective-C

__weak typeof(self) weakSelf = self;

[NSNotificationCenter.defaultCenter
    addObserverForName:FIRModelDownloadDidSucceedNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              FIRRemoteModel *model = note.userInfo[FIRModelDownloadUserInfoKeyRemoteModel];
              if ([model.name isEqualToString:@"your_remote_model"]) {
                // The model was downloaded and is available on the device
              }
            }];

[NSNotificationCenter.defaultCenter
    addObserverForName:FIRModelDownloadDidFailNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              NSError *error = note.userInfo[FIRModelDownloadUserInfoKeyError];
            }];

تحديد مدخلات ومخرجات النموذج

بعد ذلك، قم بتكوين تنسيقات الإدخال والإخراج لمترجم النموذج.

يتم استخدام نموذج TensorFlow Lite كمدخل ويتم إنتاجه كمخرج واحد أو أكثر. الصفائف متعددة الأبعاد. تحتوي هذه الصفائف إما على byte، القيم int أو long أو float. يجب اضبط حزمة تعلّم الآلة باستخدام عدد وأبعاد ("شكل") الصفائف استخدامات النموذج.

إذا كنت لا تعرف شكل ونوع البيانات لمدخلات النموذج ومخرجاته، يمكنك استخدام مترجم TensorFlow Lite بلغة Python لفحص النموذج. بالنسبة مثال:

import tensorflow as tf

interpreter = tf.lite.Interpreter(model_path="my_model.tflite")
interpreter.allocate_tensors()

# Print input shape and type
print(interpreter.get_input_details()[0]['shape'])  # Example: [1 224 224 3]
print(interpreter.get_input_details()[0]['dtype'])  # Example: <class 'numpy.float32'>

# Print output shape and type
print(interpreter.get_output_details()[0]['shape'])  # Example: [1 1000]
print(interpreter.get_output_details()[0]['dtype'])  # Example: <class 'numpy.float32'>

بعد تحديد تنسيق مدخلات ومخرجات النموذج، يمكنك تهيئة ومترجم نموذج التطبيق من خلال إنشاء ModelInputOutputOptions.

على سبيل المثال، قد يتخذ نموذج تصنيف الصور ذات النقطة العائمة كمدخل مصفوفة Nx224x224x3 مكونة من Float قيمة، تمثل مجموعة من N صور ثلاثية القنوات (RGB) مقاس 224×224، ويتم إنتاج قائمة بالإخراج 1, 000 قيمة Float، تمثّل كل منها احتمالية أن تكون الصورة عضوًا في واحدة من الفئات الـ 1000 التي يتنبأ بها النموذج.

بالنسبة إلى هذا النموذج، ستقوم بتهيئة مدخلات وإخراج مترجم النموذج كما هو موضح أدناه:

Swift

let ioOptions = ModelInputOutputOptions()
do {
    try ioOptions.setInputFormat(index: 0, type: .float32, dimensions: [1, 224, 224, 3])
    try ioOptions.setOutputFormat(index: 0, type: .float32, dimensions: [1, 1000])
} catch let error as NSError {
    print("Failed to set input or output format with error: \(error.localizedDescription)")
}

Objective-C

FIRModelInputOutputOptions *ioOptions = [[FIRModelInputOutputOptions alloc] init];
NSError *error;
[ioOptions setInputFormatForIndex:0
                             type:FIRModelElementTypeFloat32
                       dimensions:@[@1, @224, @224, @3]
                            error:&error];
if (error != nil) { return; }
[ioOptions setOutputFormatForIndex:0
                              type:FIRModelElementTypeFloat32
                        dimensions:@[@1, @1000]
                             error:&error];
if (error != nil) { return; }

إجراء استنتاج على بيانات الإدخال

وأخيرًا، لإجراء الاستنتاج باستخدام النموذج والحصول على البيانات المدخلة وإجراء أي على البيانات التي قد تكون ضرورية لنموذجك، وإنشاء عنصر Data الذي يحتوي على البيانات.

على سبيل المثال، إذا كان نموذجك يعالج الصور، وكان نموذجك يحتوي على أبعاد إدخال من [BATCH_SIZE, 224, 224, 3] قيمة النقطة العائمة، قد تضطر إلى قياسها قيم لون الصورة إلى نطاق نقطة عائمة كما في المثال التالي:

Swift

let image: CGImage = // Your input image
guard let context = CGContext(
  data: nil,
  width: image.width, height: image.height,
  bitsPerComponent: 8, bytesPerRow: image.width * 4,
  space: CGColorSpaceCreateDeviceRGB(),
  bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
) else {
  return false
}

context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
guard let imageData = context.data else { return false }

let inputs = ModelInputs()
var inputData = Data()
do {
  for row in 0 ..< 224 {
    for col in 0 ..< 224 {
      let offset = 4 * (col * context.width + row)
      // (Ignore offset 0, the unused alpha channel)
      let red = imageData.load(fromByteOffset: offset+1, as: UInt8.self)
      let green = imageData.load(fromByteOffset: offset+2, as: UInt8.self)
      let blue = imageData.load(fromByteOffset: offset+3, as: UInt8.self)

      // Normalize channel values to [0.0, 1.0]. This requirement varies
      // by model. For example, some models might require values to be
      // normalized to the range [-1.0, 1.0] instead, and others might
      // require fixed-point values or the original bytes.
      var normalizedRed = Float32(red) / 255.0
      var normalizedGreen = Float32(green) / 255.0
      var normalizedBlue = Float32(blue) / 255.0

      // Append normalized values to Data object in RGB order.
      let elementSize = MemoryLayout.size(ofValue: normalizedRed)
      var bytes = [UInt8](repeating: 0, count: elementSize)
      memcpy(&bytes, &normalizedRed, elementSize)
      inputData.append(&bytes, count: elementSize)
      memcpy(&bytes, &normalizedGreen, elementSize)
      inputData.append(&bytes, count: elementSize)
      memcpy(&ammp;bytes, &normalizedBlue, elementSize)
      inputData.append(&bytes, count: elementSize)
    }
  }
  try inputs.addInput(inputData)
} catch let error {
  print("Failed to add input: \(error)")
}

Objective-C

CGImageRef image = // Your input image
long imageWidth = CGImageGetWidth(image);
long imageHeight = CGImageGetHeight(image);
CGContextRef context = CGBitmapContextCreate(nil,
                                             imageWidth, imageHeight,
                                             8,
                                             imageWidth * 4,
                                             CGColorSpaceCreateDeviceRGB(),
                                             kCGImageAlphaNoneSkipFirst);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image);
UInt8 *imageData = CGBitmapContextGetData(context);

FIRModelInputs *inputs = [[FIRModelInputs alloc] init];
NSMutableData *inputData = [[NSMutableData alloc] initWithCapacity:0];

for (int row = 0; row < 224; row++) {
  for (int col = 0; col < 224; col++) {
    long offset = 4 * (col * imageWidth + row);
    // Normalize channel values to [0.0, 1.0]. This requirement varies
    // by model. For example, some models might require values to be
    // normalized to the range [-1.0, 1.0] instead, and others might
    // require fixed-point values or the original bytes.
    // (Ignore offset 0, the unused alpha channel)
    Float32 red = imageData[offset+1] / 255.0f;
    Float32 green = imageData[offset+2] / 255.0f;
    Float32 blue = imageData[offset+3] / 255.0f;

    [inputData appendBytes:&red length:sizeof(red)];
    [inputData appendBytes:&green length:sizeof(green)];
    [inputData appendBytes:&blue length:sizeof(blue)];
  }
}

[inputs addInput:inputData error:&error];
if (error != nil) { return nil; }

بعد إعداد إدخال النموذج (وبعد التأكد من أن النموذج )، قم بتمرير خيارات الإدخال والإدخال/الإخراج إلى run(inputs:options:completion:)للنموذج الفوري الخاص بك .

Swift

interpreter.run(inputs: inputs, options: ioOptions) { outputs, error in
    guard error == nil, let outputs = outputs else { return }
    // Process outputs
    // ...
}

Objective-C

[interpreter runWithInputs:inputs
                   options:ioOptions
                completion:^(FIRModelOutputs * _Nullable outputs,
                             NSError * _Nullable error) {
  if (error != nil || outputs == nil) {
    return;
  }
  // Process outputs
  // ...
}];

ويمكنك الحصول على الناتج من خلال استدعاء طريقة output(index:) للكائن . على سبيل المثال:

Swift

// Get first and only output of inference with a batch size of 1
let output = try? outputs.output(index: 0) as? [[NSNumber]]
let probabilities = output??[0]

Objective-C

// Get first and only output of inference with a batch size of 1
NSError *outputError;
NSArray *probabilites = [outputs outputAtIndex:0 error:&outputError][0];

وتعتمد كيفية استخدامك للمخرجات على النموذج الذي تستخدمه.

على سبيل المثال، إذا كنت بصدد إجراء التصنيف، كخطوة تالية، يمكنك: وتعيين فهارس النتيجة إلى التسميات التي تمثلها. افترض أن لديك ملف نصي يحتوي على سلاسل التصنيفات لكل فئة من فئات النموذج هل يمكنك ربط سلاسل التسمية إلى احتمالات المخرجات عن طريق القيام بشيء مثل التالي:

Swift

guard let labelPath = Bundle.main.path(forResource: "retrained_labels", ofType: "txt") else { return }
let fileContents = try? String(contentsOfFile: labelPath)
guard let labels = fileContents?.components(separatedBy: "\n") else { return }

for i in 0 ..< labels.count {
  if let probability = probabilities?[i] {
    print("\(labels[i]): \(probability)")
  }
}

Objective-C

NSError *labelReadError = nil;
NSString *labelPath = [NSBundle.mainBundle pathForResource:@"retrained_labels"
                                                    ofType:@"txt"];
NSString *fileContents = [NSString stringWithContentsOfFile:labelPath
                                                   encoding:NSUTF8StringEncoding
                                                      error:&labelReadError];
if (labelReadError != nil || fileContents == NULL) { return; }
NSArray<NSString *> *labels = [fileContents componentsSeparatedByString:@"\n"];
for (int i = 0; i < labels.count; i++) {
    NSString *label = labels[i];
    NSNumber *probability = probabilites[i];
    NSLog(@"%@: %f", label, probability.floatValue);
}

الملحق: أمان النموذج

بغض النظر عن كيفية إتاحة طُرز TensorFlow Lite تخزنها ML Kit وML Kit بتنسيق Protobuf القياسي القياسي في التخزين المحلي.

ومن الناحية النظرية، يعني هذا أنه بإمكان أي شخص نسخ نموذجك. ومع ذلك، عمليًا، تكون معظم النماذج خاصة بالتطبيقات ومشوَّشة بواسطة تتشابه المخاطر مع تلك الخاصة بالمنافسين الذين يقومون بتفكيكها إعادة استخدام التعليمات البرمجية. ومع ذلك، يجب أن تكون على دراية بهذا الخطر قبل استخدام نموذج مخصص في تطبيقك.