http://git-wip-us.apache.org/repos/asf/mahout/blob/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java new file mode 100644 index 0000000..16cbdca --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java new file mode 100644 index 0000000..121cd1a --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java new file mode 100644 index 0000000..243eaa9 --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java new file mode 100644 index 0000000..f8bf1a1 --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java new file mode 100644 index 0000000..50a16cb --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java new file mode 100644 index 0000000..97e539e --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java new file mode 100644 index 0000000..cbf20cf --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java new file mode 100644 index 0000000..f57d389 --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java new file mode 100644 index 0000000..3c4f7fc --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java new file mode 100644 index 0000000..088a203 --- /dev/null +++ b/community/mahout-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<>(); + 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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java new file mode 100644 index 0000000..1d8b862 --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java new file mode 100644 index 0000000..e98bd5e --- /dev/null +++ b/community/mahout-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<>(); + + 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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java new file mode 100644 index 0000000..6057c73 --- /dev/null +++ b/community/mahout-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<>(); + FastByIDMap<Integer> itemIDMapping = new FastByIDMap<>(); + + 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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java new file mode 100644 index 0000000..11af863 --- /dev/null +++ b/community/mahout-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<>(); + 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<>(); + + 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<>(); + + 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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java new file mode 100644 index 0000000..aebd324 --- /dev/null +++ b/community/mahout-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/5eda9e1f/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java ---------------------------------------------------------------------- diff --git a/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java b/community/mahout-mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java new file mode 100644 index 0000000..d8242e3 --- /dev/null +++ b/community/mahout-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); + } + +}
