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] / denum
to
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] / denum
Suggestion 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] / denum
that 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 error
Intuitively, 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
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:
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
to
Suggestion 2: set similarity to zero
Catch the case that the denominator is zero and set the similarity as equal to zero:
that would be similar to scikit-learn's approach:
Intuitively, 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
elifto make the user aware