#!./.venv/bin/python3.9 import os import json import math import copy import random import numpy as np from scipy.stats import norm import matplotlib.pyplot as plt import networkx as nx from pyvis.network import Network def getAllAuthors(books): authors = set() for book in books: for author in getAuthors(book): authors.add(author) return list(authors) def getAuthors(book): return book['authors'].split(' & ') def getRecommenders(book): for tag in book['tags']: if tag.find(" Recommendation") != -1: yield tag.replace(" Recommendation", "") elif tag.find("s Literature Club") != -1: yield tag.replace("s Literature Club", "") def getTags(book): for tag in book['tags']: if tag.find(" Recommendation") == -1 and tag.find("s Literature Club") == -1 and tag.find(" Top ") == -1: yield tag def getAllRecommenders(books): recs = set() for book in books: for rec in getRecommenders(book): recs.add(rec) return list(recs) def getTopLists(book): lists = set() for tag in book['tags']: if tag.find(" Top ") != -1: lists.add(tag.split(" Top ")[0]) return list(lists) def getAllTopLists(books): tops = set() for book in books: for top in getTopLists(book): tops.add(top) return list(tops) def getAllSeries(books): series = set() for book in books: if 'series' in book: series.add(book['series']) return list(series) def getAllTags(books): tags = set() for book in books: for tag in getTags(book): tags.add(tag) return list(tags) def getTopListWeight(book, topList): minScope = 100000 for tag in book['tags']: if tag.find(topList+" Top ") != -1: scope = int(tag.split(" Top ")[1]) minScope = min(minScope, scope) if minScope == 100000: raise Exception("You stupid?") if minScope == 10: return 1 elif minScope == 25: return 0.85 elif minScope == 100: return 0.5 return 50 / minScope def removeRead(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'book': if node['rating'] != None: G.remove_node(n) def removeUnread(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'book': if node['rating'] == None: G.remove_node(n) def removePriv(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'book': if 'priv' in node['tags']: G.remove_node(n) def removeDangling(G, alsoBooks=False): for n in list(G.nodes): node = G.nodes[n] if node['t'] != 'book' or alsoBooks: if not len(G.adj[n]): G.remove_node(n) def removeEdge(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] != 'book': if len(G.adj[n]) < 2: G.remove_node(n) def removeBad(G, threshold, groups=['book', 'topList', 'recommender', 'author', 'series', 'tag']): for n in list(G.nodes): node = G.nodes[n] if node['t'] in groups: if 'score' in node and (node['score'] == None or node['score'] < threshold): G.remove_node(n) def removeKeepBest(G, num, maxDistForRead=1): bestlist = [] for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'book': if 'score' in node and node['score'] != None: bestlist.append(node) bestlist.sort(key=lambda node: node['score'], reverse=True) bestlist = bestlist[:num] for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'book' and node not in bestlist or 'score' in node and node['score'] == None: if not 'rating' in node or node['rating'] == None or node['rating'] < bestlist[-1]['score']-maxDistForRead: G.remove_node(n) def removeTags(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'tag': G.remove_node(n) def pruneTags(G, minCons=2): for n in sorted(list(G.nodes), key=lambda i: G.nodes[i]['score'] + len(G.nodes[i]['feedbacks'])/5 if 'score' in G.nodes[i] and 'feedbacks' in G.nodes[i] else 0): node = G.nodes[n] if node['t'] == 'tag': foundCon = 0 for book in G.adj[n]: for con in G.adj[book]: conType = G.nodes[con]['t'] if conType not in ['topList']: if conType in ['recommender']: foundCon += 0.5 elif conType in ['tag', 'series']: foundCon += 0.25 else: foundCon += 1 if foundCon > minCons: G.remove_node(n) def pruneRecommenders(G, minCons=2): for n in sorted(list(G.nodes), key=lambda i: G.nodes[i]['score'] if 'score' in G.nodes[i] else 0): node = G.nodes[n] if node['t'] == 'recommender': foundCon = 0 for book in G.adj[n]: for con in G.adj[book]: conType = G.nodes[con]['t'] if conType not in ['topList']: if conType in ['recommender']: foundCon += 0.5 elif conType in ['tag', 'series']: foundCon += 0.25 else: foundCon += 1 if foundCon > minCons: G.remove_node(n) def pruneRecommenderCons(G, maxCons=5): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'recommender': if len(G.adj[n]) > maxCons: bestlist = [] for m in list(G.adj[n]): book = G.nodes[m] if book['t'] == 'book': if 'score' in book and book['score'] != None: bestlist.append(book) bestlist.sort(key=lambda node: node['score'], reverse=True) bestlist = bestlist[:maxCons] for m in list(G.adj[n]): book = G.nodes[m] if book['t'] == 'book' and book not in bestlist or 'score' in book and book['score'] == None: if not 'rating' in book or book['rating'] == None: foundCon = 0 for con in G.adj[m]: if G.nodes[con]['t'] not in ['topList']: foundCon += 1 if foundCon < 2: G.remove_node(m) def pruneAuthorCons(G, maxCons=3): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'author': if len(G.adj[n]) > maxCons: bestlist = [] for m in list(G.adj[n]): book = G.nodes[m] if book['t'] == 'book': if 'score' in book and book['score'] != None: bestlist.append(book) bestlist.sort(key=lambda node: node['score'], reverse=True) bestlist = bestlist[:maxCons] for m in list(G.adj[n]): book = G.nodes[m] if book['t'] == 'book' and book not in bestlist or 'score' in book and book['score'] == None: if not 'rating' in book or book['rating'] == None: foundCon = 0 for con in G.adj[m]: if G.nodes[con]['t'] not in ['topList']: foundCon += 1 if foundCon < 2: G.remove_node(m) def removeHighSpanTags(G, maxCons=5): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'tag': if len(G.adj[n]) > maxCons: G.remove_node(n) def removeHighSpanReadBooks(G, maxCons=8): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'book' and node['rating'] != None: if sum([1 for adj in G.adj[n] if G.nodes[adj]['t']=='recommender']) > maxCons: G.remove_node(n) def removeTopLists(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'topList': G.remove_node(n) def removeRecommenders(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'recommender': G.remove_node(n) def removeRestOfSeries(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'series': seriesState = 0 for adj in G.adj[n]: adjNode = G.nodes[adj] if adjNode['rating'] != None: seriesState = max(seriesState, int( adjNode['series_index'])) for adj in list(G.adj[n]): adjNode = G.nodes[adj] if adjNode['series_index'] > seriesState + 1.0001: G.remove_node(adj) def removeUnusedRecommenders(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'recommender': for adj in G.adj[n]: adjNode = G.nodes[adj] if adjNode['t']=='book' and 'score' in adjNode: break else: # No unrated recommendation G.remove_node(n) def removeUselessReadBooks(G): minForce = 1.5 minContact = 2 for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'book' and node['rating'] != None: force = 0 contacts = 0 for adj in G.adj[n]: adjNode = G.nodes[adj] contacts += 1 for cousin in G.adj[adj]: cousinNode = G.nodes[cousin] if cousinNode['t']=='book' and 'score' in cousinNode: if adjNode['t']=='recommender': force += 0.5 else: force += 1 if force < minForce or contacts < minContact: G.remove_node(n) def removeUselessTags(G, minUnread=1): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'tag': foundUnread = 0 for adj in G.adj[n]: adjNode = G.nodes[adj] if adjNode['t']=='book' and 'score' in adjNode: foundUnread += 1 if foundUnread < minUnread: G.remove_node(n) def removeUselessSeries(G, minSco=0): for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'series': if len(G.adj[n]) < 2 or node['score'] < minSco: G.remove_node(n) def scoreOpinions(G, globMu, globStd): for n in list(G.nodes): node = G.nodes[n] feedbacks = [] if node['t'] not in ['book']: adjacens = list(G.adj[n].keys()) for adj in adjacens: adjNode = G.nodes[adj] if adjNode['rating'] != None: feedbacks.append(adjNode['rating']) if len(feedbacks): node['mean'], node['std'] = norm.fit(feedbacks) node['se'] = globStd / math.sqrt(len(feedbacks)) ratio = len(feedbacks) / len(adjacens) node['score'] = node['mean'] node['feedbacks'] = feedbacks else: node['score'] = None def scoreUnread(G, globMu, globStd): for n in list(G.nodes): feedbacks = [globMu] ws = [['mu']] node = G.nodes[n] if node['t'] == 'book': if node['rating'] == None: adjacens = list(G.adj[n].keys()) for adj in adjacens: adjNode = G.nodes[adj] if 'score' in adjNode and adjNode['score'] != None: w = [adjNode['t'], G[n][adj]['weight'] if 'weight' in G[n][adj] else 1] for fb in adjNode['feedbacks']: feedbacks.append(fb) ws.append(w) if len(feedbacks): node['mean'], node['std'] = norm.fit(feedbacks) node['se'] = globStd / math.sqrt(len(feedbacks)) feedbacks.append(node['std']) ws.append(['sigma']) feedbacks.append(node['se']) ws.append(['se']) feedbacks.append(globMu) ws.append(['bias']) node['score'] = sum([fb*getWeightForType(w[0], w[1] if len(w)>1 else 1) for fb, w in zip(feedbacks, ws)])/sum([getWeightForType(w[0], w[1] if len(w)>1 else 1) for w in ws]) node['_act'] = feedbacks node['_wgh'] = ws else: node['score'] = globMu + errorFac*globStd + len(feedbacks)*0.0000000001 if 'series' in node: if node['series_index'] == 1.0: node['score'] += 0.000000001 def getWeightForType(nodeType, edgeWeight=1): global weights w = weights[nodeType] if nodeType == 'topList': return edgeWeight*w else: return w def printBestList(G, num=-1): bestlist = [] for n in list(G.nodes): node = G.nodes[n] if node['t'] == 'book': if 'score' in node and node['score'] != None: bestlist.append(node) bestlist.sort(key=lambda node: node['score'], reverse=True) for i, book in enumerate(bestlist): print("["+str(i+1).zfill(int((math.log10(num) if num!=-1 else 3)+1))+"] "+book['title'] + " ("+" & ".join(book['authors'])+"): {:.5f}".format(book['score'])) if num!=-1 and i == num-1: break def readColor(book): if 'rating' in book: return 'green' else: return 'gray' def loadBooksFromDB(): return json.loads(os.popen("calibredb list --for-machine -f all").read()) def buildBookGraph(books, darkMode=False): G = nx.Graph() # Books for book in books: if 'rating' in book: rating = book['rating'] else: rating = None if 'comments' in book: desc = '' # book['comments'] else: desc = '' if 'series' in book: series = book['series'] series_index = book['series_index'] else: series = None series_index = None G.add_node(book['id'], t='book', label=book['title'], title=book['title'], shape='image', image=book['cover'], rating=rating, tags=book['tags'], desc=desc, isbn=book['isbn'], files=book['formats'], authors=getAuthors(book), series=series, series_index=series_index) return G def graphAddAuthors(G, books, darkMode=False): for author in getAllAuthors(books): G.add_node('a/'+author, color='green', t='author', label=author) for book in books: for author in getAuthors(book): G.add_edge('a/'+author, book['id'], color=readColor(book)) return G def graphAddRecommenders(G, books, darkMode=False): for rec in getAllRecommenders(books): G.add_node('r/'+rec, color='orange', t='recommender', label=rec) for book in books: for rec in getRecommenders(book): G.add_edge('r/'+rec, book['id'], color=readColor(book)) return G def graphAddTopLists(G, books, darkMode=False): for tl in getAllTopLists(books): G.add_node('t/'+tl, color='yellow', t='topList', label=tl) for book in books: for top in getTopLists(book): G.add_edge('t/'+top, book['id'], weight=getTopListWeight( book, top), color=readColor(book)) return G def graphAddSeries(G, books, darkMode=False): for series in getAllSeries(books): G.add_node('s/'+series, color='red', t='series', label=series, shape='triangle') for book in books: if 'series' in book: G.add_edge('s/'+book['series'], book['id'], color=readColor(book)) return G def graphAddTags(G, books, darkMode=False): for tag in getAllTags(books): G.add_node('t/'+tag, color=['lightGray','darkgray'][darkMode], t='tag', label=tag, shape='box') for book in books: for tag in getTags(book): G.add_edge('t/'+tag, book['id'], color=readColor(book)) return G def calcRecDist(G, books): globRatings = [] for book in books: if G.nodes[book['id']]['rating'] != None: globRatings.append(G.nodes[book['id']]['rating']) return norm.fit(globRatings) def scaleBooksByRating(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] not in []: if 'rating' in node and node['rating'] != None: node['value'] = 20 + 5 * int(node['rating']) else: if 'score' in node and node['score'] != None: node['value'] = 20 + 5 * int(node['score']) else: node['value'] = 15 def scaleOpinionsByRating(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] in ['topList', 'recommender', 'author', 'series']: if 'score' in node and node['score'] != None: node['value'] = 20 + 5 * int(node['score']) else: node['value'] = 20 def addScoreToLabels(G): for n in list(G.nodes): node = G.nodes[n] if node['t'] not in ['tag']: if 'rating' in node and node['rating'] != None: node['label'] += " ("+str(node['rating'])+")" else: if 'score' in node and node['score'] != None and 'se' in node: node['label'] += " ({:.2f}±{:.1f})".format(node['score'], node['se']) else: node['label'] += " (0±∞)" def genAndShowHTML(G, showButtons=False, darkMode=False, arrows=False): net = Network('1080px', '1920px', directed=arrows, bgcolor=['#FFFFFF','#181818'][darkMode]) if showButtons: net.show_buttons(filter_=['configure', 'layout', 'interaction', 'physics', 'edges']) net.from_nx(G) net.show('nx.html') def buildFullGraph(darkMode=False): books = loadBooksFromDB() G = buildBookGraph(books, darkMode=darkMode) graphAddAuthors(G, books, darkMode=darkMode) graphAddRecommenders(G, books, darkMode=darkMode) graphAddTopLists(G, books, darkMode=darkMode) graphAddSeries(G, books, darkMode=darkMode) graphAddTags(G, books, darkMode=darkMode) return G, books def genScores(G, books): globMu, globStd = calcRecDist(G, books) scoreOpinions(G, globMu, globStd) scoreUnread(G, globMu, globStd) return globMu, globStd def recommendNBooksRecommenderBased(G, mu, std, n, removeTopListsB=True, removeUselessRecommenders=True): removeRestOfSeries(G) removeBad(G, mu-std*2-1) removeKeepBest(G, int(n*2) + 5, maxDistForRead=2) removeEdge(G) removeHighSpanTags(G, 6) removeDangling(G, alsoBooks=False) pruneTags(G, 10) removeBad(G, mu, groups=['book']) removeUselessReadBooks(G) pruneTags(G, 6) pruneRecommenderCons(G, int(n/7)+1) pruneAuthorCons(G, int(n/15)) removeUselessTags(G) if removeTopListsB: removeTopLists(G) removeDangling(G, alsoBooks=True) removeKeepBest(G, n+math.ceil(n/20), maxDistForRead=1.5) removeEdge(G) removeDangling(G, alsoBooks=True) removeUselessReadBooks(G) if removeUselessRecommenders: removeUnusedRecommenders(G) removeDangling(G, alsoBooks=True) removeKeepBest(G, n, maxDistForRead=1.25) scaleBooksByRating(G) scaleOpinionsByRating(G) addScoreToLabels(G) def recommendNBooksTagBased(G, mu, std, n, removeTopListsB=True): removeRestOfSeries(G) removeBad(G, mu-std*2-1) removeKeepBest(G, int(n*2) + 5, maxDistForRead=2) removeEdge(G) removeHighSpanTags(G, 12) removeDangling(G, alsoBooks=False) pruneTags(G, 24) removeBad(G, mu, groups=['book']) removeUselessReadBooks(G) pruneTags(G, 16) pruneAuthorCons(G, int(n/5)) removeRecommenders(G) removeUselessTags(G) if removeTopListsB: removeTopLists(G) removeDangling(G, alsoBooks=True) removeKeepBest(G, n+math.ceil(n/20), maxDistForRead=1.5) removeUselessReadBooks(G) removeUselessTags(G) removeKeepBest(G, n, maxDistForRead=1.25) scaleBooksByRating(G) scaleOpinionsByRating(G) addScoreToLabels(G) def recommendNBooks(G, mu, std, n, removeTopListsB=True, removeUselessRecommenders=True): removeRestOfSeries(G) removeBad(G, mu-std-0.5) removeBad(G, mu+std/2, groups=['recommender']) removeKeepBest(G, int(n*2) + 5, maxDistForRead=2) removeEdge(G) removeHighSpanTags(G, 10) removeHighSpanReadBooks(G, 10) removeDangling(G, alsoBooks=False) pruneRecommenders(G, 13) pruneTags(G, 13) removeBad(G, mu, groups=['book']) removeUselessReadBooks(G) pruneTags(G, 12) pruneAuthorCons(G, int(n/5)) pruneRecommenders(G, 12 - min(5, n/20)) removeUselessSeries(G, mu) removeUselessTags(G) if removeTopListsB: removeTopLists(G) removeDangling(G, alsoBooks=True) removeKeepBest(G, n+math.ceil(n/20)+3, maxDistForRead=1.5) removeEdge(G) removeKeepBest(G, n+1, maxDistForRead=1.25) removeUselessSeries(G, mu) removeUselessTags(G) removeUselessReadBooks(G) removeKeepBest(G, n, maxDistForRead=1.25) scaleBooksByRating(G) scaleOpinionsByRating(G) addScoreToLabels(G) def fullGraph(G, removeTopListsB=True): removeEdge(G) removeHighSpanTags(G, 7) removeDangling(G, alsoBooks=False) if removeTopListsB: removeTopLists(G) pruneTags(G, 3) removeDangling(G, alsoBooks=True) scaleBooksByRating(G) scaleOpinionsByRating(G) addScoreToLabels(G) def readBooksAnalysis(G, minRating=0, showAllTags=True, removeUnconnected=False, removeTopListsB=True): removeUnread(G) removeBad(G, minRating) if not showAllTags: removeEdge(G) removeHighSpanTags(G, 15) removeDangling(G, alsoBooks=removeUnconnected) if removeTopListsB: removeTopLists(G) pruneTags(G, 8) scaleBooksByRating(G) scaleOpinionsByRating(G) addScoreToLabels(G) def analyze(G, type_name, name, dist=2.7): from fuzzywuzzy import fuzz type_ident = type_name[0] full_name = type_ident + "/" + name bestRatio, match, n = 0, None, 0 for ni in list(G.nodes): node = G.nodes[ni] if node['t'] == type_name or type_name=="any": if name==node['label'] or full_name==node['label']: match, n = node, ni break ratio = fuzz.ratio(node['label'], name) if ratio > bestRatio: bestRatio, match, n = ratio, node, ni if bestRatio < 70: print("Best Match: "+match['label']) menge = set() waveFlow(G, match, n, dist, menge) for n in list(G.nodes): if n not in menge: G.remove_node(n) removeHighSpanTags(G, 12) if dist > 1: removeDangling(G, True) scaleBooksByRating(G) scaleOpinionsByRating(G) #match['value'] = 100 if not 'shape' in match: match['shape'] = 'star' addScoreToLabels(G) match['label'] = "*"+match['label']+"*" def waveFlow(G, node, n, dist, menge, firstEdge=False): if dist <= 0: return dist -= 1 if menge==set(): firstEdge=True if node['t'] in ['topList']: if firstEdge: menge.add(n) return menge.add(n) if node['t'] in ['tag']: if firstEdge: dist-=0.1 else: return bestlist = [] keeplist = [] for m in list(G.adj[n]): book = G.nodes[m] if book['t'] not in ['NOTHING']: if 'score' in book and book['score'] != None: bestlist.append(book) elif 'rating' in book and book['rating'] != None: keeplist.append(book) else: book['score'] = 0 bestlist.append(book) bestlist.sort(key=lambda node: node['score'], reverse=True) toKeep = min(int(dist*10), math.ceil(len(bestlist) * dist - len(keeplist)*0.5)) if toKeep <= 0: keeplist.sort(key=lambda node: node['rating'], reverse=True) keeplist = keeplist[:min(int(dist*10), int(len(keeplist) * dist))] bestlist = [] else: bestlist = bestlist[:toKeep] for m in list(G.adj[n]): node = G.nodes[m] if node in bestlist or node in keeplist: waveFlow(G, node, m, dist, menge, firstEdge=firstEdge) def evaluateFitness(books, debugPrint=False): global weights G = buildBookGraph(books) graphAddAuthors(G, books) graphAddRecommenders(G, books) graphAddTopLists(G, books) graphAddSeries(G, books) graphAddTags(G, books) ratedBooks = [n for n in list(G.nodes) if 'rating' in G.nodes[n] and G.nodes[n]['rating'] != None] boundsLoss = 0 linSepLoss = [] errSq = [] gradient = {} for wt in weights: gradient[wt] = 0 mu, sigma = genScores(G, books) for b in G.nodes: if b in ratedBooks: rating = G.nodes[b]['rating'] G.nodes[b]['rating'] = None _, _ = genScores(G, books) if G.nodes[b]['score'] > rating: # over estimated errSq.append(((rating - G.nodes[b]['score'])**2)*2) else: errSq.append((rating - G.nodes[b]['score'])**2) G.nodes[b]['rating'] = rating for wt in weights: scoreB = sum([a*(1.001 if wt==w[0] else 1)*weights[w[0]]*(w[1] if len(w)>1 else 1) for a,w in zip(G.nodes[b]['_act'], G.nodes[b]['_wgh'])])/sum([(1.001 if wt==w[0] else 1)*weights[w[0]]*(w[1] if len(w)>1 else 1) for w in G.nodes[b]['_wgh']]) gradient[wt] += ((rating - G.nodes[b]['score'])**2 - (rating - scoreB)**2)*1000 if 'score' in G.nodes[b] and G.nodes[b]['score'] != None: score = G.nodes[b]['score'] if score > 10.0: boundsLoss += (score - 10)**2 elif score < 0.0: boundsLoss += (score)**2 # reward seperation linearly linSepLoss.append(abs(score - mu)) regressionLoss = sum([(1-w)**2 for w in weights.values()]) for g in gradient: gradient[g] /= len(errSq) if debugPrint: print(sum(errSq)/len(errSq), 0.005*regressionLoss, 0.2*boundsLoss/len(ratedBooks), 1.0*sum(linSepLoss)/len(linSepLoss)) fit = sum(errSq)/len(errSq) + 0.005*regressionLoss + 0.2*boundsLoss/len(ratedBooks) - 1.0*sum(linSepLoss)/len(linSepLoss) return fit, gradient def train(initGamma = 1, full=True): global weights if full: for wt in weights: weights[wt] = random.random() gamma = initGamma books = loadBooksFromDB() bestWeights = copy.copy(weights) mse, gradient = evaluateFitness(books) delta = sum(gradient[g]**2 for g in gradient) best_mse = mse stagLen = 0 while gamma > 3.0e-06 and delta > 3.0e-05 or best_mse > 3: last_mse = mse print({'mse': mse, 'gamma': gamma, 'delta': delta}) delta = sum(gradient[g]**2 for g in gradient) for wt in weights: weights[wt] += gamma*gradient[wt] mse, gradient = evaluateFitness(books) if mse < last_mse: gamma = gamma*1.25 else: gamma *= 0.25 if mse < best_mse: saveWeights(weights) bestWeights = copy.copy(weights) best_mse = mse if mse > last_mse: stagLen += 1 else: stagLen = 0 if stagLen == 4 or mse > 50: if full or mse > 10: stagLen = 0 gamma = initGamma if random.random() < 0.50: for wt in weights: weights[wt] = random.random() else: weights = copy.copy(bestWeights) for wt in weights: weights[wt] *= 0.975+0.05*random.random() else: break print('Done.') def saveWeights(weights): with open('neuralWeights.json', 'w') as f: f.write(json.dumps(weights)) def loadWeights(): with open('neuralWeights.json', 'r') as f: weights = json.loads(f.read()) return weights def cliInterface(): import argparse parser = argparse.ArgumentParser(description='TODO: Write Description.') parser.add_argument('--keep-priv', action="store_true") parser.add_argument('--remove-read', action="store_true") parser.add_argument('--remove-unread', action="store_true") parser.add_argument('--no-web', action="store_true") parser.add_argument('--no-list', action="store_true") parser.add_argument('--remove-edge', action="store_true") parser.add_argument('--keep-top-lists', action="store_true") parser.add_argument('--keep-useless-recommenders', action="store_true") parser.add_argument('--dark-mode', action="store_true") cmds = parser.add_subparsers(required=True, dest='cmd') p_rec = cmds.add_parser('recommend', description="TODO", aliases=['rec']) p_rec.add_argument('-n', type=int, default=25, help='number of books to recommend') p_rec.add_argument('--tag-based', action="store_true") p_rec.add_argument('--recommender-based', action="store_true") p_read = cmds.add_parser('read', description="TODO", aliases=[]) p_read.add_argument('--min-rating', type=int, default=0) p_read.add_argument('--all-tags', action="store_true") p_read.add_argument('--only-connected', action="store_true") p_show = cmds.add_parser('analyze', description="TODO", aliases=[]) p_show.add_argument('type', choices=['any', 'book', 'recommender', 'author', 'series']) p_show.add_argument('name', type=str) p_show.add_argument('-d', type=float, default=2.7, help='depth of expansion') p_train = cmds.add_parser('train', description="TODO", aliases=[]) p_train.add_argument('-g', type=float, default=1, help='learning rate gamma') p_train.add_argument('--full', action="store_true") p_full = cmds.add_parser('full', description="TODO", aliases=[]) args = parser.parse_args() if args.cmd=="train": train(args.g, args.full) exit() G, books = buildFullGraph(darkMode=args.dark_mode) mu, std = genScores(G, books) if args.cmd=="recommend": if args.tag_based: if args.recommender_based: raise Exception('tag-based and recommender-based can not be be combined') recommendNBooksTagBased(G, mu, std, args.n, not args.keep_top_lists) elif args.recommender_based: recommendNBooksRecommenderBased(G, mu, std, args.n, not args.keep_top_lists, not args.keep_useless_recommenders) else: recommendNBooks(G, mu, std, args.n, not args.keep_top_lists, not args.keep_useless_recommenders) elif args.cmd=="read": readBooksAnalysis(G, args.min_rating, args.all_tags, args.only_connected, not args.keep_top_lists) elif args.cmd=="analyze": analyze(G, args.type, args.name, args.d) elif args.cmd=="full": fullGraph(G, not args.keep_top_lists) else: raise Exception("Bad") if not args.keep_priv: removePriv(G) if args.remove_read: removeRead(G) elif args.remove_unread: removeUnread(G) removeDangling(G, alsoBooks=True) if args.remove_edge: removeEdge(G) if not args.no_list: printBestList(G) if not args.no_web: genAndShowHTML(G, darkMode=args.dark_mode) weights = loadWeights() if __name__ == "__main__": cliInterface()