Building a Recommendation App With Create ML in SwiftUI
Learn how to train a model and how to give it prediction capability using Core ML and Create ML in SwiftUI. By Saeed Taheri.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Building a Recommendation App With Create ML in SwiftUI
25 mins
Believe it or not, research into artificial intelligence, or AI, goes way back to the 1950s, but it wasn’t until the late 1990s that it started to show its value by finding specific solutions to specific problems.
Machine learning, or ML, is one of the important fields of AI and primarily focuses on understanding and building methods that learn. It tries to build a model based on training data so it can make decisions or predictions without someone having programmed it to do so.
ML has two main objectives: classification and prediction.
- Classification classifies currently available data and makes decisions based on the developed models.
- Prediction makes forecasts of future outcomes.
In Apple platforms, Core ML and Create ML are the main frameworks for machine learning.
- Core ML lets you train a model based on the training data, and you can use the produced model in your apps on most Apple platforms.
- Create ML, introduced in iOS 15, provides you with a means to create a Core ML model inside your app on iOS, macOS, iPadOS, and Mac Catalyst.
In this tutorial, you’ll develop an app called Tshirtinder — an app designed to match you to the perfect t-shirt. As its name suggests, it shows you a t-shirt, then you express your interest — or lack thereof — with Tinder-style gestures of swiping right or left.
After each swipe, the app shows a selection of t-shirts it thinks would interest you. As the app learns your t-shirt preferences, the recommendations become more relevant.
Before you get to the fun part of judging t-shirts, you’ll satisfy these learning objectives:
- How to use Create ML to integrate AI within an app.
- Create and train a model.
- Build out predictive capabilities.
Getting Started
Download the starter project by clicking on the Download Materials button at the top or bottom of the tutorial.
Open TShirtinder.xcodeproj, then build and run it on your device.
Take a moment to play with the app. All the code to support core features, such as Tinder-style swipe animation, are already there for you to enjoy.
Regression vs. Classification
Regression predictive modeling problems are different from those of classification predictive modeling — in essence:
- Regression predicts a continuous quantity.
- Classification predicts a discrete class label.
Some overlaps exist between regression and classification:
- A regression algorithm may predict a discrete value if it’s in the form of an integer quantity.
- A classification algorithm may be in the form of a probability for a class label. If so, it may predict a continuous value.
With these in mind, you can use any of these modelings for your Tshirtinder. Yet, looking at the algorithms available in Create ML, a linear regression seems like a good fit.
What is Linear Regression?
Linear regression is a well-known algorithm in statistics and machine learning.
It’s a model that assumes a linear relationship between the input variables x and the single output variable y. It will calculate y from a linear combination of the input variables x.
In ML terms, people sometimes call input variables features. A feature is an individual measurable property or characteristic of a phenomenon.
Open shirts.json. As you see, all the t-shirts the app can show are in this file. For each t-shirt, there are features such as sleeve type, color, and neck type.
{
"title": "Non-Plain Polo Short-Sleeve White",
"image_name": "white-short-graphic-polo",
"color": "white",
"sleeve": "short",
"design": "non-plain",
"neck": "polo"
}
You can’t consider all the properties in each instance as features. For instance, the title
or image_name
isn’t suitable for showing the characteristics of a t-shirt — you can’t use them to predict the output.
Imagine you want to predict a value for a set of data with a single feature. You could visualize the data as such:
Linear regression tries to fit a line through the data.
Then you use it to predict an estimated output for an unseen input. Assuming you have a model with two features, a two-dimensional plane will fit through the data.
To generalize this idea, imagine that you have a model with n features, so an (n-1) dimensional plane will be the regressor.
Consider the equation below:
Y = a + b * X
Where X
is the explanatory variable and Y
is the dependent variable. The slope of the line is b
, and a
is the intercept — the value of Y
when X
equals 0.
That’s enough theory for now.
How about you get your hands dirty and let technology help you get some new threads?
Preparing Data for Training
First, have a look at the methods you’ll work with and get to know how they work.
Open MainViewModel.swift and look at loadAllShirts()
.
This method asynchronously fetches all the shirts from shirts.json then stores them as a property of type FavoriteWrapper
in MainViewModel
. This wrapper adds a property to store the favorite status of each item, but the value is nil
when there’s no information about the user’s preferences.
Now examine the other method — where most of the “magic” happens: didRemove(_:isLiked:)
. You call this method each time a user swipes an item.
The isLiked
parameter tracks if the user liked a specific item or not.
This method first removes the item from shirts
then updates the isFavorite
field of the item in allShirts
.
The shirts
property holds all the items the user hasn’t yet acted on. Here’s when the ML part of the app comes in: You’ll compute recommended shirts anytime the user swipes left or right on a given t-shirt.
RecommendationStore
handles the process of computing recommendations — it’ll train the model based on updated user inputs then suggest items the user might like.
Computing Recommendations
First, add an instance property to MainViewModel
to hold and track the task of computing t-shirt recommendations to the user:
private var recommendationsTask: Task<Void, Never>?
If this were a real app, you’d probably want the output of the task and you’d also need some error handling. But this is a tutorial, so the generic types of Void
and Never
will do.
Next, add these lines at the end of didRemove(_:isLiked:)
:
// 1
recommendationsTask?.cancel()
// 2
recommendationsTask = Task {
do {
// 3
let result = try await recommendationStore.computeRecommendations(basedOn: allShirts)
// 4
if !Task.isCancelled {
recommendations = result
}
} catch {
// 5
print(error.localizedDescription)
}
}
When the user swipes, didRemove(_:isLiked:)
is called and the following happens:
- Cancel any ongoing computation task since the user may swipe quickly.
- Store the task inside the property you just created — step 1 exemplifies why you need this.
- Ask
recommendationStore
to compute recommendations based on all the shirts. As you saw before,allShirts
is of the typeFavoriteWrapper
and holds theisFavorite
status of shirts. Disregard the compiler error — you’ll address its complaint soon. - Check for the canceled task, because by the time the
result
is ready, you might have canceled it. You check for that incident here so you don’t show stale data. If the task is still active, set the result torecommendations
published property. The view is watching this property and updates it accordingly. - Computing recommendations throws an
async
function. If it fails, print an error log to the console.
Now open RecommendationStore.swift. Inside RecommendationStore
, create this method:
func computeRecommendations(basedOn items: [FavoriteWrapper<Shirt>]) async throws -> [Shirt] {
return []
}
This is the signature you used earlier in MainViewModel
. For now, you return an empty array to silence the compiler.