import json
import gzip
import math
from collections import defaultdict
import numpy
from sklearn import linear_model
def assertFloat(x):
assert type(float(x)) == float
def assertFloatList(items, N):
assert len(items) == N
assert [type(float(x)) for x in items] == [float]*N
answers = {}
f = open("/home/julian/Downloads/spoilers.json.gz", 'r')
dataset = []
for l in f:
d = eval(l)
dataset.append(d)
f.close()
dataset[0]
{'user_id': 'b0d7e561ca59e313b728dc30a5b1862e',
'timestamp': '2013-05-06',
'review_sentences': [[0,
'The author did an excellent job of making a very readable novel about the emotional lives of Hadley and Ernest Hemingway.'],
[0,
'The many other creative people interacting with them in Paris in the 1920s were very colorful and interesting too.'],
[0,
'She captured a wonderful snapshot of the 1920s in Europe--the men returning from war, the writing, the music, the art, the fashions, the eating and drinking.'],
[0,
'Mostly told from the viewpoint of Hadley, the book shows she was very attracted to Ernest who was extroverted, interesting, ambitious, and a gifted writer.'],
[0,
'Hadley had a sweetness, and more traditional values than some of their friends in Paris.'],
[0,
'They both came from families with domineering mothers and suicidal fathers.'],
[0,
'Ernest was still suffering the traumatic effects of his time in World War I. He was also very self absorbed, and hurt Hadley very deeply.'],
[0, 'Their love story spiraled downward as his career took off.'],
[0,
'I enjoyed this historical fiction about the five years of their marriage, and was sorry to see the book end.'],
[0, 'First read August 3, 2011'],
[0, 'Reread May 7, 2013 for a bookgroup']],
'rating': 4,
'has_spoiler': False,
'book_id': '8683812',
'review_id': 'd293fbf3512a0b05faffec43a7811c55'}
reviewsPerUser = defaultdict(list)
reviewsPerItem = defaultdict(list)
for d in dataset:
u,i = d['user_id'],d['book_id']
reviewsPerUser[u].append(d)
reviewsPerItem[i].append(d)
for u in reviewsPerUser:
reviewsPerUser[u].sort(key=lambda x: x['timestamp'])
for i in reviewsPerItem:
reviewsPerItem[i].sort(key=lambda x: x['timestamp'])
[d['timestamp'] for d in reviewsPerUser['b0d7e561ca59e313b728dc30a5b1862e']]
['2012-03-13', '2013-05-06', '2013-09-03', '2015-04-05', '2016-02-10', '2016-05-29']
def MSE(y, ypred):
diffs = [(a-b)**2 for (a,b) in zip(y,ypred)]
return sum(diffs) / len(diffs)
### 1a
y, ypred = [], []
for i in reviewsPerItem:
ratings = [d['rating'] for d in reviewsPerItem[i]]
if len(ratings) < 2: continue
y.append(ratings[-1])
ypred.append(sum(ratings[:-1]) / (len(ratings) - 1))
answers['Q1a'] = MSE(y,ypred)
assertFloat(answers['Q1a'])
### 1b
y, ypred = [], []
for u in reviewsPerUser:
ratings = [d['rating'] for d in reviewsPerUser[u]]
if len(ratings) < 2: continue
y.append(ratings[-1])
ypred.append(sum(ratings[:-1]) / (len(ratings) - 1))
answers['Q1b'] = MSE(y,ypred)
answers['Q1b']
1.970416294395752
assertFloat(answers['Q1b'])
### 2
answers['Q2'] = []
for N in [1,2,3]:
y, ypred = [], []
for u in reviewsPerUser:
ratings = [d['rating'] for d in reviewsPerUser[u][-(N+1):]]
if len(ratings) < 2: continue
y.append(ratings[-1])
ypred.append(sum(ratings[:-1]) / (len(ratings) - 1))
answers['Q2'].append(MSE(y,ypred))
assertFloatList(answers['Q2'], 3)
### 3a
def feature3(N, u):
ratings = [d['rating'] for d in reviewsPerUser[u][-(N+1):-1]]
if len(ratings) < N:
raise Exception
return [1] + ratings
answers['Q3a'] = [feature3(2,dataset[0]['user_id']), feature3(3,dataset[0]['user_id'])]
assert len(answers['Q3a']) == 2
assert len(answers['Q3a'][0]) == 3
assert len(answers['Q3a'][1]) == 4
### 3b
answers['Q3b'] = []
for N in [1,2,3]:
y = []
X = []
for d in dataset:
u = d['user_id']
ratings = [d['rating'] for d in reviewsPerUser[u][-(N+1):]]
if len(ratings) < N+1: continue
y.append(ratings[-1])
X.append(feature3(N,d['user_id']))
theta,residuals,rank,s = numpy.linalg.lstsq(X,y)
mse = residuals[0] / len(y)
answers['Q3b'].append(mse)
/home/julian/.local/lib/python3.7/site-packages/ipykernel_launcher.py:12: FutureWarning: `rcond` parameter will change to the default of machine precision times ``max(M, N)`` where M and N are the input matrix dimensions. To use the future default and silence this warning we advise to pass `rcond=None`, to keep using the old, explicitly pass `rcond=-1`. if sys.path[0] == '':
assertFloatList(answers['Q3b'], 3)
### 4a
globalAverage = [d['rating'] for d in dataset]
globalAverage = sum(globalAverage) / len(globalAverage)
def featureMeanValue(N, u):
ratings = [d['rating'] for d in reviewsPerUser[u][-(N+1):-1]]
nMissing = N - len(ratings)
if len(ratings) > 0:
av = sum(ratings) / len(ratings)
else:
av = globalAverage
return [1] + [av]*nMissing + ratings
def featureMissingValue(N, u):
ratings = [d['rating'] for d in reviewsPerUser[u][-(N+1):-1]]
nMissing = N - len(ratings)
feat = [0]*nMissing + ratings
indicator = [1]*nMissing + [0]*len(ratings)
return [1] + indicator + feat
answers['Q4a'] = [featureMeanValue(10, dataset[0]['user_id']), featureMissingValue(10, dataset[0]['user_id'])]
answers['Q4a']
[[1, 4.2, 4.2, 4.2, 4.2, 4.2, 5, 4, 4, 4, 4], [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 4, 4, 4, 4]]
assert len(answers['Q4a']) == 2
assert len(answers['Q4a'][0]) == 11
assert len(answers['Q4a'][1]) == 21
### 4b
answers['Q4b'] = []
for featFunc in [featureMeanValue, featureMissingValue]:
y = []
X = []
for d in dataset:
u = d['user_id']
ratings = [d['rating'] for d in reviewsPerUser[u]]
y.append(ratings[-1])
X.append(featFunc(10,d['user_id']))
theta,residuals,rank,s = numpy.linalg.lstsq(X,y)
mse = residuals[0] / len(y)
answers['Q4b'].append(mse)
/home/julian/.local/lib/python3.7/site-packages/ipykernel_launcher.py:11: FutureWarning: `rcond` parameter will change to the default of machine precision times ``max(M, N)`` where M and N are the input matrix dimensions. To use the future default and silence this warning we advise to pass `rcond=None`, to keep using the old, explicitly pass `rcond=-1`. # This is added back by InteractiveShellApp.init_path()
assertFloatList(answers["Q4b"], 2)
### 5
def feature5(sentence):
return [1, len(sentence), sentence.count('!'), sum([c.isupper() for c in sentence])]
y = []
X = []
for d in dataset:
for spoiler,sentence in d['review_sentences']:
X.append(feature5(sentence))
y.append(spoiler)
len(X)
261176
mod = linear_model.LogisticRegression(class_weight='balanced')
mod.fit(X,y)
predictions = mod.predict(X) # Binary vector of predictions
def rates(predictions, y):
TP = [a and b for (a,b) in zip(predictions,y)]
TN = [not a and not b for (a,b) in zip(predictions,y)]
FP = [a and not b for (a,b) in zip(predictions,y)]
FN = [not a and b for (a,b) in zip(predictions,y)]
TP = sum(TP)
TN = sum(TN)
FP = sum(FP)
FN = sum(FN)
return TP, TN, FP, FN
TP, TN, FP, FN = rates(predictions, y)
BER = 0.5 * (FP / (TN + FP) + FN / (FN + TP))
BER
0.470265288006232
answers['Q5a'] = X[0]
answers['Q5b'] = [TP, TN, FP, FN, BER]
assert len(answers['Q5a']) == 4
assertFloatList(answers['Q5b'], 5)
### 6
def feature6(review):
sentences = review['review_sentences']
if len(sentences) < 6:
raise Exception
return [s[0] for s in sentences[:5]] + feature5(sentences[5][1])
y = []
X = []
for d in dataset:
sentences = d['review_sentences']
if len(sentences) < 6: continue
X.append(feature6(d))
y.append(sentences[5][0])
mod = linear_model.LogisticRegression(class_weight='balanced')
mod.fit(X,y)
predictions = mod.predict(X) # Binary vector of predictions
TP, TN, FP, FN = rates(predictions, y)
BER = 0.5 * (FP / (TN + FP) + FN / (FN + TP))
/usr/local/lib/python3.7/dist-packages/sklearn/linear_model/_logistic.py:818: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
Increase the number of iterations (max_iter) or scale the data as shown in:
https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG,
answers['Q6a'] = X[0]
answers['Q6b'] = BER
assert len(answers['Q6a']) == 9
assertFloat(answers['Q6b'])
### 7
len(X)
11907
Xtrain, Xvalid, Xtest = X[:len(X)//2], X[len(X)//2:(3*len(X))//4], X[(3*len(X))//4:]
ytrain, yvalid, ytest = y[:len(X)//2], y[len(X)//2:(3*len(X))//4], y[(3*len(X))//4:]
models = {}
bers = {}
bestC = None
for c in [0.01, 0.1, 1, 10, 100]:
mod = linear_model.LogisticRegression(C=c, class_weight='balanced')
mod.fit(Xtrain,ytrain)
predictions = mod.predict(Xvalid)
TP, TN, FP, FN = rates(predictions, yvalid)
BER = 0.5 * (FP / (TN + FP) + FN / (FN + TP))
models[c] = mod
bers[c] = BER
/usr/local/lib/python3.7/dist-packages/sklearn/linear_model/_logistic.py:818: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
Increase the number of iterations (max_iter) or scale the data as shown in:
https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG,
/usr/local/lib/python3.7/dist-packages/sklearn/linear_model/_logistic.py:818: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
Increase the number of iterations (max_iter) or scale the data as shown in:
https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG,
/usr/local/lib/python3.7/dist-packages/sklearn/linear_model/_logistic.py:818: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
Increase the number of iterations (max_iter) or scale the data as shown in:
https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG,
/usr/local/lib/python3.7/dist-packages/sklearn/linear_model/_logistic.py:818: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
Increase the number of iterations (max_iter) or scale the data as shown in:
https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG,
bestC = min(bers)
predictions = models[bestC].predict(Xtest)
TP, TN, FP, FN = rates(predictions, ytest)
BER = 0.5 * (FP / (TN + FP) + FN / (FN + TP))
answers['Q7'] = list(bers.values()) + [bestC] + [BER]
assertFloatList(answers['Q7'], 7)
### 8
def Jaccard(s1, s2):
numer = len(s1.intersection(s2))
denom = len(s1.union(s2))
if denom == 0:
return 0
return numer / denom
dataTrain = dataset[:15000]
dataTest = dataset[15000:]
itemAverages = defaultdict(list)
ratingMean = []
for d in dataTrain:
itemAverages[d['book_id']].append(d['rating'])
ratingMean.append(d['rating'])
for i in itemAverages:
itemAverages[i] = sum(itemAverages[i]) / len(itemAverages[i])
ratingMean = sum(ratingMean) / len(ratingMean)
reviewsPerUser = defaultdict(list)
usersPerItem = defaultdict(set)
for d in dataTrain:
u,i = d['user_id'], d['book_id']
reviewsPerUser[u].append(d)
usersPerItem[i].add(u)
def predictRating(user,item):
ratings = []
similarities = []
for d in reviewsPerUser[user]:
i2 = d['book_id']
if i2 == item: continue
ratings.append(d['rating'] - itemAverages[i2])
similarities.append(Jaccard(usersPerItem[item],usersPerItem[i2]))
if (sum(similarities) > 0):
weightedRatings = [(x*y) for x,y in zip(ratings,similarities)]
return itemAverages[item] + sum(weightedRatings) / sum(similarities)
else:
# User hasn't rated any similar items
return ratingMean
alwaysPredictMean = [ratingMean for d in dataTest]
simPredictions = [predictRating(d['user_id'], d['book_id']) for d in dataTest]
labels = [d['rating'] for d in dataTest]
MSE(alwaysPredictMean, labels)
1.4967523599999757
MSE(simPredictions, labels)
1.533136609277726
answers["Q8"] = MSE(simPredictions, labels)
assertFloat(answers["Q8"])
### 9
MSE0 = []
MSE15 = []
MSE5 = []
for d in dataTest:
u,i = d['user_id'], d['book_id']
y = d['rating']
ypred = predictRating(d['user_id'], d['book_id'])
err = math.fabs(y - ypred)**2
count = len(usersPerItem[i])
if count == 0:
MSE0.append(err)
elif count <= 5:
MSE15.append(err)
else:
MSE5.append(err)
MSE0 = sum(MSE0) / len(MSE0)
MSE15 = sum(MSE15) / len(MSE15)
MSE5 = sum(MSE5) / len(MSE5)
MSE0, MSE15, MSE5
(1.742012484444442, 1.5287575880478592, 1.4979280057145445)
answers["Q9"] = [MSE0, MSE15, MSE5]
assertFloatList(answers["Q9"], 3)
### 10
itsMSE = 100.0
answers["Q10"] = ("describe your solution", itsMSE)
assert type(answers["Q10"][0]) == str
assertFloat(answers["Q10"][1])
f = open("answers_midterm.txt", 'w')
f.write(str(answers) + '\n')
f.close()