Section 7/171 menit
7. Model Pipeline dan Komposisi
7. Model Pipeline dan Komposisi
Untuk task yang kompleks, seringkali perlu chain beberapa model. Core ML mendukung ini melalui model pipeline atau manual orchestration.
Pipeline Orchestration Manual
swift
// MARK: - Multi-stage ML Pipeline
actor MLPipeline {
// Stage 1: Object Detection
private let detector: VNCoreMLModel
// Stage 2: Classification per detected region
private let classifier: MLModel
// Stage 3: Feature extraction untuk embeddings
private let featureExtractor: MLModel
init() throws {
let detectorModel = try ObjectDetector(configuration: .init()).model
self.detector = try VNCoreMLModel(for: detectorModel)
self.classifier = try ImageClassifier(configuration: .init()).model
self.featureExtractor = try FeatureExtractor(configuration: .init()).model
}
struct PipelineResult {
let detectedObject: String
let confidence: Float
let embedding: [Float]
let boundingBox: CGRect
}
func process(image: UIImage) async throws -> [PipelineResult] {
guard let cgImage = image.cgImage else { throw PipelineError.invalidInput }
// Stage 1: Deteksi objek
let detectionRequest = VNCoreMLRequest(model: detector)
let requestHandler = VNImageRequestHandler(cgImage: cgImage)
try requestHandler.perform([detectionRequest])
guard let detections = detectionRequest.results as? [VNRecognizedObjectObservation] else {
return []
}
// Stage 2 & 3: Klasifikasi dan ekstraksi fitur per region
var results: [PipelineResult] = []
for detection in detections where detection.confidence > 0.5 {
// Crop region dari image
let croppedImage = try cropImage(image, boundingBox: detection.boundingBox)
guard let pixelBuffer = croppedImage.pixelBuffer(width: 224, height: 224) else {
continue
}
// Klasifikasi parallel dengan feature extraction
async let classificationTask = classifyCropped(pixelBuffer: pixelBuffer)
async let embeddingTask = extractFeatures(pixelBuffer: pixelBuffer)
let (label, confidence) = try await classificationTask
let embedding = try await embeddingTask
results.append(PipelineResult(
detectedObject: label,
confidence: confidence,
embedding: embedding,
boundingBox: detection.boundingBox
))
}
return results.sorted { $0.confidence > $1.confidence }
}
private func classifyCropped(pixelBuffer: CVPixelBuffer) async throws -> (String, Float) {
let input = ImageClassifierInput(image: pixelBuffer)
let output = try classifier.prediction(from: input)
let label = output.featureValue(for: "classLabel")?.stringValue ?? ""
let probs = output.featureValue(for: "classLabelProbs")?.dictionaryValue ?? [:]
let confidence = probs[label as NSObject] as? Float ?? 0
return (label, confidence)
}
private func extractFeatures(pixelBuffer: CVPixelBuffer) async throws -> [Float] {
let input = FeatureExtractorInput(image: pixelBuffer)
let output = try featureExtractor.prediction(from: input)
guard let embeddings = output.featureValue(for: "embeddings")?.multiArrayValue else {
return []
}
return readMultiArray(embeddings)
}
private func cropImage(_ image: UIImage, boundingBox: CGRect) throws -> UIImage {
guard let cgImage = image.cgImage else { throw PipelineError.invalidInput }
let imageSize = CGSize(width: cgImage.width, height: cgImage.height)
// VNObservation boundingBox adalah normalized (0-1), origin di bottom-left
let rect = CGRect(
x: boundingBox.minX * imageSize.width,
y: (1 - boundingBox.maxY) * imageSize.height,
width: boundingBox.width * imageSize.width,
height: boundingBox.height * imageSize.height
)
guard let cropped = cgImage.cropping(to: rect) else { throw PipelineError.cropFailed }
return UIImage(cgImage: cropped)
}
private func readMultiArray(_ array: MLMultiArray) -> [Float] {
let pointer = array.dataPointer.assumingMemoryBound(to: Float.self)
return Array(UnsafeBufferPointer(start: pointer, count: array.count))
}
}
enum PipelineError: Error { case invalidInput, cropFailed }
// Placeholder types
class ObjectDetector { init(configuration: MLModelConfiguration) throws {} var model: MLModel { fatalError() } }
class ImageClassifier { init(configuration: MLModelConfiguration) throws {} var model: MLModel { fatalError() } }
class FeatureExtractor { init(configuration: MLModelConfiguration) throws {} var model: MLModel { fatalError() } }
class ImageClassifierInput: MLFeatureProvider { init(image: CVPixelBuffer) {}; var featureNames: Set<String> { [] }; func featureValue(for featureName: String) -> MLFeatureValue? { nil } }
class FeatureExtractorInput: MLFeatureProvider { init(image: CVPixelBuffer) {}; var featureNames: Set<String> { [] }; func featureValue(for featureName: String) -> MLFeatureValue? { nil } }
Model dengan Multiple Functions (iOS 17+)
Satu .mlpackage bisa berisi beberapa fungsi (encoder, decoder, dsb) yang bisa dipanggil terpisah:
swift
// Model dengan multiple functions — misalnya encoder-decoder transformer
let modelURL = Bundle.main.url(forResource: "TranslationModel", withExtension: "mlpackage")!
// Load model
let model = try await MLModel.load(contentsOf: modelURL, configuration: .init())
// Dapatkan daftar function yang tersedia
if #available(iOS 17, *) {
// Inspect via model description
let description = model.modelDescription
// description.parameterDescriptionsByName berisi info fungsi
}
// Panggil fungsi spesifik
let encoderInput = EncoderInput(tokens: inputTokens)
let encoderOutput = try model.prediction(from: encoderInput, options: .init())
// Gunakan output encoder sebagai input decoder
let decoderInput = DecoderInput(
encoderHiddenStates: encoderOutput.featureValue(for: "hidden_states")!.multiArrayValue!,
decoderInputIDs: startTokens
)
let decoderOutput = try model.prediction(from: decoderInput)