استخدم نموذج TensorFlow Lite للاستدلال باستخدام ML Kit على نظام التشغيل iOS

يمكنك استخدام ML Kit لإجراء الاستدلال على الجهاز باستخدام نموذج TensorFlow Lite .

يمكن لـ ML Kit استخدام نماذج TensorFlow Lite فقط على الأجهزة التي تعمل بنظام iOS 9 والإصدارات الأحدث.

قبل ان تبدأ

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

    سويفت

    import Firebase

    ج موضوعية

    @import Firebase;
  4. قم بتحويل نموذج TensorFlow الذي تريد استخدامه إلى تنسيق TensorFlow Lite. راجع TOCO: محول TensorFlow Lite الأمثل .

قم باستضافة النموذج الخاص بك أو تجميعه

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

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

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

من خلال تجميع النموذج الخاص بك مع تطبيقك، يمكنك التأكد من أن ميزات تعلم الآلة في تطبيقك لا تزال تعمل عندما لا يكون النموذج المستضاف في Firebase متاحًا.

استضافة النماذج على Firebase

لاستضافة نموذج TensorFlow Lite على Firebase:

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

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

نماذج مجمعة مع التطبيق

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

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

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

قم بتكوين نموذج مستضاف على Firebase

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

سويفت

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

ج موضوعية

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

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

سويفت

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

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

ج موضوعية

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

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

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

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

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

سويفت

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

ج موضوعية

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

قم بإنشاء مترجم من النموذج الخاص بك

بعد تكوين مصادر النموذج، قم بإنشاء كائن ModelInterpreter من أحد هذه المصادر.

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

سويفت

let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)

ج موضوعية

FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];

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

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

سويفت

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

ج موضوعية

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

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

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

سويفت

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]
    // ...
}

ج موضوعية

__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 . يجب عليك تكوين ML Kit بعدد وأبعاد ("الشكل") للصفائف التي يستخدمها نموذجك.

إذا كنت لا تعرف شكل ونوع بيانات مدخلات ومخرجات النموذج الخاص بك، فيمكنك استخدام مترجم 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 .

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

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

سويفت

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)")
}

ج موضوعية

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] ، فقد يتعين عليك قياس قيم ألوان الصورة إلى نطاق الفاصلة العائمة كما في المثال التالي :

سويفت

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)")
}

ج موضوعية

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:) .

سويفت

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

ج موضوعية

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

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

سويفت

// 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]

ج موضوعية

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

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

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

سويفت

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)")
  }
}

ج موضوعية

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 المتسلسل القياسي في التخزين المحلي.

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