You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mahout.apache.org by pa...@apache.org on 2015/04/01 20:07:41 UTC
[10/51] [partial] mahout git commit: MAHOUT-1655 Refactors mr-legacy
into mahout-hdfs and mahout-mr, closes apache/mahout#86
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java
new file mode 100644
index 0000000..16cbdca
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java
@@ -0,0 +1,324 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import com.google.common.collect.Lists;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastIDSet;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
+import org.apache.mahout.cf.taste.impl.similarity.GenericItemSimilarity;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.cf.taste.recommender.CandidateItemsStrategy;
+import org.apache.mahout.cf.taste.recommender.ItemBasedRecommender;
+import org.apache.mahout.cf.taste.recommender.MostSimilarItemsCandidateItemsStrategy;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.apache.mahout.cf.taste.similarity.ItemSimilarity;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/** <p>Tests {@link GenericItemBasedRecommender}.</p> */
+public final class GenericItemBasedRecommenderTest extends TasteTestCase {
+
+ @Test
+ public void testRecommender() throws Exception {
+ Recommender recommender = buildRecommender();
+ List<RecommendedItem> recommended = recommender.recommend(1, 1);
+ assertNotNull(recommended);
+ assertEquals(1, recommended.size());
+ RecommendedItem firstRecommended = recommended.get(0);
+ assertEquals(2, firstRecommended.getItemID());
+ assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+ recommender.refresh(null);
+ recommended = recommender.recommend(1, 1);
+ firstRecommended = recommended.get(0);
+ assertEquals(2, firstRecommended.getItemID());
+ assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+ }
+
+ @Test
+ public void testHowMany() throws Exception {
+
+ DataModel dataModel = getDataModel(
+ new long[] {1, 2, 3, 4, 5},
+ new Double[][] {
+ {0.1, 0.2},
+ {0.2, 0.3, 0.3, 0.6},
+ {0.4, 0.4, 0.5, 0.9},
+ {0.1, 0.4, 0.5, 0.8, 0.9, 1.0},
+ {0.2, 0.3, 0.6, 0.7, 0.1, 0.2},
+ });
+
+ Collection<GenericItemSimilarity.ItemItemSimilarity> similarities = Lists.newArrayList();
+ for (int i = 0; i < 6; i++) {
+ for (int j = i + 1; j < 6; j++) {
+ similarities.add(
+ new GenericItemSimilarity.ItemItemSimilarity(i, j, 1.0 / (1.0 + i + j)));
+ }
+ }
+ ItemSimilarity similarity = new GenericItemSimilarity(similarities);
+ Recommender recommender = new GenericItemBasedRecommender(dataModel, similarity);
+ List<RecommendedItem> fewRecommended = recommender.recommend(1, 2);
+ List<RecommendedItem> moreRecommended = recommender.recommend(1, 4);
+ for (int i = 0; i < fewRecommended.size(); i++) {
+ assertEquals(fewRecommended.get(i).getItemID(), moreRecommended.get(i).getItemID());
+ }
+ recommender.refresh(null);
+ for (int i = 0; i < fewRecommended.size(); i++) {
+ assertEquals(fewRecommended.get(i).getItemID(), moreRecommended.get(i).getItemID());
+ }
+ }
+
+ @Test
+ public void testRescorer() throws Exception {
+
+ DataModel dataModel = getDataModel(
+ new long[] {1, 2, 3},
+ new Double[][] {
+ {0.1, 0.2},
+ {0.2, 0.3, 0.3, 0.6},
+ {0.4, 0.4, 0.5, 0.9},
+ });
+
+ Collection<GenericItemSimilarity.ItemItemSimilarity> similarities = Lists.newArrayList();
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 1, 1.0));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 2, 0.5));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 3, 0.2));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 2, 0.7));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 3, 0.5));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(2, 3, 0.9));
+ ItemSimilarity similarity = new GenericItemSimilarity(similarities);
+ Recommender recommender = new GenericItemBasedRecommender(dataModel, similarity);
+ List<RecommendedItem> originalRecommended = recommender.recommend(1, 2);
+ List<RecommendedItem> rescoredRecommended =
+ recommender.recommend(1, 2, new ReversingRescorer<Long>());
+ assertNotNull(originalRecommended);
+ assertNotNull(rescoredRecommended);
+ assertEquals(2, originalRecommended.size());
+ assertEquals(2, rescoredRecommended.size());
+ assertEquals(originalRecommended.get(0).getItemID(), rescoredRecommended.get(1).getItemID());
+ assertEquals(originalRecommended.get(1).getItemID(), rescoredRecommended.get(0).getItemID());
+ }
+
+ @Test
+ public void testIncludeKnownItems() throws Exception {
+
+ DataModel dataModel = getDataModel(
+ new long[] {1, 2, 3},
+ new Double[][] {
+ {0.1, 0.2},
+ {0.2, 0.3, 0.3, 0.6},
+ {0.4, 0.4, 0.5, 0.9},
+ });
+
+ Collection<GenericItemSimilarity.ItemItemSimilarity> similarities = Lists.newArrayList();
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 1, 0.8));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 2, 0.5));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 3, 0.2));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 2, 0.7));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 3, 0.5));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(2, 3, 0.9));
+ ItemSimilarity similarity = new GenericItemSimilarity(similarities);
+ Recommender recommender = new GenericItemBasedRecommender(dataModel, similarity);
+ List<RecommendedItem> originalRecommended = recommender.recommend(1, 4, null, true);
+ List<RecommendedItem> rescoredRecommended = recommender.recommend(1, 4, new ReversingRescorer<Long>(), true);
+ assertNotNull(originalRecommended);
+ assertNotNull(rescoredRecommended);
+ assertEquals(4, originalRecommended.size());
+ assertEquals(4, rescoredRecommended.size());
+ assertEquals(originalRecommended.get(0).getItemID(), rescoredRecommended.get(3).getItemID());
+ assertEquals(originalRecommended.get(3).getItemID(), rescoredRecommended.get(0).getItemID());
+ }
+
+ @Test
+ public void testEstimatePref() throws Exception {
+ Recommender recommender = buildRecommender();
+ assertEquals(0.1f, recommender.estimatePreference(1, 2), EPSILON);
+ }
+
+ /**
+ * Contributed test case that verifies fix for bug
+ * <a href="http://sourceforge.net/tracker/index.php?func=detail&aid=1396128&group_id=138771&atid=741665">
+ * 1396128</a>.
+ */
+ @Test
+ public void testBestRating() throws Exception {
+ Recommender recommender = buildRecommender();
+ List<RecommendedItem> recommended = recommender.recommend(1, 1);
+ assertNotNull(recommended);
+ assertEquals(1, recommended.size());
+ RecommendedItem firstRecommended = recommended.get(0);
+ // item one should be recommended because it has a greater rating/score
+ assertEquals(2, firstRecommended.getItemID());
+ assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+ }
+
+ @Test
+ public void testMostSimilar() throws Exception {
+ ItemBasedRecommender recommender = buildRecommender();
+ List<RecommendedItem> similar = recommender.mostSimilarItems(0, 2);
+ assertNotNull(similar);
+ assertEquals(2, similar.size());
+ RecommendedItem first = similar.get(0);
+ RecommendedItem second = similar.get(1);
+ assertEquals(1, first.getItemID());
+ assertEquals(1.0f, first.getValue(), EPSILON);
+ assertEquals(2, second.getItemID());
+ assertEquals(0.5f, second.getValue(), EPSILON);
+ }
+
+ @Test
+ public void testMostSimilarToMultiple() throws Exception {
+ ItemBasedRecommender recommender = buildRecommender2();
+ List<RecommendedItem> similar = recommender.mostSimilarItems(new long[] {0, 1}, 2);
+ assertNotNull(similar);
+ assertEquals(2, similar.size());
+ RecommendedItem first = similar.get(0);
+ RecommendedItem second = similar.get(1);
+ assertEquals(2, first.getItemID());
+ assertEquals(0.85f, first.getValue(), EPSILON);
+ assertEquals(3, second.getItemID());
+ assertEquals(-0.3f, second.getValue(), EPSILON);
+ }
+
+ @Test
+ public void testMostSimilarToMultipleExcludeIfNotSimilarToAll() throws Exception {
+ ItemBasedRecommender recommender = buildRecommender2();
+ List<RecommendedItem> similar = recommender.mostSimilarItems(new long[] {3, 4}, 2);
+ assertNotNull(similar);
+ assertEquals(1, similar.size());
+ RecommendedItem first = similar.get(0);
+ assertEquals(0, first.getItemID());
+ assertEquals(0.2f, first.getValue(), EPSILON);
+ }
+
+ @Test
+ public void testMostSimilarToMultipleDontExcludeIfNotSimilarToAll() throws Exception {
+ ItemBasedRecommender recommender = buildRecommender2();
+ List<RecommendedItem> similar = recommender.mostSimilarItems(new long[] {1, 2, 4}, 10, false);
+ assertNotNull(similar);
+ assertEquals(2, similar.size());
+ RecommendedItem first = similar.get(0);
+ RecommendedItem second = similar.get(1);
+ assertEquals(0, first.getItemID());
+ assertEquals(0.933333333f, first.getValue(), EPSILON);
+ assertEquals(3, second.getItemID());
+ assertEquals(-0.2f, second.getValue(), EPSILON);
+ }
+
+
+ @Test
+ public void testRecommendedBecause() throws Exception {
+ ItemBasedRecommender recommender = buildRecommender2();
+ List<RecommendedItem> recommendedBecause = recommender.recommendedBecause(1, 4, 3);
+ assertNotNull(recommendedBecause);
+ assertEquals(3, recommendedBecause.size());
+ RecommendedItem first = recommendedBecause.get(0);
+ RecommendedItem second = recommendedBecause.get(1);
+ RecommendedItem third = recommendedBecause.get(2);
+ assertEquals(2, first.getItemID());
+ assertEquals(0.99f, first.getValue(), EPSILON);
+ assertEquals(3, second.getItemID());
+ assertEquals(0.4f, second.getValue(), EPSILON);
+ assertEquals(0, third.getItemID());
+ assertEquals(0.2f, third.getValue(), EPSILON);
+ }
+
+ private static ItemBasedRecommender buildRecommender() {
+ DataModel dataModel = getDataModel();
+ Collection<GenericItemSimilarity.ItemItemSimilarity> similarities = Lists.newArrayList();
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 1, 1.0));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 2, 0.5));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 2, 0.0));
+ ItemSimilarity similarity = new GenericItemSimilarity(similarities);
+ return new GenericItemBasedRecommender(dataModel, similarity);
+ }
+
+ private static ItemBasedRecommender buildRecommender2() {
+
+ DataModel dataModel = getDataModel(
+ new long[] {1, 2, 3, 4},
+ new Double[][] {
+ {0.1, 0.3, 0.9, 0.8},
+ {0.2, 0.3, 0.3, 0.4},
+ {0.4, 0.3, 0.5, 0.1, 0.1},
+ {0.7, 0.3, 0.8, 0.5, 0.6},
+ });
+
+ Collection<GenericItemSimilarity.ItemItemSimilarity> similarities = Lists.newArrayList();
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 1, 1.0));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 2, 0.8));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 3, -0.6));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 4, 1.0));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 2, 0.9));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 3, 0.0));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 1, 1.0));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(2, 3, -0.1));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(2, 4, 0.1));
+ similarities.add(new GenericItemSimilarity.ItemItemSimilarity(3, 4, -0.5));
+ ItemSimilarity similarity = new GenericItemSimilarity(similarities);
+ return new GenericItemBasedRecommender(dataModel, similarity);
+ }
+
+
+ /**
+ * we're making sure that a user's preferences are fetched only once from the {@link DataModel} for one call to
+ * {@link GenericItemBasedRecommender#recommend(long, int)}
+ *
+ * @throws Exception
+ */
+ @Test
+ public void preferencesFetchedOnlyOnce() throws Exception {
+
+ DataModel dataModel = EasyMock.createMock(DataModel.class);
+ ItemSimilarity itemSimilarity = EasyMock.createMock(ItemSimilarity.class);
+ CandidateItemsStrategy candidateItemsStrategy = EasyMock.createMock(CandidateItemsStrategy.class);
+ MostSimilarItemsCandidateItemsStrategy mostSimilarItemsCandidateItemsStrategy =
+ EasyMock.createMock(MostSimilarItemsCandidateItemsStrategy.class);
+
+ PreferenceArray preferencesFromUser = new GenericUserPreferenceArray(
+ Arrays.asList(new GenericPreference(1L, 1L, 5.0f), new GenericPreference(1L, 2L, 4.0f)));
+
+ EasyMock.expect(dataModel.getMinPreference()).andReturn(Float.NaN);
+ EasyMock.expect(dataModel.getMaxPreference()).andReturn(Float.NaN);
+
+ EasyMock.expect(dataModel.getPreferencesFromUser(1L)).andReturn(preferencesFromUser);
+ EasyMock.expect(candidateItemsStrategy.getCandidateItems(1L, preferencesFromUser, dataModel, false))
+ .andReturn(new FastIDSet(new long[] { 3L, 4L }));
+
+ EasyMock.expect(itemSimilarity.itemSimilarities(3L, preferencesFromUser.getIDs()))
+ .andReturn(new double[] { 0.5, 0.3 });
+ EasyMock.expect(itemSimilarity.itemSimilarities(4L, preferencesFromUser.getIDs()))
+ .andReturn(new double[] { 0.4, 0.1 });
+
+ EasyMock.replay(dataModel, itemSimilarity, candidateItemsStrategy, mostSimilarItemsCandidateItemsStrategy);
+
+ Recommender recommender = new GenericItemBasedRecommender(dataModel, itemSimilarity,
+ candidateItemsStrategy, mostSimilarItemsCandidateItemsStrategy);
+
+ recommender.recommend(1L, 3);
+
+ EasyMock.verify(dataModel, itemSimilarity, candidateItemsStrategy, mostSimilarItemsCandidateItemsStrategy);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java
new file mode 100644
index 0000000..121cd1a
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java
@@ -0,0 +1,174 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
+import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.apache.mahout.cf.taste.recommender.UserBasedRecommender;
+import org.apache.mahout.cf.taste.similarity.UserSimilarity;
+import org.junit.Test;
+
+import java.util.List;
+
+/** <p>Tests {@link GenericUserBasedRecommender}.</p> */
+public final class GenericUserBasedRecommenderTest extends TasteTestCase {
+
+ @Test
+ public void testRecommender() throws Exception {
+ Recommender recommender = buildRecommender();
+ List<RecommendedItem> recommended = recommender.recommend(1, 1);
+ assertNotNull(recommended);
+ assertEquals(1, recommended.size());
+ RecommendedItem firstRecommended = recommended.get(0);
+ assertEquals(2, firstRecommended.getItemID());
+ assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+ recommender.refresh(null);
+ assertEquals(2, firstRecommended.getItemID());
+ assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+ }
+
+ @Test
+ public void testHowMany() throws Exception {
+ DataModel dataModel = getDataModel(
+ new long[] {1, 2, 3, 4, 5},
+ new Double[][] {
+ {0.1, 0.2},
+ {0.2, 0.3, 0.3, 0.6},
+ {0.4, 0.4, 0.5, 0.9},
+ {0.1, 0.4, 0.5, 0.8, 0.9, 1.0},
+ {0.2, 0.3, 0.6, 0.7, 0.1, 0.2},
+ });
+ UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
+ UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
+ Recommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
+ List<RecommendedItem> fewRecommended = recommender.recommend(1, 2);
+ List<RecommendedItem> moreRecommended = recommender.recommend(1, 4);
+ for (int i = 0; i < fewRecommended.size(); i++) {
+ assertEquals(fewRecommended.get(i).getItemID(), moreRecommended.get(i).getItemID());
+ }
+ recommender.refresh(null);
+ for (int i = 0; i < fewRecommended.size(); i++) {
+ assertEquals(fewRecommended.get(i).getItemID(), moreRecommended.get(i).getItemID());
+ }
+ }
+
+ @Test
+ public void testRescorer() throws Exception {
+ DataModel dataModel = getDataModel(
+ new long[] {1, 2, 3},
+ new Double[][] {
+ {0.1, 0.2},
+ {0.2, 0.3, 0.3, 0.6},
+ {0.4, 0.5, 0.5, 0.9},
+ });
+ UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
+ UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
+ Recommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
+ List<RecommendedItem> originalRecommended = recommender.recommend(1, 2);
+ List<RecommendedItem> rescoredRecommended =
+ recommender.recommend(1, 2, new ReversingRescorer<Long>());
+ assertNotNull(originalRecommended);
+ assertNotNull(rescoredRecommended);
+ assertEquals(2, originalRecommended.size());
+ assertEquals(2, rescoredRecommended.size());
+ assertEquals(originalRecommended.get(0).getItemID(), rescoredRecommended.get(1).getItemID());
+ assertEquals(originalRecommended.get(1).getItemID(), rescoredRecommended.get(0).getItemID());
+ }
+
+ @Test
+ public void testIncludeKnownItems() throws Exception {
+ DataModel dataModel = getDataModel(
+ new long[] {1, 2, 3},
+ new Double[][] {
+ {0.1, 0.2},
+ {0.2, 0.3, 0.3, 0.6},
+ {0.4, 0.5, 0.5, 0.9},
+ });
+ UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
+ UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
+ Recommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
+ List<RecommendedItem> originalRecommended = recommender.recommend(1, 4, null, true);
+ List<RecommendedItem> rescoredRecommended = recommender.recommend(1, 4, new ReversingRescorer<Long>(), true);
+ assertNotNull(originalRecommended);
+ assertNotNull(rescoredRecommended);
+ assertEquals(4, originalRecommended.size());
+ assertEquals(4, rescoredRecommended.size());
+ assertEquals(originalRecommended.get(0).getItemID(), rescoredRecommended.get(3).getItemID());
+ assertEquals(originalRecommended.get(3).getItemID(), rescoredRecommended.get(0).getItemID());
+ }
+
+ @Test
+ public void testEstimatePref() throws Exception {
+ Recommender recommender = buildRecommender();
+ assertEquals(0.1f, recommender.estimatePreference(1, 2), EPSILON);
+ }
+
+ @Test
+ public void testBestRating() throws Exception {
+ Recommender recommender = buildRecommender();
+ List<RecommendedItem> recommended = recommender.recommend(1, 1);
+ assertNotNull(recommended);
+ assertEquals(1, recommended.size());
+ RecommendedItem firstRecommended = recommended.get(0);
+ // item one should be recommended because it has a greater rating/score
+ assertEquals(2, firstRecommended.getItemID());
+ assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+ }
+
+ @Test
+ public void testMostSimilar() throws Exception {
+ UserBasedRecommender recommender = buildRecommender();
+ long[] similar = recommender.mostSimilarUserIDs(1, 2);
+ assertNotNull(similar);
+ assertEquals(2, similar.length);
+ assertEquals(2, similar[0]);
+ assertEquals(3, similar[1]);
+ }
+
+ @Test
+ public void testIsolatedUser() throws Exception {
+ DataModel dataModel = getDataModel(
+ new long[] {1, 2, 3, 4},
+ new Double[][] {
+ {0.1, 0.2},
+ {0.2, 0.3, 0.3, 0.6},
+ {0.4, 0.4, 0.5, 0.9},
+ {null, null, null, null, 1.0},
+ });
+ UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
+ UserNeighborhood neighborhood = new NearestNUserNeighborhood(3, similarity, dataModel);
+ UserBasedRecommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
+ long[] mostSimilar = recommender.mostSimilarUserIDs(4, 3);
+ assertNotNull(mostSimilar);
+ assertEquals(0, mostSimilar.length);
+ }
+
+ private static UserBasedRecommender buildRecommender() throws TasteException {
+ DataModel dataModel = getDataModel();
+ UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
+ UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
+ return new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java
new file mode 100644
index 0000000..243eaa9
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.junit.Test;
+
+import java.util.List;
+
+public final class ItemAverageRecommenderTest extends TasteTestCase {
+
+ @Test
+ public void testRecommender() throws Exception {
+ Recommender recommender = new ItemAverageRecommender(getDataModel());
+ List<RecommendedItem> recommended = recommender.recommend(1, 1);
+ assertNotNull(recommended);
+ assertEquals(1, recommended.size());
+ RecommendedItem firstRecommended = recommended.get(0);
+ assertEquals(2, firstRecommended.getItemID());
+ assertEquals(0.53333336f, firstRecommended.getValue(), EPSILON);
+ recommender.refresh(null);
+ assertEquals(2, firstRecommended.getItemID());
+ assertEquals(0.53333336f, firstRecommended.getValue(), EPSILON);
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java
new file mode 100644
index 0000000..f8bf1a1
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.junit.Test;
+
+import java.util.List;
+
+public final class ItemUserAverageRecommenderTest extends TasteTestCase {
+
+ @Test
+ public void testRecommender() throws Exception {
+ Recommender recommender = new ItemUserAverageRecommender(getDataModel());
+ List<RecommendedItem> recommended = recommender.recommend(1, 1);
+ assertNotNull(recommended);
+ assertEquals(1, recommended.size());
+ RecommendedItem firstRecommended = recommended.get(0);
+ assertEquals(2, firstRecommended.getItemID());
+ assertEquals(0.35151517f, firstRecommended.getValue(), EPSILON);
+ recommender.refresh(null);
+ assertEquals(2, firstRecommended.getItemID());
+ assertEquals(0.35151517f, firstRecommended.getValue(), EPSILON);
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java
new file mode 100644
index 0000000..50a16cb
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java
@@ -0,0 +1,89 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.mahout.cf.taste.common.Refreshable;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.recommender.IDRescorer;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+
+final class MockRecommender implements Recommender {
+
+ private final MutableInt recommendCount;
+
+ MockRecommender(MutableInt recommendCount) {
+ this.recommendCount = recommendCount;
+ }
+
+ @Override
+ public List<RecommendedItem> recommend(long userID, int howMany) {
+ recommendCount.increment();
+ return Collections.<RecommendedItem>singletonList(
+ new GenericRecommendedItem(1, 1.0f));
+ }
+
+ @Override
+ public List<RecommendedItem> recommend(long userID, int howMany, boolean includeKnownItems) {
+ return recommend(userID, howMany);
+ }
+
+ @Override
+ public List<RecommendedItem> recommend(long userID, int howMany, IDRescorer rescorer) {
+ return recommend(userID, howMany);
+ }
+
+ @Override
+ public List<RecommendedItem> recommend(long userID, int howMany, IDRescorer rescorer, boolean includeKnownItems) {
+ return recommend(userID, howMany);
+ }
+
+ @Override
+ public float estimatePreference(long userID, long itemID) {
+ recommendCount.increment();
+ return 0.0f;
+ }
+
+ @Override
+ public void setPreference(long userID, long itemID, float value) {
+ // do nothing
+ }
+
+ @Override
+ public void removePreference(long userID, long itemID) {
+ // do nothing
+ }
+
+ @Override
+ public DataModel getDataModel() {
+ return TasteTestCase.getDataModel(
+ new long[] {1, 2, 3},
+ new Double[][]{{1.0},{2.0},{3.0}});
+ }
+
+ @Override
+ public void refresh(Collection<Refreshable> alreadyRefreshed) {}
+
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java
new file mode 100644
index 0000000..97e539e
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.recommender.IDRescorer;
+import org.junit.Test;
+
+/** <p>Tests {@link NullRescorer}.</p> */
+public final class NullRescorerTest extends TasteTestCase {
+
+ @Test
+ public void testItemRescorer() throws Exception {
+ IDRescorer rescorer = NullRescorer.getItemInstance();
+ assertNotNull(rescorer);
+ assertEquals(1.0, rescorer.rescore(1L, 1.0), EPSILON);
+ assertEquals(1.0, rescorer.rescore(0L, 1.0), EPSILON);
+ assertEquals(0.0, rescorer.rescore(1L, 0.0), EPSILON);
+ assertTrue(Double.isNaN(rescorer.rescore(1L, Double.NaN)));
+ }
+
+ @Test
+ public void testUserRescorer() throws Exception {
+ IDRescorer rescorer = NullRescorer.getUserInstance();
+ assertNotNull(rescorer);
+ assertEquals(1.0, rescorer.rescore(1L, 1.0), EPSILON);
+ assertEquals(1.0, rescorer.rescore(0L, 1.0), EPSILON);
+ assertEquals(0.0, rescorer.rescore(1L, 0.0), EPSILON);
+ assertTrue(Double.isNaN(rescorer.rescore(1L, Double.NaN)));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java
new file mode 100644
index 0000000..cbf20cf
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java
@@ -0,0 +1,75 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.collect.Lists;
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastIDSet;
+import org.apache.mahout.cf.taste.impl.model.GenericItemPreferenceArray;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.cf.taste.recommender.CandidateItemsStrategy;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+/**
+ * Tests {@link PreferredItemsNeighborhoodCandidateItemsStrategy}
+ */
+public final class PreferredItemsNeighborhoodCandidateItemsStrategyTest extends TasteTestCase {
+
+ @Test
+ public void testStrategy() throws TasteException {
+ FastIDSet itemIDsFromUser123 = new FastIDSet();
+ itemIDsFromUser123.add(1L);
+
+ FastIDSet itemIDsFromUser456 = new FastIDSet();
+ itemIDsFromUser456.add(1L);
+ itemIDsFromUser456.add(2L);
+
+ List<Preference> prefs = Lists.newArrayList();
+ prefs.add(new GenericPreference(123L, 1L, 1.0f));
+ prefs.add(new GenericPreference(456L, 1L, 1.0f));
+ PreferenceArray preferencesForItem1 = new GenericItemPreferenceArray(prefs);
+
+ DataModel dataModel = EasyMock.createMock(DataModel.class);
+ EasyMock.expect(dataModel.getPreferencesForItem(1L)).andReturn(preferencesForItem1);
+ EasyMock.expect(dataModel.getItemIDsFromUser(123L)).andReturn(itemIDsFromUser123);
+ EasyMock.expect(dataModel.getItemIDsFromUser(456L)).andReturn(itemIDsFromUser456);
+
+ PreferenceArray prefArrayOfUser123 =
+ new GenericUserPreferenceArray(Collections.singletonList(new GenericPreference(123L, 1L, 1.0f)));
+
+ CandidateItemsStrategy strategy = new PreferredItemsNeighborhoodCandidateItemsStrategy();
+
+ EasyMock.replay(dataModel);
+
+ FastIDSet candidateItems = strategy.getCandidateItems(123L, prefArrayOfUser123, dataModel, false);
+ assertEquals(1, candidateItems.size());
+ assertTrue(candidateItems.contains(2L));
+
+ EasyMock.verify(dataModel);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java
new file mode 100644
index 0000000..f57d389
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.junit.Test;
+
+import java.util.List;
+
+public final class RandomRecommenderTest extends TasteTestCase {
+
+ @Test
+ public void testRecommender() throws Exception {
+ Recommender recommender = new RandomRecommender(getDataModel());
+ List<RecommendedItem> recommended = recommender.recommend(1, 1);
+ assertNotNull(recommended);
+ assertEquals(1, recommended.size());
+ RecommendedItem firstRecommended = recommended.get(0);
+ assertEquals(2, firstRecommended.getItemID());
+ recommender.refresh(null);
+ assertEquals(2, firstRecommended.getItemID());
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java
new file mode 100644
index 0000000..3c4f7fc
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.recommender.IDRescorer;
+import org.apache.mahout.cf.taste.recommender.Rescorer;
+
+/** <p>Simple {@link Rescorer} which negates the given score, thus reversing order of rankings.</p> */
+public final class ReversingRescorer<T> implements Rescorer<T>, IDRescorer {
+
+ @Override
+ public double rescore(T thing, double originalScore) {
+ return -originalScore;
+ }
+
+ @Override
+ public boolean isFiltered(T thing) {
+ return false;
+ }
+
+ @Override
+ public double rescore(long ID, double originalScore) {
+ return -originalScore;
+ }
+
+ @Override
+ public boolean isFiltered(long ID) {
+ return false;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java
new file mode 100644
index 0000000..687b2b1
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import com.google.common.collect.Lists;
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
+import org.apache.mahout.cf.taste.impl.common.FastIDSet;
+import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.cf.taste.recommender.CandidateItemsStrategy;
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * Tests {@link SamplingCandidateItemsStrategy}
+ */
+public final class SamplingCandidateItemsStrategyTest extends TasteTestCase {
+
+ @Test
+ public void testStrategy() throws TasteException {
+ List<Preference> prefsOfUser123 = Lists.newArrayList();
+ prefsOfUser123.add(new GenericPreference(123L, 1L, 1.0f));
+
+ List<Preference> prefsOfUser456 = Lists.newArrayList();
+ prefsOfUser456.add(new GenericPreference(456L, 1L, 1.0f));
+ prefsOfUser456.add(new GenericPreference(456L, 2L, 1.0f));
+
+ List<Preference> prefsOfUser789 = Lists.newArrayList();
+ prefsOfUser789.add(new GenericPreference(789L, 1L, 0.5f));
+ prefsOfUser789.add(new GenericPreference(789L, 3L, 1.0f));
+
+ PreferenceArray prefArrayOfUser123 = new GenericUserPreferenceArray(prefsOfUser123);
+
+ FastByIDMap<PreferenceArray> userData = new FastByIDMap<PreferenceArray>();
+ userData.put(123L, prefArrayOfUser123);
+ userData.put(456L, new GenericUserPreferenceArray(prefsOfUser456));
+ userData.put(789L, new GenericUserPreferenceArray(prefsOfUser789));
+
+ DataModel dataModel = new GenericDataModel(userData);
+
+ CandidateItemsStrategy strategy =
+ new SamplingCandidateItemsStrategy(1, 1, 1, dataModel.getNumUsers(), dataModel.getNumItems());
+
+ FastIDSet candidateItems = strategy.getCandidateItems(123L, prefArrayOfUser123, dataModel, false);
+ /* result can be either item2 or item3 or empty */
+ assertTrue(candidateItems.size() <= 1);
+ assertFalse(candidateItems.contains(1L));
+ }
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java
new file mode 100644
index 0000000..1d8b862
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java
@@ -0,0 +1,158 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender;
+
+import com.google.common.collect.Lists;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.LongPrimitiveArrayIterator;
+import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
+import org.apache.mahout.cf.taste.impl.similarity.GenericItemSimilarity;
+import org.apache.mahout.cf.taste.impl.similarity.GenericUserSimilarity;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.common.RandomUtils;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Tests for {@link TopItems}.
+ */
+public final class TopItemsTest extends TasteTestCase {
+
+ @Test
+ public void testTopItems() throws Exception {
+ long[] ids = new long[100];
+ for (int i = 0; i < 100; i++) {
+ ids[i] = i;
+ }
+ LongPrimitiveIterator possibleItemIds = new LongPrimitiveArrayIterator(ids);
+ TopItems.Estimator<Long> estimator = new TopItems.Estimator<Long>() {
+ @Override
+ public double estimate(Long thing) {
+ return thing;
+ }
+ };
+ List<RecommendedItem> topItems = TopItems.getTopItems(10, possibleItemIds, null, estimator);
+ int gold = 99;
+ for (RecommendedItem topItem : topItems) {
+ assertEquals(gold, topItem.getItemID());
+ assertEquals(gold--, topItem.getValue(), 0.01);
+ }
+ }
+
+ @Test
+ public void testTopItemsRandom() throws Exception {
+ long[] ids = new long[100];
+ for (int i = 0; i < 100; i++) {
+ ids[i] = i;
+ }
+ LongPrimitiveIterator possibleItemIds = new LongPrimitiveArrayIterator(ids);
+ final Random random = RandomUtils.getRandom();
+ TopItems.Estimator<Long> estimator = new TopItems.Estimator<Long>() {
+ @Override
+ public double estimate(Long thing) {
+ return random.nextDouble();
+ }
+ };
+ List<RecommendedItem> topItems = TopItems.getTopItems(10, possibleItemIds, null, estimator);
+ assertEquals(10, topItems.size());
+ double last = 2.0;
+ for (RecommendedItem topItem : topItems) {
+ assertTrue(topItem.getValue() <= last);
+ last = topItem.getItemID();
+ }
+ }
+
+ @Test
+ public void testTopUsers() throws Exception {
+ long[] ids = new long[100];
+ for (int i = 0; i < 100; i++) {
+ ids[i] = i;
+ }
+ LongPrimitiveIterator possibleItemIds = new LongPrimitiveArrayIterator(ids);
+ TopItems.Estimator<Long> estimator = new TopItems.Estimator<Long>() {
+ @Override
+ public double estimate(Long thing) {
+ return thing;
+ }
+ };
+ long[] topItems = TopItems.getTopUsers(10, possibleItemIds, null, estimator);
+ int gold = 99;
+ for (long topItem : topItems) {
+ assertEquals(gold--, topItem);
+ }
+ }
+
+ @Test
+ public void testTopItemItem() throws Exception {
+ List<GenericItemSimilarity.ItemItemSimilarity> sims = Lists.newArrayList();
+ for (int i = 0; i < 99; i++) {
+ sims.add(new GenericItemSimilarity.ItemItemSimilarity(i, i + 1, i / 99.0));
+ }
+
+ List<GenericItemSimilarity.ItemItemSimilarity> res = TopItems.getTopItemItemSimilarities(10, sims.iterator());
+ int gold = 99;
+ for (GenericItemSimilarity.ItemItemSimilarity re : res) {
+ assertEquals(gold--, re.getItemID2()); //the second id should be equal to 99 to start
+ }
+ }
+
+ @Test
+ public void testTopItemItemAlt() throws Exception {
+ List<GenericItemSimilarity.ItemItemSimilarity> sims = Lists.newArrayList();
+ for (int i = 0; i < 99; i++) {
+ sims.add(new GenericItemSimilarity.ItemItemSimilarity(i, i + 1, 1 - (i / 99.0)));
+ }
+
+ List<GenericItemSimilarity.ItemItemSimilarity> res = TopItems.getTopItemItemSimilarities(10, sims.iterator());
+ int gold = 0;
+ for (GenericItemSimilarity.ItemItemSimilarity re : res) {
+ assertEquals(gold++, re.getItemID1()); //the second id should be equal to 99 to start
+ }
+ }
+
+ @Test
+ public void testTopUserUser() throws Exception {
+ List<GenericUserSimilarity.UserUserSimilarity> sims = Lists.newArrayList();
+ for (int i = 0; i < 99; i++) {
+ sims.add(new GenericUserSimilarity.UserUserSimilarity(i, i + 1, i / 99.0));
+ }
+
+ List<GenericUserSimilarity.UserUserSimilarity> res = TopItems.getTopUserUserSimilarities(10, sims.iterator());
+ int gold = 99;
+ for (GenericUserSimilarity.UserUserSimilarity re : res) {
+ assertEquals(gold--, re.getUserID2()); //the second id should be equal to 99 to start
+ }
+ }
+
+ @Test
+ public void testTopUserUserAlt() throws Exception {
+ List<GenericUserSimilarity.UserUserSimilarity> sims = Lists.newArrayList();
+ for (int i = 0; i < 99; i++) {
+ sims.add(new GenericUserSimilarity.UserUserSimilarity(i, i + 1, 1 - (i / 99.0)));
+ }
+
+ List<GenericUserSimilarity.UserUserSimilarity> res = TopItems.getTopUserUserSimilarities(10, sims.iterator());
+ int gold = 0;
+ for (GenericUserSimilarity.UserUserSimilarity re : res) {
+ assertEquals(gold++, re.getUserID1()); //the first id should be equal to 0 to start
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java
new file mode 100644
index 0000000..23fa38f
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java
@@ -0,0 +1,208 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender.svd;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
+import org.apache.mahout.cf.taste.impl.common.FullRunningAverage;
+import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
+import org.apache.mahout.cf.taste.impl.common.RunningAverage;
+import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.math.DenseVector;
+import org.apache.mahout.math.Matrix;
+import org.apache.mahout.math.MatrixSlice;
+import org.apache.mahout.math.SparseRowMatrix;
+import org.apache.mahout.math.Vector;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class ALSWRFactorizerTest extends TasteTestCase {
+
+ private ALSWRFactorizer factorizer;
+ private DataModel dataModel;
+
+ private static final Logger log = LoggerFactory.getLogger(ALSWRFactorizerTest.class);
+
+ /**
+ * rating-matrix
+ *
+ * burger hotdog berries icecream
+ * dog 5 5 2 -
+ * rabbit 2 - 3 5
+ * cow - 5 - 3
+ * donkey 3 - - 5
+ */
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ FastByIDMap<PreferenceArray> userData = new FastByIDMap<PreferenceArray>();
+
+ userData.put(1L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(1L, 1L, 5.0f),
+ new GenericPreference(1L, 2L, 5.0f),
+ new GenericPreference(1L, 3L, 2.0f))));
+
+ userData.put(2L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(2L, 1L, 2.0f),
+ new GenericPreference(2L, 3L, 3.0f),
+ new GenericPreference(2L, 4L, 5.0f))));
+
+ userData.put(3L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(3L, 2L, 5.0f),
+ new GenericPreference(3L, 4L, 3.0f))));
+
+ userData.put(4L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(4L, 1L, 3.0f),
+ new GenericPreference(4L, 4L, 5.0f))));
+
+ dataModel = new GenericDataModel(userData);
+ factorizer = new ALSWRFactorizer(dataModel, 3, 0.065, 10);
+ }
+
+ @Test
+ public void setFeatureColumn() throws Exception {
+ ALSWRFactorizer.Features features = new ALSWRFactorizer.Features(factorizer);
+ Vector vector = new DenseVector(new double[] { 0.5, 2.0, 1.5 });
+ int index = 1;
+
+ features.setFeatureColumnInM(index, vector);
+ double[][] matrix = features.getM();
+
+ assertEquals(vector.get(0), matrix[index][0], EPSILON);
+ assertEquals(vector.get(1), matrix[index][1], EPSILON);
+ assertEquals(vector.get(2), matrix[index][2], EPSILON);
+ }
+
+ @Test
+ public void ratingVector() throws Exception {
+ PreferenceArray prefs = dataModel.getPreferencesFromUser(1);
+
+ Vector ratingVector = ALSWRFactorizer.ratingVector(prefs);
+
+ assertEquals(prefs.length(), ratingVector.getNumNondefaultElements());
+ assertEquals(prefs.get(0).getValue(), ratingVector.get(0), EPSILON);
+ assertEquals(prefs.get(1).getValue(), ratingVector.get(1), EPSILON);
+ assertEquals(prefs.get(2).getValue(), ratingVector.get(2), EPSILON);
+ }
+
+ @Test
+ public void averageRating() throws Exception {
+ ALSWRFactorizer.Features features = new ALSWRFactorizer.Features(factorizer);
+ assertEquals(2.5, features.averateRating(3L), EPSILON);
+ }
+
+ @Test
+ public void initializeM() throws Exception {
+ ALSWRFactorizer.Features features = new ALSWRFactorizer.Features(factorizer);
+ double[][] M = features.getM();
+
+ assertEquals(3.333333333, M[0][0], EPSILON);
+ assertEquals(5, M[1][0], EPSILON);
+ assertEquals(2.5, M[2][0], EPSILON);
+ assertEquals(4.333333333, M[3][0], EPSILON);
+
+ for (int itemIndex = 0; itemIndex < dataModel.getNumItems(); itemIndex++) {
+ for (int feature = 1; feature < 3; feature++ ) {
+ assertTrue(M[itemIndex][feature] >= 0);
+ assertTrue(M[itemIndex][feature] <= 0.1);
+ }
+ }
+ }
+
+ @ThreadLeakLingering(linger = 10)
+ @Test
+ public void toyExample() throws Exception {
+
+ SVDRecommender svdRecommender = new SVDRecommender(dataModel, factorizer);
+
+ /* a hold out test would be better, but this is just a toy example so we only check that the
+ * factorization is close to the original matrix */
+ RunningAverage avg = new FullRunningAverage();
+ LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+ while (userIDs.hasNext()) {
+ long userID = userIDs.nextLong();
+ for (Preference pref : dataModel.getPreferencesFromUser(userID)) {
+ double rating = pref.getValue();
+ double estimate = svdRecommender.estimatePreference(userID, pref.getItemID());
+ double err = rating - estimate;
+ avg.addDatum(err * err);
+ }
+ }
+
+ double rmse = Math.sqrt(avg.getAverage());
+ assertTrue(rmse < 0.2);
+ }
+
+ @Test
+ public void toyExampleImplicit() throws Exception {
+
+ Matrix observations = new SparseRowMatrix(4, 4, new Vector[] {
+ new DenseVector(new double[] { 5.0, 5.0, 2.0, 0 }),
+ new DenseVector(new double[] { 2.0, 0, 3.0, 5.0 }),
+ new DenseVector(new double[] { 0, 5.0, 0, 3.0 }),
+ new DenseVector(new double[] { 3.0, 0, 0, 5.0 }) });
+
+ Matrix preferences = new SparseRowMatrix(4, 4, new Vector[] {
+ new DenseVector(new double[] { 1.0, 1.0, 1.0, 0 }),
+ new DenseVector(new double[] { 1.0, 0, 1.0, 1.0 }),
+ new DenseVector(new double[] { 0, 1.0, 0, 1.0 }),
+ new DenseVector(new double[] { 1.0, 0, 0, 1.0 }) });
+
+ double alpha = 20;
+
+ ALSWRFactorizer factorizer = new ALSWRFactorizer(dataModel, 3, 0.065, 5, true, alpha);
+
+ SVDRecommender svdRecommender = new SVDRecommender(dataModel, factorizer);
+
+ RunningAverage avg = new FullRunningAverage();
+ Iterator<MatrixSlice> sliceIterator = preferences.iterateAll();
+ while (sliceIterator.hasNext()) {
+ MatrixSlice slice = sliceIterator.next();
+ for (Vector.Element e : slice.vector().all()) {
+
+ long userID = slice.index() + 1;
+ long itemID = e.index() + 1;
+
+ if (!Double.isNaN(e.get())) {
+ double pref = e.get();
+ double estimate = svdRecommender.estimatePreference(userID, itemID);
+
+ double confidence = 1 + alpha * observations.getQuick(slice.index(), e.index());
+ double err = confidence * (pref - estimate) * (pref - estimate);
+ avg.addDatum(err);
+ log.info("Comparing preference of user [{}] towards item [{}], was [{}] with confidence [{}] "
+ + "estimate is [{}]", slice.index(), e.index(), pref, confidence, estimate);
+ }
+ }
+ }
+ double rmse = Math.sqrt(avg.getAverage());
+ log.info("RMSE: {}", rmse);
+
+ assertTrue(rmse < 0.4);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java
new file mode 100644
index 0000000..eb8a356
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender.svd;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
+import org.junit.Test;
+
+import java.io.File;
+
+public class FilePersistenceStrategyTest extends TasteTestCase {
+
+ @Test
+ public void persistAndLoad() throws Exception {
+ FastByIDMap<Integer> userIDMapping = new FastByIDMap<Integer>();
+ FastByIDMap<Integer> itemIDMapping = new FastByIDMap<Integer>();
+
+ userIDMapping.put(123, 0);
+ userIDMapping.put(456, 1);
+
+ itemIDMapping.put(12, 0);
+ itemIDMapping.put(34, 1);
+
+ double[][] userFeatures = { { 0.1, 0.2, 0.3 }, { 0.4, 0.5, 0.6 } };
+ double[][] itemFeatures = { { 0.7, 0.8, 0.9 }, { 1.0, 1.1, 1.2 } };
+
+ Factorization original = new Factorization(userIDMapping, itemIDMapping, userFeatures, itemFeatures);
+ File storage = getTestTempFile("storage.bin");
+ PersistenceStrategy persistenceStrategy = new FilePersistenceStrategy(storage);
+
+ assertNull(persistenceStrategy.load());
+
+ persistenceStrategy.maybePersist(original);
+ Factorization clone = persistenceStrategy.load();
+
+ assertEquals(original, clone);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java
new file mode 100644
index 0000000..8a91e7a
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java
@@ -0,0 +1,355 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender.svd;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
+import com.google.common.collect.Lists;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
+import org.apache.mahout.cf.taste.impl.common.FullRunningAverage;
+import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
+import org.apache.mahout.cf.taste.impl.common.RunningAverage;
+import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
+import org.apache.mahout.cf.taste.impl.recommender.svd.ParallelSGDFactorizer.PreferenceShuffler;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.common.RandomUtils;
+import org.apache.mahout.common.RandomWrapper;
+import org.apache.mahout.math.DenseMatrix;
+import org.apache.mahout.math.DenseVector;
+import org.apache.mahout.math.Matrix;
+import org.apache.mahout.math.Vector;
+import org.apache.mahout.math.function.DoubleFunction;
+import org.apache.mahout.math.function.VectorFunction;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ParallelSGDFactorizerTest extends TasteTestCase {
+
+ protected DataModel dataModel;
+
+ protected int rank;
+ protected double lambda;
+ protected int numIterations;
+
+ private RandomWrapper random = (RandomWrapper) RandomUtils.getRandom();
+
+ protected Factorizer factorizer;
+ protected SVDRecommender svdRecommender;
+
+ private static final Logger logger = LoggerFactory.getLogger(ParallelSGDFactorizerTest.class);
+
+ private Matrix randomMatrix(int numRows, int numColumns, double range) {
+ double[][] data = new double[numRows][numColumns];
+ for (int i = 0; i < numRows; i++) {
+ for (int j = 0; j < numColumns; j++) {
+ double sqrtUniform = random.nextDouble();
+ data[i][j] = sqrtUniform * range;
+ }
+ }
+ return new DenseMatrix(data);
+ }
+
+ private void normalize(Matrix source, final double range) {
+ final double max = source.aggregateColumns(new VectorFunction() {
+ @Override
+ public double apply(Vector column) {
+ return column.maxValue();
+ }
+ }).maxValue();
+
+ final double min = source.aggregateColumns(new VectorFunction() {
+ @Override
+ public double apply(Vector column) {
+ return column.minValue();
+ }
+ }).minValue();
+
+ source.assign(new DoubleFunction() {
+ @Override
+ public double apply(double value) {
+ return (value - min) * range / (max - min);
+ }
+ });
+ }
+
+ public void setUpSyntheticData() throws Exception {
+
+ int numUsers = 2000;
+ int numItems = 1000;
+ double sparsity = 0.5;
+
+ this.rank = 20;
+ this.lambda = 0.000000001;
+ this.numIterations = 100;
+
+ Matrix users = randomMatrix(numUsers, rank, 1);
+ Matrix items = randomMatrix(rank, numItems, 1);
+ Matrix ratings = users.times(items);
+ normalize(ratings, 5);
+
+ FastByIDMap<PreferenceArray> userData = new FastByIDMap<PreferenceArray>();
+ for (int userIndex = 0; userIndex < numUsers; userIndex++) {
+ List<Preference> row= Lists.newArrayList();
+ for (int itemIndex = 0; itemIndex < numItems; itemIndex++) {
+ if (random.nextDouble() <= sparsity) {
+ row.add(new GenericPreference(userIndex, itemIndex, (float) ratings.get(userIndex, itemIndex)));
+ }
+ }
+
+ userData.put(userIndex, new GenericUserPreferenceArray(row));
+ }
+
+ dataModel = new GenericDataModel(userData);
+ }
+
+ public void setUpToyData() throws Exception {
+ this.rank = 3;
+ this.lambda = 0.01;
+ this.numIterations = 1000;
+
+ FastByIDMap<PreferenceArray> userData = new FastByIDMap<PreferenceArray>();
+
+ userData.put(1L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(1L, 1L, 5.0f),
+ new GenericPreference(1L, 2L, 5.0f),
+ new GenericPreference(1L, 3L, 2.0f))));
+
+ userData.put(2L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(2L, 1L, 2.0f),
+ new GenericPreference(2L, 3L, 3.0f),
+ new GenericPreference(2L, 4L, 5.0f))));
+
+ userData.put(3L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(3L, 2L, 5.0f),
+ new GenericPreference(3L, 4L, 3.0f))));
+
+ userData.put(4L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(4L, 1L, 3.0f),
+ new GenericPreference(4L, 4L, 5.0f))));
+ dataModel = new GenericDataModel(userData);
+ }
+
+ @Test
+ public void testPreferenceShufflerWithSyntheticData() throws Exception {
+ setUpSyntheticData();
+
+ ParallelSGDFactorizer.PreferenceShuffler shuffler = new PreferenceShuffler(dataModel);
+ shuffler.shuffle();
+ shuffler.stage();
+
+ FastByIDMap<FastByIDMap<Boolean>> checked = new FastByIDMap<FastByIDMap<Boolean>>();
+
+ for (int i = 0; i < shuffler.size(); i++) {
+ Preference pref=shuffler.get(i);
+
+ float value = dataModel.getPreferenceValue(pref.getUserID(), pref.getItemID());
+ assertEquals(pref.getValue(), value, 0.0);
+ if (!checked.containsKey(pref.getUserID())) {
+ checked.put(pref.getUserID(), new FastByIDMap<Boolean>());
+ }
+
+ assertNull(checked.get(pref.getUserID()).get(pref.getItemID()));
+
+ checked.get(pref.getUserID()).put(pref.getItemID(), true);
+ }
+
+ LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+ int index=0;
+ while (userIDs.hasNext()) {
+ long userID = userIDs.nextLong();
+ PreferenceArray preferencesFromUser = dataModel.getPreferencesFromUser(userID);
+ for (Preference preference : preferencesFromUser) {
+ assertTrue(checked.get(preference.getUserID()).get(preference.getItemID()));
+ index++;
+ }
+ }
+ assertEquals(index, shuffler.size());
+ }
+
+ @ThreadLeakLingering(linger = 1000)
+ @Test
+ public void testFactorizerWithToyData() throws Exception {
+
+ setUpToyData();
+
+ long start = System.currentTimeMillis();
+
+ factorizer = new ParallelSGDFactorizer(dataModel, rank, lambda, numIterations, 0.01, 1, 0, 0);
+
+ Factorization factorization = factorizer.factorize();
+
+ long duration = System.currentTimeMillis() - start;
+
+ /* a hold out test would be better, but this is just a toy example so we only check that the
+ * factorization is close to the original matrix */
+ RunningAverage avg = new FullRunningAverage();
+ LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+ LongPrimitiveIterator itemIDs;
+
+ while (userIDs.hasNext()) {
+ long userID = userIDs.nextLong();
+ for (Preference pref : dataModel.getPreferencesFromUser(userID)) {
+ double rating = pref.getValue();
+ Vector userVector = new DenseVector(factorization.getUserFeatures(userID));
+ Vector itemVector = new DenseVector(factorization.getItemFeatures(pref.getItemID()));
+ double estimate = userVector.dot(itemVector);
+ double err = rating - estimate;
+
+ avg.addDatum(err * err);
+ }
+ }
+
+ double sum = 0.0;
+
+ userIDs = dataModel.getUserIDs();
+ while (userIDs.hasNext()) {
+ long userID = userIDs.nextLong();
+ Vector userVector = new DenseVector(factorization.getUserFeatures(userID));
+ double regularization = userVector.dot(userVector);
+ sum += regularization;
+ }
+
+ itemIDs = dataModel.getItemIDs();
+ while (itemIDs.hasNext()) {
+ long itemID = itemIDs.nextLong();
+ Vector itemVector = new DenseVector(factorization.getUserFeatures(itemID));
+ double regularization = itemVector.dot(itemVector);
+ sum += regularization;
+ }
+
+ double rmse = Math.sqrt(avg.getAverage());
+ double loss = avg.getAverage() / 2 + lambda / 2 * sum;
+ logger.info("RMSE: " + rmse + ";\tLoss: " + loss + ";\tTime Used: " + duration);
+ assertTrue(rmse < 0.2);
+ }
+
+ @ThreadLeakLingering(linger = 1000)
+ @Test
+ public void testRecommenderWithToyData() throws Exception {
+
+ setUpToyData();
+
+ factorizer = new ParallelSGDFactorizer(dataModel, rank, lambda, numIterations, 0.01, 1, 0,0);
+ svdRecommender = new SVDRecommender(dataModel, factorizer);
+
+ /* a hold out test would be better, but this is just a toy example so we only check that the
+ * factorization is close to the original matrix */
+ RunningAverage avg = new FullRunningAverage();
+ LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+ while (userIDs.hasNext()) {
+ long userID = userIDs.nextLong();
+ for (Preference pref : dataModel.getPreferencesFromUser(userID)) {
+ double rating = pref.getValue();
+ double estimate = svdRecommender.estimatePreference(userID, pref.getItemID());
+ double err = rating - estimate;
+ avg.addDatum(err * err);
+ }
+ }
+
+ double rmse = Math.sqrt(avg.getAverage());
+ logger.info("rmse: " + rmse);
+ assertTrue(rmse < 0.2);
+ }
+
+ @Test
+ public void testFactorizerWithWithSyntheticData() throws Exception {
+
+ setUpSyntheticData();
+
+ long start = System.currentTimeMillis();
+
+ factorizer = new ParallelSGDFactorizer(dataModel, rank, lambda, numIterations, 0.01, 1, 0, 0);
+
+ Factorization factorization = factorizer.factorize();
+
+ long duration = System.currentTimeMillis() - start;
+
+ /* a hold out test would be better, but this is just a toy example so we only check that the
+ * factorization is close to the original matrix */
+ RunningAverage avg = new FullRunningAverage();
+ LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+ LongPrimitiveIterator itemIDs;
+
+ while (userIDs.hasNext()) {
+ long userID = userIDs.nextLong();
+ for (Preference pref : dataModel.getPreferencesFromUser(userID)) {
+ double rating = pref.getValue();
+ Vector userVector = new DenseVector(factorization.getUserFeatures(userID));
+ Vector itemVector = new DenseVector(factorization.getItemFeatures(pref.getItemID()));
+ double estimate = userVector.dot(itemVector);
+ double err = rating - estimate;
+
+ avg.addDatum(err * err);
+ }
+ }
+
+ double sum = 0.0;
+
+ userIDs = dataModel.getUserIDs();
+ while (userIDs.hasNext()) {
+ long userID = userIDs.nextLong();
+ Vector userVector = new DenseVector(factorization.getUserFeatures(userID));
+ double regularization=userVector.dot(userVector);
+ sum += regularization;
+ }
+
+ itemIDs = dataModel.getItemIDs();
+ while (itemIDs.hasNext()) {
+ long itemID = itemIDs.nextLong();
+ Vector itemVector = new DenseVector(factorization.getUserFeatures(itemID));
+ double regularization = itemVector.dot(itemVector);
+ sum += regularization;
+ }
+
+ double rmse = Math.sqrt(avg.getAverage());
+ double loss = avg.getAverage() / 2 + lambda / 2 * sum;
+ logger.info("RMSE: " + rmse + ";\tLoss: " + loss + ";\tTime Used: " + duration + "ms");
+ assertTrue(rmse < 0.2);
+ }
+
+ @Test
+ public void testRecommenderWithSyntheticData() throws Exception {
+
+ setUpSyntheticData();
+
+ factorizer= new ParallelSGDFactorizer(dataModel, rank, lambda, numIterations, 0.01, 1, 0, 0);
+ svdRecommender = new SVDRecommender(dataModel, factorizer);
+
+ /* a hold out test would be better, but this is just a toy example so we only check that the
+ * factorization is close to the original matrix */
+ RunningAverage avg = new FullRunningAverage();
+ LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+ while (userIDs.hasNext()) {
+ long userID = userIDs.nextLong();
+ for (Preference pref : dataModel.getPreferencesFromUser(userID)) {
+ double rating = pref.getValue();
+ double estimate = svdRecommender.estimatePreference(userID, pref.getItemID());
+ double err = rating - estimate;
+ avg.addDatum(err * err);
+ }
+ }
+
+ double rmse = Math.sqrt(avg.getAverage());
+ logger.info("rmse: " + rmse);
+ assertTrue(rmse < 0.2);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java
new file mode 100644
index 0000000..aebd324
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java
@@ -0,0 +1,86 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.recommender.svd;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastIDSet;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.cf.taste.recommender.CandidateItemsStrategy;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import java.util.List;
+
+public class SVDRecommenderTest extends TasteTestCase {
+
+ @Test
+ public void estimatePreference() throws Exception {
+ DataModel dataModel = EasyMock.createMock(DataModel.class);
+ Factorizer factorizer = EasyMock.createMock(Factorizer.class);
+ Factorization factorization = EasyMock.createMock(Factorization.class);
+
+ EasyMock.expect(factorizer.factorize()).andReturn(factorization);
+ EasyMock.expect(factorization.getUserFeatures(1L)).andReturn(new double[] { 0.4, 2 });
+ EasyMock.expect(factorization.getItemFeatures(5L)).andReturn(new double[] { 1, 0.3 });
+ EasyMock.replay(dataModel, factorizer, factorization);
+
+ SVDRecommender svdRecommender = new SVDRecommender(dataModel, factorizer);
+
+ float estimate = svdRecommender.estimatePreference(1L, 5L);
+ assertEquals(1, estimate, EPSILON);
+
+ EasyMock.verify(dataModel, factorizer, factorization);
+ }
+
+ @Test
+ public void recommend() throws Exception {
+ DataModel dataModel = EasyMock.createMock(DataModel.class);
+ PreferenceArray preferencesFromUser = EasyMock.createMock(PreferenceArray.class);
+ CandidateItemsStrategy candidateItemsStrategy = EasyMock.createMock(CandidateItemsStrategy.class);
+ Factorizer factorizer = EasyMock.createMock(Factorizer.class);
+ Factorization factorization = EasyMock.createMock(Factorization.class);
+
+ FastIDSet candidateItems = new FastIDSet();
+ candidateItems.add(5L);
+ candidateItems.add(3L);
+
+ EasyMock.expect(factorizer.factorize()).andReturn(factorization);
+ EasyMock.expect(dataModel.getPreferencesFromUser(1L)).andReturn(preferencesFromUser);
+ EasyMock.expect(candidateItemsStrategy.getCandidateItems(1L, preferencesFromUser, dataModel, false))
+ .andReturn(candidateItems);
+ EasyMock.expect(factorization.getUserFeatures(1L)).andReturn(new double[] { 0.4, 2 });
+ EasyMock.expect(factorization.getItemFeatures(5L)).andReturn(new double[] { 1, 0.3 });
+ EasyMock.expect(factorization.getUserFeatures(1L)).andReturn(new double[] { 0.4, 2 });
+ EasyMock.expect(factorization.getItemFeatures(3L)).andReturn(new double[] { 2, 0.6 });
+
+ EasyMock.replay(dataModel, candidateItemsStrategy, factorizer, factorization);
+
+ SVDRecommender svdRecommender = new SVDRecommender(dataModel, factorizer, candidateItemsStrategy);
+
+ List<RecommendedItem> recommendedItems = svdRecommender.recommend(1L, 5);
+ assertEquals(2, recommendedItems.size());
+ assertEquals(3L, recommendedItems.get(0).getItemID());
+ assertEquals(2.0f, recommendedItems.get(0).getValue(), EPSILON);
+ assertEquals(5L, recommendedItems.get(1).getItemID());
+ assertEquals(1.0f, recommendedItems.get(1).getValue(), EPSILON);
+
+ EasyMock.verify(dataModel, candidateItemsStrategy, factorizer, factorization);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java
new file mode 100644
index 0000000..d8242e3
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.similarity;
+
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.similarity.PreferenceInferrer;
+import org.junit.Test;
+
+/** <p>Tests {@link AveragingPreferenceInferrer}.</p> */
+public final class AveragingPreferenceInferrerTest extends TasteTestCase {
+
+ @Test
+ public void testInferrer() throws TasteException {
+ DataModel model = getDataModel(new long[] {1}, new Double[][] {{3.0,-2.0,5.0}});
+ PreferenceInferrer inferrer = new AveragingPreferenceInferrer(model);
+ double inferred = inferrer.inferPreference(1, 3);
+ assertEquals(2.0, inferred, EPSILON);
+ }
+
+}