Un sistema di raccomandazione di filtraggio collaborativo in Java

1. Introduzione

In questo tutorial impareremo tutto sull'algoritmo Slope One in Java.

Mostreremo anche l'implementazione di esempio per il problema del Collaborative Filtering (CF), una tecnica di apprendimento automatico utilizzata dai sistemi di raccomandazione .

Questo può essere utilizzato, ad esempio, per prevedere gli interessi degli utenti per articoli specifici.

2. Filtro collaborativo

L'algoritmo Slope One è un sistema di filtraggio collaborativo basato sugli elementi. Significa che è completamente basato sulla classificazione degli elementi dell'utente. Quando calcoliamo la somiglianza tra gli oggetti, conosciamo solo la cronologia delle classifiche, non il contenuto stesso. Questa somiglianza viene quindi utilizzata per prevedere le classifiche degli utenti potenziali per le coppie di elementi utente non presenti nel set di dati.

L'immagine sotto mostra il processo completo per ottenere e calcolare la valutazione per l'utente specifico:

In un primo momento, gli utenti valutano diversi articoli nel sistema. Successivamente, l'algoritmo calcola le somiglianze. Dopodiché, il sistema effettua previsioni per le valutazioni degli elementi utente, che l'utente non ha ancora valutato.

Per maggiori dettagli sull'argomento del filtraggio collaborativo, possiamo fare riferimento all'articolo di Wikipedia.

3. L'algoritmo Slope One

Slope One è stato nominato come la forma più semplice di filtraggio collaborativo non banale basato sugli elementi in base alle valutazioni. Per calcolare la matrice di somiglianza, tiene conto sia delle informazioni di tutti gli utenti che hanno valutato lo stesso articolo sia degli altri articoli valutati dallo stesso utente.

Nel nostro semplice esempio, prevediamo il posizionamento degli utenti sugli articoli nel negozio.

Cominciamo con un semplice modello Java per il nostro problema e dominio.

3.1. Modello Java

Nel nostro modello abbiamo due oggetti principali: oggetti e utenti. La classe Item contiene il nome dell'articolo:

private String itemName;

D'altra parte, la classe User contiene il nome utente:

private String username;

Infine, abbiamo una classe InputData che verrà utilizzata per inizializzare i dati. Supponiamo di creare cinque diversi prodotti nel negozio:

List items = Arrays.asList( new Item("Candy"), new Item("Drink"), new Item("Soda"), new Item("Popcorn"), new Item("Snacks") );

Inoltre, creeremo tre utenti che hanno valutato casualmente alcuni dei suddetti utilizzando la scala da 0,0 a 1,0, dove 0 significa nessun interesse, 0,5 in qualche modo interessato e 1,0 significa totalmente interessato. Come risultato dell'inizializzazione dei dati, otterremo una mappa con i dati di classificazione degli elementi dell'utente:

Map
    
      data;
    

3.2. Matrici di differenze e frequenze

In base ai dati disponibili, calcoleremo le relazioni tra gli elementi, nonché il numero di occorrenze degli elementi. Per ogni utente, controlliamo la sua valutazione degli articoli:

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

Nella fase successiva, controlliamo se l'elemento è presente nelle nostre matrici. Se questa è una prima occorrenza, creiamo la nuova voce nelle mappe:

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

La prima matrice viene utilizzata per calcolare le differenze tra le valutazioni degli utenti. I suoi valori potrebbero essere positivi o negativi (poiché la differenza tra le valutazioni potrebbe essere negativa) e vengono memorizzati come Double . D'altra parte, le frequenze vengono memorizzate come valori Integer .

Nel passaggio successivo confronteremo le valutazioni di tutti gli articoli:

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(); } double oldDiff = 0.0; if (diff.get(e.getKey()).containsKey(e2.getKey())){ oldDiff = diff.get(e.getKey()).get(e2.getKey()).doubleValue(); } double observedDiff = e.getValue() - e2.getValue(); freq.get(e.getKey()).put(e2.getKey(), oldCount + 1); diff.get(e.getKey()).put(e2.getKey(), oldDiff + observedDiff); }

Se qualcuno ha valutato l'articolo in precedenza, aumentiamo il conteggio della frequenza di uno. Inoltre, controlliamo la differenza media tra le valutazioni dell'articolo e calcoliamo il nuovo valore di diff .

Si prega di notare che abbiamo messo la somma di oldDiff e observedDiff come un nuovo valore di un elemento.

Infine, calcoliamo i punteggi di somiglianza all'interno delle matrici:

for (Item j : diff.keySet()) { for (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); } }

La logica principale consiste nel dividere la differenza di valutazione dell'elemento calcolato per il numero di occorrenze. Dopo questo passaggio, possiamo stampare la nostra matrice delle differenze finali.

3.3. Predizioni

Come parte principale dello Slope One, prevediamo tutte le valutazioni mancanti sulla base dei dati esistenti. Per fare ciò, dobbiamo confrontare le valutazioni degli articoli utente con la matrice delle differenze calcolata nel passaggio precedente:

for (Entry
    
      e : data.entrySet()) { for (Item j : e.getValue().keySet()) { for (Item k : diff.keySet()) { double predictedValue = diff.get(k).get(j).doubleValue() + e.getValue().get(j).doubleValue(); double 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()); } } // ... }
    

Dopodiché, dobbiamo preparare le previsioni "pulite" utilizzando il codice seguente:

HashMap clean = new HashMap(); for (Item j : uPred.keySet()) { if (uFreq.get(j) > 0) { clean.put(j, uPred.get(j).doubleValue() / uFreq.get(j).intValue()); } } for (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); } }

Il trucco da considerare con un set di dati più ampio consiste nell'utilizzare solo le voci degli elementi che hanno un valore di frequenza elevato (ad esempio> 1). Si noti che se la previsione non è possibile, il suo valore sarà pari a -1.

Infine, nota molto importante. Se il nostro algoritmo ha funzionato correttamente, dovremmo ricevere le previsioni per gli elementi che l'utente non ha valutato, ma anche le valutazioni ripetute per gli elementi che ha valutato . Quelle valutazioni ripetute non dovrebbero cambiare, altrimenti significa che c'è un bug nell'implementazione dell'algoritmo.

3.4. Suggerimenti

Ci sono pochi fattori principali che influenzano l'algoritmo Slope One. Ecco alcuni suggerimenti su come aumentare la precisione e il tempo di elaborazione:

  • prendere in considerazione la possibilità di ottenere le valutazioni degli elementi utente sul lato DB per grandi set di dati
  • impostare l'intervallo di tempo per il recupero delle valutazioni, poiché gli interessi delle persone potrebbero cambiare nel tempo - ridurrà anche il tempo necessario per elaborare i dati di input
  • dividere set di dati di grandi dimensioni in set di dati più piccoli: non è necessario calcolare ogni giorno previsioni per tutti gli utenti; puoi verificare se l'utente ha interagito con l'elemento previsto e quindi aggiungerlo / rimuoverlo dalla coda di elaborazione per il giorno successivo

4. Conclusione

In questo tutorial siamo stati in grado di conoscere l'algoritmo Slope One. Inoltre, abbiamo introdotto il problema del filtro collaborativo per i sistemi di raccomandazione degli articoli.

L' implementazione completa di questo tutorial può essere trovata nel progetto GitHub.