Een aanbevelingssysteem voor gezamenlijk filteren in Java

1. Inleiding

In deze tutorial leren we alles over het Slope One-algoritme in Java.

We laten ook de voorbeeldimplementatie zien voor het probleem van Collaborative Filtering (CF) - een machine learning-techniek gebruikt door aanbevelingssystemen.

Dit kan bijvoorbeeld worden gebruikt om gebruikersinteresses voor specifieke items te voorspellen.

2. Gezamenlijke filtering

Het Slope One-algoritme is een itemgebaseerd samenwerkingsfiltersysteem. Het betekent dat het volledig is gebaseerd op de rangschikking van gebruikersitems. Wanneer we de gelijkenis tussen objecten berekenen, kennen we alleen de geschiedenis van ranglijsten, niet de inhoud zelf. Deze overeenkomst wordt vervolgens gebruikt om potentiële gebruikersrangschikkingen te voorspellen voor paren van gebruikersitems die niet in de dataset voorkomen.

De onderstaande afbeelding toont het volledige proces van het verkrijgen en berekenen van de beoordeling voor de specifieke gebruiker:

In eerste instantie beoordelen gebruikers verschillende items in het systeem. Vervolgens berekent het algoritme de overeenkomsten. Daarna doet het systeem voorspellingen voor beoordelingen van gebruikersitems, die de gebruiker nog niet heeft beoordeeld.

Voor meer details over het onderwerp van de gezamenlijke filtering, kunnen we verwijzen naar het Wikipedia-artikel.

3. Het Slope One-algoritme

Slope One werd genoemd als de eenvoudigste vorm van niet-triviale itemgebaseerde gezamenlijke filtering op basis van beoordelingen. Het houdt rekening met zowel informatie van alle gebruikers die hetzelfde item hebben beoordeeld als van de andere items die door dezelfde gebruiker zijn beoordeeld om de gelijkenismatrix te berekenen.

In ons eenvoudige voorbeeld gaan we gebruikersrangschikkingen voor de items in de winkel voorspellen.

Laten we beginnen met een eenvoudig Java-model voor ons probleem en domein.

3.1. Java-model

In ons model hebben we twee hoofdobjecten: items en gebruikers. De Item klasse bevat de naam van het item:

private String itemName;

Aan de andere kant is het Gebruiker klasse bevat de gebruikersnaam:

private String gebruikersnaam;

Eindelijk hebben we een Invoergegevens klasse die zal worden gebruikt om de gegevens te initialiseren. Laten we aannemen dat we vijf verschillende producten in de winkel zullen maken:

Lijstitems = Arrays.asList (nieuw item ("Snoep"), nieuw item ("Drankje"), nieuw item ("Soda"), nieuw item ("Popcorn"), nieuw item ("Snacks"));

Bovendien maken we drie gebruikers die willekeurig een aantal van de bovengenoemde gebruikers hebben beoordeeld met behulp van de schaal van 0,0-1,0, waarbij 0 geen interesse betekent, 0,5 op de een of andere manier geïnteresseerd en 1,0 betekent totaal geïnteresseerd. Als resultaat van de gegevensinitialisatie krijgen we een Kaart met classificatiegegevens van gebruikersitems:

Kaart gegevens;

3.2. Matrices voor verschillen en frequenties

Op basis van de beschikbare gegevens berekenen we de relaties tussen de items, evenals het aantal items dat voorkomt. Voor elke gebruiker controleren we zijn / haar beoordeling van de items:

for (HashMap user: data.values ​​()) {for (Entry e: user.entrySet ()) {// ...}}

In de volgende stap kijken we of het item aanwezig is in onze matrices. Als dit een eerste keer is, maken we het nieuwe item in de kaarten:

if (! diff.containsKey (e.getKey ())) {diff.put (e.getKey (), nieuwe HashMap ()); freq.put (e.getKey (), nieuwe HashMap ()); } 

De eerste matrix wordt gebruikt om de verschillen tussen de gebruikersbeoordelingen te berekenen. De waarden ervan kunnen positief of negatief zijn (aangezien het verschil tussen beoordelingen negatief kan zijn) en worden opgeslagen als Dubbele. Aan de andere kant worden de frequenties opgeslagen als Geheel getal waarden.

In de volgende stap gaan we de beoordelingen van alle items vergelijken:

for (Entry e2: user.entrySet ()) {int oldCount = 0; if (freq.get (e.getKey ()). containsKey (e2.getKey ())) {oldCount = freq.get (e.getKey ()). get (e2.getKey ()). intValue (); } dubbele oldDiff = 0.0; if (diff.get (e.getKey ()). containsKey (e2.getKey ())) {oldDiff = diff.get (e.getKey ()). get (e2.getKey ()). doubleValue (); } dubbel waargenomenDiff = e.getValue () - e2.getValue (); freq.get (e.getKey ()). put (e2.getKey (), oldCount + 1); diff.get (e.getKey ()). put (e2.getKey (), oldDiff + waargenomenDiff); }

Als iemand het item eerder heeft beoordeeld, verhogen we het aantal frequenties met één. Bovendien controleren we het gemiddelde verschil tussen de beoordelingen van het item en berekenen we de nieuwe waargenomen Diff.

Houd er rekening mee dat we de som van oldDiff en waargenomen Diff als een nieuwe waarde van een item.

Ten slotte berekenen we de gelijkenisscores binnen de matrices:

voor (Item j: diff.keySet ()) {voor (Item i: diff.get (j) .keySet ()) {double oldValue = diff.get (j) .get (i) .doubleValue (); int count = freq.get (j) .get (i) .intValue (); diff.get (j) .put (i, oldValue / count); }}

De belangrijkste logica is om het verschil van de berekende itembeoordeling te delen door het aantal keren dat het voorkomt. Na die stap kunnen we onze laatste verschillenmatrix afdrukken.

3.3. Voorspellingen

Als het belangrijkste onderdeel van de Slope One gaan we alle ontbrekende beoordelingen voorspellen op basis van de bestaande gegevens. Om dat te doen, moeten we de beoordelingen van gebruikersitems vergelijken met de verschillenmatrix die in de vorige stap is berekend:

voor (Entry e: data.entrySet ()) {voor (Item j: e.getValue (). keySet ()) {voor (Item k: diff.keySet ()) {double predictedValue = diff.get (k) .get (j ) .doubleValue () + e.getValue (). get (j) .doubleValue (); dubbele finalValue = predictedValue * freq.get (k) .get (j) .intValue (); uPred.put (k, uPred.get (k) + finalValue); uFreq.put (k, uFreq.get (k) + freq.get (k) .get (j) .intValue ()); }} // ...}

Daarna moeten we de 'schone' voorspellingen voorbereiden met behulp van de onderstaande code:

HashMap clean = nieuwe HashMap (); voor (Item j: uPred.keySet ()) {if (uFreq.get (j)> 0) {clean.put (j, uPred.get (j) .doubleValue () / uFreq.get (j) .intValue ( )); }} voor (Item j: InputData.items) {if (e.getValue (). containsKey (j)) {clean.put (j, e.getValue (). get (j)); } else if (! clean.containsKey (j)) {clean.put (j, -1.0); }}

De truc om te overwegen met een grotere dataset is om alleen de itemitems te gebruiken met een grote frequentiewaarde (bijvoorbeeld> 1). Houd er rekening mee dat als de voorspelling niet mogelijk is, de waarde ervan gelijk is aan -1.

Eindelijk een zeer belangrijke opmerking. Als ons algoritme correct werkte, we zouden de voorspellingen moeten ontvangen voor items die de gebruiker niet heeft beoordeeld, maar ook de herhaalde beoordelingen voor de items die hij heeft beoordeeld. Die herhaalde beoordelingen zouden niet moeten veranderen, anders betekent het dat er een bug in uw algoritme-implementatie zit.

3.4. Tips

Er zijn enkele belangrijke factoren die van invloed zijn op het Slope One-algoritme. Hier zijn enkele tips om de nauwkeurigheid en verwerkingstijd te vergroten:

  • overweeg om de gebruikersitem-beoordelingen aan de DB-kant te verkrijgen voor grote gegevenssets
  • stel het tijdsbestek in voor het ophalen van beoordelingen, aangezien de interesses van mensen in de loop van de tijd kunnen veranderen - het vermindert ook de tijd die nodig is om invoergegevens te verwerken
  • splits grote datasets in kleinere - u hoeft niet elke dag voorspellingen voor alle gebruikers te berekenen; u kunt controleren of de gebruiker interactie heeft gehad met het voorspelde item en hem / haar vervolgens toevoegen / verwijderen uit de verwerkingswachtrij voor de volgende dag

4. Conclusie

In deze tutorial hebben we het Slope One-algoritme leren kennen. Bovendien hebben we het probleem van collaboratieve filtering geïntroduceerd voor artikelaanbevelingssystemen.

De volledige implementatie van deze tutorial is te vinden in het GitHub-project.