Software Development

What Machine Learning Is and How to Build a Model

Machine learning is a branch of artificial intelligence that enables software to identify patterns in data and use them to make predictions or support decisions. Instead of relying entirely on rules we explicitly define, a machine learning application uses examples to learn relationships between input data and expected outcomes. Once trained, the resulting model can apply those learned relationships to new data to classify information, estimate numerical values, generate recommendations, or predict future outcomes.

The main difference between machine learning and traditional programming is how decisions are made. In traditional programming, developers write the rules that process input and produce output. In machine learning, developers provide data and select an algorithm, and the training process learns patterns from the data to make predictions.

A machine learning model is the result of this learning process. Once trained, it can receive data it did not see during training and produce a prediction based on the patterns it learned. The ability to perform well on new data is more important than simply memorising the training examples, which is why evaluating a model with data it has not seen during training is an essential part of machine learning development. This article demonstrates how to build a simple machine learning application using Java and Tribuo.

1. What is a Machine Learning Model?

A machine learning model is a mathematical representation of relationships learned from training data. During training, an algorithm examines examples and adjusts internal parameters so that its predictions become closer to the known results in the training dataset.

Consider a dataset containing historical football matches. Each match could contain statistics such as the home team’s recent win percentage, the away team’s recent win percentage, average points scored, and average points conceded. The final result of each match provides the target that the model should learn to predict.

For example, a training record could contain the following information:

Home win percentage:     0.70
Away win percentage:     0.45
Home average scored:    24.5
Away average scored:    20.1
Home average conceded:  17.3
Away average conceded:  23.4
Result:                 HOME_WIN

The numerical values are the model’s features, while HOME_WIN is the label. A collection of many such records gives the learning algorithm examples from which it can estimate a relationship between the features and the result.

When a new match arrives, the model receives the same types of features but without the known result. It then uses its learned parameters to estimate the most appropriate class.

This is an example of supervised learning because the training data contains both inputs and known outcomes. Classification is one of the common supervised learning tasks, where the model chooses between predefined categories. Regression is another supervised learning task, but instead of selecting a category, it estimates a continuous numerical value.

2. Types of Machine Learning

Machine learning approaches differ in how they learn from data and how they use the information available during training. The main types are supervised learning, unsupervised learning, and reinforcement learning. Each approach addresses different problems, so choosing the right one depends on the objective of the application and the nature of the available data.

Supervised Learning

Supervised learning uses training data that contains input features and known outputs. The algorithm examines these examples to learn relationships between the inputs and their corresponding results. After training, the model can use those relationships to make predictions for new data.

Supervised learning is commonly divided into classification and regression. Classification predicts a category, such as whether a customer will cancel a subscription or whether a football team will win a match. Regression estimates a numerical value, such as a property’s selling price or the expected delivery time for an order.

The football prediction application developed in this article is an example of supervised learning. Historical match statistics serve as the input features, while the known match outcomes provide the labels.

Unsupervised Learning

Unsupervised learning works with data that does not have predefined target labels. Rather than learning to predict a known answer, the algorithm attempts to discover patterns, similarities, or structures within the available information.

Clustering is a common example. A business might use a clustering algorithm to group customers according to their purchasing behaviour without assigning customer categories in advance. Other applications include identifying unusual data points and exploring relationships within large datasets. Unlike the football classification example, an unsupervised learning task would not require the historical match outcomes as target labels. Instead, an algorithm might group teams according to similarities in their performance statistics.

Reinforcement Learning

Reinforcement learning involves an agent that interacts with an environment and receives feedback based on its actions. The agent learns a strategy for selecting actions that can maximise the rewards it receives over time.

This approach is used in areas such as robotics, game-playing systems, resource allocation, and certain control problems. For example, a robot could learn how to navigate an environment by receiving positive feedback for reaching a destination and negative feedback for undesirable actions.

Reinforcement learning differs from the other approaches described above because the learning process centres on actions, feedback, and the consequences of decisions rather than simply predicting a labelled outcome from a fixed dataset.

Choosing the Right Type of Machine Learning

The appropriate approach depends on the problem being solved and the data available. Supervised learning is suitable when examples have known outcomes that the model needs to predict. Unsupervised learning is appropriate when the objective is to discover structure in data without predefined labels. Semi-supervised learning can help when only a portion of the available data has been labelled, while reinforcement learning is designed for problems involving actions and feedback over time.

3. Building a Machine Learning Model with Java and Tribuo

Tribuo is a Java machine learning library developed by Oracle Labs. It provides APIs for several machine learning tasks, including classification, regression, and clustering. Its API is designed around Java types such as datasets, examples, models, and predictions rather than requiring developers to work directly with unstructured numerical arrays for every operation.

For this example, we will use Tribuo’s classification functionality. A Label represents the output of a classification model, while a LabelFactory creates and manages those classification outputs. Tribuo also provides trainers and evaluators that allow a model to be trained and measured using the same API.

Setting Up the Project

We will use Quarkus to expose the prediction functionality through an HTTP endpoint and Tribuo to train and run the classification model. Maven will manage the dependencies.

Generate the project with the following command:

mvn io.quarkus:quarkus-maven-plugin:3.40.1:create \
    -DprojectGroupId=com.jcg.example \
    -DprojectArtifactId=football-prediction \
    -DclassName="com.jcg.example.resource.PredictionResource" \
    -Dpath="/predictions" \
    -Dextensions="rest-jackson"

This command creates a Quarkus project named football-prediction and a REST endpoint at /predictions. Note: The command uses Quarkus 3.40.1 as the project version. If you want to use a different Quarkus release, replace that version with the one you intend to use.

Navigate into the project directory:

cd football-prediction

The rest-jackson extension allows Quarkus to receive JSON requests and return JSON responses from REST endpoints. Tribuo is a standard Java machine-learning library rather than a Quarkus extension, so we will add it separately.

Open the generated pom.xml and add the Tribuo dependency inside the existing <dependencies> element. Keep the Quarkus dependencies generated by Maven.

<dependency>
    <groupId>org.tribuo</groupId>
    <artifactId>tribuo-all</artifactId>
    <version>4.3.2</version>
    <type>pom</type>
</dependency>

This dependency gives the application access to Tribuo’s core APIs and machine learning implementations.

Define the Prediction Problem

Before writing machine learning code, the problem needs to be expressed clearly. In our case, the application should determine whether a home team is likely to win a football match based on several numerical statistics.

The model will use seven features:

  • Home team’s recent win percentage
  • Away team’s recent win percentage
  • Home team’s average points scored
  • Away team’s average points scored
  • Home team’s average points conceded
  • Away team’s average points conceded
  • Home advantage

The output will contain one of two labels:

HOME_WIN
AWAY_WIN

This makes the problem a binary classification task. There are two possible classes, and the model must select the class that best matches the supplied features. The key point is that the machine learning model should not receive information that becomes available only after the match. For example, the final score cannot be used as an input when attempting to predict the result before the match begins. Features need to represent information that would genuinely be available at prediction time.

Define the Match Data

Create MatchData.java to represent the statistics for a football match and, when known, its actual result.

public record MatchData(
        double homeWinPercentage,
        double awayWinPercentage,
        double homeAverageScored,
        double awayAverageScored,
        double homeAverageAllowed,
        double awayAverageAllowed,
        double homeAdvantage,
        String result) {

}

The record contains seven numerical input features and a result label. During training and testing, result contains HOME_WIN or AWAY_WIN. For an upcoming match, the result is unknown and is set to null; the predictor supplies Tribuo’s unknown output when creating the prediction example.

Create the Training and Test Datasets

A dataset is the collection of examples used to teach the model. Each example contains feature values and, for supervised learning, the expected output. For this tutorial, we can create the data directly in Java. A real application would normally load historical data from a CSV file, database, API, or another data source. Tribuo itself provides data loading mechanisms for sources such as CSV and other formats, making it possible to move from manually created examples to external datasets later.

Create FootballDataset.java. It supplies one dataset for training and a separate dataset for testing. Note that the example values are invented to demonstrate the application, not collected historical results.

public class FootballDataset {

    public static List<MatchData> createTraining() {
        return List.of(
                new MatchData(0.78, 0.35, 28, 17, 18, 25, 1, "HOME_WIN"),
                new MatchData(0.72, 0.42, 27, 19, 17, 24, 1, "HOME_WIN"),
                new MatchData(0.81, 0.31, 31, 16, 15, 27, 1, "HOME_WIN"),
                new MatchData(0.69, 0.40, 26, 18, 19, 26, 1, "HOME_WIN"),
                new MatchData(0.75, 0.37, 29, 20, 16, 23, 1, "HOME_WIN"),
                new MatchData(0.70, 0.45, 25, 19, 18, 24, 1, "HOME_WIN"),
                new MatchData(0.34, 0.76, 17, 28, 26, 17, 0, "AWAY_WIN"),
                new MatchData(0.41, 0.73, 19, 27, 24, 18, 0, "AWAY_WIN"),
                new MatchData(0.29, 0.82, 16, 31, 27, 15, 0, "AWAY_WIN"),
                new MatchData(0.43, 0.68, 20, 26, 25, 19, 0, "AWAY_WIN"),
                new MatchData(0.38, 0.79, 18, 29, 23, 16, 0, "AWAY_WIN"),
                new MatchData(0.45, 0.71, 21, 27, 24, 18, 0, "AWAY_WIN")
        );
    }

    public static List<MatchData> createTest() {
        return List.of(
                new MatchData(0.84, 0.30, 32, 14, 14, 28, 1, "HOME_WIN"),
                new MatchData(0.73, 0.39, 27, 18, 17, 25, 1, "HOME_WIN"),
                new MatchData(0.32, 0.84, 15, 32, 28, 14, 0, "AWAY_WIN"),
                new MatchData(0.40, 0.75, 18, 28, 25, 17, 0, "AWAY_WIN")
        );
    }
}

The training data contains twelve examples, with six for each outcome. The test data contains four additional examples that are not included in training. Keeping these sets separate allows us to check how the model handles examples that it did not learn from directly. The dataset is intentionally small so the code remains easy to follow. A real model needs a substantially larger, representative dataset and a more rigorous evaluation.

Convert the Dataset Into Tribuo Examples

Tribuo’s models consume Example objects. An example contains the features associated with one observation and, for training, the expected output. We can convert each MatchData record into a Tribuo Example<Label>:

public class MatchExampleFactory {

    public static Example<Label> create(MatchData match, LabelFactory labelFactory) {

        Label label = labelFactory.generateOutput(match.result());

        Example<Label> example = new ArrayExample<>(label);

        example.add(new Feature("homeWinPercentage", match.homeWinPercentage()));
        example.add(new Feature("awayWinPercentage", match.awayWinPercentage()));
        example.add(new Feature("homeAverageScored", match.homeAverageScored()));
        example.add(new Feature("awayAverageScored", match.awayAverageScored()));
        example.add(new Feature("homeAverageAllowed", match.homeAverageAllowed()));
        example.add(new Feature("awayAverageAllowed", match.awayAverageAllowed()));
        example.add(new Feature("homeAdvantage", match.homeAdvantage()));

        return example;
    }
}

The important part of this conversion is that each feature has a stable name and a numerical value. Tribuo can then work with these features through its typed machine learning API. The label is attached to the example because these examples are being used for supervised training. When we later create an example for prediction, we will not provide the result because that is exactly what the model is expected to determine.

Build the Tribuo Dataset

Once the examples have been created, they can be placed into a Tribuo MutableDataset. The dataset associates the examples with the appropriate output factory and provides the structure needed by the trainer. Create FootballTribuoDataset.java to turn the list of match records into a Tribuo MutableDataset. This class is used for both training and testing.

public class FootballTribuoDataset {

    public static MutableDataset<Label> createDataset(List<MatchData> matches) {

        LabelFactory labelFactory = new LabelFactory();
        List<Example<Label>> examples = new ArrayList<>();

        for (MatchData match : matches) {
            examples.add(
                    MatchExampleFactory.create(match, labelFactory)
            );
        }

        SimpleDataSourceProvenance provenance = new SimpleDataSourceProvenance("Football matches", labelFactory);

        return new MutableDataset<>(examples, provenance, labelFactory);
    }
}

MutableDataset provides a convenient structure for assembling examples in memory. In a larger application, the dataset could instead be populated from a data source such as a CSV file. Tribuo’s documentation demonstrates both loading data and passing datasets to trainers, so this manually created dataset can later be replaced without changing the fundamental training concept.

Create and Train the Model

The next step is to choose a learning algorithm. For this classification problem, we will use logistic regression, a common method for predicting categories. It looks at the input features, estimates how likely each class is, and uses these estimates to make a prediction. Tribuo provides a LogisticRegressionTrainer for training classification models.

Create FootballModelTrainer.java:

public class FootballModelTrainer {

    public static Model<Label> trainModel() {

        MutableDataset<Label> dataset = FootballTribuoDataset.createDataset(FootballDataset.createTraining());
        LogisticRegressionTrainer trainer = new LogisticRegressionTrainer();
        Model<Label> model = trainer.train(dataset);

        IO.println("Football prediction model trained.");

        return model;
    }
}

LogisticRegressionTrainer trains a classification model from the dataset and returns a Model<Label>. The trained model can then classify new examples using the same features used during training.

Make Predictions (Predict an Unseen Match)

After training, the model can be used with new match information. This time, the example does not contain a known label.Create FootballPredictor.java. This class constructs an example from the incoming match statistics and asks the trained model for a prediction.

public class FootballPredictor {

    public static Prediction<Label> predict(Model<Label> model, MatchData match) {

        LabelFactory labelFactory = new LabelFactory();

        Example<Label> example = new ArrayExample<>(labelFactory.getUnknownOutput());
        
        example.add(new Feature("homeWinPercentage",match.homeWinPercentage()));
        example.add(new Feature("awayWinPercentage", match.awayWinPercentage()));
        example.add(new Feature( "homeAverageScored", match.homeAverageScored()));
        example.add(new Feature("awayAverageScored", match.awayAverageScored()));
        example.add(new Feature("homeAverageAllowed",match.homeAverageAllowed()));
        example.add(new Feature("awayAverageAllowed",match.awayAverageAllowed()));
        example.add(new Feature("homeAdvantage",match.homeAdvantage()));

        return model.predict(example);
    }
}

Notice that the feature names are identical to those used by MatchExampleFactory. This consistency is essential: the model expects incoming features to follow the same representation as its training examples.

Evaluate the Model

Testing a machine learning model requires more than checking whether one prediction looks correct. The model should be evaluated against examples for which the expected results are already known. Tribuo provides classification evaluation support through LabelEvaluator.

Create FootballModelEvaluator.java to evaluate the trained model against the separate test dataset.

public class FootballModelEvaluator {

    public static void evaluateModel(Model<Label> model, MutableDataset<Label> testDataset) {

        LabelEvaluator evaluator = new LabelEvaluator();

        var evaluation = evaluator.evaluate(model, testDataset);

        IO.println("Football Model Evaluation:");
        IO.println(evaluation);
    }
}

LabelEvaluator compares the model’s predictions with the actual labels in the test dataset and reports classification metrics. Since these records were not used during training, they provide a better initial assessment of how the model may perform on new data. Using the same records for both training and evaluation can give a misleading impression of the model’s accuracy. However, four test records are not enough to reliably measure the model’s performance.

Create the Request DTO

The REST endpoint should accept only the match statistics, not the result label. Create MatchRequest.java:

public record MatchRequest(
        double homeWinPercentage,
        double awayWinPercentage,
        double homeAverageScored,
        double awayAverageScored,
        double homeAverageAllowed,
        double awayAverageAllowed,
        double homeAdvantage
) {
    public MatchData toMatchData() {
        return new MatchData(
                homeWinPercentage,
                awayWinPercentage,
                homeAverageScored,
                awayAverageScored,
                homeAverageAllowed,
                awayAverageAllowed,
                homeAdvantage,
                null
        );
    }
}

This record represents the JSON request body. Its toMatchData() method converts the request into the internal match representation and leaves result as null because the outcome is what the model must predict.

Integrate the Model with Quarkus

Create PredictionService.java. Quarkus manages this class as an application-scoped CDI bean. It trains the model when the service is initialized, evaluates it against the test data, and reuses the trained model for subsequent requests.

@ApplicationScoped
public class PredictionService {

    private Model

The @PostConstruct method runs after Quarkus initializes the service. Training once during initialization avoids retraining the model for every HTTP request. The evaluation is also performed during initialization so the application prints the test results when it starts.

This is a tutorial arrangement. A production application would usually train and validate models separately, persist the approved model, and load it during startup rather than retraining every time the application restarts.

Expose the Prediction REST Endpoint

Create PredictionResource.java to accept requests at POST /predictions.

@Path("/predictions")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class PredictionResource {

    @Inject
    PredictionService predictionService;

    @POST
    public Map<String, String> predict(MatchRequest request) {
        String outcome = predictionService.predictMatch(request.toMatchData());

        String message = switch (outcome) {
            case "HOME_WIN" ->
                "The model predicts a home-team win.";
            case "AWAY_WIN" ->
                "The model predicts an away-team win.";
            default ->
                "The model returned an unrecognized outcome.";
        };

        return Map.of(
                "predictedOutcome", outcome,
                "message", message
        );
    }
}

The resource injects PredictionService, converts the incoming request to MatchData, and delegates the prediction to the model. It then returns both the machine-readable label and a human-friendly message in the JSON response.

4. Run the Application

From the project directory, start Quarkus in development mode:

./mvnw quarkus:dev

Maven compiles the source files and starts the application. During startup, the model is trained and its evaluation results are printed in the terminal. Once startup is complete, the REST endpoint is available at http://localhost:8080/predictions.

Test the Prediction Endpoint

Use curl to send a request containing statistics that favor the away team:

curl -X POST http://localhost:8080/predictions \
  -H "Content-Type: application/json" \
  -d '{
    "homeWinPercentage": 0.32,
    "awayWinPercentage": 0.84,
    "homeAverageScored": 15,
    "awayAverageScored": 32,
    "homeAverageAllowed": 28,
    "awayAverageAllowed": 14,
    "homeAdvantage": 0
  }'

The response is:

{
  "predictedOutcome": "AWAY_WIN",
  "message": "The model predicts an away-team win."
}

This is the expected response format if the model predicts AWAY_WIN; the actual prediction depends on the trained model.

Model Evaluation Output

When the application starts in Quarkus development mode, the model is trained and evaluated against the separate test dataset. The evaluation results are printed in the Quarkus console, showing how well the model predicted each class. The output should look similar to the following:

Football Model Evaluation:
Class                           n          tp          fn          fp      recall        prec          f1
HOME_WIN                        2           2           0           0       1.000       1.000       1.000
AWAY_WIN                        2           2           0           0       1.000       1.000       1.000
Total                           4           4           0           0
Accuracy                                                                    1.000
Micro Average                                                               1.000       1.000       1.000
Macro Average                                                               1.000       1.000       1.000
Balanced Error Rate                                                         0.000

In this example, the model correctly predicts all four test records, giving it an accuracy of 1.000, or 100%. The precision, recall, and F1 scores are also 1.000 for both classes, while the balanced error rate is 0.000, indicating no classification errors in this test set.

However, the test dataset contains only four records, so these results do not establish that the model will achieve the same accuracy on real football matches. A larger dataset containing representative match statistics is needed to assess its performance more reliably.

You can also test a match that favors the home team:

curl -X POST http://localhost:8080/predictions \
  -H "Content-Type: application/json" \
  -d '{
    "homeWinPercentage": 0.84,
    "awayWinPercentage": 0.30,
    "homeAverageScored": 32,
    "awayAverageScored": 14,
    "homeAverageAllowed": 14,
    "awayAverageAllowed": 28,
    "homeAdvantage": 1
  }'

The model will return its predicted label and corresponding message. These inputs align with the patterns represented in the sample dataset, but the application does not force either result.

5. Why the Dataset Matters

The quality of a machine learning model depends heavily on the quality of the information used to train it. An algorithm cannot reliably learn a relationship that is poorly represented in the training data.

Suppose a football model is trained using only matches where the home team was the stronger team. It may learn a relationship that strongly favours the home team, but this does not mean that the model has learned the general factors associated with winning. The training data simply failed to provide enough variation.

The dataset should represent the situations that the model will encounter after deployment. It should also contain enough examples for the selected algorithm to learn meaningful relationships.

Data preparation is therefore not a secondary activity. Handling missing values, correcting inconsistent records, removing duplicates, selecting relevant features, and ensuring that the training and prediction pipelines produce compatible values are all important parts of machine learning development. Feature selection and feature engineering are commonly used to turn raw information into forms that are more appropriate for a learning algorithm.

Training Data Is Not the Same as Test Data

One of the easiest mistakes when learning machine learning is to train and test a model with exactly the same examples. If a model has already seen an example during training, its performance on that example does not tell us much about how it will perform on an unfamiliar input. A useful evaluation needs data that was kept separate from the training process.

A typical project therefore divides available data into separate portions. The training portion is used to learn the model, while another portion is reserved for evaluation. Some projects also maintain a validation set for making development decisions before using the final test set for an unbiased assessment.

This distinction becomes important as models become more complex. A model with many parameters can sometimes fit the training examples extremely well without learning relationships that generalize to new data. This behaviour is commonly described as overfitting.

6. Conclusion

Developing a machine learning application requires more than selecting an algorithm and training it with data. The quality of the dataset, the preparation of input features, and the evaluation of predictions all contribute to the model’s ability to handle unfamiliar examples. Using Java, Quarkus, and Tribuo, we explored these stages and integrated the trained model into a REST API that accepts match statistics and returns a predicted outcome.

Although the football dataset is intended for demonstration, the underlying workflow applies to many classification problems. With representative data, appropriate testing, and continuous evaluation, developers can extend this foundation to build machine learning applications that address practical requirements.

7. Download the Source Code

Download
You can download the full source code of this example here: What a machine learning model is and how to make one

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button