Initial commit
This commit is contained in:
commit
c309d6158a
3 changed files with 263 additions and 0 deletions
8
readme.md
Normal file
8
readme.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
## TrustGraph
|
||||
|
||||
An implementation of the basic EigenTrust algorithm (http://nlp.stanford.edu/pubs/eigentrust.pdf).
|
||||
|
||||
The algorithm is meant to find the trustworthiness of peers in a distributed system. A (potentially sparse) matrix is populated with values representing how much peers trust each other. A map is also populated with how much trust is extended by default to a sub-set of peers. From that starting point, the algorithm converges on the global trustworthiness of each peer.
|
||||
|
||||
### To-Do
|
||||
This is a first pass. It does not yet implement distributed EigenTrust. There is also some room to improve error handling.
|
142
trustgraph.go
Normal file
142
trustgraph.go
Normal file
|
@ -0,0 +1,142 @@
|
|||
// Package trustGraph is based on EigenTrust
|
||||
// http://nlp.stanford.edu/pubs/eigentrust.pdf
|
||||
package trustGraph
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Group represents a group of peers. Peers need to be given unique, int IDs.
|
||||
// Certainty represents the threshold of RMS change at which the algorithm will
|
||||
// escape. Max is the maximum number of loos the algorithm will perform before
|
||||
// escaping (regardless of certainty). These default to 0.001 and 200
|
||||
// respectivly and generally don't need to be changed.
|
||||
type Group struct {
|
||||
trustGrid map[int]map[int]float32
|
||||
initialTrust map[int]float32
|
||||
Certainty float32
|
||||
Max int
|
||||
Alpha float32
|
||||
}
|
||||
|
||||
// NewGroup is the constructor for Group.
|
||||
func NewGroup() Group {
|
||||
return Group{
|
||||
trustGrid: map[int]map[int]float32{},
|
||||
initialTrust: map[int]float32{},
|
||||
Certainty: 0.001,
|
||||
Max: 200,
|
||||
Alpha: 0.95,
|
||||
}
|
||||
}
|
||||
|
||||
// Add will add or override a trust relationship. The first arg is the peer who
|
||||
// is extending trust, the second arg is the peer being trusted (by the peer
|
||||
// in the first arg). The 3rd arg is the amount of trust, which must be
|
||||
func (g Group) Add(truster, trusted int, amount float32) (err error) {
|
||||
err = float32InRange(amount)
|
||||
if err == nil {
|
||||
a, ok := g.trustGrid[truster]
|
||||
if !ok {
|
||||
a = map[int]float32{}
|
||||
g.trustGrid[truster] = a
|
||||
}
|
||||
a[trusted] = amount
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// InitialTrust sets the vaulues used to seed the calculation as well as the
|
||||
// corrective factor used by Alpha.
|
||||
func (g Group) InitialTrust(trusted int, amount float32) (err error) {
|
||||
err = float32InRange(amount)
|
||||
if err == nil {
|
||||
g.initialTrust[trusted] = amount
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// float32InRange is a helper to check that a value is 0.0 <= x <= 1.0
|
||||
func float32InRange(x float32) error {
|
||||
if x < 0 {
|
||||
return errors.New("Trust amount cannot be less than 0")
|
||||
}
|
||||
if x > 1 {
|
||||
return errors.New("Trust amount cannot be greater than 1")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compute will approximate the trustworthyness of each peer from the
|
||||
// information known of how much peers trust eachother.
|
||||
// It wil loop, upto g.Max times or until the average difference between
|
||||
// iterations is less than g.Certainty.
|
||||
func (g Group) Compute() map[int]float32 {
|
||||
if len(g.initialTrust) == 0 {
|
||||
return map[int]float32{}
|
||||
}
|
||||
t0 := g.initialTrust //trust map for previous iteration
|
||||
|
||||
for i := 0; i < g.Max; i++ {
|
||||
t1 := *g.computeIteration(&t0) // trust map for current iteration
|
||||
d := avgD(&t0, &t1)
|
||||
t0 = t1
|
||||
if d < g.Certainty {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return t0
|
||||
}
|
||||
|
||||
// computeIteration is broken out of Compute to aid comprehension. It is the
|
||||
// inner loop of Compute. It loops over every value in t (the current trust map)
|
||||
// and looks up how much trust that peer extends to every other peer. The
|
||||
// product of the direct trust and indirect trust
|
||||
func (g Group) computeIteration(t0 *map[int]float32) *map[int]float32 {
|
||||
|
||||
t1 := map[int]float32{}
|
||||
for truster, directTrust := range *t0 {
|
||||
for trusted, indirectTrust := range g.trustGrid[truster] {
|
||||
if trusted != truster {
|
||||
t1[trusted] += directTrust * indirectTrust
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// normalize the trust values
|
||||
// in the EigenTrust paper, this was not done every step, but I prefer to
|
||||
// Not doing it means the diff (d) needs to be normalized in
|
||||
// proportion to the values (because they increase with every iteration)
|
||||
highestTrust := float32(0)
|
||||
for _, v := range t1 {
|
||||
if v > highestTrust {
|
||||
highestTrust = v
|
||||
}
|
||||
}
|
||||
//Todo handle highestTrust == 0
|
||||
for i, v := range t1 {
|
||||
t1[i] = (v/highestTrust)*g.Alpha + (1-g.Alpha)*g.initialTrust[i]
|
||||
}
|
||||
|
||||
return &t1
|
||||
}
|
||||
|
||||
// abs is helper to take abs of float32
|
||||
func abs(x float32) float32 {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// avgD is helper to compare 2 maps of float32s and return the average
|
||||
// difference between them
|
||||
func avgD(t0, t1 *map[int]float32) float32 {
|
||||
d := float32(0)
|
||||
for i, v := range *t1 {
|
||||
d += abs(v - (*t0)[i])
|
||||
}
|
||||
d = d / float32(len(*t0))
|
||||
return d
|
||||
}
|
113
trustgraph_test.go
Normal file
113
trustgraph_test.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package trustGraph
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
g := NewGroup()
|
||||
g.Add(1, 2, 1)
|
||||
g.Add(1, 3, .5)
|
||||
g.Add(2, 1, 1)
|
||||
g.Add(2, 3, .5)
|
||||
g.Add(3, 1, 1)
|
||||
g.Add(3, 2, 1)
|
||||
|
||||
g.InitialTrust(1, 1)
|
||||
|
||||
out := g.Compute()
|
||||
|
||||
if out[1] < 0.975 {
|
||||
t.Error("Trust in node 1 should be closer to 1.00")
|
||||
}
|
||||
|
||||
if out[2] < 0.93 {
|
||||
t.Error("Trust in node 2 should be closer to 1.00")
|
||||
}
|
||||
if out[3] < 0.4 || out[3] > 0.6 {
|
||||
t.Error("Trust in node 3 should be closer to 0.50")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRand(t *testing.T) {
|
||||
peers := 200
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
g := NewGroup()
|
||||
|
||||
//randomly set actual trust values for peers
|
||||
actualTrust := make([]float32, peers)
|
||||
for i := 0; i < peers; i++ {
|
||||
actualTrust[i] = rand.Float32()
|
||||
}
|
||||
|
||||
// peer0 is set to and granted 100% trust
|
||||
actualTrust[0] = 1
|
||||
g.InitialTrust(0, 1)
|
||||
|
||||
// set 30% of trust values to +/- 10% of actual trust
|
||||
for i := 0; i < peers; i++ {
|
||||
for j := 0; j < peers; j++ {
|
||||
if rand.Float32() > .7 {
|
||||
g.Add(i, j, randNorm(actualTrust[j]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compute trust
|
||||
out := g.Compute()
|
||||
|
||||
// find RMS error
|
||||
e := float32(0)
|
||||
for i := 0; i < peers; i++ {
|
||||
x := actualTrust[i] - out[i]
|
||||
e += x * x
|
||||
}
|
||||
e = float32(math.Sqrt(float64(e / float32(peers))))
|
||||
|
||||
if e > .2 {
|
||||
t.Error("RMS Error should be less than 20% for a 30% full trust grid of 200 nodes")
|
||||
}
|
||||
}
|
||||
|
||||
// randNorm takes a float and returns a value within +/- 10%,
|
||||
// without going over 1
|
||||
func randNorm(x float32) float32 {
|
||||
r := rand.Float32()*.2 + .9
|
||||
x *= r
|
||||
if x > 1 {
|
||||
return 1
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func TestRangeError(t *testing.T) {
|
||||
g := NewGroup()
|
||||
|
||||
err := g.Add(1, 2, 1.1)
|
||||
if err.Error() != "Trust amount cannot be greater than 1" {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
|
||||
err = g.Add(1, 2, -1)
|
||||
if err.Error() != "Trust amount cannot be less than 0" {
|
||||
t.Error("Expected error less than 0 error")
|
||||
}
|
||||
|
||||
err = g.Add(1, 2, 1)
|
||||
if err != nil {
|
||||
t.Error("Did not expected error")
|
||||
}
|
||||
|
||||
err = g.Add(1, 2, 0)
|
||||
if err != nil {
|
||||
t.Error("Did not expected error")
|
||||
}
|
||||
|
||||
err = g.Add(1, 2, 0.5)
|
||||
if err != nil {
|
||||
t.Error("Did not expected error")
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue