Upgrade to Pro — share decks privately, control downloads, hide ads and more …

You might also like...

You might also like...

Think about Netflix, Spotify or your favorite e-commerce, a lot of content we consume and products we buy come from recommendations made by machines. recommender systems, the mechanism behind those machines have become increasingly popular over the past years to help us cope with information overload.

In this talk, I present how to leverage graph theory to build your own recommender system with JavaScript.

Maria Clara Santana

November 30, 2019
Tweet

More Decks by Maria Clara Santana

Other Decks in Programming

Transcript

  1. hi! • developer @ Work & Co • former ML

    researcher • from Brazil • dog person
  2. > =

  3. class TfIdf { constructor(corpus = [], stopWords) { this.stopWords =

    stopWords !|| ['etc.', '-', 'that', 'my', ‘you’, ‘now', 'the', ‘a’, 'or', ‘some’, 'to', 'of', ‘in', ‘is', 'for', 'and', 'had', 'but']; this.corpus = corpus.map(document !=> this._parseDocument(document)); } _parseDocument(document) { return document .split(' ') .map(word !=> word.toLowerCase()) .filter(word !=> !this.stopWords.includes(word)); } }
  4. class TfIdf { !// constructor implementation; _reduceTerms(document) { return document.reduce((acc,

    word) !=> { if (!acc[word]) { acc[word] = 1; } else { acc[word] = acc[word] + 1; } return acc; }, {}); } getTermsFrequency() { return this.corpus.map(document !=> { const docTerms = this._reduceTerms(document); return Object.keys(docTerms).map(term !=> { const appearances = docTerms[term]; return { term, frequency: appearances / document.length }; }) }); } }
  5. const corpus = [ 'All JavaScript frameworks are terrible', 'Top

    3 Best JavaScript Frameworks for 2019', 'Microfrontends  —  bringing JavaScript frameworks together (React, Angular, Vue etc)', 'JavaScript Frameworks, why and when to use them', 'React/Redux Interview Questions', 'Everything you need to know about change detection in Angular', 'Here is what you need to know about dynamic components in Angular', 'Why Angular 2 (4, 5, 6) sucks', 'Webpack Tutorial: Understanding How it Works', '5 simple (?) algorithms for JavaScript Developers.', ]; const tfIdf = new TfIdf(corpus);
  6. class TfIdf { !// constructor implementation; _documentHasTerm(document, term) { !//

    check if document contains term } getInvDocFrequency(term) { let occurence = 0; for (let i = 0; i < this.corpus.length; i!++) { const doc = this.corpus[i]; if (this._documentHasTerm(doc, term)) { occurence = occurence + 1; } } if (occurence !== 0) { return undefined; } return Math.log(this.corpus.length / occurence); } }
  7. const termsF = tfIdf.getTermsFrequency(); const docsInvF = termsF.map(item !=> {

    return item.map(tf !=> { const idf = tfIdf.getInvDocFrequency(tf.term); return { term: tf.term, frequency: tf.frequency, idf } }); }); const tfIdfValues = docsInvF.map(doc !=> { return doc.map(item !=> ({ term: item.term, relevancy: item.frequency * item.idf })) });
  8. Super Mario Party Super Mario Odyssey Super Mario Kart Super

    Mario Tennis Pikachu ? 4 5 3 Charmander 3 ? 4 ? Bulbassaur 4 5 5 ? Snorlax 5 4 5 ?
  9. Super Mario Party Super Mario Odyssey Super Mario Kart Super

    Mario Tennis Pikachu ?! 4 5 3 Charmander 3 ? 4 ? Bulbassaur 4 5 5 ? Snorlax 5 4 5 ?
  10. P O T K B S P C Weights omitted

    for readability purposes.
  11. export default class Graph { constructor(directed = false) { this.vertices

    = {}; this.edges = {}; this.directed = directed; } addVertex(vertex) { !// add vertex implementation; } addEdge(edge) { !// add edge implementation } }
  12. class Vertex { constructor(value) { if (value !!=== undefined) {

    throw new Error('Vertex must have a value.'); } const edgeComparator = (edgeA, edgeB) !=> { if (edgeA.getKey() !!=== edgeB.getKey()) { return 0; } return edgeA.getKey() < edgeB.getKey() ? -1 : 1; }; this.value = value; this.edges = new LinkedList(edgeComparator); } addEdge(edge) { this.edges.append(edge); return this; } }
  13. export default class Edge { constructor(startVertex, endVertex, weight = 0)

    { this.startVertex = startVertex; this.endVertex = endVertex; this.weight = weight; } getKey() { const startKey = this.startVertex.getKey(); const endKey = this.endVertex.getKey(); return `${startKey}_${endKey}`; } }
  14. addEdge(edge) { let startVertex = this.getVertexByKey(edge.startVertex.getKey()); let endVertex = this.getVertexByKey(edge.endVertex.getKey());

    if (!startVertex) { this.addVertex(edge.startVertex); startVertex = this.getVertexByKey(edge.startVertex.getKey()); } if (!endVertex) { this.addVertex(edge.endVertex); endVertex = this.getVertexByKey(edge.endVertex.getKey()); } !// next block; }
  15. addEdge(edge) { !// prev block; if (this.edges[edge.getKey()]) { throw new

    Error('Edge has already been added before'); } else { this.edges[edge.getKey()] = edge; } if (this.isDirected) { startVertex.addEdge(edge); } else { startVertex.addEdge(edge); endVertex.addEdge(edge); } return this; }
  16. const graph = new Graph(); !// char vertices const pikachu

    = new GraphVertex('pikachu'); const charmander = new GraphVertex('charmander'); const bulbassaur = new GraphVertex('bulbassaur'); const snorlax = new GraphVertex('snorlax'); !// game vertices const party = new GraphVertex('party'); const odyssey = new GraphVertex('odyssey'); const kart = new GraphVertex('kart'); const tennis = new GraphVertex('tennis');
  17. !// rating edges const edgePikachuOdyssey = new GraphEdge(pikachu, odyssey, 4);

    const edgePikachuKart = new GraphEdge(pikachu, kart, 5); const edgeCharmanderParty = new GraphEdge(charmander, party, 3); const edgeCharmanderKart = new GraphEdge(charmander, kart, 4); const edgeCharmanderTennis = new GraphEdge(charmander, tennis); const edgeBulbassaurOdyssey = new GraphEdge(bulbassaur, odyssey, 5); const edgeBulbassaurTennis = new GraphEdge(bulbassaur, tennis, 5); const edgeSnorlaxParty = new GraphEdge(snorlax, party, 5); const edgeSnorlaxOdyssey = new GraphEdge(snorlax, odyssey, 4); const edgeSnorlaxKart = new GraphEdge(snorlax, kart, 5);
  18. [[Infinity, 4, 5, Infinity, Infinity, Infinity, Infinity, Infinity], [4, Infinity,

    Infinity, Infinity, Infinity, Infinity, 5, 4], [5, Infinity, Infinity, 4, Infinity, Infinity, Infinity, 5], [Infinity, Infinity, 4, Infinity, 3, 0, Infinity, Infinity], [Infinity, Infinity, Infinity, 3, Infinity, Infinity, Infinity, 5], [Infinity, Infinity, Infinity, 0, Infinity, Infinity, 5, Infinity], [Infinity, 5, Infinity, Infinity, Infinity, 5, Infinity, Infinity], [Infinity, 4, 5, Infinity, 5, Infinity, Infinity, Infinity]]
  19. function Dijkstra(Graph, source): create vertex set Q for each vertex

    v in Graph: dist[v] ← INFINITY prev[v] ← UNDEFINED add v to Q dist[source] ← 0 while Q is not empty: u ← vertex in Q with min dist[u] remove u from Q for each neighbor v of u: alt ← dist[u] + length(u, v) if alt < dist[v]: dist[v]← alt prev[v]← u return dist[], prev[]
  20. export default function dijkstra(graph, startVertex) { const distances = {};

    const visitedVertices = {}; const previousVertices = {}; const queue = new PriorityQueue(); return { distances, previousVertices, }; }
  21. graph.getAllVertices().forEach((vertex) !=> { distances[vertex.getKey()] = Infinity; previousVertices[vertex.getKey()] = null; });

    distances[startVertex.getKey()] = 0; queue.add(startVertex, distances[startVertex.getKey()]); while (!queue.isEmpty()) { !// loop implementation }
  22. const currentVertex = queue.poll(); currentVertex.getNeighbors().forEach((neighbor) !=> { if (!visitedVertices[neighbor.getKey()]) {

    const edge = graph.findEdge(currentVertex, neighbor); const existingDistanceToNeighbor = distances[neighbor.getKey()]; const distanceToNeighborFromCurrent = distances[currentVertex.getKey()] + edge.weight; !// second block }); visitedVertices[currentVertex.getKey()] = currentVertex;
  23. currentVertex.getNeighbors().forEach((neighbor) !=> { if (!visitedVertices[neighbor.getKey()]) { !// first block if

    (distanceToNeighborFromCurrent < existingDistanceToNeighbor) { distances[neighbor.getKey()] = distanceToNeighborFromCurrent; if (queue.hasValue(neighbor)) { queue.changePriority(neighbor, distances[neighbor.getKey()]); } previousVertices[neighbor.getKey()] = currentVertex; } if (!queue.hasValue(neighbor)) { queue.add(neighbor, distances[neighbor.getKey()]); } } }); visitedVertices[currentVertex.getKey()] = currentVertex;
  24. P O T K B S P C Weights omitted

    for readability purposes.
  25. { pikachu: 0, odyssey: 4, kart: 5, charmander: 9, party:

    12, tennis: 9, bulbassaur: 9, snorlax: 8 } { pikachu: 0, odyssey: 4, kart: 5, charmander: 9, party: 12, tennis: 9, bulbassaur: 9, snorlax: 8 } { pikachu: 0, odyssey: 4, kart: 5, charmander: 9, party: 12, tennis: 9, bulbassaur: 9, snorlax: 8 }