Cosine similarity ZeroDivisionError
Firstly, thanks for the great library!
I came across an issue while doing a large RandomizedSearchCV which was raising a ZeroDivisionError. This turned out to be because one of the users has all zero ratings, and cosine similarity was being used in that iteration, so obviously the denominator was zero. However it was tricky to debug because it wasn't immediately clear which similarity measure was being used when the error was raised. Because RandomizedSearchCV is parallelised, the traceback wasn't helpful.
MRE:
from surprise import Dataset, Reader, KNNBasic
import pandas as pd
df = pd.DataFrame([
['u1', 'i1', 0], # user u1 only has 0 ratings
['u2', 'i1', 1],
['u2', 'i2', 0],
])
# df[2] = df[2] + 1 # uncommenting this line fixes the issue
reader = Reader(rating_scale=(df[2].min(), df[2].max()))
data = Dataset.load_from_df(df, reader)
trainset = data.build_full_trainset()
algo = KNNBasic(sim_options={'name': 'cosine'})
algo.fit(trainset)Suggestion 1: clear error message
Catch the case that the denominator is zero and give a clear error message:
change similarities.pyx L76C1-L86C1 from
for xi in range(n_x):
sim[xi, xi] = 1
for xj in range(xi + 1, n_x):
if freq[xi, xj] < min_sprt:
sim[xi, xj] = 0
else:
denum = sqrt(sqi[xi, xj] * sqj[xi, xj])
sim[xi, xj] = prods[xi, xj] / denumto
for xi in range(n_x):
sim[xi, xi] = 1
for xj in range(xi + 1, n_x):
if freq[xi, xj] < min_sprt:
sim[xi, xj] = 0
elif (sqi[xi, xj]==0) or (sqj[xi, xj]==0):
raise ZeroDivisionError(
"Cosine similarity failed: at least one user (or item) has only zero "
"ratings. Rescale your ratings, or remove all-zero users/items."
)
else:
denum = sqrt(sqi[xi, xj] * sqj[xi, xj])
sim[xi, xj] = prods[xi, xj] / denumSuggestion 2: set similarity to zero
Catch the case that the denominator is zero and set the similarity as equal to zero:
for xi in range(n_x):
sim[xi, xi] = 1
for xj in range(xi + 1, n_x):
if freq[xi, xj] < min_sprt:
sim[xi, xj] = 0
elif (sqi[xi, xj]==0) or (sqj[xi, xj]==0):
sim[xi, xj] = 0
else:
denum = sqrt(sqi[xi, xj] * sqj[xi, xj])
sim[xi, xj] = prods[xi, xj] / denumthat would be similar to scikit-learn's approach:
import sklearn.metrics
X = [[0, 0, 0]]
sklearn.metrics.pairwise.cosine_similarity(X, X)
# returns array([[0.]]), no errorIntuitively, if cosine is the criteria for similarity, then when that is undefined we interpret this as zero similarity. Maybe there should be a warning in the elif to make the user aware
Source: NicolasHug/Surprise