# Vision Framework et CoreML : questions d'entretien ML on-device iOS > Préparez vos entretiens iOS avec les questions essentielles sur Vision Framework et CoreML : reconnaissance d'images, détection d'objets et ML on-device. - Published: 2026-03-07 - Updated: 2026-03-28 - Author: SharpSkill - Tags: vision, coreml, ios, machine-learning, interview - Reading time: 14 min --- Le machine learning on-device représente un avantage compétitif majeur pour les applications iOS modernes. Vision Framework et CoreML permettent d'exécuter des modèles directement sur l'appareil, garantissant confidentialité des données et performance en temps réel. Ces questions d'entretien couvrent les concepts essentiels que tout développeur iOS senior doit maîtriser. > **Structure de ce guide** > > Les questions sont organisées par thème : fondamentaux CoreML, Vision Framework, optimisation des performances, et cas pratiques. Chaque réponse inclut du code Swift moderne et des explications détaillées. ## Fondamentaux CoreML ### 1. Qu'est-ce que CoreML et quels sont ses avantages ? CoreML est le framework d'Apple pour intégrer des modèles de machine learning dans les applications iOS, macOS, watchOS et tvOS. Il optimise automatiquement les modèles pour le matériel Apple (CPU, GPU, Neural Engine) et garantit l'exécution on-device sans connexion réseau. Les principaux avantages incluent la confidentialité des données (aucune donnée ne quitte l'appareil), la latence réduite (pas de round-trip réseau), et l'optimisation automatique pour le Neural Engine des puces Apple Silicon. ```swift // CoreMLBasics.swift import CoreML // Chargement d'un modèle CoreML compilé (.mlmodelc) class ImageClassifier { // Le modèle est compilé à la build time pour optimiser le chargement private let model: VNCoreMLModel init() throws { // Configuration pour utiliser le Neural Engine si disponible let config = MLModelConfiguration() config.computeUnits = .all // CPU + GPU + Neural Engine // Chargement du modèle avec configuration personnalisée let mlModel = try MobileNetV2(configuration: config).model model = try VNCoreMLModel(for: mlModel) } // Méthode pour classifier une image func classify(image: CGImage) async throws -> [(String, Float)] { // Création de la requête Vision avec le modèle CoreML let request = VNCoreMLRequest(model: model) request.imageCropAndScaleOption = .centerCrop // Handler pour traiter l'image let handler = VNImageRequestHandler(cgImage: image, options: [:]) try handler.perform([request]) // Extraction des résultats guard let results = request.results as? [VNClassificationObservation] else { return [] } // Retourne les 5 meilleures prédictions avec leur confiance return results.prefix(5).map { ($0.identifier, $0.confidence) } } } ``` ### 2. Comment convertir un modèle TensorFlow ou PyTorch vers CoreML ? La conversion utilise coremltools, un package Python officiel d'Apple. Il supporte TensorFlow, PyTorch, ONNX et d'autres formats populaires. La conversion peut inclure des optimisations comme la quantification pour réduire la taille du modèle. ```python # convert_model.py import coremltools as ct import torch # Conversion depuis PyTorch class MyClassifier(torch.nn.Module): def __init__(self): super().__init__() self.conv = torch.nn.Conv2d(3, 64, 3) self.fc = torch.nn.Linear(64, 10) def forward(self, x): x = self.conv(x) x = x.mean([2, 3]) # Global average pooling return self.fc(x) # Exemple d'entrée pour le tracing example_input = torch.rand(1, 3, 224, 224) # Trace le modèle PyTorch traced_model = torch.jit.trace(MyClassifier(), example_input) # Conversion vers CoreML avec métadonnées mlmodel = ct.convert( traced_model, inputs=[ct.ImageType(name="image", shape=(1, 3, 224, 224))], classifier_config=ct.ClassifierConfig(["cat", "dog", "bird"]), minimum_deployment_target=ct.target.iOS17 ) # Sauvegarde du modèle avec compression mlmodel.save("MyClassifier.mlpackage") ``` Le modèle `.mlpackage` peut ensuite être ajouté directement au projet Xcode, qui génère automatiquement une classe Swift typée. ### 3. Quelle est la différence entre MLModel et VNCoreMLModel ? `MLModel` est la classe de base CoreML pour charger et exécuter des modèles ML. `VNCoreMLModel` est un wrapper qui permet d'utiliser un modèle CoreML avec Vision Framework, offrant un preprocessing automatique des images et une intégration avec les pipelines Vision. ```swift // MLModelVsVNCoreML.swift import CoreML import Vision // Utilisation directe de MLModel (bas niveau) func predictWithMLModel(features: MLFeatureProvider) async throws -> String { let config = MLModelConfiguration() let model = try MyModel(configuration: config) // Prédiction directe avec un feature provider let prediction = try model.prediction(from: features) // Accès manuel aux outputs guard let output = prediction.featureValue(for: "classLabel")?.stringValue else { throw PredictionError.invalidOutput } return output } // Utilisation avec VNCoreMLModel (haut niveau, recommandé pour images) func predictWithVision(image: CGImage) async throws -> [VNClassificationObservation] { let config = MLModelConfiguration() let mlModel = try MyModel(configuration: config).model // Wrapper pour utiliser avec Vision let visionModel = try VNCoreMLModel(for: mlModel) // Vision gère automatiquement le redimensionnement et le preprocessing let request = VNCoreMLRequest(model: visionModel) request.imageCropAndScaleOption = .scaleFill let handler = VNImageRequestHandler(cgImage: image) try handler.perform([request]) return request.results as? [VNClassificationObservation] ?? [] } ``` > **Quand utiliser quoi ?** > > `MLModel` direct pour des données tabulaires ou des entrées non-image. `VNCoreMLModel` pour tout ce qui implique des images, car Vision gère automatiquement les conversions de format et le preprocessing. ### 4. Comment gérer les différentes versions iOS avec CoreML ? CoreML évolue avec chaque version iOS. Il est essentiel de définir une cible de déploiement minimale lors de la conversion et de gérer les fonctionnalités non disponibles sur les anciennes versions. ```swift // CoreMLVersioning.swift import CoreML class AdaptiveMLManager { // Vérifie les capacités du modèle selon la version iOS func loadOptimalModel() throws -> MLModel { let config = MLModelConfiguration() // iOS 17+ : Neural Engine optimisé avec compute budget if #available(iOS 17, *) { config.computeUnits = .cpuAndNeuralEngine // Nouveau en iOS 17 : limite de puissance de calcul config.allowLowPrecisionAccumulationOnGPU = true return try AdvancedModel(configuration: config).model } // iOS 16 : support GPU amélioré else if #available(iOS 16, *) { config.computeUnits = .all return try StandardModel(configuration: config).model } // iOS 15 : fallback CPU uniquement pour fiabilité else { config.computeUnits = .cpuOnly return try LegacyModel(configuration: config).model } } // Vérifie si le Neural Engine est disponible var hasNeuralEngine: Bool { if #available(iOS 16, *) { // Les appareils avec A11+ ont le Neural Engine var sysinfo = utsname() uname(&sysinfo) let machine = String(bytes: Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN)), encoding: .ascii)? .trimmingCharacters(in: .controlCharacters) ?? "" // iPhone X et ultérieurs ont le Neural Engine return machine.contains("iPhone10") || machine.hasPrefix("iPhone1") && machine.count > 7 } return false } } ``` ## Vision Framework ### 5. Quels types de requêtes Vision Framework supporte-t-il ? Vision Framework offre une large gamme de requêtes pour l'analyse d'images. Les principales catégories incluent la détection de visages, la reconnaissance de texte (OCR), la détection d'objets, le suivi d'objets vidéo, et l'analyse de similarité d'images. ```swift // VisionRequests.swift import Vision class VisionAnalyzer { // Détection de visages avec landmarks func detectFaces(in image: CGImage) async throws -> [VNFaceObservation] { let request = VNDetectFaceLandmarksRequest() request.revision = VNDetectFaceLandmarksRequestRevision3 let handler = VNImageRequestHandler(cgImage: image) try handler.perform([request]) return request.results ?? [] } // Reconnaissance de texte (OCR) func recognizeText(in image: CGImage) async throws -> [String] { let request = VNRecognizeTextRequest() request.recognitionLevel = .accurate // .fast pour temps réel request.recognitionLanguages = ["fr-FR", "en-US"] request.usesLanguageCorrection = true let handler = VNImageRequestHandler(cgImage: image) try handler.perform([request]) return request.results?.compactMap { observation in observation.topCandidates(1).first?.string } ?? [] } // Détection et classification d'objets func detectObjects(in image: CGImage) async throws -> [VNRecognizedObjectObservation] { // Utilise un modèle CoreML pour la détection let config = MLModelConfiguration() let detector = try YOLOv8(configuration: config) let visionModel = try VNCoreMLModel(for: detector.model) let request = VNCoreMLRequest(model: visionModel) request.imageCropAndScaleOption = .scaleFill let handler = VNImageRequestHandler(cgImage: image) try handler.perform([request]) return request.results as? [VNRecognizedObjectObservation] ?? [] } // Calcul de similarité entre images func computeSimilarity(image1: CGImage, image2: CGImage) async throws -> Float { // Génère les feature prints des deux images let request = VNGenerateImageFeaturePrintRequest() let handler1 = VNImageRequestHandler(cgImage: image1) try handler1.perform([request]) guard let print1 = request.results?.first else { throw VisionError.noResults } let handler2 = VNImageRequestHandler(cgImage: image2) try handler2.perform([request]) guard let print2 = request.results?.first else { throw VisionError.noResults } // Calcule la distance entre les deux embeddings var distance: Float = 0 try print1.computeDistance(&distance, to: print2) // Convertit la distance en score de similarité (0-1) return 1.0 / (1.0 + distance) } } ``` ### 6. Comment implémenter le suivi d'objets en temps réel avec Vision ? Le suivi d'objets utilise `VNTrackObjectRequest` pour suivre un objet détecté à travers les frames d'une vidéo. L'initialisation se fait avec une observation de détection, puis les frames suivantes utilisent la même requête pour le suivi. ```swift // ObjectTracking.swift import Vision import AVFoundation class ObjectTracker: NSObject { private var trackingRequest: VNTrackObjectRequest? private let sequenceHandler = VNSequenceRequestHandler() // Callback pour notifier les mises à jour de position var onTrackingUpdate: ((CGRect) -> Void)? var onTrackingLost: (() -> Void)? // Initialise le suivi avec une détection initiale func startTracking(observation: VNDetectedObjectObservation) { // Crée la requête de suivi à partir de l'observation trackingRequest = VNTrackObjectRequest( detectedObjectObservation: observation ) { [weak self] request, error in self?.handleTrackingResult(request: request, error: error) } // Configuration du suivi trackingRequest?.trackingLevel = .accurate // .fast pour 60fps } // Traite chaque nouvelle frame vidéo func processFrame(_ pixelBuffer: CVPixelBuffer) { guard let request = trackingRequest else { return } do { // Le sequence handler maintient le contexte entre les frames try sequenceHandler.perform([request], on: pixelBuffer) } catch { onTrackingLost?() trackingRequest = nil } } private func handleTrackingResult(request: VNRequest, error: Error?) { guard let result = request.results?.first as? VNDetectedObjectObservation else { onTrackingLost?() return } // Vérifie la confiance du suivi if result.confidence < 0.3 { onTrackingLost?() trackingRequest = nil return } // Met à jour la requête pour la prochaine frame trackingRequest = VNTrackObjectRequest(detectedObjectObservation: result) { [weak self] request, error in self?.handleTrackingResult(request: request, error: error) } // Notifie la nouvelle position (coordonnées normalisées) DispatchQueue.main.async { [weak self] in self?.onTrackingUpdate?(result.boundingBox) } } } // Intégration avec AVCaptureSession extension ObjectTracker: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput( _ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection ) { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } processFrame(pixelBuffer) } } ``` ### 7. Comment optimiser les performances de Vision pour le temps réel ? L'optimisation passe par plusieurs techniques : utiliser le bon niveau de reconnaissance, traiter les frames sur une queue dédiée, et limiter le nombre de requêtes simultanées. Le choix entre précision et vitesse dépend du cas d'usage. ```swift // VisionOptimization.swift import Vision import AVFoundation class OptimizedVisionPipeline { // Queue dédiée pour le traitement Vision (évite le main thread) private let processingQueue = DispatchQueue( label: "com.app.vision", qos: .userInteractive, attributes: .concurrent ) // Limite le nombre de frames traitées simultanément private let semaphore = DispatchSemaphore(value: 2) // Réutilise les requêtes pour éviter les allocations private lazy var textRequest: VNRecognizeTextRequest = { let request = VNRecognizeTextRequest() request.recognitionLevel = .fast // .accurate si précision > vitesse request.usesLanguageCorrection = false // Désactive pour +20% perf request.minimumTextHeight = 0.05 // Ignore le texte trop petit return request }() // Réutilise le handler de séquence pour le suivi private let sequenceHandler = VNSequenceRequestHandler() // Traitement optimisé d'une frame func processFrame(_ pixelBuffer: CVPixelBuffer) { // Skip si le pipeline est saturé guard semaphore.wait(timeout: .now()) == .success else { return // Drop la frame plutôt que bloquer } processingQueue.async { [weak self] in defer { self?.semaphore.signal() } guard let self = self else { return } do { // Utilise le sequence handler pour de meilleures performances try self.sequenceHandler.perform( [self.textRequest], on: pixelBuffer, orientation: .up ) // Traite les résultats if let results = self.textRequest.results { self.handleResults(results) } } catch { print("Vision error: \(error)") } } } // Batch processing pour les images statiques func processImages(_ images: [CGImage]) async throws -> [[VNObservation]] { // Traitement parallèle avec TaskGroup try await withThrowingTaskGroup(of: (Int, [VNObservation]).self) { group in for (index, image) in images.enumerated() { group.addTask { let handler = VNImageRequestHandler(cgImage: image) let request = VNDetectFaceRectanglesRequest() try handler.perform([request]) return (index, request.results ?? []) } } // Collecte les résultats dans l'ordre original var results = [[VNObservation]](repeating: [], count: images.count) for try await (index, observations) in group { results[index] = observations } return results } } private func handleResults(_ results: [VNRecognizedTextObservation]) { // Traitement asynchrone des résultats } } ``` ### 8. Comment implémenter la détection de pose humaine avec Vision ? Vision Framework iOS 14+ offre `VNDetectHumanBodyPoseRequest` pour détecter les articulations du corps. Cette fonctionnalité est utilisée pour les applications fitness, les jeux AR, et l'analyse de mouvements. ```swift // PoseDetection.swift import Vision struct DetectedPose { let joints: [VNHumanBodyPoseObservation.JointName: CGPoint] let confidence: Float // Calcule l'angle entre trois articulations func angleBetween( _ joint1: VNHumanBodyPoseObservation.JointName, _ joint2: VNHumanBodyPoseObservation.JointName, _ joint3: VNHumanBodyPoseObservation.JointName ) -> Double? { guard let p1 = joints[joint1], let p2 = joints[joint2], let p3 = joints[joint3] else { return nil } let v1 = CGVector(dx: p1.x - p2.x, dy: p1.y - p2.y) let v2 = CGVector(dx: p3.x - p2.x, dy: p3.y - p2.y) let dot = v1.dx * v2.dx + v1.dy * v2.dy let mag1 = sqrt(v1.dx * v1.dx + v1.dy * v1.dy) let mag2 = sqrt(v2.dx * v2.dx + v2.dy * v2.dy) return acos(dot / (mag1 * mag2)) * 180 / .pi } } class PoseDetector { private let request = VNDetectHumanBodyPoseRequest() func detectPose(in image: CGImage) async throws -> DetectedPose? { let handler = VNImageRequestHandler(cgImage: image) try handler.perform([request]) guard let observation = request.results?.first else { return nil } // Extrait toutes les articulations détectées var joints: [VNHumanBodyPoseObservation.JointName: CGPoint] = [:] // Liste des articulations principales let jointNames: [VNHumanBodyPoseObservation.JointName] = [ .nose, .neck, .leftShoulder, .rightShoulder, .leftElbow, .rightElbow, .leftWrist, .rightWrist, .leftHip, .rightHip, .leftKnee, .rightKnee, .leftAnkle, .rightAnkle ] for jointName in jointNames { if let point = try? observation.recognizedPoint(jointName), point.confidence > 0.3 { // Convertit les coordonnées normalisées en points joints[jointName] = CGPoint(x: point.x, y: point.y) } } return DetectedPose( joints: joints, confidence: observation.confidence ) } // Détecte si la personne fait un squat func isSquatting(pose: DetectedPose) -> Bool { guard let kneeAngle = pose.angleBetween( .leftHip, .leftKnee, .leftAnkle ) else { return false } // Un squat a typiquement un angle au genou < 100° return kneeAngle < 100 } } ``` ## Optimisation et Production ### 9. Comment quantifier un modèle CoreML pour réduire sa taille ? La quantification réduit la précision des poids (de Float32 à Float16 ou Int8) pour diminuer la taille du modèle et accélérer l'inférence. Le trade-off est une légère perte de précision. ```python # quantize_model.py import coremltools as ct from coremltools.models.neural_network import quantization_utils # Charge le modèle existant model = ct.models.MLModel("MyModel.mlpackage") # Quantification Float16 (recommandé, bonne balance taille/précision) model_fp16 = ct.models.neural_network.quantization_utils.quantize_weights( model, nbits=16, quantization_mode="linear" ) model_fp16.save("MyModel_FP16.mlpackage") # Quantification Int8 (plus petite taille, perte de précision possible) # Nécessite un dataset de calibration pour de meilleurs résultats def calibration_data(): import numpy as np for _ in range(100): yield {"image": np.random.rand(1, 3, 224, 224).astype(np.float32)} model_int8 = ct.compression_utils.affine_quantize_weights( model, mode="linear_symmetric", dtype=ct.converters.mil.mil.types.int8 ) model_int8.save("MyModel_INT8.mlpackage") ``` ```swift // QuantizationComparison.swift import CoreML class ModelBenchmark { // Compare les performances des différentes versions func benchmark() async throws { let configs: [(String, URL)] = [ ("Full Precision", Bundle.main.url(forResource: "Model", withExtension: "mlmodelc")!), ("Float16", Bundle.main.url(forResource: "Model_FP16", withExtension: "mlmodelc")!), ("Int8", Bundle.main.url(forResource: "Model_INT8", withExtension: "mlmodelc")!) ] for (name, url) in configs { let model = try MLModel(contentsOf: url) // Mesure le temps d'inférence moyen sur 100 itérations let startTime = CFAbsoluteTimeGetCurrent() for _ in 0..<100 { let input = try prepareInput() _ = try model.prediction(from: input) } let elapsed = CFAbsoluteTimeGetCurrent() - startTime // Taille du modèle let size = try FileManager.default.attributesOfItem(atPath: url.path)[.size] as? Int ?? 0 print("\(name): \(elapsed/100*1000)ms/inference, \(size/1024/1024)MB") } } private func prepareInput() throws -> MLFeatureProvider { // Prépare un input de test fatalError("Implement based on model requirements") } } ``` ### 10. Comment gérer la mémoire lors du traitement de grandes images ? Le traitement d'images haute résolution peut causer des pics mémoire. Les techniques incluent le downsampling intelligent, le traitement par tuiles, et la libération proactive des ressources. ```swift // MemoryOptimization.swift import Vision import CoreImage class MemoryEfficientProcessor { // Context CoreImage réutilisable pour éviter les allocations private let ciContext = CIContext(options: [ .useSoftwareRenderer: false, .cacheIntermediates: false // Réduit l'usage mémoire ]) // Downsample intelligent d'une grande image func downsampleImage(at url: URL, to maxDimension: CGFloat) -> CGImage? { // Options pour le downsampling à la lecture (évite de charger l'image entière) let options: [CFString: Any] = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceThumbnailMaxPixelSize: maxDimension, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceShouldCacheImmediately: false ] guard let source = CGImageSourceCreateWithURL(url as CFURL, nil), let image = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else { return nil } return image } // Traitement par tuiles pour les très grandes images func processByTiles( image: CGImage, tileSize: CGSize, processor: (CGImage) throws -> [VNObservation] ) throws -> [VNObservation] { var allObservations: [VNObservation] = [] let imageWidth = CGFloat(image.width) let imageHeight = CGFloat(image.height) // Parcourt l'image par tuiles var y: CGFloat = 0 while y < imageHeight { var x: CGFloat = 0 while x < imageWidth { // Calcule le rectangle de la tuile let tileRect = CGRect( x: x, y: y, width: min(tileSize.width, imageWidth - x), height: min(tileSize.height, imageHeight - y) ) // Extrait la tuile autoreleasepool { if let tile = image.cropping(to: tileRect) { do { let observations = try processor(tile) // Ajuste les coordonnées relatives à l'image complète let adjusted = observations.compactMap { obs -> VNObservation? in guard let detected = obs as? VNDetectedObjectObservation else { return obs } // Recalcule le bounding box en coordonnées globales var box = detected.boundingBox box.origin.x = (box.origin.x * tileRect.width + x) / imageWidth box.origin.y = (box.origin.y * tileRect.height + y) / imageHeight box.size.width = box.size.width * tileRect.width / imageWidth box.size.height = box.size.height * tileRect.height / imageHeight return detected } allObservations.append(contentsOf: adjusted) } catch { print("Tile processing error: \(error)") } } } x += tileSize.width * 0.9 // Overlap de 10% pour éviter de couper les objets } y += tileSize.height * 0.9 } return allObservations } } ``` > **Attention aux fuites mémoire** > > Toujours utiliser `autoreleasepool` dans les boucles de traitement d'images et vérifier les retain cycles dans les closures des requêtes Vision. ### 11. Comment implémenter un pipeline ML avec Create ML Components ? Create ML Components (iOS 16+) permet de créer des pipelines ML modulaires avec des transformateurs prédéfinis. C'est plus flexible que les modèles monolithiques traditionnels. ```swift // CreateMLComponents.swift import CreateMLComponents import CoreImage @available(iOS 16.0, *) class MLPipeline { // Pipeline de classification d'images avec preprocessing func createImageClassificationPipeline() throws -> some Transformer { // Composition de transformateurs let pipeline = ImageReader() .appending(ImageScaler(targetSize: .init(width: 224, height: 224))) .appending(ImageNormalizer(mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225])) .appending(try ImageFeaturePrint()) .appending(try NearestNeighborClassifier .load(from: trainingDataURL)) return pipeline } // Pipeline personnalisé avec étapes custom func createCustomPipeline() -> some Transformer { // Étape 1: Prétraitement let preprocess = CIImageTransformer { image in // Applique des filtres CoreImage let adjusted = image .applyingFilter("CIColorControls", parameters: [ kCIInputContrastKey: 1.2, kCIInputSaturationKey: 1.1 ]) return adjusted } // Étape 2: Détection let detect = VisionTransformer { image in let request = VNDetectFaceRectanglesRequest() let handler = VNImageRequestHandler(ciImage: image) try handler.perform([request]) return request.results ?? [] } // Étape 3: Analyse let analyze = ResultTransformer<[VNFaceObservation], AnalysisResult> { faces in AnalysisResult( faceCount: faces.count, averageConfidence: faces.map(\.confidence).reduce(0, +) / Float(faces.count) ) } return preprocess .appending(detect) .appending(analyze) } } struct AnalysisResult { let faceCount: Int let averageConfidence: Float } ``` ### 12. Comment tester et valider un modèle CoreML ? Les tests incluent la validation de la précision, des tests de performance, et des tests d'intégration. Il est crucial de tester sur différents appareils et conditions. ```swift // MLModelTests.swift import XCTest import CoreML import Vision class CoreMLModelTests: XCTestCase { var model: VNCoreMLModel! override func setUpWithError() throws { let config = MLModelConfiguration() config.computeUnits = .cpuOnly // Reproductible sur CI let mlModel = try MyClassifier(configuration: config).model model = try VNCoreMLModel(for: mlModel) } // Test de précision avec dataset de validation func testClassificationAccuracy() async throws { let testCases: [(imageName: String, expectedClass: String)] = [ ("cat_001", "cat"), ("dog_001", "dog"), ("bird_001", "bird") ] var correct = 0 for testCase in testCases { let image = try loadTestImage(named: testCase.imageName) let prediction = try await classify(image: image) if prediction == testCase.expectedClass { correct += 1 } } let accuracy = Double(correct) / Double(testCases.count) XCTAssertGreaterThan(accuracy, 0.95, "Accuracy should be > 95%") } // Test de performance (temps d'inférence) func testInferencePerformance() throws { let image = try loadTestImage(named: "test_image") measure(metrics: [XCTClockMetric(), XCTMemoryMetric()]) { let request = VNCoreMLRequest(model: model) let handler = VNImageRequestHandler(cgImage: image) try? handler.perform([request]) } } // Test de robustesse aux transformations func testRobustness() async throws { let originalImage = try loadTestImage(named: "cat_001") let originalPrediction = try await classify(image: originalImage) // Teste avec rotation let rotated = try applyTransform(originalImage, rotation: .pi / 6) let rotatedPrediction = try await classify(image: rotated) XCTAssertEqual(originalPrediction, rotatedPrediction) // Teste avec bruit let noisy = try addNoise(to: originalImage, intensity: 0.1) let noisyPrediction = try await classify(image: noisy) XCTAssertEqual(originalPrediction, noisyPrediction) } // Test de gestion des cas limites func testEdgeCases() async throws { // Image très petite let smallImage = try loadTestImage(named: "tiny_10x10") let smallResult = try await classify(image: smallImage) XCTAssertNotNil(smallResult) // Image monochrome let monoImage = try loadTestImage(named: "grayscale") let monoResult = try await classify(image: monoImage) XCTAssertNotNil(monoResult) } // Helpers private func classify(image: CGImage) async throws -> String { let request = VNCoreMLRequest(model: model) let handler = VNImageRequestHandler(cgImage: image) try handler.perform([request]) guard let results = request.results as? [VNClassificationObservation], let top = results.first else { throw TestError.noResults } return top.identifier } private func loadTestImage(named: String) throws -> CGImage { guard let url = Bundle(for: type(of: self)) .url(forResource: named, withExtension: "jpg"), let source = CGImageSourceCreateWithURL(url as CFURL, nil), let image = CGImageSourceCreateImageAtIndex(source, 0, nil) else { throw TestError.imageNotFound } return image } } ``` ## Questions de conception système ### 13. Comment concevoir une architecture ML on-device pour une app de production ? Une architecture ML robuste sépare les préoccupations : modèle, preprocessing, postprocessing, et caching. Elle doit gérer les mises à jour de modèles et le fallback gracieux. ```swift // MLArchitecture.swift import CoreML import Vision // Protocol pour l'abstraction des modèles protocol MLModelProvider { associatedtype Input associatedtype Output func predict(_ input: Input) async throws -> Output var modelVersion: String { get } } // Gestionnaire de modèles avec mise à jour OTA class ModelManager { static let shared = ModelManager() private var models: [String: any MLModel] = [:] private let modelDirectory: URL private init() { modelDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] .appendingPathComponent("MLModels") try? FileManager.default.createDirectory(at: modelDirectory, withIntermediateDirectories: true) } // Charge un modèle avec fallback vers la version bundled func loadModel( named name: String, type: T.Type ) async throws -> T { // Vérifie si une version téléchargée existe let downloadedURL = modelDirectory.appendingPathComponent("\(name).mlmodelc") if FileManager.default.fileExists(atPath: downloadedURL.path) { // Valide l'intégrité du modèle téléchargé do { let model = try await loadAndValidate(from: downloadedURL, type: type) return model } catch { // Fallback vers version bundled si corrupted print("Downloaded model corrupted, falling back to bundled version") try? FileManager.default.removeItem(at: downloadedURL) } } // Charge la version bundled guard let bundledURL = Bundle.main.url(forResource: name, withExtension: "mlmodelc") else { throw ModelError.modelNotFound(name) } return try await loadAndValidate(from: bundledURL, type: type) } // Télécharge et installe une nouvelle version du modèle func updateModel(named name: String, from url: URL) async throws { // Télécharge le modèle let (tempURL, _) = try await URLSession.shared.download(from: url) // Compile le modèle si nécessaire let compiledURL: URL if tempURL.pathExtension == "mlmodel" { compiledURL = try MLModel.compileModel(at: tempURL) } else { compiledURL = tempURL } // Valide avant installation let config = MLModelConfiguration() _ = try MLModel(contentsOf: compiledURL, configuration: config) // Installe dans le répertoire des modèles let destURL = modelDirectory.appendingPathComponent("\(name).mlmodelc") try? FileManager.default.removeItem(at: destURL) try FileManager.default.moveItem(at: compiledURL, to: destURL) // Notifie l'app de la mise à jour NotificationCenter.default.post(name: .modelUpdated, object: name) } private func loadAndValidate( from url: URL, type: T.Type ) async throws -> T { let config = MLModelConfiguration() config.computeUnits = .all let model = try T(contentsOf: url, configuration: config) // Validation basique du modèle // Vérifier que les inputs/outputs correspondent aux attentes return model } } extension Notification.Name { static let modelUpdated = Notification.Name("MLModelUpdated") } ``` ### 14. Comment gérer les erreurs et le monitoring en production ? Un système de monitoring robuste capture les métriques de performance, les erreurs, et permet le debugging à distance. L'intégration avec des outils d'analytics est essentielle. ```swift // MLMonitoring.swift import OSLog class MLMonitor { static let shared = MLMonitor() private let logger = Logger(subsystem: "com.app.ml", category: "inference") private var metrics: [InferenceMetric] = [] struct InferenceMetric: Codable { let modelName: String let inferenceTime: Double let inputSize: CGSize? let confidence: Float? let timestamp: Date let success: Bool let errorDescription: String? } // Enregistre une inférence func recordInference( model: String, duration: TimeInterval, inputSize: CGSize? = nil, confidence: Float? = nil, error: Error? = nil ) { let metric = InferenceMetric( modelName: model, inferenceTime: duration, inputSize: inputSize, confidence: confidence, timestamp: Date(), success: error == nil, errorDescription: error?.localizedDescription ) metrics.append(metric) // Log pour debugging if let error = error { logger.error("ML inference failed: \(model) - \(error.localizedDescription)") } else { logger.info("ML inference: \(model) completed in \(duration)s") } // Détecte les anomalies checkForAnomalies(metric) } // Wrapper pour mesurer automatiquement func measure( model: String, inputSize: CGSize? = nil, operation: () async throws -> T ) async rethrows -> T { let start = CFAbsoluteTimeGetCurrent() do { let result = try await operation() let duration = CFAbsoluteTimeGetCurrent() - start recordInference( model: model, duration: duration, inputSize: inputSize ) return result } catch { let duration = CFAbsoluteTimeGetCurrent() - start recordInference( model: model, duration: duration, inputSize: inputSize, error: error ) throw error } } // Détecte les problèmes de performance private func checkForAnomalies(_ metric: InferenceMetric) { // Alerte si le temps d'inférence dépasse le seuil if metric.inferenceTime > 1.0 { logger.warning("Slow inference detected: \(metric.modelName) took \(metric.inferenceTime)s") // Envoie une alerte si disponible Task { await AnalyticsService.shared.reportAnomaly( type: .slowInference, details: metric ) } } // Alerte si la confiance est trop basse if let confidence = metric.confidence, confidence < 0.5 { logger.info("Low confidence prediction: \(confidence) for \(metric.modelName)") } } // Génère un rapport de performance func generateReport() -> PerformanceReport { let recentMetrics = metrics.filter { $0.timestamp > Date().addingTimeInterval(-3600) // Dernière heure } let avgInferenceTime = recentMetrics.map(\.inferenceTime).reduce(0, +) / Double(recentMetrics.count) let successRate = Double(recentMetrics.filter(\.success).count) / Double(recentMetrics.count) return PerformanceReport( totalInferences: recentMetrics.count, averageInferenceTime: avgInferenceTime, successRate: successRate, modelBreakdown: Dictionary(grouping: recentMetrics, by: \.modelName) ) } } struct PerformanceReport { let totalInferences: Int let averageInferenceTime: Double let successRate: Double let modelBreakdown: [String: [MLMonitor.InferenceMetric]] } ``` ## Conclusion Vision Framework et CoreML représentent la fondation du machine learning on-device sur iOS. Maîtriser ces technologies est essentiel pour développer des applications modernes qui respectent la vie privée des utilisateurs tout en offrant des fonctionnalités ML avancées. ### Checklist de révision - ✅ Comprendre CoreML et ses avantages (confidentialité, latence, offline) - ✅ Savoir convertir des modèles TensorFlow/PyTorch vers CoreML - ✅ Maîtriser les requêtes Vision (détection visages, OCR, classification) - ✅ Implémenter le suivi d'objets en temps réel - ✅ Optimiser les performances (quantification, gestion mémoire) - ✅ Concevoir des architectures ML robustes pour la production - ✅ Mettre en place le monitoring et la gestion des erreurs ### Points clés à retenir La performance on-device dépend fortement du choix entre CPU, GPU et Neural Engine. La quantification des modèles offre un excellent compromis taille/performance. Le monitoring en production est crucial pour détecter les régressions. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/ios/vision-coreml-questions-entretien-ml-ios