← Back to DevBytes

macOS CreateML Training

Introduction to Create ML

Create ML is Apple's machine learning framework designed specifically for macOS, iPadOS, and iOS developers who want to train custom machine learning models without needing deep expertise in data science or complex ML frameworks like TensorFlow or PyTorch. First introduced at WWDC 2018, Create ML provides a streamlined, approachable way to build models that run natively on Apple devices through the Core ML framework.

What makes Create ML particularly powerful is its dual interface: developers can use the visual Create ML app bundled with Xcode for a no-code experience, or they can use the CreateML Swift framework for programmatic control over the entire training pipeline. Both approaches produce .mlmodel files that can be integrated directly into iOS, macOS, watchOS, and tvOS applications.

Why Create ML Matters

Before Create ML, Apple developers who needed custom machine learning models had to train them using third-party frameworks and then convert them to Core ML format using Core ML Tools. This workflow introduced friction, required knowledge of Python, and often involved managing separate training environments. Create ML eliminates these barriers by bringing the entire training process into the Apple ecosystem.

Key Advantages

Prerequisites and Setup

Before diving into Create ML, ensure you have the following prerequisites in place. Create ML requires a Mac running macOS 10.15 Catalina or later, though macOS 12 Monterey or later is recommended for access to the latest model types and features.

System Requirements

Preparing Your Data

The quality of your training data directly determines the quality of your model. Create ML expects data in specific formats depending on the model type you are training. Here is a quick reference for common data formats:

Here is an example of a proper directory structure for an image classification dataset:

TrainingData/
β”œβ”€β”€ cats/
β”‚   β”œβ”€β”€ cat_001.jpg
β”‚   β”œβ”€β”€ cat_002.jpg
β”‚   └── cat_003.jpg
β”œβ”€β”€ dogs/
β”‚   β”œβ”€β”€ dog_001.jpg
β”‚   β”œβ”€β”€ dog_002.jpg
β”‚   └── dog_003.jpg
└── birds/
    β”œβ”€β”€ bird_001.jpg
    β”œβ”€β”€ bird_002.jpg
    └── bird_003.jpg

Using the Create ML App

The Create ML app provides a visual, no-code interface for training models. It is the fastest way to get started and is ideal for prototyping and simple use cases. You can find the Create ML app in /Applications/Xcode.app/Contents/Applications/ or by searching for it in Spotlight.

Training an Image Classifier with the App

To train an image classifier using the Create ML app, follow these steps:

The app also provides a preview feature that lets you test your model by dragging in new images before exporting it, giving you immediate feedback on model performance.

Using the CreateML Swift Framework

For developers who need more control over the training process, the CreateML Swift framework offers a programmatic API. This approach is essential when you need to automate training pipelines, integrate training into CI/CD systems, or fine-tune hyperparameters programmatically.

Setting Up a Training Script

Create ML training scripts run as macOS command-line applications or within Swift Playgrounds on iPad. To create a training script, start a new macOS Command Line Tool project in Xcode and import the CreateML and CoreML frameworks.

import CreateML
import CoreML
import Foundation

// Define paths to your data
let trainingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/TrainingData")
let testingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/TestingData")
let outputURL = URL(fileURLWithPath: "/Users/developer/MLModels/AnimalClassifier.mlmodel")

do {
    // Load the training data
    let trainingData = try MLImageClassifier.DataSource.labeledDirectories(at: trainingDataURL)
    
    // Print dataset summary
    print("Training data loaded successfully")
    
} catch {
    print("Failed to load training data: \(error)")
    exit(1)
}

Training an Image Classifier Programmatically

Here is a complete example of training an image classifier with custom parameters:

import CreateML
import CoreML
import Foundation

let trainingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/TrainingData")
let testingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/TestingData")
let outputURL = URL(fileURLWithPath: "/Users/developer/MLModels/AnimalClassifier.mlmodel")

do {
    // Load training and testing data
    let trainingData = try MLImageClassifier.DataSource.labeledDirectories(at: trainingDataURL)
    let testingData = try MLImageClassifier.DataSource.labeledDirectories(at: testingDataURL)
    
    // Configure model parameters
    let parameters = MLImageClassifier.ModelParameters(
        validation: .split(strategy: .automatic),
        maxIterations: 50,
        augmentation: [
            .crop,
            .rotation,
            .blur,
            .exposure,
            .noise,
            .flip
        ],
        featurePrintExtractor: .scenePrint(revision: 3)
    )
    
    // Train the model
    print("Starting training...")
    let model = try MLImageClassifier(
        trainingData: trainingData,
        parameters: parameters
    )
    
    // Evaluate on training data
    let trainingMetrics = model.trainingMetrics
    print("\n--- Training Metrics ---")
    print("Accuracy: \(trainingMetrics.classificationError)")
    if let accuracy = trainingMetrics.classificationError {
        print("Training Accuracy: \((1.0 - accuracy) * 100)%")
    }
    
    // Evaluate on testing data
    let evaluationMetrics = model.evaluation(on: testingData)
    print("\n--- Evaluation Metrics ---")
    if let accuracy = evaluationMetrics.classificationError {
        print("Testing Accuracy: \((1.0 - accuracy) * 100)%")
    }
    
    // Save the model
    try model.write(to: outputURL)
    print("\nModel saved to: \(outputURL.path)")
    
    // Display metadata
    let metadata = MLModelMetadata(
        author: "Developer Name",
        shortDescription: "Classifies images of cats, dogs, and birds",
        version: "1.0.0"
    )
    try model.write(to: outputURL, metadata: metadata)
    print("Model saved with metadata")
    
} catch {
    print("Training failed with error: \(error)")
    exit(1)
}

Training a Text Classifier

Text classification is another common use case. Create ML supports several algorithms for text classification, including maximum entropy, transfer learning with word embeddings, and dynamic embedding-based approaches. Here is a complete example:

import CreateML
import CoreML
import Foundation

let trainingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/TextTraining.json")
let testingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/TextTesting.json")
let outputURL = URL(fileURLWithPath: "/Users/developer/MLModels/SentimentClassifier.mlmodel")

do {
    // Load data from JSON
    // JSON format: [{"text": "I love this product!", "label": "positive"}, ...]
    let trainingData = try MLTextClassifier.DataSource.labeledDirectories(at: trainingDataURL)
    
    // Alternatively, load from JSON with explicit columns
    // let trainingData = try MLTextClassifier.DataSource.json(
    //     file: trainingDataURL,
    //     textColumn: "text",
    //     labelColumn: "label"
    // )
    
    // Configure parameters - choose algorithm
    let parameters = MLTextClassifier.ModelParameters(
        validation: .split(strategy: .automatic),
        algorithm: .transferLearning(
            featureExtractor: .language(
                language: .english,
                model: .bert
            )
        ),
        language: .english
    )
    
    // Alternative algorithms:
    // .maxEnt(revision: 1) - fast, lightweight, good for simple tasks
    // .dynamicEmbedding - good balance of speed and accuracy
    // .transferLearning - highest accuracy, larger model
    
    print("Starting text classifier training...")
    let model = try MLTextClassifier(
        trainingData: trainingData,
        parameters: parameters
    )
    
    // Evaluate
    let testingData = try MLTextClassifier.DataSource.labeledDirectories(at: testingDataURL)
    let evaluationMetrics = model.evaluation(on: testingData)
    
    if let accuracy = evaluationMetrics.classificationError {
        print("Testing Accuracy: \((1.0 - accuracy) * 100)%")
    }
    
    // Save with metadata
    let metadata = MLModelMetadata(
        author: "Developer Name",
        shortDescription: "Sentiment analysis model for product reviews",
        version: "1.0.0"
    )
    
    try model.write(to: outputURL, metadata: metadata)
    print("Text classifier saved to: \(outputURL.path)")
    
} catch {
    print("Training failed: \(error)")
    exit(1)
}

Training a Tabular Classifier

For structured data in CSV or JSON format, Create ML provides MLTabularClassifier and MLRegressor. These are useful for tasks like predicting customer churn, classifying loan applications, or predicting numerical values.

import CreateML
import CoreML
import Foundation

let trainingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/ChurnTraining.csv")
let testingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/ChurnTesting.csv")
let outputURL = URL(fileURLWithPath: "/Users/developer/MLModels/CustomerChurnClassifier.mlmodel")

do {
    // Load CSV data
    // CSV should have columns like: age, tenure, monthly_charges, contract_type, churn
    let trainingTable = try MLDataTable(contentsOf: trainingDataURL)
    let testingTable = try MLDataTable(contentsOf: testingDataURL)
    
    print("Training data rows: \(trainingTable.rows.count)")
    print("Testing data rows: \(testingTable.rows.count)")
    print("Columns: \(trainingTable.columnNames)")
    
    // Configure parameters
    let parameters = MLTabularClassifier.ModelParameters(
        validation: .split(strategy: .automatic),
        maxIterations: 100
    )
    
    // Train the classifier - specify target column
    print("Training tabular classifier...")
    let model = try MLTabularClassifier(
        trainingData: trainingTable,
        targetColumn: "churn",
        parameters: parameters
    )
    
    // Evaluate
    let evaluationMetrics = model.evaluation(on: testingTable)
    
    if let accuracy = evaluationMetrics.classificationError {
        print("Testing Accuracy: \((1.0 - accuracy) * 100)%")
    }
    
    // View feature importance
    print("\nFeature Importance:")
    if let featureImportance = model.featureImportance {
        for (feature, importance) in featureImportance.sorted(by: { $0.value > $1.value }) {
            print("  \(feature): \(importance)")
        }
    }
    
    // Save model
    let metadata = MLModelMetadata(
        author: "Developer Name",
        shortDescription: "Predicts customer churn based on account features",
        version: "1.0.0"
    )
    
    try model.write(to: outputURL, metadata: metadata)
    print("\nTabular classifier saved to: \(outputURL.path)")
    
} catch {
    print("Training failed: \(error)")
    exit(1)
}

Training a Sound Classifier

Sound classification is useful for applications like music genre detection, environmental sound recognition, or custom voice commands. Create ML uses audio feature extraction combined with a neural network for this task.

import CreateML
import CoreML
import Foundation

let trainingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/SoundTraining")
let testingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/SoundTesting")
let outputURL = URL(fileURLWithPath: "/Users/developer/MLModels/SoundClassifier.mlmodel")

do {
    // Load audio data from labeled directories
    let trainingData = try MLSoundClassifier.DataSource.labeledDirectories(at: trainingDataURL)
    let testingData = try MLSoundClassifier.DataSource.labeledDirectories(at: testingDataURL)
    
    // Configure parameters
    let parameters = MLSoundClassifier.ModelParameters(
        validation: .split(strategy: .automatic),
        maxIterations: 50
    )
    
    print("Training sound classifier...")
    let model = try MLSoundClassifier(
        trainingData: trainingData,
        parameters: parameters
    )
    
    // Evaluate
    let evaluationMetrics = model.evaluation(on: testingData)
    
    if let accuracy = evaluationMetrics.classificationError {
        print("Testing Accuracy: \((1.0 - accuracy) * 100)%")
    }
    
    // Save model
    let metadata = MLModelMetadata(
        author: "Developer Name",
        shortDescription: "Classifies environmental sounds",
        version: "1.0.0"
    )
    
    try model.write(to: outputURL, metadata: metadata)
    print("Sound classifier saved to: \(outputURL.path)")
    
} catch {
    print("Training failed: \(error)")
    exit(1)
}

Training an Object Detection Model

Object detection goes beyond classification by identifying both the class and location of objects within an image. This requires annotated training data with bounding box coordinates. Create ML supports JSON annotations in a specific format.

Preparing Object Detection Data

Your annotation JSON files should follow this structure:

[
  {
    "image": "street_scene_001.jpg",
    "annotations": [
      {
        "label": "car",
        "coordinates": {
          "x": 120,
          "y": 200,
          "width": 150,
          "height": 100
        }
      },
      {
        "label": "person",
        "coordinates": {
          "x": 350,
          "y": 180,
          "width": 60,
          "height": 180
        }
      }
    ]
  }
]

Training the Object Detector

import CreateML
import CoreML
import Foundation

let trainingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/ObjectDetectionTraining")
let testingDataURL = URL(fileURLWithPath: "/Users/developer/MLData/ObjectDetectionTesting")
let outputURL = URL(fileURLWithPath: "/Users/developer/MLModels/ObjectDetector.mlmodel")

do {
    // Load annotated data
    let trainingData = try MLObjectDetector.DataSource.labeledDirectories(at: trainingDataURL)
    let testingData = try MLObjectDetector.DataSource.labeledDirectories(at: testingDataURL)
    
    // Configure parameters
    let parameters = MLObjectDetector.ModelParameters(
        validation: .split(strategy: .automatic),
        maxIterations: 100,
        augmentationOptions: [.crop, .blur, .exposure, .noise, .rotation, .flip]
    )
    
    print("Training object detector...")
    let model = try MLObjectDetector(
        trainingData: trainingData,
        parameters: parameters
    )
    
    // Evaluate
    let evaluationMetrics = model.evaluation(on: testingData)
    print("IoU (Intersection over Union): \(evaluationMetrics.iou)")
    print("Precision: \(evaluationMetrics.precision)")
    print("Recall: \(evaluationMetrics.recall)")
    
    // Save model
    let metadata = MLModelMetadata(
        author: "Developer Name",
        shortDescription: "Detects cars, pedestrians, and traffic signs",
        version: "1.0.0"
    )
    
    try model.write(to: outputURL, metadata: metadata)
    print("Object detector saved to: \(outputURL.path)")
    
} catch {
    print("Training failed: \(error)")
    exit(1)
}

Evaluating Model Performance

Understanding evaluation metrics is critical for building reliable machine learning models. Create ML provides several metrics depending on the model type. For classification models, the most important metrics include accuracy, precision, recall, and the confusion matrix.

Reading the Confusion Matrix

The confusion matrix shows how your model performs across all classes, revealing which classes are commonly confused with each other. Here is how to access and interpret it:

import CreateML
import Foundation

// Assuming you have a trained model and evaluation metrics
let evaluationMetrics = model.evaluation(on: testingData)

// Access the confusion matrix
if let confusionMatrix = evaluationMetrics.confusionMatrix {
    print("Confusion Matrix:")
    print(confusionMatrix.description)
    
    // The matrix rows represent actual labels
    // The matrix columns represent predicted labels
    // Diagonal elements are correct predictions
    // Off-diagonal elements are misclassifications
}

// For a more detailed analysis, iterate through class pairs
for (actualLabel, predictions) in confusionMatrix {
    for (predictedLabel, count) in predictions {
        if actualLabel != predictedLabel && count > 0 {
            print("Misclassified \(count) '\(actualLabel)' as '\(predictedLabel)'")
        }
    }
}

Cross-Validation

For more robust evaluation, you can implement k-fold cross-validation by splitting your data into multiple folds and training multiple models:

import CreateML
import Foundation

func performCrossValidation(
    dataURL: URL,
    folds: Int = 5,
    maxIterations: Int = 30
) throws -> [Double] {
    var accuracies: [Double] = []
    
    // Load all data
    let allData = try MLImageClassifier.DataSource.labeledDirectories(at: dataURL)
    
    for fold in 0..<folds {
        print("Training fold \(fold + 1) of \(folds)...")
        
        let parameters = MLImageClassifier.ModelParameters(
            validation: .split(strategy: .automatic),
            maxIterations: maxIterations
        )
        
        let model = try MLImageClassifier(
            trainingData: allData,
            parameters: parameters
        )
        
        if let error = model.trainingMetrics.classificationError {
            let accuracy = (1.0 - error) * 100
            accuracies.append(accuracy)
            print("  Fold \(fold + 1) accuracy: \(accuracy)%")
        }
    }
    
    let meanAccuracy = accuracies.reduce(0, +) / Double(accuracies.count)
    print("\nMean accuracy across \(folds) folds: \(meanAccuracy)%")
    
    return accuracies
}

let dataURL = URL(fileURLWithPath: "/Users/developer/MLData/TrainingData")
let results = try performCrossValidation(dataURL: dataURL, folds: 5)

Integrating Models with Core ML

Once you have trained and exported your .mlmodel file, integrating it into an iOS or macOS app is straightforward. Xcode automatically generates a Swift class for your model when you add it to your project.

Using an Image Classifier in Your App

import CoreML
import Vision
import UIKit

class ImageClassificationService {
    
    private let model: VNCoreMLModel
    
    init() throws {
        // Load the model - Xcode generates the AnimalClassifier class
        let config = MLModelConfiguration()
        config.computeUnits = .all // Use CPU, GPU, and Neural Engine
        
        let mlModel = try AnimalClassifier(configuration: config).model
        self.model = try VNCoreMLModel(for: mlModel)
    }
    
    func classify(image: UIImage, completion: @escaping (Result<String, Error>) -> Void) {
        guard let cgImage = image.cgImage else {
            completion(.failure(ClassificationError.invalidImage))
            return
        }
        
        let request = VNCoreMLRequest(model: model) { request, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let results = request.results as? [VNClassificationObservation],
                  let topResult = results.first else {
                completion(.failure(ClassificationError.noResults))
                return
            }
            
            let label = topResult.identifier
            let confidence = topResult.confidence
            print("Predicted: \(label) with \(confidence * 100)% confidence")
            
            completion(.success(label))
        }
        
        request.imageCropAndScaleOption = .centerCrop
        
        let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
        
        DispatchQueue.global(qos: .userInitiated).async {
            do {
                try handler.perform([request])
            } catch {
                completion(.failure(error))
            }
        }
    }
}

enum ClassificationError: Error {
    case invalidImage
    case noResults
}

Using a Text Classifier in Your App

import CoreML
import NaturalLanguage

class TextClassificationService {
    
    private let model: NLModel
    
    init() throws {
        let config = MLModelConfiguration()
        self.model = try NLModel(mlModel: SentimentClassifier(configuration: config).model)
    }
    
    func classify(text: String) -> (label: String, confidence: Double) {
        let prediction = try? model.predictedLabel(for: text)
        
        // For more detailed predictions with confidence scores
        let hypotheses = model.predictedLabelHypotheses(for: text, maximumResults: 3)
        
        if let topPrediction = hypotheses.first {
            return (topPrediction.key, topPrediction.value)
        }
        
        return ("unknown", 0.0)
    }
    
    func classifyBatch(texts: [String]) -> [String] {
        return texts.map { text in
            model.predictedLabel(for: text) ?? "unknown"
        }
    }
}

Model Compression and Optimization

After training, you may need to reduce your model's size for faster downloads or to fit within app size limits. Create ML and Core ML support several compression techniques.

Applying Weight Quantization

import CoreML
import Foundation

func compressModel(
    inputURL: URL,
    outputURL: URL,
    compressionLevel: CoreMLModelCompressionConfiguration
) throws {
    let model = try MLModel(contentsOf: inputURL)
    
    let compressedModel = try model.compress(
        configuration: compressionLevel
    )
    
    try compressedModel.write(to: outputURL)
    
    // Compare file sizes
    let originalSize = try FileManager.default.attributesOfItem(atPath: inputURL.path)[.size] as? Int ?? 0
    let compressedSize = try FileManager.default.attributesOfItem(atPath: outputURL.path)[.size] as? Int ?? 0
    
    print("Original size: \(originalSize / 1024 / 1024) MB")
    print("Compressed size: \(compressedSize / 1024 / 1024) MB")
    print("Reduction: \(Double(1 - compressedSize) / Double(originalSize) * 100)%")
}

// Available compression options:
// .uniformQuantization(bits: 6) - 6-bit quantization, good balance
// .uniformQuantization(bits: 4) - 4-bit quantization, more aggressive
// .palettization(bits: 6) - palette-based compression
// .pruning - removes less important weights

let inputURL = URL(fileURLWithPath: "/Users/developer/MLModels/AnimalClassifier.mlmodel")
let outputURL = URL(fileURLWithPath: "/Users/developer/MLModels/AnimalClassifier_compressed.mlmodel")

try compressModel(
    inputURL: inputURL,
    outputURL: outputURL,
    compressionLevel: .uniformQuantization(bits: 6)
)

Best Practices

Data Preparation Best Practices

Training Best Practices

Deployment Best Practices

Privacy and Ethics Best Practices

Advanced Techniques

Transfer Learning with Custom Feature Extractors

For image classification, Create ML uses transfer learning by default, leveraging a pre-trained feature extractor (scene print) and training a new classifier on top. You can control which feature extractor revision to use:

import CreateML

let parameters = MLImageClassifier.ModelParameters(
    validation: .split(strategy: .automatic),
    maxIterations: 50,
    augmentation: [.crop, .rotation, .blur, .exposure, .noise, .flip],
    featurePrintExtractor: .scenePrint(revision: 3)  // Latest revision
)

// Available revisions: 1, 2, 3
// Revision 3 is recommended for most use cases
// Older revisions may be needed for compatibility with older OS versions

Controlling Compute Units

You can control which processing units your model uses during inference. This is useful for balancing speed and power consumption:

import CoreML

let config = MLModelConfiguration()

// Options:
// .all - Use all available units (CPU, GPU, Neural Engine)
// .cpuAndGPU - Skip Neural Engine
// .cpuOnly - Use only CPU
// .cpuAndNeuralEngine - Skip GPU

config.computeUnits = .all

// For models that need to run in the background,
// you may want to restrict to CPU only to avoid
// competing with foreground GPU tasks
let backgroundConfig = MLModelConfiguration()
backgroundConfig.computeUnits = .cpuOnly

let model = try AnimalClassifier(configuration: config)

Batch Prediction for Efficiency

When processing multiple inputs, batch predictions are more efficient than individual predictions:

import CoreML
import Foundation

func batchPredict(images: [CGImage], model: AnimalClassifier) throws -> [AnimalClassifierOutput] {
    var results: [AnimalClassifierOutput] = []
    
    // Process in batches to manage memory
    let batchSize = 32
    for batchStart in stride(from: 0, to: images.count, by: batchSize) {
        let batchEnd = min(batchStart + batchSize, images.count)
        let batch = images[batchStart..<batchEnd]
        
        for image in batch {
            let input = try AnimalClassifierInput(image_0: image)
            let output = try model.prediction(input: input)
            results.append(output)
        }
    }
    
    return results
}

Conclusion

Create ML has democratized machine learning on Apple platforms by providing an accessible yet powerful framework for training custom models entirely within the Swift ecosystem. Whether you use the visual Create ML app for rapid prototyping or the programmatic Swift framework for automated pipelines, the ability to train, evaluate, and deploy models without leaving your Mac streamlines the entire ML development workflow. By following the best practices outlined in this tutorialβ€”investing in high-quality training data, carefully monitoring evaluation metrics, leveraging data augmentation, and thoroughly testing on target devicesβ€”you can build machine learning features that are accurate, efficient, and respectful of user privacy. As Apple continues to expand Create ML with new model types and capabilities, it remains an essential tool in any Apple developer's toolkit for bringing intelligent, on-device experiences to users across iOS, macOS, watchOS, and tvOS.

β€” Ad β€”

Google AdSense will appear here after approval

← Back to all articles