{"id":1842,"date":"2010-04-15T11:08:49","date_gmt":"2010-04-15T09:08:49","guid":{"rendered":"http:\/\/blog.jteam.nl\/?p=1842"},"modified":"2010-04-15T11:08:49","modified_gmt":"2010-04-15T09:08:49","slug":"mahout-taste-part-two-getting-started","status":"publish","type":"post","link":"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/","title":{"rendered":"Mahout &#8211; Taste :: Part Two &#8211; Getting started"},"content":{"rendered":"<div>This blog is a &#8216;getting started&#8217; article and shows you how to build a simple web-based movie recommender with Mahout \/ Taste, Wicket and the Movielens dataset from Grouplens research group at the University of Minnesota. I will discuss which components you need, how to wire them up in Spring, and how to create a Wicket frontend for displaying movies and their recommendations. Along the way I give some tips and pointers about developing a recommender. Additionally I show the <span style=\"font-family: 'courier new';font-size: medium\">ResourceDataModel<\/span>, a Mahout <span style=\"font-family: 'courier new';font-size: medium\">DataModel<\/span> implementation which reads preferences from a Spring <span style=\"font-family: 'courier new';font-size: medium\">Resource<\/span>.<br \/>\n<!--more--><strong> <\/strong><\/p>\n<h3><strong>Online movie store<\/strong><\/h3>\n<p>Our running example for this post is an online DVD shop in which you can view and rent movies. Visitors go to a movie&#8217;s page to check out the plot description and will be shown a list of similar movies. These are other movies that were watched by people who also rented that specific movie. Because of space- and time-constraints, this application only provides a view on the dataset and the recommended movies. Other features expected of an online movie rental service, such as registration and payment are left out.<\/p>\n<h3><strong>Movielens Dataset<\/strong><\/h3>\n<p>The movies and their ratings originate from the <a href=\"http:\/\/www.grouplens.org\/node\/73\">Movielens dataset<\/a> of the <a href=\"http:\/\/www.grouplens.org\/\">Grouplens research group<\/a> from the University of Minnesota. There are datasets contains 100.000, 1 million and 10 million ratings. Note that these ratings are <em>explicit<\/em>, ranging from 1 to 5. This is different from the example in <a id=\"icdy\" title=\"my earlier blog\" href=\"..\/2009\/12\/09\/mahout-taste-part-one-introduction\/\">my earlier blog<\/a>, which featured <em>implicit<\/em> ratings. Implicit ratings only indicate if a user purchased or liked an item but not how much someone liked it. In this example I used the 100.000 ratings file.<\/p>\n<h3><strong>Item-based or user-based algorithms<\/strong><\/h3>\n<p>For this application we use an item-based recommender, which is a good choice performance-wise if the number of items is less than the number of users. This is likely to be the case for an online movie rental store. Item-based recommendation is different from user-based recommendation, which works by identifying a user neighbourhood of similar users and recommending items from the user neighbourhood to other users. User-based recommendations are <em>personalized<\/em> while with item-based recommendations each user will get <em>the same recommendations for a given item<\/em>. In our case, all users that visit a specific movie page will see the same recommended movies for that movie. An example of user-based recommendation is <a href=\"http:\/\/www.stumbleupon.com\">Stumbleupon<\/a>, which provides personalized website recommendations. Stumbleupon requires that you to login first so that it can perform its user-based recommendation based on your profile of likes and dislikes of certain sites.<\/p>\n<h3><strong>EuclidianDistanceSimilarity<\/strong><\/h3>\n<p>Now we need to select an item-based algorithm that fits our dataset.  The <span style=\"font-family: 'courier new';font-size: medium\">EuclidianDistanceSimilarity<\/span> is one of Taste&#8217;s algorithms that is suitable for explicit ratings and will be used for our demonstration. Before we construct our recommender we introduce the euclidian distance similarity in more detail. The algorithm computes the euclidian distance between each item&#8217;s preference vector. The shorter the distance between these vectors, the greater the similarity. For instance, suppose we have users u1, u2 and u3 and item i1. Let&#8217;s say these preferences for these users are 2, 4 and 5 respectively. We now have a preference vector [2,4,5] for item i1. The euclidian distance between two of such vectors can now be computed and used as a measure of their similarity. The formula for computing the euclidian distance between two vectors i and j equals the root of the sum of squared differences between coordinates of a pair of vectors. See the formula below:<br \/>\n$$!d_{ij}=\\sqrt{\\sum\\limits_{k=1}^n \\left(x_{ik} &#8211; x_{jk}\\right)^2}$$<\/p>\n<p>The <span style=\"font-family: 'courier new';font-size: medium\">EuclidianDistanceSimilarity<\/span> calculates this similarity for each pair of items and then returns<\/p>\n<p>$$!\\frac{1}{1 + d_{ij}}$$<\/p>\n<p>which results in a value between 0 and 1. The <span style=\"font-family: 'courier new';font-size: medium\">EuclidianDistanceSimilarity<\/span> can also be weighted. If you pass in the <span style=\"font-family: 'courier new';font-size: medium\">Weighting.WEIGHTED<\/span> enum to the constructor of <span style=\"font-family: 'courier new';font-size: medium\">EuclidianDistanceSimilarity<\/span> then the algorithm will weight the values based on the number of users and the number of co-occurring preferences.<\/p>\n<h3><strong>Creating a web-based recommender<\/strong><\/h3>\n<p>Below is a list of components we need to create a small web-based recommendation engine, using Taste, Wicket and JPA\/Hibernate. I won&#8217;t cover all the details of building this webapp, just the main building blocks. You can download the code for this example <a href=\"http:\/\/blog.jteam.nl\/wp-content\/uploads\/2010\/04\/taste-getting-started.zip\">here<\/a> and look at the specific details. We need the following components:<\/p>\n<ul>\n<li><span style=\"font-family: 'courier new';font-size: medium\">Datamodel<\/span><br \/>\nA <span style=\"font-family: 'courier new';font-size: medium\">FileDataModel<\/span> that reads movie ids, user ids and ratings from the Movielens dataset directly into memory.<\/li>\n<li><span style=\"font-family: 'courier new';font-size: medium\">EuclidianDistanceSimilarity<\/span><br \/>\nComputes item similarities for each pair of items in the datset.<\/li>\n<li><span style=\"font-family: 'courier new';font-size: medium\">GenericItemBasedRecommender<\/span><br \/>\nUses both the datamodel and the similarity algorithm to compute similar items in memory.<\/li>\n<li><span style=\"font-family: 'courier new';font-size: medium\">MovieRepository<\/span><br \/>\nJPA repository for retrieving <span style=\"font-family: 'courier new';font-size: medium\">Movie<\/span> objects<\/li>\n<li><span style=\"font-family: 'courier new';font-size: medium\">MovieService<\/span><br \/>\nUses the recommender and the <span style=\"font-family: 'courier new';font-size: medium\">MovieRepository<\/span> to retrieve most similar movies for a given movie id.<\/li>\n<li>Wicket <span style=\"font-family: 'courier new';font-size: medium\">MoviePage<\/span>, HTML + CSS<br \/>\nThis includes a page for viewing a movie along with similar movies, a few model classes, some HTML and CSS, and a few code tweaks to the original <a id=\"hsdj\" title=\"wicket quickstart\" href=\"http:\/\/wicket.apache.org\/quickstart.html\">wicket quickstart<\/a> project.<\/li>\n<\/ul>\n<p>Note that Taste ships with a preconfigured <span style=\"font-family: 'courier new';font-size: medium\">MovielensRecommender<\/span>. For the purpose of this article however, I wanted to show you how to build a recommender from the ground up.<\/p>\n<h3><strong>Configuring the ResourceDataModel, EuclidianDistanceSimilarity and GenericItemBasedRecommender<\/strong><\/h3>\n<p>Because of license restrictions the movielens data cannot be shipped with this demo, so you need to download it <a href=\"http:\/\/www.grouplens.org\/node\/73\">here<\/a>. Place the <span style=\"font-family: 'courier new';font-size: medium\">u.data<\/span> file under <span style=\"font-family: 'courier new';font-size: medium\">src\/main\/resources\/grouplens\/100K\/ratings\/<\/span> and place the <span style=\"font-family: 'courier new';font-size: medium\">u.item<\/span> file under <span style=\"font-family: 'courier new';font-size: medium\">src\/main\/resources\/grouplens\/100K\/data\/<\/span>. Now that you have the ratings data setup you need to feed it into a <span style=\"font-family: 'courier new';font-size: medium\">DataModel<\/span> class. You can use a <span style=\"font-family: 'courier new';font-size: medium\">FileDataModel<\/span> for this but for this you need to use an absolute path. Instead I implement a <span style=\"font-family: 'courier new';font-size: medium\">ResourceDataModel<\/span> which reads ratings files from the classpath. Below is the implementation of the <span style=\"font-family: 'courier new';font-size: medium\">ResourceDataModel<\/span>, which is a wrapper around a <span style=\"font-family: 'courier new';font-size: medium\">FileDataModel<\/span>. More on how to wire this below.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\npackage nl.jteam.mahout.gettingstarted.datamodel;\n\n\/\/ Imports omitted.\n\n\/**\n * DataModel implementation which reads a Spring {@link org.springframework.core.io.Resource} into a\n * {@link org.apache.mahout.cf.taste.impl.model.file.FileDataModel} delegate.\n *\n * @author Frank Scholten\n *\/\npublic class ResourceDataModel implements DataModel {\n    FileDataModel delegate;\n\n    ResourceDataModel() { \/\/ For testing\n    }\n\n    \/**\n     * Reads the preferences from the given {@link org.springframework.core.io.Resource}\n     *\n     * @param resource with user IDs, items IDs and preferences\n     *\/\n    public ResourceDataModel(Resource resource) {\n        try {\n            this.delegate = new FileDataModel(resource.getFile());\n        } catch (IOException e) {\n            throw new RuntimeException(&quot;Could not read resource &quot; + resource.getDescription(), e);\n        }\n    }\n\n    @Override\n    public LongPrimitiveIterator getUserIDs() throws TasteException {\n        return delegate.getUserIDs();\n    }\n\n    @Override\n    public PreferenceArray getPreferencesFromUser(long userID) throws TasteException {\n        return delegate.getPreferencesFromUser(userID);\n    }\n\n    @Override\n    public FastIDSet getItemIDsFromUser(long userID) throws TasteException {\n        return delegate.getItemIDsFromUser(userID);\n    }\n\n    @Override\n    public LongPrimitiveIterator getItemIDs() throws TasteException {\n        return delegate.getItemIDs();\n    }\n\n    @Override\n    public PreferenceArray getPreferencesForItem(long itemID) throws TasteException {\n        return delegate.getPreferencesForItem(itemID);\n    }\n\n    @Override\n    public Float getPreferenceValue(long userID, long itemID) throws TasteException {\n        return delegate.getPreferenceValue(userID, itemID);\n    }\n\n    @Override\n    public int getNumItems() throws TasteException {\n        return delegate.getNumItems();\n    }\n\n    @Override\n    public int getNumUsers() throws TasteException {\n        return delegate.getNumUsers();\n    }\n\n    @Override\n    public int getNumUsersWithPreferenceFor(long... itemIDs) throws TasteException {\n        return delegate.getNumUsersWithPreferenceFor(itemIDs);\n    }\n\n    @Override\n    public void setPreference(long userID, long itemID, float value) throws TasteException {\n        delegate.setPreference(userID, itemID, value);\n    }\n\n    @Override\n    public void removePreference(long userID, long itemID) throws TasteException {\n        delegate.removePreference(userID, itemID);\n    }\n\n    @Override\n    public boolean hasPreferenceValues() {\n        return delegate.hasPreferenceValues();\n    }\n\n    @Override\n    public void refresh(Collection&lt;Refreshable&gt; alreadyRefreshed) {\n        delegate.refresh(alreadyRefreshed);\n    }\n}\n<\/pre>\n<p>Next you need to initialize the movie database. Run the <span style=\"font-family: 'courier new';font-size: medium\">initialize_movielens_db.sql<\/span> from the <span style=\"font-family: 'courier new';font-size: medium\">src\/main\/resources\/sql<\/span> folder. This will create the <span style=\"font-family: 'courier new';font-size: medium\">movielens<\/span> database and a user with username and password <span style=\"font-family: 'courier new';font-size: medium\">movielens<\/span>. Additionally, it creates the movie database and loads it with the movie titles from the <span style=\"font-family: 'courier new';font-size: medium\">u.item<\/span> file.<\/p>\n<p>Now we wire up the <span style=\"font-family: 'courier new';font-size: medium\">GenericItemBasedRecommender<\/span> and its dependencies, the <span style=\"font-family: 'courier new';font-size: medium\">EuclidianDistanceSimilarity<\/span> and the <span style=\"font-family: 'courier new';font-size: medium\">ResourceDataModel<\/span> in the following spring context:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;\n&lt;beans xmlns=&quot;http:\/\/www.springframework.org\/schema\/beans&quot;\n       xmlns:xsi=&quot;http:\/\/www.w3.org\/2001\/XMLSchema-instance&quot;\n       xsi:schemaLocation=&quot;http:\/\/www.springframework.org\/schema\/beans http:\/\/www.springframework.org\/schema\/beans\/spring-beans.xsd&quot;&gt;\n\n    &lt;!-- Recommender --&gt;\n\n    &lt;bean id=&quot;euclidianDistanceRecommender&quot; class=&quot;org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender&quot;&gt;\n        &lt;constructor-arg index=&quot;0&quot; ref=&quot;movielensDataModel100K&quot;\/&gt;\n        &lt;constructor-arg index=&quot;1&quot; ref=&quot;euclidianDistanceSimilarity&quot;\/&gt;\n    &lt;\/bean&gt;\n\n    &lt;!-- DataModel --&gt;\n\n    &lt;bean id=&quot;movielensDataModel100K&quot; class=&quot;nl.jteam.mahout.gettingstarted.datamodel.ResourceDataModel&quot;&gt;\n        &lt;constructor-arg value=&quot;classpath:\/grouplens\/100K\/ratings\/u.data&quot;\/&gt;\n    &lt;\/bean&gt;\n\n    &lt;!-- Similarity --&gt;\n\n    &lt;bean id=&quot;euclidianDistanceSimilarity&quot; class=&quot;org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity&quot;&gt;\n        &lt;constructor-arg ref=&quot;movielensDataModel100K&quot;\/&gt;\n        &lt;constructor-arg value=&quot;WEIGHTED&quot;\/&gt;\n    &lt;\/bean&gt;\n\n&lt;\/beans&gt;\n<\/pre>\n<h3><strong>MovieRepository, MovieService &amp; MoviePage<\/strong><\/h3>\n<p>Our recommender is now ready to determine similar movies for a given movie ID. However, the <span style=\"font-family: 'courier new';font-size: medium\">GenericItemBasedRecommender<\/span> interface only returns movie IDs of the type <span style=\"font-family: 'courier new';font-size: medium\">long<\/span>. In order to display the actual movie information to users we need to create a <span style=\"font-family: 'courier new';font-size: medium\">MovieRepository<\/span> which fetches recommended <span style=\"font-family: 'courier new';font-size: medium\">Movie<\/span> objects. Additionally, we need a <span style=\"font-family: 'courier new';font-size: medium\">MovieService<\/span> which coordinates the <span style=\"font-family: 'courier new';font-size: medium\">MovieRepository<\/span> and the <span style=\"font-family: 'courier new';font-size: medium\">GenericItemBasedRecommender<\/span> so that we can retrieve recommended <span style=\"font-family: 'courier new';font-size: medium\">Movie<\/span> objects for a given movie ID. Below is a snippet of a JPA implementation of a <span style=\"font-family: 'courier new';font-size: medium\">MovieRepository<\/span>.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\npackage nl.jteam.mahout.gettingstarted.repository;\n\n\/\/ Imports omitted.\n\n\/**\n * Repository for retrieving {@link Movie}s\n *\n * @author Frank Scholten\n *\/\n@Repository\npublic class JpaMovieRepository implements MovieRepository {\n\n    @PersistenceContext\n    private EntityManager entityManager;\n\n    \/** {@inheritDoc} *\/\n    @Override\n    public Movie getMovieById(long id) {\n        return entityManager.find(Movie.class, id);\n    }\n\n    \/** {@inheritDoc} *\/\n    @Override\n    @SuppressWarnings(&quot;unchecked&quot;)\n    public List&lt;Movie&gt; getMoviesById(List&lt;Long&gt; movieIds) {\n        return (List&lt;Movie&gt;) entityManager.createQuery(&quot;SELECT m FROM movie m WHERE m.id IN (:movieIds)&quot;)\n                .setParameter(&quot;movieIds&quot;, movieIds)\n                .getResultList();\n    }\n}\n<\/pre>\n<p>Below is a code snippet of a default implementation of the <span style=\"font-family: 'courier new';font-size: medium\">MovieService<\/span>.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\npackage nl.jteam.mahout.gettingstarted.service;\n\n\/\/ Imports omitted.\n\n\/**\n * Service for retrieving and recommending {@link Movie}s.\n *\n * @author Frank Scholten\n *\/\n@Transactional\n@Service\npublic class DefaultMovieService implements MovieService {\n\n    @Autowired\n    private MovieRepository movieRepository;\n\n    @Autowired\n    private ItemBasedRecommender movieRecommender;\n\n    public Movie getMovieById(long id) {\n        return movieRepository.getMovieById(id);\n    }\n\n    @SuppressWarnings(&quot;unchecked&quot;)\n    public List&lt;Movie&gt; moreLikeThis(long movieId) {\n        try {\n            List&lt;RecommendedItem&gt; recommendedItems = movieRecommender.mostSimilarItems(movieId, 5);\n\n            List&lt;Long&gt; ids = new ArrayList();\n            for (RecommendedItem r : recommendedItems) {\n                ids.add(r.getItemID());\n            }\n\n            return movieRepository.getMoviesById(ids);\n\n        } catch (TasteException e) {\n            return (List&lt;Movie&gt;) Collections.EMPTY_LIST;\n        }\n    }\n}\n<\/pre>\n<p>Finally, below is the snippet of the Wicket <span style=\"font-family: 'courier new';font-size: medium\">MoviePage<\/span> which displays the current movie and similar movies fetched through specially created Wickets models.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\npackage nl.jteam.mahout.gettingstarted.web.page;\n\n\/\/ Imports omitted.\n\n\/**\n * Page for showing a single {@link Movie} from the Movielens dataset along\n * with recommended movies i.e. 'more like this'.\n *\n * @author Frank Scholten\n *\/\npublic class MoviePage extends WebPage {\n\n    private static final String MOVIE_ID = &quot;0&quot;;\n\n    public MoviePage(PageParameters pageParameters) {\n        final long movieId = pageParameters.getLong(MOVIE_ID, 1);\n\n        MovieModel model = new MovieModel(movieId);\n        add(new Label(&quot;title&quot;, model.getObject().getTitle()));\n\n        PropertyListView&lt;Movie&gt; recommendedMovies = new PropertyListView&lt;Movie&gt;(&quot;moreLikeThis&quot;, new RecommendedMoviesModel(movieId)) {\n            @Override\n            protected void populateItem(ListItem listItem) {\n                Movie movie = (Movie) listItem.getModelObject();\n                PageParameters pageParameters = new PageParameters();\n                pageParameters.put(MOVIE_ID, movie.getId());\n\n                BookmarkablePageLink&lt;MoviePage&gt; movieLink = new BookmarkablePageLink&lt;MoviePage&gt;(&quot;link&quot;, MoviePage.class, pageParameters);\n                listItem.add(movieLink);\n                Label movieTitle = new Label(&quot;title&quot;);\n                movieTitle.setRenderBodyOnly(true);\n                movieLink.add(movieTitle);\n            }\n        };\n        add(recommendedMovies);\n    }\n}\n<\/pre>\n<h3><strong>Running the web application<\/strong><\/h3>\n<p>First you need to download the Movielens dataset and add the ratings file on the classpath under <span style=\"font-family: 'courier new';font-size: medium\">grouplens\/100K\/ratings<\/span>. See the spring context above. Since this example is based on the Wicket quickstart project you can start the application via Jetty through the <span style=\"font-family: 'courier new';font-size: medium\">Start<\/span> class and run it in your favourite IDE. Go to <span style=\"font-family: 'courier new';font-size: medium\">http:\/\/localhost:9090\/<\/span> and you can browse through movies via recommendations. Alternatively you can build the WAR and drop it into tomcat.<\/p>\n<h3><strong>Performance tweaks<\/strong><\/h3>\n<ul>\n<li>If you like to experiment with the larger datasets you need to add <span style=\"font-family: 'courier new';font-size: medium\">-Xmx512m<\/span> as a VM parameter if you want to run this application with the 10 million ratings dataset. Also, Tastes <span style=\"font-family: 'courier new';font-size: medium\">FileDataModel<\/span> uses commas and tabs as delimiter. You may need to run the Movielens files through <span style=\"font-family: 'courier new';font-size: medium\">sed&lt;\/<span style=\"font-family: 'courier new';font-size: medium\"> before feeding them into a <span style=\"font-family: 'courier new';font-size: medium\">FileDataModel<\/span><\/li>\n<li>The <span style=\"font-family: 'courier new';font-size: medium\">FileDataModel<\/span> reads everything in memory before computation. This is way faster than using a <span style=\"font-family: 'courier new';font-size: medium\">MySQLJDBCDataModel<\/span>, since this requires around O(n<sup>2<\/sup>) database queries to compute similarities between all pairs. Reading all data in memory is not always feasible however. An alternative is to precompute the similarities for the item pairs and store the results in the database and read them via the <span style=\"font-family: 'courier new';font-size: medium\">MySQLJDBCItemSimilarity<\/span>.<\/li>\n<li>You can also sample the dataset and\/or remove noise elements to speed things up a little more<\/li>\n<\/ul>\n<p>These performance aspects are an interesting topic for a later blogpost. If any of you reading this has experience with these type of issues please post a comment and we&#8217;ll discuss them.<\/p>\n<h3><strong>Conclusions<\/strong><\/h3>\n<p>This concludes the getting started post on Mahout \/ Taste. What we didn&#8217;t cover was how to update the recommender and how to customize your recommender with boosting of items. These are all subjects for future blog posts.<\/p>\n<h3><strong>References<\/strong><\/h3>\n<ul>\n<li>Getting started demo &#8211; source code<\/li>\n<p>You can download the source code of this example <a href=\"http:\/\/blog.jteam.nl\/wp-content\/uploads\/2010\/04\/taste-getting-started.zip\">here<\/a>.<\/p>\n<li>Grouplens datasets<\/li>\n<p>The <a href=\"http:\/\/www.grouplens.org\/\">Grouplens<\/a> research group of the University of Minnesota have made a few <a href=\"http:\/\/www.grouplens.org\/node\/73\">datasets<\/a> publicly available for research purposes.<\/p>\n<li><a href=\"http:\/\/www.manning.com\/owen\/\">Mahout in Action EAP<\/a><\/li>\n<p>This is a great resource on Mahout and explains a lot about performance issues and details how the algorithms stack up against eachother. Also it provides a lot of examples and case studies on how to use Mahout in practice.<\/ul>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>This blog is a &#8216;getting started&#8217; article and shows you how to build a simple web-based movie recommender with Mahout \/ Taste, Wicket and the Movielens dataset from Grouplens research group at the University of Minnesota. I will discuss which components you need, how to wire them up in Spring, and how to create a [&hellip;]<\/p>\n","protected":false},"author":6,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[40,15,10],"tags":[156,157,11,158,159],"class_list":["post-1842","post","type-post","status-publish","format-standard","hentry","category-mahout","category-enterprise-search","category-development","tag-mahout","tag-collaborative_filtering","tag-java","tag-recommendations","tag-taste"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Mahout - Taste :: Part Two - Getting started - Trifork Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Mahout - Taste :: Part Two - Getting started - Trifork Blog\" \/>\n<meta property=\"og:description\" content=\"This blog is a &#8216;getting started&#8217; article and shows you how to build a simple web-based movie recommender with Mahout \/ Taste, Wicket and the Movielens dataset from Grouplens research group at the University of Minnesota. I will discuss which components you need, how to wire them up in Spring, and how to create a [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/\" \/>\n<meta property=\"og:site_name\" content=\"Trifork Blog\" \/>\n<meta property=\"article:published_time\" content=\"2010-04-15T09:08:49+00:00\" \/>\n<meta name=\"author\" content=\"frank\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"frank\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/\",\"url\":\"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/\",\"name\":\"Mahout - Taste :: Part Two - Getting started - Trifork Blog\",\"isPartOf\":{\"@id\":\"https:\/\/trifork.nl\/blog\/#website\"},\"datePublished\":\"2010-04-15T09:08:49+00:00\",\"author\":{\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/00fad6c5829f6770345f23ccace2e54f\"},\"breadcrumb\":{\"@id\":\"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/trifork.nl\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Mahout &#8211; Taste :: Part Two &#8211; Getting started\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/trifork.nl\/blog\/#website\",\"url\":\"https:\/\/trifork.nl\/blog\/\",\"name\":\"Trifork Blog\",\"description\":\"Keep updated on the technical solutions Trifork is working on!\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/trifork.nl\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/00fad6c5829f6770345f23ccace2e54f\",\"name\":\"frank\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/5c39a948f2b70fa900b25dc79cde8643?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/5c39a948f2b70fa900b25dc79cde8643?s=96&d=mm&r=g\",\"caption\":\"frank\"},\"url\":\"https:\/\/trifork.nl\/blog\/author\/frank\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Mahout - Taste :: Part Two - Getting started - Trifork Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/","og_locale":"en_US","og_type":"article","og_title":"Mahout - Taste :: Part Two - Getting started - Trifork Blog","og_description":"This blog is a &#8216;getting started&#8217; article and shows you how to build a simple web-based movie recommender with Mahout \/ Taste, Wicket and the Movielens dataset from Grouplens research group at the University of Minnesota. I will discuss which components you need, how to wire them up in Spring, and how to create a [&hellip;]","og_url":"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/","og_site_name":"Trifork Blog","article_published_time":"2010-04-15T09:08:49+00:00","author":"frank","twitter_card":"summary_large_image","twitter_misc":{"Written by":"frank","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/","url":"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/","name":"Mahout - Taste :: Part Two - Getting started - Trifork Blog","isPartOf":{"@id":"https:\/\/trifork.nl\/blog\/#website"},"datePublished":"2010-04-15T09:08:49+00:00","author":{"@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/00fad6c5829f6770345f23ccace2e54f"},"breadcrumb":{"@id":"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/trifork.nl\/blog\/mahout-taste-part-two-getting-started\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/trifork.nl\/blog\/"},{"@type":"ListItem","position":2,"name":"Mahout &#8211; Taste :: Part Two &#8211; Getting started"}]},{"@type":"WebSite","@id":"https:\/\/trifork.nl\/blog\/#website","url":"https:\/\/trifork.nl\/blog\/","name":"Trifork Blog","description":"Keep updated on the technical solutions Trifork is working on!","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/trifork.nl\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/00fad6c5829f6770345f23ccace2e54f","name":"frank","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/5c39a948f2b70fa900b25dc79cde8643?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5c39a948f2b70fa900b25dc79cde8643?s=96&d=mm&r=g","caption":"frank"},"url":"https:\/\/trifork.nl\/blog\/author\/frank\/"}]}},"_links":{"self":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts\/1842","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/comments?post=1842"}],"version-history":[{"count":0,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts\/1842\/revisions"}],"wp:attachment":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/media?parent=1842"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/categories?post=1842"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/tags?post=1842"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}