forked from timdrijvers/recommendation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
similarities.go
127 lines (108 loc) · 2.92 KB
/
similarities.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package recommendation
import (
"sort"
"gonum.org/v1/gonum/mat"
"github.com/recoilme/recommendation/matrix"
"github.com/yourbasic/bit"
)
// Similarities maps a item to a list of similar items
type Similarities []*bit.Set
func intSliceBuilder(set *bit.Set) []int {
ret := []int{}
if set == nil {
return ret
}
set.Visit(func(n int) bool {
ret = append(ret, n)
return false
})
return ret
}
// NewSimilarities creates a new empty similaritites strucuture
func NewSimilarities(entries int) Similarities {
return make(Similarities, entries)
}
// NewTopSimilaritiesFromMatrix creates a filled similaritites structure with max top items per item
func NewTopSimilaritiesFromMatrix(m *matrix.CosineLabeledMatrix, top int) Similarities {
width := m.Width()
itemItemSimilarities := NewSimilarities(width)
for row := 0; row < width; row++ {
indexRowVector := matrix.NewLabelVector()
indexRowVector.AppendVector(m.RowView(row))
sort.Sort(sort.Reverse(indexRowVector))
itemItemSimilarities.Set(row, indexRowVector.HeadLabeles(top))
}
return itemItemSimilarities
}
// Set a list of entries at index
func (m Similarities) Set(index int, entries []int) {
m[index] = bit.New(entries...)
}
// Get a list of entries at index
func (m Similarities) Get(index int) []int {
return intSliceBuilder(m[index])
}
// GetAll returns a merged list of entries at indexes
func (m Similarities) GetAll(indexes []int) []int {
var mergedSet *bit.Set
for _, index := range indexes {
if m[index] == nil {
continue
}
if mergedSet == nil {
mergedSet = m[index]
} else {
mergedSet.SetOr(mergedSet, m[index])
}
}
return intSliceBuilder(mergedSet)
}
// GetAllVector returns a merged list of entries, for items in where vector is set
func (m Similarities) GetAllVector(vector mat.Vector) []int {
var mergedSet *bit.Set
for index := 0; index < vector.Len(); index++ {
if vector.AtVec(index) == 0 {
continue
}
if m[index] == nil {
continue
}
if mergedSet == nil {
mergedSet = m[index]
} else {
mergedSet.SetOr(mergedSet, m[index])
}
}
return intSliceBuilder(mergedSet)
}
func contains(slice []int, value int) bool {
for _, i := range slice {
if i == value {
return true
}
}
return false
}
// ScoredSimilar creates a vector of recommended items based on a binary vector of already liked items
func (m Similarities) ScoredSimilar(cosineMatrix *mat.Dense, vector mat.Vector) matrix.LabeledVector {
similarLikes := m.GetAllVector(vector)
scoreVector := matrix.NewLabelVector()
for _, item := range similarLikes {
// Skip already liked
if vector.AtVec(item) > 0 {
continue
}
itemScore := float64(0)
itemSum := float64(0)
row := cosineMatrix.RawRowView(item)
for j, cosine := range row {
if contains(similarLikes, j) {
itemScore += cosine * vector.AtVec(j)
itemSum += cosine
}
}
scoreVector.Append(item, itemScore/itemSum)
}
sort.Sort(sort.Reverse(scoreVector))
return scoreVector
}