Content Based recommender system 1 - UCM-GAIA/RecoLibry-Core GitHub Wiki

The complete example is implemented in the test1 of RecoLibry-Example repository.


In this section, we explain how to build a content-based recommender system with RecoLibry-Core. This example implements a recommender system using a Java class. We will make a recommender system of movies. It will recommend movies with similar genres than a user profile. To build this recommender system, it is necessary to download the file movies.csv form Movielens 100K dataset and insert it in your project.

The schema of our project will be as follow:

Project Structure

Next, we add the RecoLibry-Core in our project adding the dependency in our pom.xml file. To remember that you can visit the Start section.

Configuration

First, we are going to configure the recommender system. In this example, we use a class that extends the RecSysConfiguration class. We explain how complete the functions generateClass() and configure(). We create a new class called TestConfiguration and it extends the abstract class RecSysConfiguration. In addition, we declare the methods that we must implement:

public class TestConfiguration extends RecSysConfiguration {
  
  @Override
  protected void generateClass() {
    
  }
  
  @Override
  protected void configure() {
    
  }
  
}

Generate auxiliary class

In our example, we need to make an auxiliary class to save the movies information. For this reason, we must complete the generated method. This class will call MovieDescription and should contain the next attributes based on the movies.csv file:

  • id: Movie identity number. It is an Integer type.
  • name: Movie name. It is a String value.
  • genres: List with genres of the movie. The list type is String.

With this information, we can start to implement our configuration class. We will create 2 constants to save the package, that contains the new class, and the name of the new class. We write this information in our TestConfiguration class:

public class TestConfiguration extends RecSysConfiguration {

  private static final String PACKAGE_NAME = "com.recolibry.example.MoviesRecommender";
	private static final String CLASS_NAME = "MovieDescription"; 

  ...
}

Next, we must define the attributes included in MoviesDescription. To do that, we use an array of String with 2 positions. The first position contains the attribute name. Next, we define the type of this attribute. When the attribute type is a list, the type to define is the list type. The general scheme to define attributes is as follow:

String[] attribute = new String[] {name_attribute, type_attribute}

The attribute types that we can select are Integer, Double, Float, String and Boolean . The list types must be also of these types. Then, we define all attributes for MovieDescription:

@Override
protected void generateClass() {

  // Define single type attributes with their names and types.
  String[] attr1 = new String[] {"id", "Integer"};
  String[] attr2 = new String[] {"title", "String"};
  
  // Define list type attributes with their names and types.
  String[] attr3 = new String[] {"genres", "String"};
  
  ...
}

When we define the attributes, we must save them in 2 different lists. The first list is for attributes with a simple data type. The second list contains all attributes with list type. In our case, we write these code lines:

@Override
protected void generateClass() {
  ...
    
  // Add single type attributes in a list
  List<String[]> attributes = new ArrayList<>();
  attributes.add(attr1);
  attributes.add(attr2);
  
  // Add list type attributes in a different list.
  List<String[]> attributesList = new ArrayList<>();
  attributesList.add(attr3);
  
  ...
}

Next, we create a ClassGenerator object. It will create the MovieDescription class when we build the recommender system. It uses the library Jiowa Code Generator to make a new class based on a template. For this reason, we make an object of JiowaCodeGenConfig with the configuration saved in RecoLibry-Core. Next, we create the ClassConfigurator using the Jiowa configuration, and we set the MovieDescription information: package, name, simple attributes, and list attributes. We write the following code in the generateClass() method:

@Override
protected void generateClass() {
  ...
  
  // Load Jiowa configuration included in RecoLibry-Core.
  JiowaCodeGenConfig config = new JiowaCodeGenConfig("codegen.properties");
  
  // Add all information to ClassGenerator object.
  ClassGenerator classGenerator = new ClassGenerator(config);
  classGenerator.setClassName(CLASS_NAME);
  classGenerator.setPackageName(PACKAGE_NAME);
  classGenerator.setAttributes(attributes);
  classGenerator.setAttributesList(attributesList);
  
  ...
}

Finally, we configure the Jiowa engine to build the new class based on the ClassGenerator object. In addition, we must define the path where we want to save the java file for our new class:

@Override
protected void generateClass() {
	...
    
  // Build the new class with Jiowa engine and the class configuration.
  JiowaCodeGeneratorEngine engine = new JiowaCodeGeneratorEngine(classGenerator);
  engine.start();

  // Save the java file in our project.
  file = System.getProperty("user.dir").replace("\\","/") + "/src/main/java/" + PACKAGE_NAME.replace(".", "/") + "/" + CLASS_NAME + ".java";
  file.replace("/", System.getProperty("path.separator"));
}

We implemented the method to make a class that saves the movies information. Next, we must implement how to build our recommender system.

Define components

In RecoLibry-Core, the recommender system configuration consists on to define the components that we will use. RecoLibry-Core uses the library Guice from Google to build the recommender system. If you need more information about how it works in RecoLibry-Core, you can visit the Configuration section.

To configure our recommender system, we will implement the configure function. First, we must add all instructions to build the MoviesDescription class, compile it, and load the class to include in our recommender system. Then, we write the following instructions:

protected void configure() {
  //Build MovieDescription class
  generateClass();
  
  //Compile the class
  compile();
  
  //Save the class object in a variable
  Class<?> clazz = Class.forName(PACKAGE_NAME + "." + CLASS_NAME);
  ...  
}

Next, we configure the recommender algorithm. In this example, we will use the RecommenderJColibri algorithm. This algorithm needs extra components: a connector and similarity functions. We start to define the connector.

The movie's description is in CSV file. For this reason, we are going to configure a CSVConnector. This connector needs 3 elements: a BeansFactory to convert the CSV data in MoviesDescriptions, the path of out file and a boolean data that set if the CSV file contains a title row. We bind these elements to our CSVConnector:

protected void configure() {
  ...
  
  //Configure BeansFactory to make MovieDescription objects
  BeansFactory factory = new BeansFactory(clazz);
  
  //Add BeanFactory to CSVConnector
  bind(BeansFactory.class)
    .annotatedWith(Names.named("beansFactory"))
    .toInstance(factory);
  
  //Define the file path
  String path = System.getProperty("user.dir") + "/data/movies.csv";
  
  bind(String.class)
    .annotatedWith(Names.named("fileName"))
    .toInstance(path);
  
  //Set that movies.csv contains title row
   bind(Boolean.class)
     .annotatedWith(Names.named("existTitleRow"))
     .toInstance(true);
   
  ...  
}

With these instructions, we have defined all component for CSVConnector. Then, we can bind our CSVConnector to RecommendeJColibri:

protected void configure() {
  ...

  //Bind CSVConnector with Connector of RecommenderJColibri
  bind(Connector.class)
    .to(CSVConnector.class);
  
  ...  
}

The next elements that our recommender system needs are similarity functions. In JColibry there are 2 types of similarity functions: global similarity functions and local similarity functions. Global similarity functions return the similarity between 2 objects. On the other hand, local similarity functions return the similarity between 2 attributes with the same name in 2 objects. In our example, we will have a local similarity function to compare the movies genres respect the genres in the query. This similarity function is not included in jColibri. For this reason, we need to create this in a class called GenresSimilarity. You can copy the next code in your new class:

public class GenresSimilarity implements LocalSimilarityFunction {

	@Override
	public double compute(Object caseObject, Object queryObject) throws NoApplicableSimilarityFunctionException {
		List<String> caseGenres = (List<String>) caseObject;
		List<String> queryGenres = (List<String>) queryObject;
		
		double equal = 0;
		double totalGenres = 0;
		
		for(int i=0; i<caseGenres.size(); i++)
			for(int j=0; j<queryGenres.size(); j++)
				if (caseGenres.get(i).equals(queryGenres.get(j)))
					equal++;
		
		totalGenres = caseGenres.size() + queryGenres.size() - equal;
		
		return equal / totalGenres;
	}

	public boolean isApplicable(Object caseObject, Object queryObject) {
		if (caseObject != null && queryObject != null && 
				caseObject instanceof List && queryObject instanceof List)
			return true;
		else
			return false;
	}

}

This class will be our local similarity function to compare genres. To add local similarities functions in our recommender system, it is necessary to create a list with all local similarities functions. Then, we can bind this list to our recommender algorithm:

protected void configure() {
  ...
    
  //Configure the list of local similarity functions
  List<LocalSimilarityConfiguration> configurations = new ArrayList<LocalSimilarityConfiguration>();
	LocalSimilarityConfiguration conf = new LocalSimilarityConfiguration("genres", clazz, new GenresSimilarity());
	configurations.add(conf);
  
  //Bind local similarity functions
  bind(new TypeLiteral<List<LocalSimilarityConfiguration>>() {})
		.toInstance(configurations);
  
  ...
}

In addition, we must define which global similarity function we will use. In this case, we will use the class Average from jColibri:

protected void configure() {
  ...
    
  //Bind global similarity function
  bind(GlobalSimilarityFunction.class)
		.to(Average.class);
    
  ...
}

The last element that we need to include in our recommender algorithm is how many results we want to recover. To do that, we bind the number of results using the N-Results property. Our example will return 10 results:

protected void configure() {
  ...
  
  //Bind the number of results to recover
  bind(Integer.class)
    .annotatedWith(Names.named("N-Results"))
    .toInstance(10);
    
  ...
}

At this moment, we have all the components for the recommender algorithm. The next step is to define which algorithm we will use in the recommender system. To do that, we set the RecommenderJColibry class as our recommender algorithm:

protected void configure() {
  ...
  
  //Bind the algorithm to use in RecoLibry
  bind(RecommenderAlgorithm.class)
    .to(RecommenderJColibri.class);
    
  ...
}

Finally, we need to define the Query for our recommender system. In our case, the query will be a QueryJColibri that contains a MovieDescription. First, we bind the MovieDescription in the Query and, next, we set the query type in our recommender system:

protected void configure() {
  ...
  
  //Bind the name of Java Bean Class (MovieDescription)
  bind(String.class)
    .annotatedWith(Names.named("BeanClass"))
    .toInstance(PACKAGE_NAME + "." + CLASS_NAME);

  //Bind the query class used in the recommender system
  bind(Query.class)
    .to(QueryJColibri.class);
}

We complete all the configuration of our recommender system. If you need to check your configuration, in the RecoLibry-Examples repository you have the complete configuration. Next, we can create a new instance of our recommender system.

Build recommender system

For the next steps, we will create a new class called Main. This class will contain a static method to get an instance of our recommender system and the main method to execute it:

public class Main {

	public static RecommenderSystem getRecommender() {
	
	}
	
	public static void main(String[] args) {
	
	}

}

In this section, we will complete the method to obtain an instance of the system. In this method, we will create an object of the TestConfiguration class. Next, we will create the recommender system using the RecommenderSystemFactory. To do that, we call the method makeRecommender() with our configuration. Finally, we return the system created:

public static RecommenderSystem getRecommenderSystem() {
  //Object with recommender system configuration
  RecSysConfiguration configuration = new TestConfiguration();
  
  //Make new instance of recommender system
  RecommenderSystemFactory factory = new RecommenderSystemFactory();
	factory.makeRecommender(configuration);
  
  // Return recommender system
  return factory.getRecommender();
}

Now, we can execute our recommender system.

Execute recommender system

In this section, we explain how to use the recommender system created before. In this example, we will ask our system which movies recommend to a user who likes the genres: Adventure, Fantasy, and Children.

First, we call the method to obtain the recommender system that we will execute. Next, we call the method getQuery() in our recommender system. It returns the query object that can use in our system:

public static void main(String[] args) {

  //Get recommender system
  RecommenderSystem recSys = getRecommenderSystem();
	
  //Get query object
  Query query = recSys.getQuery();
  
  ...
}

Next, we need to prepare our query before to execute in our recommender system. To do that, we call the query method initialize(). After that, we add the genres of the user in the attribute genre of MovieDescription:

public static void main(String[] args) {
	...
  
  //Initialize query
  query.initialize();
  
  //Add genres in our query
  String[] genresArray = new String[]{"Adventure", "Children", "Fantasy"};
  List<String> genres = new ArrayList<>(Arrays.asList(genresArray));
  query.setAttributeValue("genres", genres);
  
  ...
}

At this moment, our query contains all information and we can send it to the recommender system. Before executing the recommender system, we must call the function initRecommender(). Next, we execute the method recommend() with the query object completed before. Finally, if we don't need to execute the recommender system again, we can finish its execution calling the method closeRecommender(). Our example has the next instructions:

public static void main(String[] args) {
	...
    
  //Initialize recommender system
  recSys.initRecommender();
  
  //Get recommendations from query
  List<RecommenderResult> results = recSys.recommend(query);

  // Close recommender system
  recSys.closeRecommender();
  
  ...
}

Finally, we print the result of our recommender system:

public static void main(String[] args) {
  ...
  
  // Print the result of recommender system
  for(RecommenderResult r : results)
    System.out.println(r);
}
⚠️ **GitHub.com Fallback** ⚠️