Retail Product Recommendations using word2vec
Creating a system that automatically recommends a certain number of products to the consumers on an E-commerce website based on the past purchase behavior of the consumers.
- Data gathering and understanding
- Data Preprocessing
- Data Preparation
- Build word2vec Embeddings for Products
- Visualize word2vec Embeddings
- Generate and validate recommendations
- References
A person involved in sports-related activities might have an online buying pattern similar to this:
If we can represent each of these products by a vector, then we can easily find similar products. So, if a user is checking out a product online, then we can easily recommend him/her similar products by using the vector similarity score between the products.
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/00352/Online%20Retail.xlsx
df = pd.read_excel('Online Retail.xlsx')
df.head()
Given below is the description of the fields in this dataset:
-
InvoiceNo: Invoice number, a unique number assigned to each transaction.
-
StockCode: Product/item code. a unique number assigned to each distinct product.
-
Description: Product description
-
Quantity: The quantities of each product per transaction.
-
InvoiceDate: Invoice Date and time. The day and time when each transaction was generated.
-
CustomerID: Customer number, a unique number assigned to each customer.
df.isnull().sum()
Since we have sufficient data, we will drop all the rows with missing values.
df.dropna(inplace=True)
# again check missing values
df.isnull().sum()
df['StockCode']= df['StockCode'].astype(str)
customers = df["CustomerID"].unique().tolist()
len(customers)
There are 4,372 customers in our dataset. For each of these customers we will extract their buying history. In other words, we can have 4,372 sequences of purchases.
It is a good practice to set aside a small part of the dataset for validation purpose. Therefore, we will use data of 90% of the customers to create word2vec embeddings. Let's split the data.
random.shuffle(customers)
# extract 90% of customer ID's
customers_train = [customers[i] for i in range(round(0.9*len(customers)))]
# split data into train and validation set
train_df = df[df['CustomerID'].isin(customers_train)]
validation_df = df[~df['CustomerID'].isin(customers_train)]
Let's create sequences of purchases made by the customers in the dataset for both the train and validation set.
purchases_train = []
# populate the list with the product codes
for i in tqdm(customers_train):
temp = train_df[train_df["CustomerID"] == i]["StockCode"].tolist()
purchases_train.append(temp)
purchases_val = []
# populate the list with the product codes
for i in tqdm(validation_df['CustomerID'].unique()):
temp = validation_df[validation_df["CustomerID"] == i]["StockCode"].tolist()
purchases_val.append(temp)
model = Word2Vec(window = 10, sg = 1, hs = 0,
negative = 10, # for negative sampling
alpha=0.03, min_alpha=0.0007,
seed = 14)
model.build_vocab(purchases_train, progress_per=200)
model.train(purchases_train, total_examples = model.corpus_count,
epochs=10, report_delay=1)
model.save("word2vec_2.model")
As we do not plan to train the model any further, we are calling init_sims(), which will make the model much more memory-efficient
model.init_sims(replace=True)
print(model)
Now we will extract the vectors of all the words in our vocabulary and store it in one place for easy access
X = model[model.wv.vocab]
X.shape
It is always quite helpful to visualize the embeddings that you have created. Over here we have 100 dimensional embeddings. We can't even visualize 4 dimensions let alone 100. Therefore, we are going to reduce the dimensions of the product embeddings from 100 to 2 by using the UMAP algorithm, it is used for dimensionality reduction.
import umap
cluster_embedding = umap.UMAP(n_neighbors=30, min_dist=0.0,
n_components=2, random_state=42).fit_transform(X)
plt.figure(figsize=(10,9))
plt.scatter(cluster_embedding[:, 0], cluster_embedding[:, 1], s=3, cmap='Spectral');
Every dot in this plot is a product. As you can see, there are several tiny clusters of these datapoints. These are groups of similar products.
We are finally ready with the word2vec embeddings for every product in our online retail dataset. Now our next step is to suggest similar products for a certain product or a product's vector.
Let's first create a product-ID and product-description dictionary to easily map a product's description to its ID and vice versa.
products = train_df[["StockCode", "Description"]]
# remove duplicates
products.drop_duplicates(inplace=True, subset='StockCode', keep="last")
# create product-ID and product-description dictionary
products_dict = products.groupby('StockCode')['Description'].apply(list).to_dict()
products_dict['84029E']
We have defined the function below. It will take a product's vector (n) as input and return top 6 similar products.
Let's try out our function by passing the vector of the product '90019A' ('SILVER M.O.P ORBIT BRACELET')
similar_products(model['90019A'])
Cool! The results are pretty relevant and match well with the input product. However, this output is based on the vector of a single product only. What if we want recommend a user products based on the multiple purchases he or she has made in the past?
One simple solution is to take average of all the vectors of the products he has bought so far and use this resultant vector to find similar products. For that we will use the function below that takes in a list of product ID's and gives out a 100 dimensional vector which is mean of vectors of the products in the input list.
def aggregate_vectors(products):
product_vec = []
for i in products:
try:
product_vec.append(model[i])
except KeyError:
continue
return np.mean(product_vec, axis=0)
If you can recall, we have already created a separate list of purchase sequences for validation purpose. Now let's make use of that.
The length of the first list of products purchased by a user is 314. We will pass this products' sequence of the validation set to the function aggregate_vectors.
Well, the function has returned an array of 100 dimension. It means the function is working fine. Now we can use this result to get the most similar products. Let's do it.
similar_products(aggregate_vectors(purchases_val[0]))
As it turns out, our system has recommended 6 products based on the entire purchase history of a user. Moreover, if you want to get products suggestions based on the last few purchases only then also you can use the same set of functions.
Below we are giving only the last 10 products purchased as input.
similar_products(aggregate_vectors(purchases_val[0][-10:]))
References
- https://www.analyticsvidhya.com/blog/2019/07/how-to-build-recommendation-system-word2vec-python/
- https://mccormickml.com/2018/06/15/applying-word2vec-to-recommenders-and-advertising/
- https://www.analyticsinsight.net/building-recommendation-system-using-item2vec/
- https://towardsdatascience.com/using-word2vec-for-music-recommendations-bb9649ac2484
- https://capablemachine.com/2020/06/23/word-embedding/