Implicit Feedback Matrices

Introduction to Implicit Feedback

Welcome back! As you continue your journey through the fascinating world of recommendation systems, it's important to understand not just explicit feedback — such as star ratings — but also implicit feedback. Implicit feedback is obtained from user behavior patterns, like watch times or click histories. While it's much easier to gather, it doesn't directly reveal user satisfaction as explicit feedback does.

In the previous unit, you used ALS with explicit ratings and tried to reconstruct missing user-item scores directly. The core matrix-factorization idea still matters here, but the input changes: instead of modeling stars or ratings, we model whether an interaction happened and how strongly we trust that signal. Most classical models utilize either implicit or explicit feedback separately due to the complexities involved in integrating both types into a unified system. In this course, we'll focus on analyzing implicit feedback independently first, and then use those matrices as the input to IALS.

Binary Matrix of Interactions

Now, let's delve into the binary matrix of interactions. In the context of implicit feedback, this matrix is a simplified representation showing whether a user interacted with an item or not. Each entry in the matrix is a binary value:

  • 1 indicates an interaction (e.g., a user watched an item),
  • 0 implies no interaction.

For example, let's say User 1 interacted with Items 1, 2, and 4. The binary matrix would look like this:

Markdown
| User/Item | Item 1 | Item 2 | Item 3 | Item 4 |
|-----------|--------|--------|--------|--------|
| User 1    |   1    |   1    |   0    |   1    |

This matrix is crucial, as it helps algorithms understand which items have been interacted with, providing a baseline for recommending new items to users.

Confidence Matrix Explanation

The confidence matrix goes beyond the binary matrix by incorporating the confidence we have in each interaction. This confidence is calculated based on user behaviors such as watchTime. Longer watch times suggest higher interest and, thus, greater confidence in the interaction.

The important distinction is:

  • the interaction matrix answers: "did something happen?"
  • the confidence matrix answers: "how strongly should the model trust that signal?"

This is why a user who barely sampled an item and a user who watched it for a long time can both have interaction value 1, while still receiving very different confidence values.

You can think of the formula as starting from a baseline and then scaling upward with evidence. The 1 is that baseline: once we have observed an interaction at all, we do not want its confidence to drop to zero. The alpha value controls how quickly confidence grows as watch_time increases. A larger alpha makes the model react more strongly to differences in engagement, while a smaller one keeps confidence values closer together. In this lesson, we use alpha = 40 as a simple teaching default that makes the effect visible in small examples. In practice, it is a tunable hyperparameter rather than a universal constant.

Here's how you might compute a confidence matrix in Go, where watchTime plays a significant role:

Go
package main

import (
    "fmt"
)

func main() {
    // Let's assume some watch times for User 1
    watchTimes := []int{30, 28, 11, 51} // For Items 1, 2, 3, 4 respectively
    alpha := 40                         // Constant factor

    // Initialize a confidence matrix for 1 user and 4 items
    confidenceMatrix := make([][]int, 1)
    confidenceMatrix[0] = make([]int, 4)

    // Fill the confidence matrix using the formula
    for i, time := range watchTimes {
        confidenceMatrix[0][i] = 1 + alpha*time
    }

    fmt.Println("Confidence Matrix:")
    fmt.Println(confidenceMatrix)
}

This might result in:

Markdown
| User/Item | Item 1 | Item 2 | Item 3 | Item 4 |
|-----------|--------|--------|--------|--------|
| User 1    | 1201   | 1121   |  441   | 2041   |

Here, higher values denote greater confidence that the user is interested in those items, which is invaluable for personalizing recommendations.

These confidence values are not ratings and they do not have natural user-facing units like "stars" or "minutes." They are algorithmic weights. Their job is to tell the model how strongly to fit each observed interaction. Later, when we train IALS, entries with larger confidence will influence the factor updates more strongly than weak or uncertain interactions.

Generally, there are various ways of evaluating implicit feedback. Of course, you can come up with your own! The approach we described is the one posted in the article called Collaborative Filtering for Implicit Feedback Datasets by researchers from AT&T Labs. We will use this approach to train a special version of ALS, called IALS, which works with implicit feedback efficiently, in the next lesson.

In this formula, the constant 1 gives every observed interaction a baseline level of confidence, while alpha * watchTime increases confidence as engagement grows. So the model is not learning that watchTime itself is a rating. Instead, it learns a binary preference signal and a separate confidence weight that tells it how strongly to trust that preference during training.

Data Format and Reading

The dataset is a JSON file where each entry contains entries for user, item, rating, and watch_time. Each record describes an interaction a user had with an item.

JSON
[
    {"user": 1, "item": 1, "rating": 2, "watch_time": 30},
    {"user": 1, "item": 2, "rating": 2, "watch_time": 28},
    {"user": 1, "item": 4, "rating": -1, "watch_time": 11}
    // ...
]

Here's an excerpt explaining how to read the data in Go:

Go
package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type RatingEntry struct {
    User      int `json:"user"`
    Item      int `json:"item"`
    Rating    int `json:"rating"`
    WatchTime int `json:"watch_time"`
}

func main() {
    // Read the JSON file
    fileData, err := os.ReadFile("ratings.json")
    if err != nil {
        panic(err)
    }

    // Parse the JSON data
    var data []RatingEntry
    if err := json.Unmarshal(fileData, &data); err != nil {
        panic(err)
    }

    // Determine the size of the matrices
    maxUser, maxItem := 0, 0
    for _, entry := range data {
        if entry.User > maxUser {
            maxUser = entry.User
        }
        if entry.Item > maxItem {
            maxItem = entry.Item
        }
    }

    fmt.Printf("Max user: %d, Max item: %d\n", maxUser, maxItem)
}

In this block, we read the JSON file and calculate maxUser and maxItem to ascertain the dimensions of our matrices. The JSON still contains a rating field because the raw event log can store both explicit and implicit signals side by side. In this implicit-feedback unit, however, we ignore rating and build our matrices from watch_time.

Initializing and Filling the Matrices

Following the data read, we initialize the matrices and populate them with interactions and confidence values:

Go
package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type RatingEntry struct {
    User      int `json:"user"`
    Item      int `json:"item"`
    Rating    int `json:"rating"`
    WatchTime int `json:"watch_time"`
}

func main() {
    // Read the JSON file
    fileData, err := os.ReadFile("ratings.json")
    if err != nil {
        panic(err)
    }

    var data []RatingEntry
    if err := json.Unmarshal(fileData, &data); err != nil {
        panic(err)
    }

    // Find the maximum user and item IDs
    maxUser, maxItem := 0, 0
    for _, entry := range data {
        if entry.User > maxUser {
            maxUser = entry.User
        }
        if entry.Item > maxItem {
            maxItem = entry.Item
        }
    }

    // Initialize the matrices
    interactionMatrix := make([][]int, maxUser)
    confidenceMatrix := make([][]int, maxUser)
    for i := 0; i < maxUser; i++ {
        interactionMatrix[i] = make([]int, maxItem)
        confidenceMatrix[i] = make([]int, maxItem)
    }

    alpha := 40 // Constant for scaling confidence

    // Fill the matrices with interactions and confidence values
    for _, entry := range data {
        userID := entry.User - 1 // Convert to zero-based index
        itemID := entry.Item - 1 // Convert to zero-based index
        interactionMatrix[userID][itemID] = 1
        confidenceMatrix[userID][itemID] = 1 + alpha*entry.WatchTime
    }

    fmt.Println("Interaction Matrix (Binary):")
    for _, row := range interactionMatrix {
        fmt.Println(row)
    }

    fmt.Println("Confidence Matrix:")
    for _, row := range confidenceMatrix {
        fmt.Println(row)
    }
}

Here, interactionMatrix is filled with 1s indicating a user-item interaction, while confidenceMatrix is filled using the formula:

confidence = 1 + alpha * watchTime

This formula is taken from the article that we mentioned before. In practice, you can experiment and come up with different approaches to calculate the implicit feedback value. For example, in the same article, the authors offer an alternative formula for confidence that also worked well for them:

confidence = 1 + alpha * ln(1 + watchTime/epsilon)

Different units in this course will use watch_time in slightly different forms depending on the goal:

  • sometimes as the raw value from the data source,
  • sometimes normalized to a 0..1-style proportion using item length,
  • and sometimes above 1 when the user rewatched or spent more than one full item length engaging with the content.

Those are all valid inputs as long as you are clear about the meaning of the signal before turning it into confidence weights.

Example of Resulting Matrices

Here's a short example showing how the resulting matrices might look:

plaintext
Interaction Matrix (Binary):
[1 1 0 0]
[0 0 1 1]

Confidence Matrix:
[1201 1121    0    0]
[   0    0  601  881]

This output reflects interactions and confidence levels across users and items.

You might wonder, why don't we use only the confidence matrix, as it contains all the information? The reason is that splitting user preferences (interactions) and our confidence in their preferences allows us to work with these values distinctly and construct a model that treats them separately. It generally improves the model's performance.

In the next lesson, we will train one example of such a model. But before that, let's wrap it up and have some practice!

Summary and Preparation for Practice

In this lesson, you focused on understanding and creating interaction and confidence matrices based on implicit feedback like user watch times. You now have both the theoretical understanding and practical skills to process implicit feedback. This enables you to create a more nuanced and personalized recommendation system.

In the next session, you'll have the opportunity to explore practice exercises that reinforce today's lesson. These exercises will help solidify your understanding and make the transition to advanced models seamless. Keep up the great work as you advance towards mastering recommendation systems!

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal