How Vector Search Can Influence Customer Shopping Habits
The Problem
Today we met with the Promotions team. They’re looking for our help in making some smarter decisions about customer advertisements, offers and coupons. Right now, promotions are mainly based on geographic markets. So promotions sent out to customers in one city will be different from promotions offered to customers in another city. Thus, these campaigns have to be planned out ahead of time. The issue is that the marketing department is putting pressure on them to provide more strategic methods of targeting customers, geographic regions notwithstanding. The thought is that specific customers might be more likely to take advantage of certain offers, based on their purchase history. If we can find a way to offer them in real time, like a 10-percent discount on a related product (per cart), we might be able to drive some additional sales. First, I decided to have a look at some of our anonymized order data in their Apache Cassandra cluster. It’s clear that there are definitely some patterns in the data, with some customers purchasing the same items (mostly grocery products) with some predictable regularity. Maybe there’s some way we can leverage this data?Improving the Experience
One thing that we have going for us: Our customers tend to engage with us via multiple channels. Some folks use the website, some use the mobile app and some folks will still walk into any one of our 1,000 or so brick-and-mortar stores. And more than half of those customers in the store use the mobile app concurrently. Another interesting point: If we aggregate the item sale data by household address, instead of just by customer ID, we see even more rigid shopping patterns. After pulling data together from a few different sources, we can start to paint a picture of just what this data looks like. Example: A couple has a dog. Usually, one spouse buys the dog food. But sometimes the other spouse does. Those events at the individual customer level don’t make much of a pattern. But when aggregated together at the household level, they do. In fact, they consistently buy more than one 6-pound roll of “HealthyFresh – Chicken Raw Dog Food” each week. Judging by the recommended serving sizes, they also either have a half-dozen little dogs or one big dog, probably the latter. A visualization of this data can be seen in Figure 1.
Figure 1 – A graph showing the structure of customer household data, including address, customer names, known device IDs, frequent store IDs and items that are frequently purchased.
Computing Vectors to Find Similar Products
Finding similar products means we’ll need to compute similarity vectors for our products. There are a few ways that we can do this. In the interest of putting a minimum viable product together, we can focus only on the product names and build a natural language processing (NLP) model based on a “bag of words” approach. In this approach, we take every word from all the product names and give each unique word an entry. This is our vocabulary. The similarity vectors that we create and store with each product become an array of ones and zeros indicating whether the current product name possesses that word, as shown below in Table 1. We can use a platform like TensorFlow to build and train our machine learning (ML) model.
Table 1 – A bag of words NLP vocabulary for product names under the Pet Supplies category, showing how each vector is assembled.
CREATE TABLE pet_supply_vectors (
product_id TEXT PRIMARY KEY,
product_name TEXT,
product_vector vector<float, 14>);
CREATE CUSTOM INDEX ON pet_supply_vectors(product_vector) USING 'StorageAttachedIndex';
We can then load the output from our ML model into Cassandra. With the data present, the next step is to add a new service, which performs the vector search using the following query:
SELECT * FROM pet_supply_vectors
ORDER BY product_vector
ANN OF [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] LIMIT 2;
customer_id: a3f5c9a3
device_id: e6f40454
product_id: pf1843
product_name: “HealthyFresh - Chicken raw dog food”
product_vector: [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]