graphaware / neo4j-reco

Neo4j-based recommendation engine module with real-time and pre-computed recommendations.
374 stars 77 forks source link

Multiple Nodes as Input #26

Closed mkubenka closed 8 years ago

mkubenka commented 8 years ago

Is it possible to use list of nodes as input? So, for example I would like to find recommendations for a product based on another 3 user-picked products.

ikwattro commented 8 years ago

Hi @mkubenka

There is no such built-in engine in the module. However as it is very generic, you can easily create your own for your use cases. For eg, the following demo engine use a List<Node> as input and add all their related nodes as recommendations where the score will the the internal id value of the reco node (this is a quick demo :) )

package com.graphaware.reco.demo.multi;

import com.graphaware.reco.generic.context.Context;
import com.graphaware.reco.generic.engine.RecommendationEngine;
import com.graphaware.reco.generic.policy.ParticipationPolicy;
import com.graphaware.reco.generic.result.PartialScore;
import com.graphaware.reco.generic.result.Recommendations;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;

import java.util.List;

public class MultipleInputRecoEngine implements RecommendationEngine<Node, List<Node>> {

    @Override
    public String name() {
        return "test_multi_engine";
    }

    @Override
    public ParticipationPolicy<Node, List<Node>> participationPolicy(Context<Node, List<Node>> context) {
        return ParticipationPolicy.ALWAYS;
    }

    @Override
    public Recommendations<Node> recommend(List<Node> input, Context<Node, List<Node>> context) {
        Recommendations<Node> result = new Recommendations<>();

        for (Node partiaInput : input) {
            for (Relationship relationship : partiaInput.getRelationships()) {
                Node reco = relationship.getOtherNode(partiaInput);
                result.add(reco, name(), new PartialScore(reco.getId()));
            }
        }

        return result;
    }
}

And a simple test would be :

package com.graphaware.reco.demo;

import com.graphaware.reco.demo.multi.MultipleInputRecoEngine;
import com.graphaware.reco.generic.config.SimpleConfig;
import com.graphaware.reco.generic.context.SimpleContext;
import com.graphaware.reco.generic.engine.RecommendationEngine;
import com.graphaware.reco.generic.result.Recommendation;
import com.graphaware.reco.generic.result.Recommendations;
import com.graphaware.test.integration.GraphAwareApiTest;
import org.junit.Test;
import org.neo4j.graphdb.*;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

public class MultiInputRecoEngineDemo extends GraphAwareApiTest {

    private static final Label LABEL = DynamicLabel.label("Node");

    @Test
    public void testMultiRecoEngine() throws Exception {
        createData();
        MultipleInputRecoEngine engine = new MultipleInputRecoEngine();
        List<Node> input = new ArrayList<>();
        try (Transaction tx = getDatabase().beginTx()) {
            ResourceIterator<Node> it = getDatabase().findNodes(LABEL);
            while (it.hasNext()) {
                input.add(it.next());
            }

            tx.success();
        }

        Recommendations recommendations = getRecommendations(input, engine);
        if (null == recommendations) {
            assertEquals(1, 2);
        }

        assertEquals(3, recommendations.size());
        int i = 1;
        for (Object reco : recommendations.get()) {
            Recommendation<Node> r = (Recommendation<Node>) reco;
            assertEquals(i, r.getItem().getId(), 0L);
            assertEquals(i, r.getScore().getTotalScore(), 0.0f);
            i += 2;
        }
    }

    private Recommendations<Node> getRecommendations(List<Node> input, RecommendationEngine recommendationEngine){
        Recommendations recommendations = null;
        try (Transaction tx = getDatabase().beginTx()) {
            recommendations = recommendationEngine.recommend(input, new SimpleContext(input, new SimpleConfig(10)));
            tx.success();
        }

        return recommendations;
    }

    private void createData() {
        try (Transaction tx = getDatabase().beginTx()) {
            for (int i = 0; i < 3; ++i) {
                Node a = getDatabase().createNode();
                a.addLabel(LABEL);
                Node b = getDatabase().createNode();
                a.createRelationshipTo(b, DynamicRelationshipType.withName("RELATES"));
            }

            tx.success();
        }
    }
}
1
{total:1.0, test_multi_engine:1.0}
3
{total:3.0, test_multi_engine:3.0}
5
{total:5.0, test_multi_engine:5.0}

If you end up creating a generic enough implementation with some tests, we would be happy to merge it in the repository.

Let us know if this helps,

Chris

ikwattro commented 8 years ago

@mkubenka is it answering your question ?