Check out what’s new from Firebase at Google I/O 2022. Learn more

ใช้โมเดล 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'
    
    หลังจากที่คุณติดตั้งหรืออัปเดต Pod ของโปรเจ็กต์แล้ว อย่าลืมเปิดโปรเจ็กต์ Xcode โดยใช้ . .xcworkspace
  3. ในแอปของคุณ ให้นำเข้า Firebase:

    Swift

    import Firebase

    วัตถุประสงค์-C

    @import Firebase;
  4. แปลงโมเดล TensorFlow ที่คุณต้องการใช้เป็นรูปแบบ TensorFlow Lite ดู TOCO: TensorFlow Lite Optimizing Converter

โฮสต์หรือรวมโมเดลของคุณ

ก่อนที่คุณจะสามารถใช้โมเดล TensorFlow Lite สำหรับการอนุมานในแอปของคุณได้ คุณต้องทำให้โมเดลพร้อมใช้งานใน ML Kit ML Kit สามารถใช้โมเดล TensorFlow Lite ที่โฮสต์จากระยะไกลโดยใช้ Firebase ที่มาพร้อมกับไบนารีของแอป หรือทั้งสองอย่าง

ด้วยการโฮสต์โมเดลบน Firebase คุณสามารถอัปเดตโมเดลโดยไม่ต้องเปิดตัวแอปเวอร์ชันใหม่ และคุณสามารถใช้การกำหนดค่าระยะไกลและการทดสอบ A/B เพื่อแสดงโมเดลต่างๆ แบบไดนามิกให้กับผู้ใช้กลุ่มต่างๆ

หากคุณเลือกที่จะระบุโมเดลโดยโฮสต์โมเดลกับ Firebase เท่านั้น และไม่ได้รวมเข้ากับแอปของคุณ คุณสามารถลดขนาดการดาวน์โหลดเริ่มต้นของแอปได้ อย่างไรก็ตาม พึงระลึกไว้เสมอว่า หากโมเดลนั้นไม่ได้รวมอยู่ในแอพของคุณ ฟังก์ชันที่เกี่ยวข้องกับโมเดลจะไม่สามารถใช้ได้จนกว่าแอพของคุณจะดาวน์โหลดโมเดลนั้นเป็นครั้งแรก

เมื่อรวมโมเดลเข้ากับแอป คุณจะมั่นใจได้ว่าฟีเจอร์ ML ของแอปจะยังทำงานเมื่อโมเดลที่โฮสต์กับ 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 โดยระบุชื่อที่คุณกำหนดให้กับโมเดลเมื่อคุณเผยแพร่:

Swift

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

วัตถุประสงค์-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
)

วัตถุประสงค์-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)

วัตถุประสงค์-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)

วัตถุประสงค์-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)
}

วัตถุประสงค์-C

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

หากคุณมีเฉพาะโมเดลที่โฮสต์จากระยะไกล คุณควรปิดใช้งานฟังก์ชันที่เกี่ยวข้องกับโมเดล เช่น สีเทาหรือซ่อนส่วนหนึ่งของ UI ของคุณ จนกว่าคุณจะยืนยันว่าได้ดาวน์โหลดโมเดลแล้ว

คุณสามารถรับสถานะการดาวน์โหลดแบบจำลองได้โดยการแนบผู้สังเกตการณ์เข้ากับศูนย์การแจ้งเตือนเริ่มต้น อย่าลืมใช้การอ้างอิงที่อ่อนแอถึง 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]
    // ...
}

วัตถุประสงค์-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 คุณต้องกำหนดค่า 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) และสร้างรายการค่า Float ต 1000 รายการเป็นเอาต์พุต ความน่าจะเป็นที่รูปภาพจะเป็นหนึ่งใน 1,000 หมวดหมู่ที่แบบจำลองคาดการณ์

สำหรับโมเดลดังกล่าว คุณจะต้องกำหนดค่าอินพุตและเอาต์พุตของตัวแปลโมเดลดังที่แสดงด้านล่าง:

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

วัตถุประสงค์-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)")
}

วัตถุประสงค์-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:) ของ model interpreter

Swift

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

วัตถุประสงค์-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]

วัตถุประสงค์-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)")
  }
}

วัตถุประสงค์-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 จะจัดเก็บโมเดลเหล่านี้ไว้ในรูปแบบโปรโตบัฟซีเรียลไลซ์มาตรฐานในที่จัดเก็บในเครื่อง

ตามทฤษฎีแล้ว นี่หมายความว่าใครๆ ก็สามารถคัดลอกแบบจำลองของคุณได้ อย่างไรก็ตาม ในทางปฏิบัติ โมเดลส่วนใหญ่มีความเฉพาะเจาะจงกับแอปพลิเคชันและทำให้สับสนโดยการปรับให้เหมาะสม ซึ่งความเสี่ยงนั้นใกล้เคียงกับของคู่แข่งในการถอดแยกชิ้นส่วนและนำโค้ดของคุณมาใช้ซ้ำ อย่างไรก็ตาม คุณควรตระหนักถึงความเสี่ยงนี้ก่อนที่คุณจะใช้โมเดลแบบกำหนดเองในแอปของคุณ