Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web3Recommend #160

Open
wants to merge 48 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
2f970b1
Add Node to Node network
rmadhwal Mar 29, 2023
a790a17
Added Node To Song Network and done basic setup, serialization still …
rmadhwal Apr 1, 2023
8b7e1b8
Add serialisation for Node To Song network, remove JSON serialisation…
rmadhwal Apr 1, 2023
8e1975b
Initial random walk for personalised page rank (untested)
rmadhwal Apr 5, 2023
89e451a
Set up basic test for personalized page rank
rmadhwal Apr 6, 2023
d93e6e2
Update random walk iterator to be always present for pagerank
rmadhwal Apr 7, 2023
58bfdfe
update test to reflect pagerank expectations
rmadhwal Apr 11, 2023
312880b
add modification option to random walks iterator (not tested yet)
rmadhwal Apr 11, 2023
73b6459
add ability to remove node, test iterative modifications and allow se…
rmadhwal Apr 12, 2023
25a3ec4
add skeleton for Hybrid Random Walk and an initial implementation for…
rmadhwal Apr 12, 2023
dd2b853
Mostly refactoring, unify node and song interfaces, make an interface…
rmadhwal Apr 13, 2023
8b913e2
implement Hybrid Random Walks partially
rmadhwal Apr 14, 2023
27af486
Add tests to assert properties of random walk algos
rmadhwal Apr 14, 2023
4779d48
test modification of edges in hybrid salse
rmadhwal Apr 17, 2023
a2e96ca
alter tests for hybrid personalized salsa, modify NodeOrSong so nodes…
rmadhwal Apr 17, 2023
7ce9b41
Add test tp assert removal of edge from NtS network
rmadhwal Apr 18, 2023
7540f48
add skeleton to enable gossipping
rmadhwal Apr 18, 2023
e473f0a
set up code for overlying trust network TODO: update existing tests a…
rmadhwal Apr 19, 2023
03c3e9e
bug fixes and initial test for trust network and edge gossiper
rmadhwal Apr 21, 2023
0e23913
bug fixes
rmadhwal Apr 21, 2023
d35a794
Implement beta decays from merit rank, some refactoring
rmadhwal Apr 23, 2023
c899000
add tests for edge gossiping init
rmadhwal Apr 25, 2023
6963447
bug fix and add time window to edge gossiper
rmadhwal Apr 25, 2023
e13b9a4
Implement beta decays from merit rank, some refactoring
rmadhwal May 2, 2023
41df694
add collaborative filtering and enhance tests to create initial conne…
rmadhwal May 4, 2023
1897199
fix beta decay implementation and make it memory efficient
rmadhwal May 6, 2023
03ecb0e
add non heap efficient implementation
rmadhwal May 6, 2023
d8443d6
add beta decays for hybrid merit rank and fix computation of scores t…
rmadhwal May 6, 2023
c4736a4
add functionality to reward recommenders for song recommendation
rmadhwal May 6, 2023
373aeb8
test network is set up
rmadhwal May 6, 2023
ef272f7
change beta decays to be dynamically imposed
rmadhwal May 7, 2023
d49d538
mass commits with a lot of constants - fix later
rmadhwal May 22, 2023
fb3fec7
working, I think
rmadhwal May 23, 2023
a83411c
maybe works
rmadhwal May 23, 2023
590dd5e
completely working
rmadhwal May 23, 2023
d1269d8
Add experiments
rmadhwal May 26, 2023
980d530
Big Commit - Add local song count increments and create gossiper
rmadhwal May 30, 2023
0296d74
recommendations now working - todo: refactor
rmadhwal Jun 1, 2023
6be4781
versioning fixes
rmadhwal Jun 1, 2023
ee6b08a
Experiment tweeks
rmadhwal Jun 20, 2023
5421470
Fix tests, delete files related to experiments
rmadhwal Jun 20, 2023
36a191c
QoL fixes
rmadhwal Jun 21, 2023
7b7a52c
cleanliness
rmadhwal Jun 21, 2023
5c8a4f1
refactoring
rmadhwal Jul 7, 2023
7f317fe
lint fixes
rmadhwal Jul 7, 2023
dc6b8f7
More lint fixes
rmadhwal Jul 7, 2023
9a7a1a1
test lint fixes
rmadhwal Jul 7, 2023
a28c650
serialization doesnt seem to work on github runners
rmadhwal Jul 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@
android:enabled="true"
android:exported="false" />

<service
android:name="nl.tudelft.trustchain.musicdao.core.recommender.gossip.EdgeGossiperService"
android:enabled="true"
android:exported="false" />

<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyBAGd8-KwX1epS_0CPl5RF0hD8VeTmM-7Y" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import nl.tudelft.trustchain.eurotoken.db.TrustStore
import nl.tudelft.trustchain.gossipML.RecommenderCommunity
import nl.tudelft.trustchain.gossipML.db.RecommenderStore
import nl.tudelft.trustchain.literaturedao.ipv8.LiteratureCommunity
import nl.tudelft.trustchain.musicdao.core.ipv8.TrustedRecommenderCommunity
import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity
import nl.tudelft.trustchain.peerchat.db.PeerChatStore
import nl.tudelft.trustchain.valuetransfer.community.IdentityCommunity
Expand Down Expand Up @@ -119,7 +120,8 @@ class TrustChainApplication : Application() {
createRecommenderCommunity(),
createIdentityCommunity(),
createFOCCommunity(),
createDeToksCommunity()
createDeToksCommunity(),
createTrustedRecommenderCommunity()
),
walkerInterval = 5.0
)
Expand Down Expand Up @@ -457,6 +459,14 @@ class TrustChainApplication : Application() {
)
}

private fun createTrustedRecommenderCommunity(): OverlayConfiguration<TrustedRecommenderCommunity> {
val randomWalk = RandomWalk.Factory()
return OverlayConfiguration(
TrustedRecommenderCommunity.Factory(this),
listOf(randomWalk)
)
}

private fun getIdAlgorithmKey(idFormat: String): BonehPrivateKey {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val privateKey = prefs.getString(idFormat, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ class FOCCommunity(
val torrentHash = payload.message.substringAfter("magnet:?xt=urn:btih:")
.substringBefore("&dn=")
if (torrentMessagesList.none {
it.second
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looked like a typo in a previous PR, I've removed it, wasn't sure if it was appropriate to do it as a part of this PR - will remove it if you'd rather I commit it separately

val existingHash = it.second.message.substringAfter("magnet:?xt=urn:btih:").substringBefore("&dn=")
torrentHash == existingHash
}
Expand Down
4 changes: 4 additions & 0 deletions musicdao/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ apply plugin: 'org.jlleitschuh.gradle.ktlint'
apply plugin: 'androidx.navigation.safeargs'
apply plugin: 'kotlin-kapt'
apply plugin: "dagger.hilt.android.plugin"
apply plugin: 'kotlinx-serialization'

buildscript {
repositories {
Expand Down Expand Up @@ -176,6 +177,9 @@ dependencies {
implementation("com.google.code.gson:gson:2.8.9")
implementation("org.apache.commons:commons-csv:1.9.0")

//JGraphT
implementation "org.jgrapht:jgrapht-core:1.5.1"
implementation "org.jgrapht:jgrapht-io:1.5.1"
}

repositories {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.components.ActivityComponent
import kotlinx.coroutines.*
import nl.tudelft.trustchain.currencyii.coin.WalletManager
import nl.tudelft.trustchain.musicdao.core.recommender.gossip.EdgeGossiperService
import javax.inject.Inject

/**
Expand Down Expand Up @@ -68,6 +69,9 @@ class MusicActivity : AppCompatActivity() {
lateinit var setupMusicCommunity: SetupMusicCommunity

lateinit var mService: MusicGossipingService

lateinit var mEdgeGossiperService: EdgeGossiperService

var mBound: Boolean = false

@DelicateCoroutinesApi
Expand All @@ -90,6 +94,11 @@ class MusicActivity : AppCompatActivity() {
bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}

Intent(this, EdgeGossiperService::class.java).also { intent ->
startService(intent)
bindService(intent, mGossipConnection, Context.BIND_AUTO_CREATE)
}

Log.d(
"MusicDao2",
"DEBUG: $walletManager"
Expand Down Expand Up @@ -179,6 +188,7 @@ class MusicActivity : AppCompatActivity() {
super.onDestroy()
if (mBound) {
unbindService(mConnection)
unbindService(mGossipConnection)
}
}

Expand All @@ -196,6 +206,20 @@ class MusicActivity : AppCompatActivity() {
}
}

private val mGossipConnection = object : ServiceConnection {
// Called when the connection with the service is established
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as EdgeGossiperService.LocalBinder
mEdgeGossiperService = binder.getService()
mBound = true
}

// Called when the connection with the service disconnects unexpectedly
override fun onServiceDisconnected(className: ComponentName) {
mBound = false
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import nl.tudelft.trustchain.musicdao.core.cache.parser.Converters

@Database(
entities = [AlbumEntity::class],
version = 5,
version = 8,
exportSchema = false
)
@TypeConverters(Converters::class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package nl.tudelft.trustchain.musicdao.core.ipv8

import android.content.Context
import nl.tudelft.ipv8.Community
import nl.tudelft.ipv8.Overlay
import nl.tudelft.ipv8.Peer
import nl.tudelft.ipv8.messaging.Packet
import nl.tudelft.trustchain.musicdao.core.recommender.gossip.NodeToNodeEdgeGossip
import nl.tudelft.trustchain.musicdao.core.recommender.gossip.NodeToRecEdgeGossip
import nl.tudelft.trustchain.musicdao.core.recommender.model.NodeRecEdge
import nl.tudelft.trustchain.musicdao.core.recommender.model.NodeTrustEdgeWithSourceAndTarget
import nl.tudelft.trustchain.musicdao.core.recommender.networks.SongRecTrustNetwork
import java.sql.Timestamp
import java.util.ArrayList

class TrustedRecommenderCommunity(
context: Context
) : Community() {
private val appDirectory = context.cacheDir
override val serviceId = "12313685c1912a141279f8248fc8db5899c5df6c"

lateinit var trustNetwork: SongRecTrustNetwork

init {
messageHandlers[MessageId.NODE_TO_NODE_EDGE] = ::onNodeToNodeEdge
messageHandlers[MessageId.NODE_TO_REC_EDGE] = ::onNodeToRecEdge
}

fun sendNodeToNodeEdges(peer: Peer, nodeToNodeEdges: List<NodeTrustEdgeWithSourceAndTarget>) {
for (edge in nodeToNodeEdges) {
val packet = serializePacket(MessageId.NODE_TO_NODE_EDGE, NodeToNodeEdgeGossip(edge), sign = false)
send(peer, packet)
}
}

fun sendNodeRecEdges(peer: Peer, nodeToSongEdges: List<NodeRecEdge>) {
for (edge in nodeToSongEdges) {
val packet = serializePacket(MessageId.NODE_TO_REC_EDGE, NodeToRecEdgeGossip(edge), sign = false)
send(peer, packet)
}
}

private fun onNodeToNodeEdge(packet: Packet) {
if (!::trustNetwork.isInitialized) {
trustNetwork = SongRecTrustNetwork.getInstance(myPeer.key.pub().toString(), appDirectory.path.toString())
}
val payload = packet.getPayload(NodeToNodeEdgeGossip.Deserializer).edge
if (trustNetwork.nodeToNodeNetwork.graph.containsVertex(payload.sourceNode)) {
val existingEdges = trustNetwork.nodeToNodeNetwork.graph.outgoingEdgesOf(payload.sourceNode)
if (existingEdges.size > 4) {
val oldestEdge = existingEdges.minByOrNull { it.timestamp }!!
if (oldestEdge.timestamp > payload.nodeTrustEdge.timestamp) return
trustNetwork.nodeToNodeNetwork.removeEdge(oldestEdge)
}
} else {
trustNetwork.addNode(payload.sourceNode)
}
if (!trustNetwork.nodeToNodeNetwork.graph.containsVertex(payload.targetNode)) {
trustNetwork.addNode(payload.targetNode)
}
trustNetwork.addNodeToNodeEdge(payload)
}

private fun onNodeToRecEdge(packet: Packet) {
if (!::trustNetwork.isInitialized) {
trustNetwork = SongRecTrustNetwork.getInstance(myPeer.key.pub().toString(), appDirectory.path.toString())
}
val payload = packet.getPayload(NodeToRecEdgeGossip.Deserializer).edge
if (trustNetwork.nodeToSongNetwork.graph.containsVertex(payload.node)) {
val existingEdges = trustNetwork.nodeToSongNetwork.graph.outgoingEdgesOf(payload.node)
if (existingEdges.size > 4) {
val oldestEdge = existingEdges.minByOrNull { it.timestamp }!!
if (oldestEdge.timestamp > payload.nodeSongEdge.timestamp) return
trustNetwork.nodeToSongNetwork.removeEdge(oldestEdge)
}
} else {
trustNetwork.addNode(payload.node)
}
if (!trustNetwork.nodeToSongNetwork.graph.containsVertex(payload.rec)) {
trustNetwork.addSongRec(payload.rec)
}
trustNetwork.addNodeToSongEdge(payload)
}

object MessageId {
const val NODE_TO_NODE_EDGE = 1
const val NODE_TO_REC_EDGE = 2
}

class Factory(
private val context: Context
) : Overlay.Factory<TrustedRecommenderCommunity>(TrustedRecommenderCommunity::class.java) {
override fun create(): TrustedRecommenderCommunity {
return TrustedRecommenderCommunity(context)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nl.tudelft.trustchain.musicdao.core.recommender.collaborativefiltering

import nl.tudelft.trustchain.musicdao.core.recommender.model.Node
import nl.tudelft.trustchain.musicdao.core.recommender.model.NodeRecEdge

interface CollaborativeFiltering {
fun similarNodes(nodeToSongEdges: List<NodeRecEdge>, size: Int): Map<Node, Double>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package nl.tudelft.trustchain.musicdao.core.recommender.collaborativefiltering

import nl.tudelft.trustchain.musicdao.core.recommender.networks.TrustNetwork
import nl.tudelft.trustchain.musicdao.core.recommender.model.*
import java.lang.Float.POSITIVE_INFINITY
import kotlin.math.sqrt

class UserBasedTrustedCollaborativeFiltering(
var sortedTopTrustedUsers: List<Node>,
val trustNetwork: TrustNetwork,
val a: Double,
val b: Double
) : CollaborativeFiltering {
companion object {
private lateinit var instance: UserBasedTrustedCollaborativeFiltering
fun getInstance(
topTrustedUsers: List<Node>,
trustNetwork: TrustNetwork,
a: Double,
b: Double
): UserBasedTrustedCollaborativeFiltering {
if (!Companion::instance.isInitialized) {
instance = UserBasedTrustedCollaborativeFiltering(topTrustedUsers, trustNetwork, a, b)
}
return instance
}
}

override fun similarNodes(nodeToSongEdges: List<NodeRecEdge>, size: Int): Map<Node, Double> {
val sourceAffinities = nodeToSongEdges.associateBy({ it.rec }, { it.nodeSongEdge.affinity })
val nodeSimilarities = mutableListOf<NodeSimilarity>()
for (node in sortedTopTrustedUsers) {
val targetSongEdges = trustNetwork.nodeToSongNetwork.graph.outgoingEdgesOf(node)
val targetAffinities =
targetSongEdges.associateBy(
{ trustNetwork.nodeToSongNetwork.graph.getEdgeTarget(it) as Recommendation },
{ it.affinity }
)
val commonItems = sourceAffinities.keys.intersect(targetAffinities.keys)
val nodeSimilarity = NodeSimilarity(node)
if (commonItems.isNotEmpty()) {
nodeSimilarity.pcc = calculatePearsonCorrelationCoefficient(sourceAffinities, targetAffinities, commonItems)
nodeSimilarity.rdci = calculateRatingDifferenceOfCommonItem(sourceAffinities, targetAffinities, commonItems)
nodeSimilarity.commonItems = commonItems.size
}
nodeSimilarities.add(nodeSimilarity)
}
val maxCommonItems = nodeSimilarities.map { it.commonItems }.maxOrNull()
return if (maxCommonItems == null || maxCommonItems == 0) {
sortedTopTrustedUsers.takeLast(size).associateBy({ it }, { it.getPersonalizedPageRankScore() })
} else {
val nodesTrustAndSimilarity = mutableListOf<Pair<Node, Double>>()
for (nodeSim in nodeSimilarities) {
val cf = nodeSim.commonItems.toDouble() / maxCommonItems
val nSim = cf * nodeSim.pcc
val similarity = (nSim * b) + (nodeSim.rdci * (1 - b))
val combinedTrustAndSimilarity =
(nodeSim.node.getPersonalizedPageRankScore() * a) + ((similarity + 1) * (1 - a))
nodesTrustAndSimilarity.add(Pair(nodeSim.node, combinedTrustAndSimilarity))
}
nodesTrustAndSimilarity.sortedBy { it.second }.takeLast(size).associateBy({ it.first }, { it.second })
}
}

private fun calculateRatingDifferenceOfCommonItem(
sourceAffinities: Map<Recommendation, Double>,
targetAffinities: Map<Recommendation, Double>,
commonItems: Set<Recommendation>
): Double {
var maxRating = 0.0
var minRating = POSITIVE_INFINITY.toDouble()
val cumRatingDifference = commonItems.map {
val largerRating = maxOf(sourceAffinities[it]!!, targetAffinities[it]!!)
val smallerRating = minOf(sourceAffinities[it]!!, targetAffinities[it]!!)
if (largerRating > maxRating) maxRating = largerRating
if (smallerRating < minRating) minRating = smallerRating
largerRating - smallerRating
}.sum()
val ratingDifferenceFactor = cumRatingDifference / commonItems.size
val ratingSpan = maxRating - minRating
return if (ratingSpan == 0.0) 0.0
else 1 - (ratingDifferenceFactor * (1 / ratingSpan))
}

private fun calculatePearsonCorrelationCoefficient(
sourceAffinities: Map<Recommendation, Double>,
targetAffinities: Map<Recommendation, Double>,
commonItems: Set<Recommendation>
): Double {
val sourceAvg = sourceAffinities.values.average()
val targetAvg = targetAffinities.values.average()
val sourceVariances = commonItems.associateBy({ it }, { sourceAffinities[it]!! - sourceAvg })
val targetVariances = commonItems.associateBy({ it }, { targetAffinities[it]!! - targetAvg })
val numerator = commonItems.sumOf { song ->
sourceVariances[song]!! * targetVariances[song]!!
}

return if (numerator != 0.0) {
val sourceDenominator = commonItems.sumOf { song ->
sqrt(sourceVariances[song]!! * sourceVariances[song]!!)
}
val targetDenominator = commonItems.sumOf { song ->
sqrt(targetVariances[song]!! * targetVariances[song]!!)
}
val denominator = sourceDenominator * targetDenominator
numerator / denominator
} else {
numerator
}
}
}
Loading