7.3.4 Learning from user behavior
As ads are shown to users, we have the opportunity to gain insight into what can cause
someone to click on an ad. In the last section, we talked about using words as bonuses to ads that have already matched the required location. In this section, we’ll talk about
how we can record information about those words and the ads that were targeted to
discover basic patterns about user behavior in order to develop per-word, per-adtargeting
A crucial question you should be asking yourself is “Why are we using words in the
web page content to try to find better ads?” The simple reason is that ad placement is
all about context. If a web page has content related to the safety of children’s toys, showing
an ad for a sports car probably won’t do well. By matching words in the ad with
words in the web page content, we get a form of context matching quickly and easily.
One thing to remember during this discussion is that we aren’t trying to be perfect.
We aren’t trying to solve the ad-targeting and learning problem completely; we’re trying
to build something that will work “pretty well” with simple and straightforward methods.
As such, our note about the fact that this isn’t mathematically rigorous still applies.
The first step in our learning process is recording the results of our ad targeting with
the record_targeting_result() function that we called earlier from listing 7.11.
Overall, we’ll record some information about the ad-targeting results, which we’ll
later use to help us calculate click-through rates, action rates, and ultimately eCPM
bonuses for each word. We’ll record the following:
- Which words were targeted with the given ad
- The total number of times that a given ad has been targeted
- The total number of times that a word in the ad was part of the bonus calculation
To record this information, we’ll store a SET of the words that were targeted and keep
counts of the number of times that the ad and words were seen as part of a single ZSET
per ad. Our code for recording this information is shown next.
That function does everything we said it would do, and you’ll notice a call to
update_cpms(). This update_cpms() function is called every 100th time the ad is
returned from a call. This function really is the core of the learning phase—it writes
back to our per-word, per-ad-targeting bonus ZSETs.
We’ll get to updating the eCPM of an ad in a moment, but first, let’s see what happens
when an ad is clicked.
RECORDING CLICKS AND ACTIONS
As we record views, we’re recording half of the data for calculating CTRs. The other
half of the data that we need to record is information about the clicks themselves, or
in the case of a cost per action ad, the action. Numerically, this is because our eCPM
calculations are based on this formula: (value of a click or action) x (clicks or actions)
/ views. Without recording clicks and actions, the numerator of our value calculation
is 0, so we can’t discover anything useful.
When someone actually clicks on an ad, prior to redirecting them to their final
destination, we’ll record the click in the total aggregates for the type of ad, as well as
whether the ad got a click and which words matched the clicked ad. We’ll record the
same information for actions. Our function for recording clicks is shown next.
You’ll notice there are a few parts of the recording function that we didn’t mention
earlier. In particular, when we receive a click or an action for a CPA ad, we’ll refresh
the expiration of the words that were a part of the ad-targeting call. This will let an
action following a click count up to 15 minutes after the initial click-through to the
destination site happened.
Another change is that we’ll optionally be recording actions in this call for CPA
ads; we’ll assume e that this function is called with the action parameter set to True in
And finally, we’ll call the update_cpms() function for every click/action because
they should happen roughly once every 100–2000 views (or more), so each individual
click/action is important relative to a view.
Exercise: Changing the way we count clicks and actions
In listing 7.15, we define a record_click() function to add 1 to every word that
was targeted as part of an ad that was clicked on. Can you think of a different number
to add to a word that may make more sense? Hint: You may want to consider
this number to be related to the count of matched words. Can you update
finish_scoring() and record_click() to take into consideration this new click/
To complete our learning process, we only need to define our final update_cpms()
We’ve been talking about and using the update_cpms() function for a couple of sections
now, and hopefully you already have an idea of what happens inside of it.
Regardless, we’ll walk through the different parts of how we’ll update our per-word,
per-ad bonus eCPMs, as well as how we’ll update our per-ad eCPMs.
The first part to updating our eCPMs is to know the click-through rate of an ad by
itself. Because we’ve recorded both the clicks and views for each ad overall, we have
the click-through rate by pulling both of those scores from the relevant ZSETs. By combining
that click-through rate with the ad’s actual value, which we fetch from the ad’s
base value ZSET, we can calculate the eCPM of the ad over all clicks and views.
The second part to updating our eCPMs is to know the CTR of words that were
matched in the ad itself. Again, because we recorded all views and clicks involving the
ad, we have that information. And because we have the ad’s base value, we can calculate
the eCPM. When we have the word’s eCPM, we can subtract the ad’s eCPM from it
to determine the bonus that the word matching contributes. This difference is what’s
added to the per-word, per-ad bonus ZSETs.
The same calculation is performed for actions as was performed for clicks, the only
difference being that we use the action count ZSETs instead of the click count ZSETs.
Our method for updating eCPMs for clicks and actions can be seen in the next listing.
Exercise: Optimizing eCPM calculations
In listing 7.16, we perform a number of round trips to Redis that’s relative to the
number of words that were targeted. More specifically, we perform the number of
words plus three round trips. In most cases, this should be relatively small (considering
that most ads won’t have a lot of content or related keywords). But even so,
some of these round trips can be avoided. Can you update the update_cpms() function
to perform a total of only three round trips?
In our update_cpms() function, we updated the global per-type click-through and
action rates, the per-ad eCPMs, and the per-word, per-ad bonus eCPMs.
With the learning portion of our ad-targeting process now complete, we’ve now
built a complete ad-targeting engine from scratch. This engine will learn over time,
adapt the ads it returns over time, and more. We can make many possible additions or
changes to this engine to make it perform even better, some of which are mentioned
in the exercises, and several of which are listed next. These are just starting points for
building with Redis:
- Over time, the total number of clicks and views for each ad will stabilize around
a particular ratio, and subsequent clicks and views won’t alter that ratio significantly.
The real CTR of an ad will vary based on time of day, day of week, and
more. Consider degrading an ad’s click/action and view count on a regular
basis, as we did in section 2.5 with our rescale_viewed() call. This same concept
applies to our global expected CTRs.
- To extend the learning ability beyond just a single count, consider keeping
counts over the last day, week, and other time slices. Also consider ways of
weighing those time slices differently, depending on their age. Can you think of
a method to learn proper weights of different ages of counts?
- All of the big ad networks use second-price auctions in order to charge for a
given ad placement. More specifically, rather than charging a fixed rate per
click, per thousand views, or per action, you charge a rate that’s relative to the
second-highest value ad that was targeted.
- In most ad networks, there’ll be a set of ads that rotate through the highest-value
slot for a given set of keywords as each of them runs out of money. These ads are
there because their high value and CTR earn the top slot. This means that new
ads that don’t have sufficiently high values will never be seen, so the network will
never discover them. Rather than picking the highest-eCPM ads 100% of the
time, fetch the top 100 ads and choose ads based on the relative values of their
eCPMs anywhere from 10%-50% of the time (depending on how you want to balance
learning true eCPMs and earning the most money).
- When ads are initially placed into the system, we know little about what to
expect in terms of eCPM. We addressed this briefly by using the average CTR of
all ads of the same type, but this is moot the moment a single click comes in.
Another method mixes the average CTR for a given ad type, along with the seen
CTR for the ad based on the number of views that the ad has seen. A simple
inverse linear or inverse sigmoid relationship between the two can be used until
the ad has had sufficient views (2,000–5,000 views is typically enough to determine
a reliable CTR).
- In addition to mixing the average CTR for a given ad type with the CTR of the
ad during the learning process, ads that are in the process of reaching an
initial 2,000–5,000 views can have their CTR/eCPM artificially boosted. This
can ensure sufficient traffic for the system to learn the ads’ actual eCPMs.
- Our method of learning per-word bonuses is similar to Bayesian statistics. We
could use real Bayesian statistics, neural networks, association rule learning,
clustering, or other techniques to calculate the bonuses. These other methods
may offer more mathematically rigorous results, which may result in better
CTRs, and ultimately more money earned.
- In our code listings, we recorded views, clicks, and actions as part of the calls
that return the ads or handle any sort of redirection. These operations may take
a long time to execute, so should be handled after the calls have returned by
being executed as an external task, as we discussed in section 6.4.
As you can see from our list, many additions and improvements can and should be
made to this platform. But as an initial pass, what we’ve provided can get you started
in learning about and building the internet’s next-generation ad-targeting platform.
Now that you’ve learned about how to build an ad-targeting platform, let’s keep
going to see how to use search tools to find jobs that candidates are qualified for as
part of a job-search tool.