The bridging algorithm behind Community Notes
There’s a fascinating article from earlier this year in Asterisk magazine about how the Community Notes feature on Twitter/X came to be. The article focuses more on the iterative design and rollout process behind Community Notes—formerly known by the wonderful name Birdwatch—than on the “bridging” algorithm that Twitter/X uses to evaluate notes. Thankfully, an October 2022 paper published by the feature’s designers (and linked in the article) gives more details.The current Community Notes evaluation algorithm, described here with code here, contains substantially more bells and whistles.
The bridging algorithm is quite clever, and I found both certain choices that the designers made and the nature of their results to be non-intuitive. I’ll go through the details of the algorithm, then talk a little bit about what the results mean. I’ll also include some details about matrix factorization that aren’t relevant to the bridging algorithm implementation, but were useful to me in thinking about why the authors chose the minimization algorithm they did.
The data
The only data the bridging algorithm uses are ratings assigned by users to prospective community notes. Per the paper, users have three options for ratings: “helpful”, “somewhat helpful”, or “not helpful”. A key perspective behind the bridging algorithm is to view this data as a matrix indexed by user and note ; this is a standard approach in the recommender-systems literature. For the bridging algorithm, the entry takes the value for a “not helpful” rating, for a “helpful” rating, and is null if the user has not rated the note.It’s not clear from the paper how “somewhat helpful” ratings are handled.
The matrix has a couple features worth noting:
- is massive: at all-of-Twitter/X scale, we should imagine the number of users satisfying and maybe . It’s less clear how big the number of notes is in practice, since not all tweets get prospective notes attached. In the paper’s example, and ; in the daily data snapshots, we have and .
- is very sparse: most notes have only a handful of ratings, and most users rate only a few notes (in a given time period). Hence most entries of are null. In the paper’s example, there are only user-note pairs with non-null entries of , so the density of nonzero entries in is This is about 25 times denser than the ratings matrices in the daily data snapshots, which has around 200 million nonzero entries.
Matrix factorization
The key idea behind the bridging algorithm is to try and approximate the rating matrix as where is a scalar, and are vectors indexed by user, and and are vectors indexed by note. This is simply a rank-one approximate matrix factorization of together with certain intercept terms. There are free parameters.Interpreting the parameters
Let’s go through each of the five parameters () and try to understand how various features of the data are going to affect each of them.- The global intercept scalar will simply reflect how likely a rating is to be helpful. In the example data, around 62% of the 149,888 ratings are helpful, so we should have .
-
The note content vector measures where a note falls on the “principal component” of the rating data. What is this principal component? Let’s explain with a thought experiment.
Suppose that users fall into two groups: dog lovers and cat lovers. Also suppose that all dog lovers rate a note helpful if and only if it contains at least one picture of a dog, and likewise for cat lovers and pictures of cats. Then the note content vector will roughly measure “does this note contain pictures of dogs or pictures of cats?”.In the real world, it’s (as always) the left/right partisan axis.
- The user affinity vector measures how a user’s rating of a note depends on the note’s content. Continuing the above thought experiment, will measure whether a user is a dog lover or a cat lover.
-
The user intercept vector will reflect how likely a given user is to rate a note as helpful, above the baseline and independent of the user’s affinity for the note’s content (as measured by the value). In our thought experiment, all values will be approximately zero, because a user’s rating depends only on the note’s content and their affinity for it.The reader is invited to construct a thought experiment where users can be dog lovers/haters and cat lovers/haters independently so that the user-intercept values are nonzero.
- The note intercept vector will reflect how likely a given note is to receive a “helpful” rating, again above the baseline and independent of the user’s affinity for the note’s content. In our thought experiment, will be positive if a note contains both dog and cat pictures, and negative if a note contains neither. (The values of for dog-only/cat-only posts depend on the relative size of the dog-loving/cat-loving audiences.)
The choice of sign on might affect whether people suspect us of secretly being dog lovers or cat lovers, though.
Now, in the recommender-system setting, we want to find items such that is large and the item content aligns with the user affinity .This is a perfect use case for vector-retrieval databases.
But if instead we want to evalute a note’s usefulness in the Community Notes setting, the scalar value is the only thing we should focus on. If we want to show all users, no matter their position on the petisan axis, notes that they’ll find helpful, we should just show notes with both dog and cat pictures: that is, notes with positive values. And this is exactly what Community Notes does: in order to be displayed to all users, notes must have a note intercept value of at least in the system described in the paper. More generally, notes with intercept values below are categorized as “unhelpful”, notes with intercepts above are categorized as “helpful”, and notes with intercepts between and are categorized as “needs more ratings”.
Why rank one?
It is possible to make the scalars and defined above vectors instead, provided we replace multiplication by taking the dot product. This makes a matrix and a matrix (the vector case above being ), and takes the total number of parameters from to . This is the traditional approach in the recommender-systems literature, where we can imagine distinct axes such as “is this a horror movie?” or “does this movie pass the Bechdel test?”. We should expect that adding these parameters (called factor dimensions) improves the fit, and the authors note that taking modestly improved RMSE on held-out samples from 0.076 to 0.073. Unfortunately, they also note that the additional factor dimension “reduced interpretability and replicability”, and hence they used only one factor dimension in the system evaluated in the paper. This isn’t terribly surprising, since in the paper’s example we are quite close to the overparametrized regime: taking even would yield more free parameters than data points. (Another point in favor of taking is that higher make the diamond plots below more complicated.)The choice of norm
We also need to decide how we’re going to evaluate the quality of a proposed matrix factorization. The authors use the objective function with and . This is a pretty straightforwards choice: it’s basically the first thing you’d write down, and it’s got some nice properties. Let’s go through the three terms in order.- The first term is the average /Frobenius norm of our approximation, with the key detail that we only evaluate this at the user-note pairs where we actually have data since most of is null. This will heavily penalize large prediction errors.
-
The second term is a regularization on the three intercepts: the global intercept , the user intercept vector , and the note intercept vector . I’m not quite sure why we need to regularize ; my instinct is that we’d get a really good estimate for the single parameter without the regularization term since we have so much data.On the other hand, because we have so much data I also suspect that the regularization on here won’t matter very much.The and intercepts make somewhat more sense: because is so sparse, most users rate only a handful of notes and most notes are rated by only a handful of users, so we want to make sure we’re confident in large nonzero values (which will control deviations of predictions from the baseline value).
- The third term is a regularization on the factor vectors. Note that it wouldn’t really matter if we had different penalties on and , since the rescaling discussion above (i.e., the global multiplication by ) would let us transfer the penalty between and . The justification for the regularization terms here is the same as for and above.
Learning the factorization
So now that we know what we’re looking for, and why we’re looking for it, we only need address how we’re going to find good values for our parameters . If it were just the Frobenius term and were dense, we could use the rank-1 singular value decomposition. Unfortunately, but the regularization terms make a linear-algebraic solution rather more challenging. So, following a tradition dating back (at least) to Simon Funk’s work on the Netflix Prize dataset, we just do gradient descent. After all, gradient descent on the Frobenius norm will actually find arbitrary-rank SVD approximations of a matrix, so we should expect it to do fine here. But there are a few fun insights in store for us about how to actually do the gradient descent!Doing it ourselves
Twitter/X makes daily snapshots of Community Notes data available for download,I believe that logging in is required for downloading the data, and that snapshot data is delayed approximately 48 hours to prevent interference with notes that are currently being rated.
so we can try and replicate these results. We’re going to deliberately skip a few tweaks that we discuss below, and we’re not going to spend much time optimizing gradient-descent hyperparameters, so we shouldn’t necessarily expect to get results anywhere near those of the paper. On the other hand, we have a couple orders of magnitude more data: recent snapshots contain 150M+ ratings by 1.5M+ users on 1.5M+ notes.
I slapped together some code that gets ratings files, drops all the data we don’t need to do matrix factorization, filters out notes/raters with too few ratings, and then runs the matrix factorization algorithm. You can find it
One battle after another
I hit pitfall after pitfall when setting this up. I started off by not reading the code and doing a pretty vanilla ML setup: Adam, learning rate around , and batching. Given the form of the loss function above, we should expect initial loss on the order of 0.25-0.5, simply because guessing 0.5 for and something around zero for everything else is a pretty decent starting point. Indeed, the pre-epoch-one losses were in this neighborhood. But the post-epoch-one losses were enormous: often greater than 10. So what went wrong? This is a good puzzle, so feel free to stop and think about it for a bit. If you want, here’s a hint: it’s not momentum-related, as the same phenomenon occurs if we switch from Adam to SGD. The answer turns out to be the batching. If we step our optimizer every 100,000 ratings or so, it’s very likely that a number of notes or raters only appear once in that ratings batch. So even with the regularization terms, the optimizer willDiamond plots
Further tweaks
Two further tweaks are worth mentioning: diamond regularization and filtering for rater helpfulness.Diamond regularization
While not mentioned in the paper, diamond regularization is another tool to help ensure that only Community Notes with cross-factor consensus have good intercept. The idea is to add the term to our loss functional; a suggested value is . This term penalizes notes that have both a large note content coefficient (in either direction) and a large note intercept, which makes sense: we should be hesitant to assign a large note-intercept value to a note whose ratings are also helped to explain via a large note-content coefficient, since that suggests that much of the appeal of the note is due to partisanship rather than consensus. The name “diamond regularization” then suggests itself: we are penalizing notes that lie outside of the diamond shape on our plots above.Rater helpfulness
Unsurprisingly, humans being humans, many users will give good prospective Community Notes bad ratings: for example, if the note is a consensus correction to a partisan tweet and the author agrees with the perspective of the tweet. Because our threshold for unhelpfulness is so low, a few such early ratings can doom a good consensus-perspective note before it gets off the ground. One way to combat this is to perform two rounds of our matrix-factorization algorithm. In the first round, the authors use all rating data to assign provisional ratings (using the same “helpful”/“needs more ratings”/“unhelpful” trichotomy as above) to all notes. Then they select for raters whose ratings generally agreeThe threshold in the paper is 66% agreement.
with the provisional ratings, counting only ratings made before the provisional rating on a note became public. After a further filtering-out of raters who have authored low-quality Community Notes, all ratings by filtered-out raters are eliminated. In the paper’s example, roughly half of the raters and ratings remained after this filtering. Then a second round of matrix-factorization ratings are computed and used as the final ratings.