├── .gitignore └── src └── main ├── plugin-metadata ├── plugin-security.policy └── plugin-descriptor.properties ├── java └── org │ └── codelibs │ └── elasticsearch │ └── taste │ ├── eval │ ├── EvaluatorFactory.java │ ├── AverageAbsoluteDifferenceEvaluatorFactory.java │ ├── RMSEvaluatorFactory.java │ ├── EvaluationConfig.java │ ├── LoadStatistics.java │ ├── LoadCallable.java │ ├── RecommenderBuilder.java │ ├── DataModelBuilder.java │ ├── RMSEvaluator.java │ ├── AverageAbsoluteDifferenceEvaluator.java │ ├── RMSRecommenderEvaluator.java │ ├── AverageAbsoluteDifferenceRecommenderEvaluator.java │ ├── IRStatistics.java │ ├── RecommenderIRStatsEvaluator.java │ ├── Evaluation.java │ ├── Evaluator.java │ ├── StatsCallable.java │ ├── RelevantItemsDataSplitter.java │ └── LoadEvaluator.java │ ├── similarity │ ├── SimilarityFactory.java │ ├── CityBlockSimilarityFactory.java │ ├── LogLikelihoodSimilarityFactory.java │ ├── SpearmanCorrelationSimilarityFactory.java │ ├── TanimotoCoefficientSimilarityFactory.java │ ├── AbstractUserSimilarityFactory.java │ ├── UncenteredCosineSimilarityFactory.java │ ├── EuclideanDistanceSimilarityFactory.java │ ├── PearsonCorrelationSimilarityFactory.java │ ├── precompute │ │ ├── SimilarItemsWriter.java │ │ ├── SimilarItem.java │ │ ├── BatchItemSimilarities.java │ │ ├── FileSimilarItemsWriter.java │ │ └── SimilarItems.java │ ├── LongPairMatchPredicate.java │ ├── PreferenceInferrer.java │ ├── UserSimilarity.java │ ├── ItemSimilarity.java │ ├── AbstractItemSimilarity.java │ ├── UncenteredCosineSimilarity.java │ └── AveragingPreferenceInferrer.java │ ├── neighborhood │ ├── UserNeighborhoodFactory.java │ ├── ThresholdUserNeighborhoodFactory.java │ ├── AbstractUserNeighborhoodFactory.java │ ├── NearestNUserNeighborhoodFactory.java │ ├── UserNeighborhood.java │ ├── AbstractUserNeighborhood.java │ └── CachingUserNeighborhood.java │ ├── module │ └── TasteModule.java │ ├── exception │ ├── NotFoundException.java │ ├── MissingShardsException.java │ ├── OperationFailedException.java │ ├── InvalidParameterException.java │ ├── NoSuchItemException.java │ ├── NoSuchUserException.java │ └── TasteException.java │ ├── model │ ├── cache │ │ ├── DmValue.java │ │ └── DmKey.java │ ├── Preference.java │ ├── UpdatableIDMigrator.java │ ├── AbstractDataModel.java │ ├── MemoryIDMigrator.java │ ├── BooleanPreference.java │ ├── AbstractIDMigrator.java │ ├── GenericPreference.java │ ├── IDMigrator.java │ └── PlusAnonymousUserLongPrimitiveIterator.java │ ├── rest │ └── handler │ │ ├── RequestHandler.java │ │ └── RequestHandlerChain.java │ ├── util │ ├── SettingsUtils.java │ ├── ClusterUtils.java │ └── ListenerUtils.java │ ├── service │ └── TasteService.java │ ├── common │ ├── AbstractLongPrimitiveIterator.java │ ├── Weighting.java │ ├── Retriever.java │ ├── RunningAverageAndStdDev.java │ ├── iterator │ │ ├── CountingIterator.java │ │ └── FixedSizeSamplingIterator.java │ ├── SkippingIterator.java │ ├── LongPrimitiveIterator.java │ ├── InvertedRunningAverage.java │ ├── FixedRunningAverageAndStdDev.java │ ├── InvertedRunningAverageAndStdDev.java │ ├── Refreshable.java │ ├── RunningAverage.java │ ├── FixedRunningAverage.java │ ├── LongPair.java │ ├── LongPrimitiveArrayIterator.java │ └── BitSet.java │ ├── recommender │ ├── ItemBasedRecommenderBuilder.java │ ├── svd │ │ ├── Factorizer.java │ │ ├── NoPersistenceStrategy.java │ │ ├── PersistenceStrategy.java │ │ └── SVDPreference.java │ ├── MostSimilarItemsCandidateItemsStrategy.java │ ├── CandidateItemsStrategy.java │ ├── RecommendedItem.java │ ├── EstimatedPreferenceCapper.java │ ├── ByValueRecommendedItemComparator.java │ ├── AllUnknownItemsCandidateItemsStrategy.java │ ├── IDRescorer.java │ ├── SimilarUser.java │ ├── UserBasedRecommender.java │ ├── AbstractRecommenderBuilder.java │ ├── AllSimilarItemsCandidateItemsStrategy.java │ ├── AbstractCandidateItemsStrategy.java │ ├── Rescorer.java │ ├── PreferredItemsNeighborhoodCandidateItemsStrategy.java │ ├── ByRescoreComparator.java │ ├── GenericRecommendedItem.java │ ├── UserBasedRecommenderBuilder.java │ ├── NullRescorer.java │ └── GenericBooleanPrefItemBasedRecommender.java │ ├── TastePlugin.java │ ├── TasteConstants.java │ └── writer │ ├── AbstractWriter.java │ └── ObjectWriter.java └── assemblies └── plugin.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .project 3 | .settings/ 4 | .classpath 5 | .idea 6 | *.iml 7 | -------------------------------------------------------------------------------- /src/main/plugin-metadata/plugin-security.policy: -------------------------------------------------------------------------------- 1 | grant { 2 | permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; 3 | permission java.lang.RuntimePermission "getClassLoader"; 4 | }; 5 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/EvaluatorFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.eval; 2 | 3 | import java.util.Map; 4 | 5 | public interface EvaluatorFactory { 6 | void init(Map settings); 7 | 8 | Evaluator create(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/SimilarityFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.similarity; 2 | 3 | import java.util.Map; 4 | 5 | public interface SimilarityFactory { 6 | 7 | void init(Map settings); 8 | 9 | T create(); 10 | 11 | } -------------------------------------------------------------------------------- /src/main/plugin-metadata/plugin-descriptor.properties: -------------------------------------------------------------------------------- 1 | description=This plugin provides Mahout Taste implementation. 2 | version=${project.version} 3 | name=taste 4 | jvm=true 5 | classname=${elasticsearch.plugin.classname} 6 | elasticsearch.version= 7 | java.version=${maven.compiler.target} 8 | isolated=true 9 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/neighborhood/UserNeighborhoodFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.neighborhood; 2 | 3 | import java.util.Map; 4 | 5 | public interface UserNeighborhoodFactory { 6 | 7 | void init(Map settings); 8 | 9 | UserNeighborhood create(); 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/module/TasteModule.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.module; 2 | 3 | import org.codelibs.elasticsearch.taste.service.TasteService; 4 | import org.elasticsearch.common.inject.AbstractModule; 5 | 6 | public class TasteModule extends AbstractModule { 7 | 8 | @Override 9 | protected void configure() { 10 | bind(TasteService.class).asEagerSingleton(); 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/CityBlockSimilarityFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.similarity; 2 | 3 | public class CityBlockSimilarityFactory extends 4 | AbstractUserSimilarityFactory { 5 | @Override 6 | public T create() { 7 | @SuppressWarnings("unchecked") 8 | final T t = (T) new CityBlockSimilarity(dataModel); 9 | return t; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/LogLikelihoodSimilarityFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.similarity; 2 | 3 | public class LogLikelihoodSimilarityFactory extends 4 | AbstractUserSimilarityFactory { 5 | 6 | @Override 7 | public T create() { 8 | @SuppressWarnings("unchecked") 9 | final T t = (T) new LogLikelihoodSimilarity(dataModel); 10 | return t; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/SpearmanCorrelationSimilarityFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.similarity; 2 | 3 | public class SpearmanCorrelationSimilarityFactory extends 4 | AbstractUserSimilarityFactory { 5 | 6 | @Override 7 | public T create() { 8 | @SuppressWarnings("unchecked") 9 | final T t = (T) new SpearmanCorrelationSimilarity(dataModel); 10 | return t; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/TanimotoCoefficientSimilarityFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.similarity; 2 | 3 | public class TanimotoCoefficientSimilarityFactory extends 4 | AbstractUserSimilarityFactory { 5 | 6 | @Override 7 | public T create() { 8 | @SuppressWarnings("unchecked") 9 | final T t = (T) new TanimotoCoefficientSimilarity(dataModel); 10 | return t; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.exception; 2 | 3 | public class NotFoundException extends TasteException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public NotFoundException(final String message, final Throwable cause) { 8 | super(message, cause); 9 | } 10 | 11 | public NotFoundException(final String message) { 12 | super(message); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/exception/MissingShardsException.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.exception; 2 | 3 | public class MissingShardsException extends TasteException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public MissingShardsException(final String message, final Throwable cause) { 8 | super(message, cause); 9 | } 10 | 11 | public MissingShardsException(final String message) { 12 | super(message); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/exception/OperationFailedException.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.exception; 2 | 3 | public class OperationFailedException extends TasteException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public OperationFailedException(final String message, final Throwable cause) { 8 | super(message, cause); 9 | } 10 | 11 | public OperationFailedException(final String message) { 12 | super(message); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/exception/InvalidParameterException.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.exception; 2 | 3 | public class InvalidParameterException extends TasteException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public InvalidParameterException(final String message, final Throwable cause) { 8 | super(message, cause); 9 | } 10 | 11 | public InvalidParameterException(final String message) { 12 | super(message); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/cache/DmValue.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.model.cache; 2 | 3 | public class DmValue { 4 | private final Object value; 5 | 6 | private final int size; 7 | 8 | public DmValue(final Object value, final int size) { 9 | this.value = value; 10 | this.size = size; 11 | } 12 | 13 | @SuppressWarnings("unchecked") 14 | public T getValue() { 15 | return (T) value; 16 | } 17 | 18 | public int getSize() { 19 | return size; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/rest/handler/RequestHandler.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.rest.handler; 2 | 3 | import java.util.Map; 4 | 5 | import org.elasticsearch.common.xcontent.ToXContent.Params; 6 | 7 | public interface RequestHandler { 8 | 9 | void execute(Params params, RequestHandler.OnErrorListener listener, 10 | Map requestMap, Map paramMap, 11 | RequestHandlerChain chain); 12 | 13 | public interface OnErrorListener { 14 | void onError(Throwable t); 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/AverageAbsoluteDifferenceEvaluatorFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.eval; 2 | 3 | import java.util.Map; 4 | 5 | public class AverageAbsoluteDifferenceEvaluatorFactory implements 6 | EvaluatorFactory { 7 | 8 | @Override 9 | public void init(final Map settings) { 10 | } 11 | 12 | @Override 13 | public Evaluator create() { 14 | final AverageAbsoluteDifferenceEvaluator evaluator = new AverageAbsoluteDifferenceEvaluator(); 15 | return evaluator; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/AbstractUserSimilarityFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.similarity; 2 | 3 | import java.util.Map; 4 | 5 | import org.codelibs.elasticsearch.taste.model.DataModel; 6 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 7 | 8 | public abstract class AbstractUserSimilarityFactory implements 9 | SimilarityFactory { 10 | 11 | protected DataModel dataModel; 12 | 13 | @Override 14 | public void init(final Map settings) { 15 | dataModel = SettingsUtils.get(settings, "dataModel"); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/util/SettingsUtils.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.util; 2 | 3 | import java.util.Map; 4 | 5 | public final class SettingsUtils { 6 | private SettingsUtils() { 7 | } 8 | 9 | public static T get(final Map settings, final String key) { 10 | return get(settings, key, null); 11 | } 12 | 13 | @SuppressWarnings("unchecked") 14 | public static T get(final Map settings, final String key, 15 | final T defaultValue) { 16 | if (settings != null) { 17 | final V value = settings.get(key); 18 | if (value != null) { 19 | return (T) value; 20 | } 21 | } 22 | return defaultValue; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/neighborhood/ThresholdUserNeighborhoodFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.neighborhood; 2 | 3 | import java.util.Map; 4 | 5 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 6 | 7 | 8 | public class ThresholdUserNeighborhoodFactory extends 9 | AbstractUserNeighborhoodFactory { 10 | protected double threshold; 11 | 12 | @Override 13 | public void init(final Map settings) { 14 | super.init(settings); 15 | threshold = SettingsUtils.get(settings, "threshold", Double.NaN); 16 | } 17 | 18 | @Override 19 | public UserNeighborhood create() { 20 | return new ThresholdUserNeighborhood(threshold, userSimilarity, 21 | dataModel); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/neighborhood/AbstractUserNeighborhoodFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.neighborhood; 2 | 3 | import java.util.Map; 4 | 5 | import org.codelibs.elasticsearch.taste.model.DataModel; 6 | import org.codelibs.elasticsearch.taste.similarity.UserSimilarity; 7 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 8 | 9 | 10 | public abstract class AbstractUserNeighborhoodFactory implements 11 | UserNeighborhoodFactory { 12 | 13 | protected DataModel dataModel; 14 | 15 | protected UserSimilarity userSimilarity; 16 | 17 | @Override 18 | public void init(final Map settings) { 19 | dataModel = SettingsUtils.get(settings, "dataModel"); 20 | userSimilarity = SettingsUtils.get(settings, "userSimilarity"); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/service/TasteService.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.service; 2 | 3 | import org.elasticsearch.ElasticsearchException; 4 | import org.elasticsearch.common.component.AbstractLifecycleComponent; 5 | import org.elasticsearch.common.inject.Inject; 6 | import org.elasticsearch.common.settings.Settings; 7 | 8 | public class TasteService extends AbstractLifecycleComponent { 9 | 10 | @Inject 11 | public TasteService(final Settings settings) { 12 | super(settings); 13 | } 14 | 15 | @Override 16 | protected void doStart() throws ElasticsearchException { 17 | } 18 | 19 | @Override 20 | protected void doStop() throws ElasticsearchException { 21 | } 22 | 23 | @Override 24 | protected void doClose() throws ElasticsearchException { 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/util/ClusterUtils.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.util; 2 | 3 | import java.util.List; 4 | 5 | import org.elasticsearch.ElasticsearchException; 6 | import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; 7 | import org.elasticsearch.client.Client; 8 | 9 | public final class ClusterUtils { 10 | private ClusterUtils() { 11 | } 12 | 13 | public static void waitForAvailable(final Client client, 14 | final String... indices) { 15 | final ClusterHealthResponse response = client.admin().cluster() 16 | .prepareHealth(indices).setWaitForYellowStatus().execute() 17 | .actionGet(); 18 | final List failures = response.getValidationFailures(); 19 | if (!failures.isEmpty()) { 20 | throw new ElasticsearchException( 21 | "Cluster is not available: " + failures.toString()); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/rest/handler/RequestHandlerChain.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.rest.handler; 2 | 3 | import java.util.Map; 4 | 5 | import org.elasticsearch.common.xcontent.ToXContent.Params; 6 | 7 | public class RequestHandlerChain { 8 | RequestHandler[] handlers; 9 | 10 | int position = 0; 11 | 12 | public RequestHandlerChain(final RequestHandler[] handlers) { 13 | this.handlers = handlers; 14 | } 15 | 16 | public void execute(final Params params, 17 | final RequestHandler.OnErrorListener listener, 18 | final Map requestMap, 19 | final Map paramMap) { 20 | synchronized (handlers) { 21 | if (position < handlers.length) { 22 | final RequestHandler handler = handlers[position]; 23 | position++; 24 | handler.execute(params, listener, requestMap, paramMap, this); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/RMSEvaluatorFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.eval; 2 | 3 | import java.util.Map; 4 | 5 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 6 | 7 | public class RMSEvaluatorFactory implements EvaluatorFactory { 8 | protected Number maxPreference; 9 | 10 | protected Number minPreference; 11 | 12 | @Override 13 | public void init(final Map settings) { 14 | maxPreference = SettingsUtils.get(settings, "max_preference"); 15 | minPreference = SettingsUtils.get(settings, "min_preference"); 16 | } 17 | 18 | @Override 19 | public Evaluator create() { 20 | final RMSEvaluator evaluator = new RMSEvaluator(); 21 | if (maxPreference != null) { 22 | evaluator.setMaxPreference(maxPreference.floatValue()); 23 | } 24 | if (minPreference != null) { 25 | evaluator.setMinPreference(minPreference.floatValue()); 26 | } 27 | return evaluator; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/EvaluationConfig.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.eval; 2 | 3 | public class EvaluationConfig { 4 | private double trainingPercentage; 5 | 6 | private double evaluationPercentage; 7 | 8 | private float marginForError; 9 | 10 | public double getTrainingPercentage() { 11 | return trainingPercentage; 12 | } 13 | 14 | public void setTrainingPercentage(final double trainingPercentage) { 15 | this.trainingPercentage = trainingPercentage; 16 | } 17 | 18 | public double getEvaluationPercentage() { 19 | return evaluationPercentage; 20 | } 21 | 22 | public void setEvaluationPercentage(final double evaluationPercentage) { 23 | this.evaluationPercentage = evaluationPercentage; 24 | } 25 | 26 | public float getMarginForError() { 27 | return marginForError; 28 | } 29 | 30 | public void setMarginForError(final float marginForError) { 31 | this.marginForError = marginForError; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/util/ListenerUtils.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.util; 2 | 3 | import org.elasticsearch.action.ActionListener; 4 | 5 | public final class ListenerUtils { 6 | private ListenerUtils() { 7 | } 8 | 9 | public static ActionListener on( 10 | final OnResponseListener responseListener, 11 | final OnFailureListener failureListener) { 12 | return new ActionListener() { 13 | 14 | @Override 15 | public void onResponse(final Response response) { 16 | responseListener.onResponse(response); 17 | } 18 | 19 | @Override 20 | public void onFailure(final Throwable e) { 21 | failureListener.onFailure(e); 22 | } 23 | }; 24 | } 25 | 26 | public interface OnResponseListener { 27 | public void onResponse(Response response); 28 | } 29 | 30 | public interface OnFailureListener { 31 | public void onFailure(Throwable t); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/assemblies/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | plugin 4 | 5 | zip 6 | 7 | false 8 | 9 | 10 | ${basedir}/src/main/plugin-metadata/plugin-descriptor.properties 11 | 12 | true 13 | 14 | 15 | ${basedir}/src/main/plugin-metadata/plugin-security.policy 16 | 17 | true 18 | 19 | 20 | 21 | 22 | / 23 | true 24 | true 25 | 26 | org.elasticsearch:elasticsearch 27 | org.apache.lucene:lucene-core 28 | org.apache.lucene:lucene-analyzers-common 29 | com.google.guava:guava 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/AbstractLongPrimitiveIterator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | public abstract class AbstractLongPrimitiveIterator implements 21 | LongPrimitiveIterator { 22 | 23 | @Override 24 | public Long next() { 25 | return nextLong(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/neighborhood/NearestNUserNeighborhoodFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.neighborhood; 2 | 3 | import java.util.Map; 4 | 5 | import org.codelibs.elasticsearch.taste.exception.TasteException; 6 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 7 | 8 | public class NearestNUserNeighborhoodFactory extends 9 | AbstractUserNeighborhoodFactory { 10 | 11 | protected int neighborhoodSize; 12 | 13 | protected double minSimilarity; 14 | 15 | @Override 16 | public void init(final Map settings) { 17 | super.init(settings); 18 | neighborhoodSize = SettingsUtils.get(settings, "neighborhood_size", 10); 19 | minSimilarity = SettingsUtils.get(settings, "min_similarity", 20 | Double.NEGATIVE_INFINITY); 21 | } 22 | 23 | @Override 24 | public UserNeighborhood create() { 25 | try { 26 | return new NearestNUserNeighborhood(neighborhoodSize, 27 | minSimilarity, userSimilarity, dataModel); 28 | } catch (final Exception e) { 29 | throw new TasteException("Failed to create an instance.", e); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/ItemBasedRecommenderBuilder.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.recommender; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.codelibs.elasticsearch.taste.model.DataModel; 7 | import org.codelibs.elasticsearch.taste.model.IndexInfo; 8 | import org.codelibs.elasticsearch.taste.similarity.ItemSimilarity; 9 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 10 | 11 | public class ItemBasedRecommenderBuilder extends AbstractRecommenderBuilder { 12 | 13 | public ItemBasedRecommenderBuilder(final IndexInfo indexInfo, 14 | final Map rootSettings) { 15 | super(indexInfo, rootSettings); 16 | } 17 | 18 | @Override 19 | public Recommender buildRecommender(final DataModel dataModel) { 20 | final Map similaritySettings = SettingsUtils.get( 21 | rootSettings, "similarity", new HashMap()); 22 | similaritySettings.put(DATA_MODEL_ATTR, dataModel); 23 | final ItemSimilarity similarity = createSimilarity(similaritySettings); 24 | 25 | return new GenericItemBasedRecommender(dataModel, similarity); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/Weighting.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | /** 21 | *

22 | * A simple enum which gives symbolic names to the ideas of "weighted" and "unweighted", to make various API 23 | * calls which take a weighting parameter more readable. 24 | *

25 | */ 26 | public enum Weighting { 27 | 28 | WEIGHTED, UNWEIGHTED 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/svd/Factorizer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender.svd; 19 | 20 | import org.codelibs.elasticsearch.taste.common.Refreshable; 21 | 22 | /** 23 | * Implementation must be able to create a factorization of a rating matrix 24 | */ 25 | public interface Factorizer extends Refreshable { 26 | 27 | Factorization factorize(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/Retriever.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | /** 21 | *

22 | * Implementations can retrieve a value for a given key. 23 | *

24 | */ 25 | public interface Retriever { 26 | 27 | /** 28 | * @param key key for which a value should be retrieved 29 | * @return value for key 30 | */ 31 | V get(K key); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/UncenteredCosineSimilarityFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.similarity; 2 | 3 | import java.util.Map; 4 | 5 | import org.codelibs.elasticsearch.taste.common.Weighting; 6 | import org.codelibs.elasticsearch.taste.exception.TasteException; 7 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 8 | 9 | public class UncenteredCosineSimilarityFactory extends 10 | AbstractUserSimilarityFactory { 11 | 12 | protected Weighting weighting; 13 | 14 | @Override 15 | public void init(final Map settings) { 16 | super.init(settings); 17 | final String value = SettingsUtils.get(settings, "weighting"); 18 | if ("WEIGHTED".equalsIgnoreCase(value)) { 19 | weighting = Weighting.WEIGHTED; 20 | } else { 21 | weighting = Weighting.UNWEIGHTED; 22 | } 23 | } 24 | 25 | @Override 26 | public T create() { 27 | try { 28 | @SuppressWarnings("unchecked") 29 | final T t = (T) new UncenteredCosineSimilarity(dataModel, weighting); 30 | return t; 31 | } catch (final Exception e) { 32 | throw new TasteException("Failed to create an instance.", e); 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/EuclideanDistanceSimilarityFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.similarity; 2 | 3 | import java.util.Map; 4 | 5 | import org.codelibs.elasticsearch.taste.common.Weighting; 6 | import org.codelibs.elasticsearch.taste.exception.TasteException; 7 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 8 | 9 | public class EuclideanDistanceSimilarityFactory extends 10 | AbstractUserSimilarityFactory { 11 | 12 | protected Weighting weighting; 13 | 14 | @Override 15 | public void init(final Map settings) { 16 | super.init(settings); 17 | final String value = SettingsUtils.get(settings, "weighting"); 18 | if ("WEIGHTED".equalsIgnoreCase(value)) { 19 | weighting = Weighting.WEIGHTED; 20 | } else { 21 | weighting = Weighting.UNWEIGHTED; 22 | } 23 | } 24 | 25 | @Override 26 | public T create() { 27 | try { 28 | @SuppressWarnings("unchecked") 29 | final T t = (T) new EuclideanDistanceSimilarity(dataModel, 30 | weighting); 31 | return t; 32 | } catch (final Exception e) { 33 | throw new TasteException("Failed to create an instance.", e); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/PearsonCorrelationSimilarityFactory.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.similarity; 2 | 3 | import java.util.Map; 4 | 5 | import org.codelibs.elasticsearch.taste.common.Weighting; 6 | import org.codelibs.elasticsearch.taste.exception.TasteException; 7 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 8 | 9 | public class PearsonCorrelationSimilarityFactory extends 10 | AbstractUserSimilarityFactory { 11 | 12 | protected Weighting weighting; 13 | 14 | @Override 15 | public void init(final Map settings) { 16 | super.init(settings); 17 | final String value = SettingsUtils.get(settings, "weighting"); 18 | if ("WEIGHTED".equalsIgnoreCase(value)) { 19 | weighting = Weighting.WEIGHTED; 20 | } else { 21 | weighting = Weighting.UNWEIGHTED; 22 | } 23 | } 24 | 25 | @Override 26 | public T create() { 27 | try { 28 | @SuppressWarnings("unchecked") 29 | final T t = (T) new PearsonCorrelationSimilarity(dataModel, 30 | weighting); 31 | return t; 32 | } catch (final Exception e) { 33 | throw new TasteException("Failed to create an instance.", e); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/LoadStatistics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import org.codelibs.elasticsearch.taste.common.RunningAverage; 21 | 22 | public final class LoadStatistics { 23 | 24 | private final RunningAverage timing; 25 | 26 | LoadStatistics(final RunningAverage timing) { 27 | this.timing = timing; 28 | } 29 | 30 | public RunningAverage getTiming() { 31 | return timing; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/exception/NoSuchItemException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.exception; 19 | 20 | public final class NoSuchItemException extends TasteException { 21 | 22 | /** 23 | * 24 | */ 25 | private static final long serialVersionUID = 1L; 26 | 27 | public NoSuchItemException(final long itemID) { 28 | this(String.valueOf(itemID)); 29 | } 30 | 31 | public NoSuchItemException(final String message) { 32 | super(message); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/exception/NoSuchUserException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.exception; 19 | 20 | public final class NoSuchUserException extends TasteException { 21 | 22 | /** 23 | * 24 | */ 25 | private static final long serialVersionUID = 1L; 26 | 27 | public NoSuchUserException(final long userID) { 28 | this(String.valueOf(userID)); 29 | } 30 | 31 | public NoSuchUserException(final String message) { 32 | super(message); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/precompute/SimilarItemsWriter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity.precompute; 19 | 20 | import java.io.Closeable; 21 | import java.io.IOException; 22 | 23 | /** 24 | * Used to persist the results of a batch item similarity computation 25 | * conducted with a {@link BatchItemSimilarities} implementation 26 | */ 27 | public interface SimilarItemsWriter extends Closeable { 28 | 29 | void open() throws IOException; 30 | 31 | void add(SimilarItems similarItems) throws IOException; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/exception/TasteException.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.exception; 2 | 3 | import org.elasticsearch.common.logging.ESLogger; 4 | import org.elasticsearch.common.logging.Loggers; 5 | 6 | public class TasteException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = 1L; 9 | 10 | private static final ESLogger logger = Loggers 11 | .getLogger(TasteException.class); 12 | 13 | public TasteException(final Throwable cause) { 14 | super(cause); 15 | rethrowInterruptedException("Interrupted.", cause); 16 | } 17 | 18 | public TasteException(final String message, final Throwable cause) { 19 | super(message, cause); 20 | rethrowInterruptedException(message, cause); 21 | } 22 | 23 | public TasteException(final String message) { 24 | super(message); 25 | } 26 | 27 | private void rethrowInterruptedException(final String message, 28 | final Throwable cause) { 29 | Throwable t = cause; 30 | while (t != null) { 31 | if (t instanceof InterruptedException) { 32 | if (logger.isDebugEnabled()) { 33 | logger.debug(message, t); 34 | } 35 | Thread.currentThread().interrupt(); 36 | break; 37 | } 38 | t = cause.getCause(); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/MostSimilarItemsCandidateItemsStrategy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FastIDSet; 21 | import org.codelibs.elasticsearch.taste.common.Refreshable; 22 | import org.codelibs.elasticsearch.taste.model.DataModel; 23 | 24 | /** 25 | * Used to retrieve all items that could possibly be similar 26 | */ 27 | public interface MostSimilarItemsCandidateItemsStrategy extends Refreshable { 28 | 29 | FastIDSet getCandidateItems(long[] itemIDs, DataModel dataModel); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/RunningAverageAndStdDev.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | /** 21 | *

22 | * Extends {@link RunningAverage} by adding standard deviation too. 23 | *

24 | */ 25 | public interface RunningAverageAndStdDev extends RunningAverage { 26 | 27 | /** @return standard deviation of data */ 28 | double getStandardDeviation(); 29 | 30 | /** 31 | * @return a (possibly immutable) object whose average is the negative of this object's 32 | */ 33 | @Override 34 | RunningAverageAndStdDev inverse(); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/svd/NoPersistenceStrategy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender.svd; 19 | 20 | import java.io.IOException; 21 | 22 | /** 23 | * A {@link PersistenceStrategy} which does nothing. 24 | */ 25 | public class NoPersistenceStrategy implements PersistenceStrategy { 26 | 27 | @Override 28 | public Factorization load() throws IOException { 29 | return null; 30 | } 31 | 32 | @Override 33 | public void maybePersist(final Factorization factorization) 34 | throws IOException { 35 | // do nothing. 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/LoadCallable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import java.util.concurrent.Callable; 21 | 22 | import org.codelibs.elasticsearch.taste.recommender.Recommender; 23 | 24 | final class LoadCallable implements Callable { 25 | 26 | private final Recommender recommender; 27 | 28 | private final long userID; 29 | 30 | LoadCallable(final Recommender recommender, final long userID) { 31 | this.recommender = recommender; 32 | this.userID = userID; 33 | } 34 | 35 | @Override 36 | public Void call() throws Exception { 37 | recommender.recommend(userID, 10); 38 | return null; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/iterator/CountingIterator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common.iterator; 19 | 20 | import com.google.common.collect.AbstractIterator; 21 | 22 | /** 23 | * Iterates over the integers from 0 through {@code to-1}. 24 | */ 25 | public final class CountingIterator extends AbstractIterator { 26 | 27 | private int count; 28 | 29 | private final int to; 30 | 31 | public CountingIterator(final int to) { 32 | this.to = to; 33 | } 34 | 35 | @Override 36 | protected Integer computeNext() { 37 | if (count < to) { 38 | return count++; 39 | } else { 40 | return endOfData(); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/SkippingIterator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | import java.util.Iterator; 21 | 22 | /** 23 | * Adds ability to skip ahead in an iterator, perhaps more efficiently than by calling {@link #next()} 24 | * repeatedly. 25 | */ 26 | public interface SkippingIterator extends Iterator { 27 | 28 | /** 29 | * Skip the next n elements supplied by this {@link Iterator}. If there are less than n elements remaining, 30 | * this skips all remaining elements in the {@link Iterator}. This method has the same effect as calling 31 | * {@link #next()} n times, except that it will never throw {@link java.util.NoSuchElementException}. 32 | */ 33 | void skip(int n); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/LongPairMatchPredicate.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity; 19 | 20 | import org.codelibs.elasticsearch.taste.common.Cache; 21 | import org.codelibs.elasticsearch.taste.common.LongPair; 22 | 23 | /** 24 | * A {@link Cache.MatchPredicate} which will match an ID against either element of a 25 | * {@link LongPair}. 26 | */ 27 | final class LongPairMatchPredicate implements Cache.MatchPredicate { 28 | 29 | private final long id; 30 | 31 | LongPairMatchPredicate(final long id) { 32 | this.id = id; 33 | } 34 | 35 | @Override 36 | public boolean matches(final LongPair pair) { 37 | return pair.getFirst() == id || pair.getSecond() == id; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/CandidateItemsStrategy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FastIDSet; 21 | import org.codelibs.elasticsearch.taste.common.Refreshable; 22 | import org.codelibs.elasticsearch.taste.model.DataModel; 23 | import org.codelibs.elasticsearch.taste.model.PreferenceArray; 24 | 25 | /** 26 | * Used to retrieve all items that could possibly be recommended to the user 27 | */ 28 | public interface CandidateItemsStrategy extends Refreshable { 29 | 30 | /** 31 | * @return IDs of all items that could be recommended to the user 32 | */ 33 | FastIDSet getCandidateItems(long userID, 34 | PreferenceArray preferencesFromUser, DataModel dataModel); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/neighborhood/UserNeighborhood.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.neighborhood; 19 | 20 | import java.util.List; 21 | 22 | import org.codelibs.elasticsearch.taste.common.Refreshable; 23 | import org.codelibs.elasticsearch.taste.recommender.SimilarUser; 24 | 25 | /** 26 | *

27 | * Implementations of this interface compute a "neighborhood" of users like a given user. This neighborhood 28 | * can be used to compute recommendations then. 29 | *

30 | */ 31 | public interface UserNeighborhood extends Refreshable { 32 | 33 | /** 34 | * @param userID 35 | * ID of user for which a neighborhood will be computed 36 | * @return IDs of users in the neighborhood 37 | */ 38 | List getUserNeighborhood(long userID); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/RecommendedItem.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | /** 21 | *

22 | * Implementations encapsulate items that are recommended, and include the item recommended and a value 23 | * expressing the strength of the preference. 24 | *

25 | */ 26 | public interface RecommendedItem { 27 | 28 | /** @return the recommended item ID */ 29 | long getItemID(); 30 | 31 | /** 32 | *

33 | * A value expressing the strength of the preference for the recommended item. The range of the values 34 | * depends on the implementation. Implementations must use larger values to express stronger preference. 35 | *

36 | * 37 | * @return strength of the preference 38 | */ 39 | float getValue(); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/LongPrimitiveIterator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | /** 21 | * Adds notion of iterating over {@code long} primitives in the style of an {@link java.util.Iterator} -- as 22 | * opposed to iterating over {@link Long}. Implementations of this interface however also implement 23 | * {@link java.util.Iterator} and {@link Iterable} over {@link Long} for convenience. 24 | */ 25 | public interface LongPrimitiveIterator extends SkippingIterator { 26 | 27 | /** 28 | * @return next {@code long} in iteration 29 | * @throws java.util.NoSuchElementException 30 | * if no more elements exist in the iteration 31 | */ 32 | long nextLong(); 33 | 34 | /** 35 | * @return next {@code long} in iteration without advancing iteration 36 | */ 37 | long peek(); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/svd/PersistenceStrategy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender.svd; 19 | 20 | import java.io.IOException; 21 | 22 | /** 23 | * Provides storage for {@link Factorization}s 24 | */ 25 | public interface PersistenceStrategy { 26 | 27 | /** 28 | * Load a factorization from a persistent store. 29 | * 30 | * @return a Factorization or null if the persistent store is empty. 31 | * 32 | * @throws IOException 33 | */ 34 | Factorization load() throws IOException; 35 | 36 | /** 37 | * Write a factorization to a persistent store unless it already 38 | * contains an identical factorization. 39 | * 40 | * @param factorization 41 | * 42 | * @throws IOException 43 | */ 44 | void maybePersist(Factorization factorization) throws IOException; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/svd/SVDPreference.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender.svd; 19 | 20 | import org.codelibs.elasticsearch.taste.model.GenericPreference; 21 | 22 | import com.google.common.base.Preconditions; 23 | 24 | final class SVDPreference extends GenericPreference { 25 | 26 | /** 27 | * 28 | */ 29 | private static final long serialVersionUID = 1L; 30 | 31 | private double cache; 32 | 33 | SVDPreference(final long userID, final long itemID, final float value, 34 | final double cache) { 35 | super(userID, itemID, value); 36 | setCache(cache); 37 | } 38 | 39 | public double getCache() { 40 | return cache; 41 | } 42 | 43 | public void setCache(final double value) { 44 | Preconditions.checkArgument(!Double.isNaN(value), "NaN cache value"); 45 | cache = value; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/RecommenderBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import org.codelibs.elasticsearch.taste.model.DataModel; 21 | import org.codelibs.elasticsearch.taste.recommender.Recommender; 22 | 23 | /** 24 | *

25 | * Implementations of this inner interface are simple helper classes which create a {@link Recommender} to be 26 | * evaluated based on the given {@link DataModel}. 27 | *

28 | */ 29 | public interface RecommenderBuilder { 30 | 31 | /** 32 | *

33 | * Builds a {@link Recommender} implementation to be evaluated, using the given {@link DataModel}. 34 | *

35 | * 36 | * @param dataModel 37 | * {@link DataModel} to build the {@link Recommender} on 38 | * @return {@link Recommender} based upon the given {@link DataModel} 39 | */ 40 | Recommender buildRecommender(DataModel dataModel); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/EstimatedPreferenceCapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import org.codelibs.elasticsearch.taste.model.DataModel; 21 | 22 | /** 23 | * Simple class which encapsulates restricting a preference value 24 | * to a predefined range. The simple logic is wrapped up here for 25 | * performance reasons. 26 | */ 27 | public final class EstimatedPreferenceCapper { 28 | 29 | private final float min; 30 | 31 | private final float max; 32 | 33 | public EstimatedPreferenceCapper(final DataModel model) { 34 | min = model.getMinPreference(); 35 | max = model.getMaxPreference(); 36 | } 37 | 38 | public float capEstimate(float estimate) { 39 | if (estimate > max) { 40 | estimate = max; 41 | } else if (estimate < min) { 42 | estimate = min; 43 | } 44 | return estimate; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/PreferenceInferrer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity; 19 | 20 | import org.codelibs.elasticsearch.taste.common.Refreshable; 21 | 22 | /** 23 | *

24 | * Implementations of this interface compute an inferred preference for a user and an item that the user has 25 | * not expressed any preference for. This might be an average of other preferences scores from that user, for 26 | * example. This technique is sometimes called "default voting". 27 | *

28 | */ 29 | public interface PreferenceInferrer extends Refreshable { 30 | 31 | /** 32 | *

33 | * Infers the given user's preference value for an item. 34 | *

35 | * 36 | * @param userID 37 | * ID of user to infer preference for 38 | * @param itemID 39 | * item ID to infer preference for 40 | * @return inferred preference 41 | */ 42 | float inferPreference(long userID, long itemID); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/Preference.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.model; 19 | 20 | /** 21 | *

22 | * A {@link Preference} encapsulates an item and a preference value, which indicates the strength of the 23 | * preference for it. {@link Preference}s are associated to users. 24 | *

25 | */ 26 | public interface Preference { 27 | 28 | /** @return ID of user who prefers the item */ 29 | long getUserID(); 30 | 31 | /** @return item ID that is preferred */ 32 | long getItemID(); 33 | 34 | /** 35 | * @return strength of the preference for that item. Zero should indicate "no preference either way"; 36 | * positive values indicate preference and negative values indicate dislike 37 | */ 38 | float getValue(); 39 | 40 | /** 41 | * Sets the strength of the preference for this item 42 | * 43 | * @param value 44 | * new preference 45 | */ 46 | void setValue(float value); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/precompute/SimilarItem.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity.precompute; 19 | 20 | import java.util.Comparator; 21 | 22 | import com.google.common.primitives.Doubles; 23 | 24 | /** 25 | * Modeling similarity towards another item 26 | */ 27 | public class SimilarItem { 28 | 29 | public static final Comparator COMPARE_BY_SIMILARITY = (s1, s2) -> Doubles.compare(s1.similarity, s2.similarity); 30 | 31 | private long itemID; 32 | 33 | private double similarity; 34 | 35 | public SimilarItem(final long itemID, final double similarity) { 36 | set(itemID, similarity); 37 | } 38 | 39 | public void set(final long itemID, final double similarity) { 40 | this.itemID = itemID; 41 | this.similarity = similarity; 42 | } 43 | 44 | public long getItemID() { 45 | return itemID; 46 | } 47 | 48 | public double getSimilarity() { 49 | return similarity; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/UpdatableIDMigrator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.model; 19 | 20 | public interface UpdatableIDMigrator extends IDMigrator { 21 | 22 | /** 23 | * Stores the reverse long-to-String mapping in some kind of backing store. Note that this must be called 24 | * directly (or indirectly through {@link #initialize(Iterable)}) for every String that might be encountered 25 | * in the application, or else the mapping will not be known. 26 | * 27 | * @param longID 28 | * long ID 29 | * @param stringID 30 | * string ID that maps to/from that long ID 31 | */ 32 | void storeMapping(long longID, String stringID); 33 | 34 | /** 35 | * Make the mapping aware of the given string IDs. This must be called initially before the implementation 36 | * is used, or else it will not be aware of reverse long-to-String mappings. 37 | * 38 | */ 39 | void initialize(Iterable stringIDs); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/ByValueRecommendedItemComparator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import java.io.Serializable; 21 | import java.util.Comparator; 22 | 23 | /** 24 | * Defines a natural ordering from most-preferred item (highest value) to least-preferred. 25 | */ 26 | public final class ByValueRecommendedItemComparator implements 27 | Comparator, Serializable { 28 | 29 | /** 30 | * 31 | */ 32 | private static final long serialVersionUID = 1L; 33 | 34 | private static final Comparator INSTANCE = new ByValueRecommendedItemComparator(); 35 | 36 | public static Comparator getInstance() { 37 | return INSTANCE; 38 | } 39 | 40 | @Override 41 | public int compare(final RecommendedItem o1, final RecommendedItem o2) { 42 | final float value1 = o1.getValue(); 43 | final float value2 = o2.getValue(); 44 | return value1 > value2 ? -1 : value1 < value2 ? 1 : 0; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/TastePlugin.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste; 2 | 3 | import java.util.Collection; 4 | 5 | import org.codelibs.elasticsearch.taste.module.TasteModule; 6 | import org.codelibs.elasticsearch.taste.rest.TasteActionRestAction; 7 | import org.codelibs.elasticsearch.taste.rest.TasteEventRestAction; 8 | import org.codelibs.elasticsearch.taste.rest.TasteSearchRestAction; 9 | import org.codelibs.elasticsearch.taste.service.TasteService; 10 | import org.elasticsearch.common.component.LifecycleComponent; 11 | import org.elasticsearch.common.inject.Module; 12 | import org.elasticsearch.plugins.Plugin; 13 | import org.elasticsearch.rest.RestModule; 14 | 15 | import com.google.common.collect.Lists; 16 | 17 | public class TastePlugin extends Plugin { 18 | @Override 19 | public String name() { 20 | return "taste"; 21 | } 22 | 23 | @Override 24 | public String description() { 25 | return "Taste plugin recommends items from data in indices."; 26 | } 27 | 28 | // for Rest API 29 | public void onModule(final RestModule module) { 30 | module.addRestAction(TasteEventRestAction.class); 31 | module.addRestAction(TasteSearchRestAction.class); 32 | module.addRestAction(TasteActionRestAction.class); 33 | } 34 | 35 | // for Service 36 | @Override 37 | public Collection nodeModules() { 38 | final Collection modules = Lists.newArrayList(); 39 | modules.add(new TasteModule()); 40 | return modules; 41 | } 42 | 43 | // for Service 44 | @SuppressWarnings("rawtypes") 45 | @Override 46 | public Collection> nodeServices() { 47 | final Collection> services = Lists 48 | .newArrayList(); 49 | services.add(TasteService.class); 50 | return services; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/DataModelBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FastByIDMap; 21 | import org.codelibs.elasticsearch.taste.model.DataModel; 22 | import org.codelibs.elasticsearch.taste.model.PreferenceArray; 23 | 24 | /** 25 | *

26 | * Implementations of this inner interface are simple helper classes which create a {@link DataModel} to be 27 | * used while evaluating a {@link org.codelibs.elasticsearch.taste.recommender.Recommender}. 28 | * 29 | * @see RecommenderBuilder 30 | * @see RecommenderEvaluator 31 | */ 32 | public interface DataModelBuilder { 33 | 34 | /** 35 | *

36 | * Builds a {@link DataModel} implementation to be used in an evaluation, given training data. 37 | *

38 | * 39 | * @param trainingData 40 | * data to be used in the {@link DataModel} 41 | * @return {@link DataModel} based upon the given data 42 | */ 43 | DataModel buildDataModel(FastByIDMap trainingData); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/AllUnknownItemsCandidateItemsStrategy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FastIDSet; 21 | import org.codelibs.elasticsearch.taste.common.LongPrimitiveIterator; 22 | import org.codelibs.elasticsearch.taste.model.DataModel; 23 | 24 | public final class AllUnknownItemsCandidateItemsStrategy extends 25 | AbstractCandidateItemsStrategy { 26 | 27 | /** 28 | * return all items the user has not yet seen 29 | */ 30 | @Override 31 | protected FastIDSet doGetCandidateItems(final long[] preferredItemIDs, 32 | final DataModel dataModel) { 33 | final FastIDSet possibleItemIDs = new FastIDSet(dataModel.getNumItems()); 34 | final LongPrimitiveIterator allItemIDs = dataModel.getItemIDs(); 35 | while (allItemIDs.hasNext()) { 36 | possibleItemIDs.add(allItemIDs.nextLong()); 37 | } 38 | possibleItemIDs.removeAll(preferredItemIDs); 39 | return possibleItemIDs; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/AbstractDataModel.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.model; 19 | 20 | /** 21 | * Contains some features common to all implementations. 22 | */ 23 | public abstract class AbstractDataModel implements DataModel { 24 | 25 | /** 26 | * 27 | */ 28 | private static final long serialVersionUID = 1L; 29 | 30 | private float maxPreference; 31 | 32 | private float minPreference; 33 | 34 | protected AbstractDataModel() { 35 | maxPreference = Float.NaN; 36 | minPreference = Float.NaN; 37 | } 38 | 39 | @Override 40 | public float getMaxPreference() { 41 | return maxPreference; 42 | } 43 | 44 | protected void setMaxPreference(final float maxPreference) { 45 | this.maxPreference = maxPreference; 46 | } 47 | 48 | @Override 49 | public float getMinPreference() { 50 | return minPreference; 51 | } 52 | 53 | protected void setMinPreference(final float minPreference) { 54 | this.minPreference = minPreference; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/IDRescorer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | /** 21 | *

22 | * A {@link Rescorer} which operates on {@code long} primitive IDs, rather than arbitrary {@link Object}s. 23 | * This is provided since most uses of this interface in the framework take IDs (as {@code long}) as an 24 | * argument, and so this can be used to avoid unnecessary boxing/unboxing. 25 | *

26 | */ 27 | public interface IDRescorer { 28 | 29 | /** 30 | * @param id 31 | * ID of thing (user, item, etc.) to rescore 32 | * @param originalScore 33 | * original score 34 | * @return modified score, or {@link Double#NaN} to indicate that this should be excluded entirely 35 | */ 36 | double rescore(long id, double originalScore); 37 | 38 | /** 39 | * Returns {@code true} to exclude the given thing. 40 | * 41 | * @param id 42 | * ID of thing (user, item, etc.) to rescore 43 | * @return {@code true} to exclude, {@code false} otherwise 44 | */ 45 | boolean isFiltered(long id); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/InvertedRunningAverage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | public final class InvertedRunningAverage implements RunningAverage { 21 | 22 | private final RunningAverage delegate; 23 | 24 | public InvertedRunningAverage(final RunningAverage delegate) { 25 | this.delegate = delegate; 26 | } 27 | 28 | @Override 29 | public void addDatum(final double datum) { 30 | throw new UnsupportedOperationException(); 31 | } 32 | 33 | @Override 34 | public void removeDatum(final double datum) { 35 | throw new UnsupportedOperationException(); 36 | } 37 | 38 | @Override 39 | public void changeDatum(final double delta) { 40 | throw new UnsupportedOperationException(); 41 | } 42 | 43 | @Override 44 | public int getCount() { 45 | return delegate.getCount(); 46 | } 47 | 48 | @Override 49 | public double getAverage() { 50 | return -delegate.getAverage(); 51 | } 52 | 53 | @Override 54 | public RunningAverage inverse() { 55 | return delegate; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/MemoryIDMigrator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.model; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FastByIDMap; 21 | 22 | /** 23 | * Implementation which stores the reverse long-to-String mapping in memory. 24 | */ 25 | public final class MemoryIDMigrator extends AbstractIDMigrator implements 26 | UpdatableIDMigrator { 27 | 28 | private final FastByIDMap longToString; 29 | 30 | public MemoryIDMigrator() { 31 | longToString = new FastByIDMap<>(100); 32 | } 33 | 34 | @Override 35 | public void storeMapping(final long longID, final String stringID) { 36 | synchronized (longToString) { 37 | longToString.put(longID, stringID); 38 | } 39 | } 40 | 41 | @Override 42 | public String toStringID(final long longID) { 43 | synchronized (longToString) { 44 | return longToString.get(longID); 45 | } 46 | } 47 | 48 | @Override 49 | public void initialize(final Iterable stringIDs) { 50 | for (final String stringID : stringIDs) { 51 | storeMapping(toLongID(stringID), stringID); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/SimilarUser.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.recommender; 2 | 3 | import org.apache.mahout.common.RandomUtils; 4 | 5 | /** Simply encapsulates a user and a similarity value. */ 6 | public final class SimilarUser implements Comparable { 7 | 8 | private final long userID; 9 | 10 | private final double similarity; 11 | 12 | public SimilarUser(final long userID, final double similarity) { 13 | this.userID = userID; 14 | this.similarity = similarity; 15 | } 16 | 17 | public long getUserID() { 18 | return userID; 19 | } 20 | 21 | public double getSimilarity() { 22 | return similarity; 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return (int) userID ^ RandomUtils.hashDouble(similarity); 28 | } 29 | 30 | @Override 31 | public boolean equals(final Object o) { 32 | if (!(o instanceof SimilarUser)) { 33 | return false; 34 | } 35 | final SimilarUser other = (SimilarUser) o; 36 | return userID == other.getUserID() 37 | && !(similarity < other.getSimilarity() 38 | || similarity > other.getSimilarity()); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "SimilarUser[user:" + userID + ", similarity:" + similarity 44 | + ']'; 45 | } 46 | 47 | /** Defines an ordering from most similar to least similar. */ 48 | @Override 49 | public int compareTo(final SimilarUser other) { 50 | final double otherSimilarity = other.getSimilarity(); 51 | if (similarity > otherSimilarity) { 52 | return -1; 53 | } 54 | if (similarity < otherSimilarity) { 55 | return 1; 56 | } 57 | final long otherUserID = other.getUserID(); 58 | if (userID < otherUserID) { 59 | return -1; 60 | } 61 | if (userID > otherUserID) { 62 | return 1; 63 | } 64 | return 0; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/FixedRunningAverageAndStdDev.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | /** 21 | *

22 | * A simple class that represents a fixed value of an average, count and standard deviation. This is useful 23 | * when an API needs to return {@link RunningAverageAndStdDev} but is not in a position to accept 24 | * updates to it. 25 | *

26 | */ 27 | public final class FixedRunningAverageAndStdDev extends FixedRunningAverage 28 | implements RunningAverageAndStdDev { 29 | 30 | /** 31 | * 32 | */ 33 | private static final long serialVersionUID = 1L; 34 | 35 | private final double stdDev; 36 | 37 | public FixedRunningAverageAndStdDev(final double average, 38 | final double stdDev, final int count) { 39 | super(average, count); 40 | this.stdDev = stdDev; 41 | } 42 | 43 | @Override 44 | public RunningAverageAndStdDev inverse() { 45 | return new InvertedRunningAverageAndStdDev(this); 46 | } 47 | 48 | @Override 49 | public synchronized String toString() { 50 | return super.toString() + ',' + stdDev; 51 | } 52 | 53 | @Override 54 | public double getStandardDeviation() { 55 | return stdDev; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/UserBasedRecommender.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.codelibs.elasticsearch.taste.recommender; 18 | 19 | import java.util.List; 20 | 21 | import org.codelibs.elasticsearch.taste.common.LongPair; 22 | 23 | /** 24 | *

25 | * Interface implemented by "user-based" recommenders. 26 | *

27 | */ 28 | public interface UserBasedRecommender extends Recommender { 29 | 30 | /** 31 | * @param userID 32 | * ID of user for which to find most similar other users 33 | * @param howMany 34 | * desired number of most similar users to find 35 | * @return users most similar to the given user 36 | */ 37 | List mostSimilarUserIDs(long userID, int howMany); 38 | 39 | /** 40 | * @param userID 41 | * ID of user for which to find most similar other users 42 | * @param howMany 43 | * desired number of most similar users to find 44 | * @param rescorer 45 | * {@link Rescorer} which can adjust user-user similarity estimates used to determine most similar 46 | * users 47 | * @return IDs of users most similar to the given user 48 | */ 49 | List mostSimilarUserIDs(long userID, int howMany, 50 | Rescorer rescorer); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/AbstractRecommenderBuilder.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.recommender; 2 | 3 | import java.util.Map; 4 | 5 | import org.codelibs.elasticsearch.taste.eval.RecommenderBuilder; 6 | import org.codelibs.elasticsearch.taste.exception.TasteException; 7 | import org.codelibs.elasticsearch.taste.model.DataModel; 8 | import org.codelibs.elasticsearch.taste.model.IndexInfo; 9 | import org.codelibs.elasticsearch.taste.similarity.SimilarityFactory; 10 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 11 | 12 | public abstract class AbstractRecommenderBuilder implements RecommenderBuilder { 13 | protected static final String DATA_MODEL_ATTR = "dataModel"; 14 | 15 | protected static final String USER_SIMILARITY_ATTR = "userSimilarity"; 16 | 17 | protected IndexInfo indexInfo; 18 | 19 | protected Map rootSettings; 20 | 21 | public AbstractRecommenderBuilder(final IndexInfo indexInfo, 22 | final Map rootSettings) { 23 | this.indexInfo = indexInfo; 24 | this.rootSettings = rootSettings; 25 | } 26 | 27 | protected T createSimilarity( 28 | final Map similaritySettings) { 29 | final String factoryName = SettingsUtils 30 | .get(similaritySettings, "factory", 31 | "org.codelibs.elasticsearch.taste.similarity.LogLikelihoodSimilarityFactory"); 32 | try { 33 | final Class clazz = Class.forName(factoryName); 34 | @SuppressWarnings("unchecked") 35 | final SimilarityFactory similarityFactory = (SimilarityFactory) clazz 36 | .newInstance(); 37 | similarityFactory.init(similaritySettings); 38 | return similarityFactory.create(); 39 | } catch (ClassNotFoundException | InstantiationException 40 | | IllegalAccessException e) { 41 | throw new TasteException("Could not create an instance of " 42 | + factoryName, e); 43 | } 44 | } 45 | 46 | @Override 47 | public abstract Recommender buildRecommender(DataModel dataModel); 48 | 49 | } -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/InvertedRunningAverageAndStdDev.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | public final class InvertedRunningAverageAndStdDev implements 21 | RunningAverageAndStdDev { 22 | 23 | private final RunningAverageAndStdDev delegate; 24 | 25 | public InvertedRunningAverageAndStdDev( 26 | final RunningAverageAndStdDev delegate) { 27 | this.delegate = delegate; 28 | } 29 | 30 | @Override 31 | public void addDatum(final double datum) { 32 | throw new UnsupportedOperationException(); 33 | } 34 | 35 | @Override 36 | public void removeDatum(final double datum) { 37 | throw new UnsupportedOperationException(); 38 | } 39 | 40 | @Override 41 | public void changeDatum(final double delta) { 42 | throw new UnsupportedOperationException(); 43 | } 44 | 45 | @Override 46 | public int getCount() { 47 | return delegate.getCount(); 48 | } 49 | 50 | @Override 51 | public double getAverage() { 52 | return -delegate.getAverage(); 53 | } 54 | 55 | @Override 56 | public double getStandardDeviation() { 57 | return delegate.getStandardDeviation(); 58 | } 59 | 60 | @Override 61 | public RunningAverageAndStdDev inverse() { 62 | return delegate; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/RMSEvaluator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FullRunningAverage; 21 | import org.codelibs.elasticsearch.taste.common.RunningAverage; 22 | import org.codelibs.elasticsearch.taste.model.Preference; 23 | 24 | /** 25 | *

26 | * A {@link org.codelibs.elasticsearch.taste.eval.RecommenderEvaluator} which computes the "root mean squared" 27 | * difference between predicted and actual ratings for users. This is the square root of the average of this 28 | * difference, squared. 29 | *

30 | */ 31 | public final class RMSEvaluator extends AbstractDifferenceEvaluator { 32 | 33 | private RunningAverage average; 34 | 35 | @Override 36 | protected void reset() { 37 | average = new FullRunningAverage(); 38 | } 39 | 40 | @Override 41 | protected void processOneEstimate(final float estimatedPreference, 42 | final Preference realPref) { 43 | final double diff = realPref.getValue() - estimatedPreference; 44 | average.addDatum(diff * diff); 45 | } 46 | 47 | @Override 48 | protected double computeFinalEvaluation() { 49 | return Math.sqrt(average.getAverage()); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "RMSEvaluator"; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/AllSimilarItemsCandidateItemsStrategy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FastIDSet; 21 | import org.codelibs.elasticsearch.taste.model.DataModel; 22 | import org.codelibs.elasticsearch.taste.similarity.ItemSimilarity; 23 | 24 | import com.google.common.base.Preconditions; 25 | 26 | /** 27 | * returns the result of {@link ItemSimilarity#allSimilarItemIDs(long)} as candidate items 28 | */ 29 | public class AllSimilarItemsCandidateItemsStrategy extends 30 | AbstractCandidateItemsStrategy { 31 | 32 | private final ItemSimilarity similarity; 33 | 34 | public AllSimilarItemsCandidateItemsStrategy(final ItemSimilarity similarity) { 35 | Preconditions.checkArgument(similarity != null, "similarity is null"); 36 | this.similarity = similarity; 37 | } 38 | 39 | @Override 40 | protected FastIDSet doGetCandidateItems(final long[] preferredItemIDs, 41 | final DataModel dataModel) { 42 | final FastIDSet candidateItemIDs = new FastIDSet(); 43 | for (final long itemID : preferredItemIDs) { 44 | candidateItemIDs.addAll(similarity.allSimilarItemIDs(itemID)); 45 | } 46 | candidateItemIDs.removeAll(preferredItemIDs); 47 | return candidateItemIDs; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/AbstractCandidateItemsStrategy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import java.util.Collection; 21 | 22 | import org.codelibs.elasticsearch.taste.common.FastIDSet; 23 | import org.codelibs.elasticsearch.taste.common.Refreshable; 24 | import org.codelibs.elasticsearch.taste.model.DataModel; 25 | import org.codelibs.elasticsearch.taste.model.PreferenceArray; 26 | 27 | /** 28 | * Abstract base implementation for retrieving candidate items to recommend 29 | */ 30 | public abstract class AbstractCandidateItemsStrategy implements 31 | CandidateItemsStrategy, MostSimilarItemsCandidateItemsStrategy { 32 | 33 | @Override 34 | public FastIDSet getCandidateItems(final long userID, 35 | final PreferenceArray preferencesFromUser, final DataModel dataModel) { 36 | return doGetCandidateItems(preferencesFromUser.getIDs(), dataModel); 37 | } 38 | 39 | @Override 40 | public FastIDSet getCandidateItems(final long[] itemIDs, 41 | final DataModel dataModel) { 42 | return doGetCandidateItems(itemIDs, dataModel); 43 | } 44 | 45 | protected abstract FastIDSet doGetCandidateItems(long[] preferredItemIDs, 46 | DataModel dataModel); 47 | 48 | @Override 49 | public void refresh(final Collection alreadyRefreshed) { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/Rescorer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | /** 21 | *

22 | * A {@link Rescorer} simply assigns a new "score" to a thing like an ID of an item or user which a 23 | * {@link Recommender} is considering returning as a top recommendation. It may be used to arbitrarily re-rank 24 | * the results according to application-specific logic before returning recommendations. For example, an 25 | * application may want to boost the score of items in a certain category just for one request. 26 | *

27 | * 28 | *

29 | * A {@link Rescorer} can also exclude a thing from consideration entirely by returning {@code true} from 30 | * {@link #isFiltered(Object)}. 31 | *

32 | */ 33 | public interface Rescorer { 34 | 35 | /** 36 | * @param thing 37 | * thing to rescore 38 | * @param originalScore 39 | * original score 40 | * @return modified score, or {@link Double#NaN} to indicate that this should be excluded entirely 41 | */ 42 | double rescore(T thing, double originalScore); 43 | 44 | /** 45 | * Returns {@code true} to exclude the given thing. 46 | * 47 | * @param thing 48 | * the thing to filter 49 | * @return {@code true} to exclude, {@code false} otherwise 50 | */ 51 | boolean isFiltered(T thing); 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/AverageAbsoluteDifferenceEvaluator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FullRunningAverage; 21 | import org.codelibs.elasticsearch.taste.common.RunningAverage; 22 | import org.codelibs.elasticsearch.taste.model.Preference; 23 | 24 | /** 25 | *

26 | * A {@link org.codelibs.elasticsearch.taste.eval.RecommenderEvaluator} which computes the average absolute 27 | * difference between predicted and actual ratings for users. 28 | *

29 | * 30 | *

31 | * This algorithm is also called "mean average error". 32 | *

33 | */ 34 | public final class AverageAbsoluteDifferenceEvaluator extends 35 | AbstractDifferenceEvaluator { 36 | 37 | private RunningAverage average; 38 | 39 | @Override 40 | protected void reset() { 41 | average = new FullRunningAverage(); 42 | } 43 | 44 | @Override 45 | protected void processOneEstimate(final float estimatedPreference, 46 | final Preference realPref) { 47 | average.addDatum(Math.abs(realPref.getValue() - estimatedPreference)); 48 | } 49 | 50 | @Override 51 | protected double computeFinalEvaluation() { 52 | return average.getAverage(); 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "AverageAbsoluteDifferenceEvaluator"; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/RMSRecommenderEvaluator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FullRunningAverage; 21 | import org.codelibs.elasticsearch.taste.common.RunningAverage; 22 | import org.codelibs.elasticsearch.taste.model.Preference; 23 | 24 | /** 25 | *

26 | * A {@link org.codelibs.elasticsearch.taste.eval.RecommenderEvaluator} which computes the "root mean squared" 27 | * difference between predicted and actual ratings for users. This is the square root of the average of this 28 | * difference, squared. 29 | *

30 | */ 31 | public final class RMSRecommenderEvaluator extends 32 | AbstractDifferenceRecommenderEvaluator { 33 | 34 | private RunningAverage average; 35 | 36 | @Override 37 | protected void reset() { 38 | average = new FullRunningAverage(); 39 | } 40 | 41 | @Override 42 | protected void processOneEstimate(final float estimatedPreference, 43 | final Preference realPref) { 44 | final double diff = realPref.getValue() - estimatedPreference; 45 | average.addDatum(diff * diff); 46 | } 47 | 48 | @Override 49 | protected double computeFinalEvaluation() { 50 | return Math.sqrt(average.getAverage()); 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "RMSRecommenderEvaluator"; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/AverageAbsoluteDifferenceRecommenderEvaluator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FullRunningAverage; 21 | import org.codelibs.elasticsearch.taste.common.RunningAverage; 22 | import org.codelibs.elasticsearch.taste.model.Preference; 23 | 24 | /** 25 | *

26 | * A {@link org.codelibs.elasticsearch.taste.eval.RecommenderEvaluator} which computes the average absolute 27 | * difference between predicted and actual ratings for users. 28 | *

29 | * 30 | *

31 | * This algorithm is also called "mean average error". 32 | *

33 | */ 34 | public final class AverageAbsoluteDifferenceRecommenderEvaluator extends 35 | AbstractDifferenceRecommenderEvaluator { 36 | 37 | private RunningAverage average; 38 | 39 | @Override 40 | protected void reset() { 41 | average = new FullRunningAverage(); 42 | } 43 | 44 | @Override 45 | protected void processOneEstimate(final float estimatedPreference, 46 | final Preference realPref) { 47 | average.addDatum(Math.abs(realPref.getValue() - estimatedPreference)); 48 | } 49 | 50 | @Override 51 | protected double computeFinalEvaluation() { 52 | return average.getAverage(); 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "AverageAbsoluteDifferenceRecommenderEvaluator"; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/UserSimilarity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity; 19 | 20 | import org.codelibs.elasticsearch.taste.common.Refreshable; 21 | 22 | /** 23 | *

24 | * Implementations of this interface define a notion of similarity between two users. Implementations should 25 | * return values in the range -1.0 to 1.0, with 1.0 representing perfect similarity. 26 | *

27 | * 28 | * @see ItemSimilarity 29 | */ 30 | public interface UserSimilarity extends Refreshable { 31 | 32 | /** 33 | *

34 | * Returns the degree of similarity, of two users, based on the their preferences. 35 | *

36 | * 37 | * @param userID1 first user ID 38 | * @param userID2 second user ID 39 | * @return similarity between the users, in [-1,1] or {@link Double#NaN} similarity is unknown 40 | * @throws org.codelibs.elasticsearch.taste.exception.NoSuchUserException 41 | * if either user is known to be non-existent in the data 42 | */ 43 | double userSimilarity(long userID1, long userID2); 44 | 45 | // Should we implement userSimilarities() like ItemSimilarity.itemSimilarities()? 46 | 47 | /** 48 | *

49 | * Attaches a {@link PreferenceInferrer} to the {@link UserSimilarity} implementation. 50 | *

51 | * 52 | * @param inferrer {@link PreferenceInferrer} 53 | */ 54 | void setPreferenceInferrer(PreferenceInferrer inferrer); 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/TasteConstants.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste; 2 | 3 | public class TasteConstants { 4 | 5 | private TasteConstants() { 6 | } 7 | 8 | public static final String EMPTY_STRING = ""; 9 | 10 | public static final String TRUE = "true"; 11 | 12 | public static final String YES = "yes"; 13 | 14 | public static final String ITEM_TYPE = "item"; 15 | 16 | public static final String USER_TYPE = "user"; 17 | 18 | public static final String PREFERENCE_TYPE = "preference"; 19 | 20 | public static final String ITEM_SIMILARITY_TYPE = "item_similarity"; 21 | 22 | public static final String USER_SIMILARITY_TYPE = "user_similarity"; 23 | 24 | public static final String REPORT_TYPE = "report"; 25 | 26 | public static final String RECOMMENDATION_TYPE = "recommendation"; 27 | 28 | public static final String VALUE_FIELD = "value"; 29 | 30 | public static final String ITEM_ID_FIELD = "item_id"; 31 | 32 | public static final String USER_ID_FIELD = "user_id"; 33 | 34 | public static final String TIMESTAMP_FIELD = "@timestamp"; 35 | 36 | public static final String ITEMS_FILED = "items"; 37 | 38 | public static final String RESULT_TYPE = "result"; 39 | 40 | public static final String USERS_FILED = "users"; 41 | 42 | public static final String REQUEST_PARAM_USER_ID_FIELD = "user_id_field"; 43 | 44 | public static final String REQUEST_PARAM_ITEM_ID_FIELD = "item_id_field"; 45 | 46 | public static final String REQUEST_PARAM_VALUE_FIELD = "value_field"; 47 | 48 | public static final String REQUEST_PARAM_TIMESTAMP_FIELD = "timestamp_field"; 49 | 50 | public static final String REQUEST_PARAM_USER_INDEX = "user_index"; 51 | 52 | public static final String REQUEST_PARAM_USER_TYPE = "user_type"; 53 | 54 | public static final String REQUEST_PARAM_ITEM_INDEX = "item_index"; 55 | 56 | public static final String REQUEST_PARAM_ITEM_TYPE = "item_type"; 57 | 58 | public static final String REQUEST_PARAM_PREFERENCE_INDEX = "preference_index"; 59 | 60 | public static final String REQUEST_PARAM_PREFERENCE_TYPE = "preference_type"; 61 | 62 | public static final String REQUEST_PARAM_TARGET_ID_FIELD = "target_id_field"; 63 | 64 | public static final String REQUEST_PARAM_ID_FIELD = "id_field"; 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/iterator/FixedSizeSamplingIterator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common.iterator; 19 | 20 | import java.util.Iterator; 21 | import java.util.List; 22 | import java.util.Random; 23 | 24 | import org.apache.mahout.common.RandomUtils; 25 | 26 | import com.google.common.collect.ForwardingIterator; 27 | import com.google.common.collect.Lists; 28 | 29 | /** 30 | * Sample a fixed number of elements from an Iterator. The results can appear in any order. 31 | */ 32 | public final class FixedSizeSamplingIterator extends ForwardingIterator { 33 | 34 | private final Iterator delegate; 35 | 36 | public FixedSizeSamplingIterator(final int size, final Iterator source) { 37 | final List buf = Lists.newArrayListWithCapacity(size); 38 | int sofar = 0; 39 | final Random random = RandomUtils.getRandom(); 40 | while (source.hasNext()) { 41 | final T v = source.next(); 42 | sofar++; 43 | if (buf.size() < size) { 44 | buf.add(v); 45 | } else { 46 | final int position = random.nextInt(sofar); 47 | if (position < buf.size()) { 48 | buf.set(position, v); 49 | } 50 | } 51 | } 52 | delegate = buf.iterator(); 53 | } 54 | 55 | @Override 56 | protected Iterator delegate() { 57 | return delegate; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/BooleanPreference.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.model; 19 | 20 | import java.io.Serializable; 21 | 22 | /** 23 | * Encapsulates a simple boolean "preference" for an item whose value does not matter (is fixed at 1.0). This 24 | * is appropriate in situations where users conceptually have only a general "yes" preference for items, 25 | * rather than a spectrum of preference values. 26 | */ 27 | public final class BooleanPreference implements Preference, Serializable { 28 | 29 | /** 30 | * 31 | */ 32 | private static final long serialVersionUID = 1L; 33 | 34 | private final long userID; 35 | 36 | private final long itemID; 37 | 38 | public BooleanPreference(final long userID, final long itemID) { 39 | this.userID = userID; 40 | this.itemID = itemID; 41 | } 42 | 43 | @Override 44 | public long getUserID() { 45 | return userID; 46 | } 47 | 48 | @Override 49 | public long getItemID() { 50 | return itemID; 51 | } 52 | 53 | @Override 54 | public float getValue() { 55 | return 1.0f; 56 | } 57 | 58 | @Override 59 | public void setValue(final float value) { 60 | throw new UnsupportedOperationException(); 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return "BooleanPreference[userID: " + userID + ", itemID:" + itemID 66 | + ']'; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/PreferredItemsNeighborhoodCandidateItemsStrategy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FastIDSet; 21 | import org.codelibs.elasticsearch.taste.model.DataModel; 22 | import org.codelibs.elasticsearch.taste.model.PreferenceArray; 23 | 24 | public final class PreferredItemsNeighborhoodCandidateItemsStrategy extends 25 | AbstractCandidateItemsStrategy { 26 | 27 | /** 28 | * returns all items that have not been rated by the user and that were preferred by another user 29 | * that has preferred at least one item that the current user has preferred too 30 | */ 31 | @Override 32 | protected FastIDSet doGetCandidateItems(final long[] preferredItemIDs, 33 | final DataModel dataModel) { 34 | final FastIDSet possibleItemsIDs = new FastIDSet(); 35 | for (final long itemID : preferredItemIDs) { 36 | final PreferenceArray itemPreferences = dataModel 37 | .getPreferencesForItem(itemID); 38 | final int numUsersPreferringItem = itemPreferences.length(); 39 | for (int index = 0; index < numUsersPreferringItem; index++) { 40 | possibleItemsIDs.addAll(dataModel 41 | .getItemIDsFromUser(itemPreferences.getUserID(index))); 42 | } 43 | } 44 | possibleItemsIDs.removeAll(preferredItemIDs); 45 | return possibleItemsIDs; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/AbstractIDMigrator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.model; 19 | 20 | import java.security.MessageDigest; 21 | import java.security.NoSuchAlgorithmException; 22 | import java.util.Collection; 23 | 24 | import org.codelibs.elasticsearch.taste.common.Refreshable; 25 | 26 | import com.google.common.base.Charsets; 27 | 28 | public abstract class AbstractIDMigrator implements IDMigrator { 29 | 30 | private final MessageDigest md5Digest; 31 | 32 | protected AbstractIDMigrator() { 33 | try { 34 | md5Digest = MessageDigest.getInstance("MD5"); 35 | } catch (final NoSuchAlgorithmException nsae) { 36 | // Can't happen 37 | throw new IllegalStateException(nsae); 38 | } 39 | } 40 | 41 | /** 42 | * @return most significant 8 bytes of the MD5 hash of the string, as a long 43 | */ 44 | protected final long hash(final String value) { 45 | byte[] md5hash; 46 | synchronized (md5Digest) { 47 | md5hash = md5Digest.digest(value.getBytes(Charsets.UTF_8)); 48 | md5Digest.reset(); 49 | } 50 | long hash = 0L; 51 | for (int i = 0; i < 8; i++) { 52 | hash = hash << 8 | md5hash[i] & 0x00000000000000FFL; 53 | } 54 | return hash; 55 | } 56 | 57 | @Override 58 | public long toLongID(final String stringID) { 59 | return hash(stringID); 60 | } 61 | 62 | @Override 63 | public void refresh(final Collection alreadyRefreshed) { 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/precompute/BatchItemSimilarities.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity.precompute; 19 | 20 | import java.io.IOException; 21 | 22 | import org.codelibs.elasticsearch.taste.recommender.ItemBasedRecommender; 23 | 24 | public abstract class BatchItemSimilarities { 25 | 26 | private final ItemBasedRecommender recommender; 27 | 28 | private final int similarItemsPerItem; 29 | 30 | /** 31 | * @param recommender recommender to use 32 | * @param similarItemsPerItem number of similar items to compute per item 33 | */ 34 | protected BatchItemSimilarities(final ItemBasedRecommender recommender, 35 | final int similarItemsPerItem) { 36 | this.recommender = recommender; 37 | this.similarItemsPerItem = similarItemsPerItem; 38 | } 39 | 40 | protected ItemBasedRecommender getRecommender() { 41 | return recommender; 42 | } 43 | 44 | protected int getSimilarItemsPerItem() { 45 | return similarItemsPerItem; 46 | } 47 | 48 | /** 49 | * @param degreeOfParallelism number of threads to use for the computation 50 | * @param maxDurationInHours maximum duration of the computation 51 | * @param writer {@link SimilarItemsWriter} used to persist the results 52 | * @return the number of similarities precomputed 53 | * @throws IOException 54 | */ 55 | public abstract int computeItemSimilarities(int degreeOfParallelism, 56 | int maxDurationInHours, SimilarItemsWriter writer) 57 | throws IOException; 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/ByRescoreComparator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import java.io.Serializable; 21 | import java.util.Comparator; 22 | 23 | /** 24 | *

25 | * Defines ordering on {@link RecommendedItem} by the rescored value of the recommendations' estimated 26 | * preference value, from high to low. 27 | *

28 | */ 29 | final class ByRescoreComparator implements Comparator, 30 | Serializable { 31 | 32 | /** 33 | * 34 | */ 35 | private static final long serialVersionUID = 1L; 36 | 37 | private final IDRescorer rescorer; 38 | 39 | ByRescoreComparator(final IDRescorer rescorer) { 40 | this.rescorer = rescorer; 41 | } 42 | 43 | @Override 44 | public int compare(final RecommendedItem o1, final RecommendedItem o2) { 45 | double rescored1; 46 | double rescored2; 47 | if (rescorer == null) { 48 | rescored1 = o1.getValue(); 49 | rescored2 = o2.getValue(); 50 | } else { 51 | rescored1 = rescorer.rescore(o1.getItemID(), o1.getValue()); 52 | rescored2 = rescorer.rescore(o2.getItemID(), o2.getValue()); 53 | } 54 | if (rescored1 < rescored2) { 55 | return 1; 56 | } else if (rescored1 > rescored2) { 57 | return -1; 58 | } else { 59 | return 0; 60 | } 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return "ByRescoreComparator[rescorer:" + rescorer + ']'; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/GenericPreference.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.model; 19 | 20 | import java.io.Serializable; 21 | 22 | import com.google.common.base.Preconditions; 23 | 24 | /** 25 | *

26 | * A simple {@link Preference} encapsulating an item and preference value. 27 | *

28 | */ 29 | public class GenericPreference implements Preference, Serializable { 30 | 31 | /** 32 | * 33 | */ 34 | private static final long serialVersionUID = 1L; 35 | 36 | private final long userID; 37 | 38 | private final long itemID; 39 | 40 | private float value; 41 | 42 | public GenericPreference(final long userID, final long itemID, 43 | final float value) { 44 | Preconditions.checkArgument(!Float.isNaN(value), "NaN value"); 45 | this.userID = userID; 46 | this.itemID = itemID; 47 | this.value = value; 48 | } 49 | 50 | @Override 51 | public long getUserID() { 52 | return userID; 53 | } 54 | 55 | @Override 56 | public long getItemID() { 57 | return itemID; 58 | } 59 | 60 | @Override 61 | public float getValue() { 62 | return value; 63 | } 64 | 65 | @Override 66 | public void setValue(final float value) { 67 | Preconditions.checkArgument(!Float.isNaN(value), "NaN value"); 68 | this.value = value; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return "GenericPreference[userID: " + userID + ", itemID:" + itemID 74 | + ", value:" + value + ']'; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/Refreshable.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | import java.util.Collection; 21 | 22 | /** 23 | *

24 | * Implementations of this interface have state that can be periodically refreshed. For example, an 25 | * implementation instance might contain some pre-computed information that should be periodically refreshed. 26 | * The {@link #refresh(Collection)} method triggers such a refresh. 27 | *

28 | * 29 | *

30 | * All Taste components implement this. In particular, 31 | * {@link org.codelibs.elasticsearch.taste.recommender.Recommender}s do. Callers may want to call 32 | * {@link #refresh(Collection)} periodically to re-compute information throughout the system and bring it up 33 | * to date, though this operation may be expensive. 34 | *

35 | */ 36 | public interface Refreshable { 37 | 38 | /** 39 | *

40 | * Triggers "refresh" -- whatever that means -- of the implementation. The general contract is that any 41 | * {@link Refreshable} should always leave itself in a consistent, operational state, and that the refresh 42 | * atomically updates internal state from old to new. 43 | *

44 | * 45 | * @param alreadyRefreshed 46 | * {@link org.codelibs.elasticsearch.taste.common.Refreshable}s that are known to have already been 47 | * refreshed as a result of an initial call to a {@link #refresh(Collection)} method on some 48 | * object. This ensure that objects in a refresh dependency graph aren't refreshed twice 49 | * needlessly. 50 | */ 51 | void refresh(Collection alreadyRefreshed); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/RunningAverage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | /** 21 | *

22 | * Interface for classes that can keep track of a running average of a series of numbers. One can add to or 23 | * remove from the series, as well as update a datum in the series. The class does not actually keep track of 24 | * the series of values, just its running average, so it doesn't even matter if you remove/change a value that 25 | * wasn't added. 26 | *

27 | */ 28 | public interface RunningAverage { 29 | 30 | /** 31 | * @param datum 32 | * new item to add to the running average 33 | * @throws IllegalArgumentException 34 | * if datum is {@link Double#NaN} 35 | */ 36 | void addDatum(double datum); 37 | 38 | /** 39 | * @param datum 40 | * item to remove to the running average 41 | * @throws IllegalArgumentException 42 | * if datum is {@link Double#NaN} 43 | * @throws IllegalStateException 44 | * if count is 0 45 | */ 46 | void removeDatum(double datum); 47 | 48 | /** 49 | * @param delta 50 | * amount by which to change a datum in the running average 51 | * @throws IllegalArgumentException 52 | * if delta is {@link Double#NaN} 53 | * @throws IllegalStateException 54 | * if count is 0 55 | */ 56 | void changeDatum(double delta); 57 | 58 | int getCount(); 59 | 60 | double getAverage(); 61 | 62 | /** 63 | * @return a (possibly immutable) object whose average is the negative of this object's 64 | */ 65 | RunningAverage inverse(); 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/ItemSimilarity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity; 19 | 20 | import org.codelibs.elasticsearch.taste.common.Refreshable; 21 | 22 | /** 23 | *

24 | * Implementations of this interface define a notion of similarity between two items. Implementations should 25 | * return values in the range -1.0 to 1.0, with 1.0 representing perfect similarity. 26 | *

27 | * 28 | * @see UserSimilarity 29 | */ 30 | public interface ItemSimilarity extends Refreshable { 31 | 32 | /** 33 | *

34 | * Returns the degree of similarity, of two items, based on the preferences that users have expressed for 35 | * the items. 36 | *

37 | * 38 | * @param itemID1 first item ID 39 | * @param itemID2 second item ID 40 | * @return similarity between the items, in [-1,1] or {@link Double#NaN} similarity is unknown 41 | * @throws org.codelibs.elasticsearch.taste.exception.NoSuchItemException 42 | * if either item is known to be non-existent in the data 43 | */ 44 | double itemSimilarity(long itemID1, long itemID2); 45 | 46 | /** 47 | *

A bulk-get version of {@link #itemSimilarity(long, long)}.

48 | * 49 | * @param itemID1 first item ID 50 | * @param itemID2s second item IDs to compute similarity with 51 | * @return similarity between itemID1 and other items 52 | * @throws org.codelibs.elasticsearch.taste.exception.NoSuchItemException 53 | * if any item is known to be non-existent in the data 54 | */ 55 | double[] itemSimilarities(long itemID1, long[] itemID2s); 56 | 57 | /** 58 | * @return all IDs of similar items, in no particular order 59 | */ 60 | long[] allSimilarItemIDs(long itemID); 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/precompute/FileSimilarItemsWriter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity.precompute; 19 | 20 | import java.io.BufferedWriter; 21 | import java.io.File; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.io.OutputStreamWriter; 25 | 26 | import com.google.common.base.Charsets; 27 | import com.google.common.io.Closeables; 28 | 29 | /** 30 | * Persist the precomputed item similarities to a file that can later be used 31 | * by a {@link org.codelibs.elasticsearch.taste.impl.similarity.file.FileItemSimilarity} 32 | */ 33 | public class FileSimilarItemsWriter implements SimilarItemsWriter { 34 | 35 | private final File file; 36 | 37 | private BufferedWriter writer; 38 | 39 | public FileSimilarItemsWriter(final File file) { 40 | this.file = file; 41 | } 42 | 43 | @Override 44 | public void open() throws IOException { 45 | writer = new BufferedWriter(new OutputStreamWriter( 46 | new FileOutputStream(file), Charsets.UTF_8)); 47 | } 48 | 49 | @Override 50 | public void add(final SimilarItems similarItems) throws IOException { 51 | final String itemID = String.valueOf(similarItems.getItemID()); 52 | for (final SimilarItem similarItem : similarItems.getSimilarItems()) { 53 | writer.write(itemID); 54 | writer.write(','); 55 | writer.write(String.valueOf(similarItem.getItemID())); 56 | writer.write(','); 57 | writer.write(String.valueOf(similarItem.getSimilarity())); 58 | writer.newLine(); 59 | } 60 | } 61 | 62 | @Override 63 | public void close() throws IOException { 64 | Closeables.close(writer, false); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/cache/DmKey.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.model.cache; 2 | 3 | public class DmKey { 4 | public static final int PREFERENCES_FROM_USER = 1; 5 | 6 | public static final int ITEMIDS_FROM_USER = 2; 7 | 8 | public static final int PREFERENCES_FROM_ITEM = 3; 9 | 10 | public static final int PREFERENCE_VALUE = 4; 11 | 12 | public static final int PREFERENCE_TIME = 5; 13 | 14 | public static final int EXISTS_USER_ID = 6; 15 | 16 | public static final int EXISTS_ITEM_ID = 7; 17 | 18 | public static final int NUM_USERS_FOR_ITEM = 8; 19 | 20 | public static final int NUM_USERS_FOR_ITEMS = 9; 21 | 22 | private static final ThreadLocal localDmKey = new ThreadLocal<>(); 23 | 24 | private int type; 25 | 26 | private long id1; 27 | 28 | private long id2; 29 | 30 | @Override 31 | public int hashCode() { 32 | final int prime = 31; 33 | int result = 1; 34 | result = prime * result + (int) (id1 ^ id1 >>> 32); 35 | result = prime * result + (int) (id2 ^ id2 >>> 32); 36 | result = prime * result + type; 37 | return result; 38 | } 39 | 40 | @Override 41 | public boolean equals(final Object obj) { 42 | if (obj == null) { 43 | return false; 44 | } 45 | if (getClass() != obj.getClass()) { 46 | return false; 47 | } 48 | final DmKey other = (DmKey) obj; 49 | if (type != other.type) { 50 | return false; 51 | } 52 | if (id1 != other.id1) { 53 | return false; 54 | } 55 | if (id2 != other.id2) { 56 | return false; 57 | } 58 | return true; 59 | } 60 | 61 | public static DmKey key(final int type, final long id1) { 62 | return key(type, id1, 0); 63 | } 64 | 65 | public static DmKey key(final int type, final long id1, final long id2) { 66 | DmKey dmKey = localDmKey.get(); 67 | if (dmKey == null) { 68 | dmKey = new DmKey(); 69 | localDmKey.set(dmKey); 70 | } 71 | dmKey.type = type; 72 | dmKey.id1 = id1; 73 | dmKey.id2 = id2; 74 | return dmKey; 75 | } 76 | 77 | public static DmKey create(final int type, final long id1) { 78 | return create(type, id1, 0); 79 | } 80 | 81 | public static DmKey create(final int type, final long id1, final long id2) { 82 | final DmKey dmKey = new DmKey(); 83 | dmKey.type = type; 84 | dmKey.id1 = id1; 85 | dmKey.id2 = id2; 86 | return dmKey; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/GenericRecommendedItem.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import java.io.Serializable; 21 | 22 | import org.apache.mahout.common.RandomUtils; 23 | 24 | import com.google.common.base.Preconditions; 25 | 26 | /** 27 | *

28 | * A simple implementation of {@link RecommendedItem}. 29 | *

30 | */ 31 | public final class GenericRecommendedItem implements RecommendedItem, 32 | Serializable { 33 | 34 | /** 35 | * 36 | */ 37 | private static final long serialVersionUID = 1L; 38 | 39 | private final long itemID; 40 | 41 | private final float value; 42 | 43 | /** 44 | * @throws IllegalArgumentException 45 | * if item is null or value is NaN 46 | */ 47 | public GenericRecommendedItem(final long itemID, final float value) { 48 | Preconditions.checkArgument(!Float.isNaN(value), "value is NaN"); 49 | this.itemID = itemID; 50 | this.value = value; 51 | } 52 | 53 | @Override 54 | public long getItemID() { 55 | return itemID; 56 | } 57 | 58 | @Override 59 | public float getValue() { 60 | return value; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return "RecommendedItem[item:" + itemID + ", value:" + value + ']'; 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | return (int) itemID ^ RandomUtils.hashFloat(value); 71 | } 72 | 73 | @Override 74 | public boolean equals(final Object o) { 75 | if (!(o instanceof GenericRecommendedItem)) { 76 | return false; 77 | } 78 | final RecommendedItem other = (RecommendedItem) o; 79 | return itemID == other.getItemID() 80 | && !(value < other.getValue() || value > other.getValue()); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/IRStatistics.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | /** 21 | *

22 | * Implementations encapsulate information retrieval-related statistics about a 23 | * {@link org.codelibs.elasticsearch.taste.recommender.Recommender}'s recommendations. 24 | *

25 | * 26 | *

27 | * See Information retrieval. 28 | *

29 | */ 30 | public interface IRStatistics { 31 | 32 | /** 33 | *

34 | * See Precision. 35 | *

36 | */ 37 | double getPrecision(); 38 | 39 | /** 40 | *

41 | * See Recall. 42 | *

43 | */ 44 | double getRecall(); 45 | 46 | /** 47 | *

48 | * See Fall-Out. 49 | *

50 | */ 51 | double getFallOut(); 52 | 53 | /** 54 | *

55 | * See F-measure. 56 | *

57 | */ 58 | double getF1Measure(); 59 | 60 | /** 61 | *

62 | * See F-measure. 63 | *

64 | */ 65 | double getFNMeasure(double n); 66 | 67 | /** 68 | *

69 | * See 70 | * Normalized Discounted Cumulative Gain. 71 | *

72 | */ 73 | double getNormalizedDiscountedCumulativeGain(); 74 | 75 | /** 76 | * @return the fraction of all users for whom recommendations could be produced 77 | */ 78 | double getReach(); 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/RecommenderIRStatsEvaluator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import org.codelibs.elasticsearch.taste.model.DataModel; 21 | import org.codelibs.elasticsearch.taste.recommender.IDRescorer; 22 | 23 | /** 24 | *

25 | * Implementations collect information retrieval-related statistics on a 26 | * {@link org.codelibs.elasticsearch.taste.recommender.Recommender}'s performance, including precision, recall and 27 | * f-measure. 28 | *

29 | * 30 | *

31 | * See Information retrieval. 32 | */ 33 | public interface RecommenderIRStatsEvaluator { 34 | 35 | /** 36 | * @param recommenderBuilder 37 | * object that can build a {@link org.codelibs.elasticsearch.taste.recommender.Recommender} to test 38 | * @param dataModelBuilder 39 | * {@link DataModelBuilder} to use, or if null, a default {@link DataModel} implementation will be 40 | * used 41 | * @param dataModel 42 | * dataset to test on 43 | * @param rescorer 44 | * if any, to use when computing recommendations 45 | * @param at 46 | * as in, "precision at 5". The number of recommendations to consider when evaluating precision, 47 | * etc. 48 | * @param relevanceThreshold 49 | * items whose preference value is at least this value are considered "relevant" for the purposes 50 | * of computations 51 | * @return {@link IRStatistics} with resulting precision, recall, etc. 52 | */ 53 | IRStatistics evaluate(RecommenderBuilder recommenderBuilder, 54 | DataModelBuilder dataModelBuilder, DataModel dataModel, 55 | IDRescorer rescorer, int at, double relevanceThreshold, 56 | double evaluationPercentage); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/AbstractItemSimilarity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity; 19 | 20 | import java.util.Collection; 21 | 22 | import org.codelibs.elasticsearch.taste.common.FastIDSet; 23 | import org.codelibs.elasticsearch.taste.common.LongPrimitiveIterator; 24 | import org.codelibs.elasticsearch.taste.common.RefreshHelper; 25 | import org.codelibs.elasticsearch.taste.common.Refreshable; 26 | import org.codelibs.elasticsearch.taste.model.DataModel; 27 | 28 | import com.google.common.base.Preconditions; 29 | 30 | public abstract class AbstractItemSimilarity implements ItemSimilarity { 31 | 32 | private final DataModel dataModel; 33 | 34 | private final RefreshHelper refreshHelper; 35 | 36 | protected AbstractItemSimilarity(final DataModel dataModel) { 37 | Preconditions.checkArgument(dataModel != null, "dataModel is null"); 38 | this.dataModel = dataModel; 39 | refreshHelper = new RefreshHelper(null); 40 | refreshHelper.addDependency(this.dataModel); 41 | } 42 | 43 | protected DataModel getDataModel() { 44 | return dataModel; 45 | } 46 | 47 | @Override 48 | public long[] allSimilarItemIDs(final long itemID) { 49 | final FastIDSet allSimilarItemIDs = new FastIDSet(); 50 | final LongPrimitiveIterator allItemIDs = dataModel.getItemIDs(); 51 | while (allItemIDs.hasNext()) { 52 | final long possiblySimilarItemID = allItemIDs.nextLong(); 53 | if (!Double.isNaN(itemSimilarity(itemID, possiblySimilarItemID))) { 54 | allSimilarItemIDs.add(possiblySimilarItemID); 55 | } 56 | } 57 | return allSimilarItemIDs.toArray(); 58 | } 59 | 60 | @Override 61 | public void refresh(final Collection alreadyRefreshed) { 62 | refreshHelper.refresh(alreadyRefreshed); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/Evaluation.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.eval; 2 | 3 | public class Evaluation { 4 | 5 | private double score; 6 | 7 | private int noEstimate = 0; 8 | 9 | private int successful = 0; 10 | 11 | private int failure = 0; 12 | 13 | private int totalPreference = 0; 14 | 15 | private int training = 0; 16 | 17 | private int test = 0; 18 | 19 | private long totalProcessingTime = 0; 20 | 21 | private long maxProcessingTime = 0; 22 | 23 | private long averageProcessingTime = 0; 24 | 25 | public double getScore() { 26 | return score; 27 | } 28 | 29 | public void setScore(final double score) { 30 | this.score = score; 31 | } 32 | 33 | public int getNoEstimate() { 34 | return noEstimate; 35 | } 36 | 37 | public void setNoEstimate(final int noEstimate) { 38 | this.noEstimate = noEstimate; 39 | } 40 | 41 | public int getSuccessful() { 42 | return successful; 43 | } 44 | 45 | public void setSuccessful(final int successful) { 46 | this.successful = successful; 47 | } 48 | 49 | public int getFailure() { 50 | return failure; 51 | } 52 | 53 | public void setFailure(final int failure) { 54 | this.failure = failure; 55 | } 56 | 57 | public int getTotalPreference() { 58 | return totalPreference; 59 | } 60 | 61 | public void setTotalPreference(final int totalPreference) { 62 | this.totalPreference = totalPreference; 63 | } 64 | 65 | public int getTraining() { 66 | return training; 67 | } 68 | 69 | public void setTraining(final int training) { 70 | this.training = training; 71 | } 72 | 73 | public int getTest() { 74 | return test; 75 | } 76 | 77 | public void setTest(final int test) { 78 | this.test = test; 79 | } 80 | 81 | public long getTotalProcessingTime() { 82 | return totalProcessingTime; 83 | } 84 | 85 | public void setTotalProcessingTime(final long totalProcessingTime) { 86 | this.totalProcessingTime = totalProcessingTime; 87 | } 88 | 89 | public long getMaxProcessingTime() { 90 | return maxProcessingTime; 91 | } 92 | 93 | public void setMaxProcessingTime(final long maxProcessingTime) { 94 | this.maxProcessingTime = maxProcessingTime; 95 | } 96 | 97 | public long getAverageProcessingTime() { 98 | return averageProcessingTime; 99 | } 100 | 101 | public void setAverageProcessingTime(final long averageProcessingTime) { 102 | this.averageProcessingTime = averageProcessingTime; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/UserBasedRecommenderBuilder.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.recommender; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.codelibs.elasticsearch.taste.exception.TasteException; 7 | import org.codelibs.elasticsearch.taste.model.DataModel; 8 | import org.codelibs.elasticsearch.taste.model.IndexInfo; 9 | import org.codelibs.elasticsearch.taste.neighborhood.UserNeighborhood; 10 | import org.codelibs.elasticsearch.taste.neighborhood.UserNeighborhoodFactory; 11 | import org.codelibs.elasticsearch.taste.similarity.UserSimilarity; 12 | import org.codelibs.elasticsearch.taste.util.SettingsUtils; 13 | 14 | public class UserBasedRecommenderBuilder extends AbstractRecommenderBuilder { 15 | 16 | public UserBasedRecommenderBuilder(final IndexInfo indexInfo, 17 | final Map rootSettings) { 18 | super(indexInfo, rootSettings); 19 | } 20 | 21 | @Override 22 | public Recommender buildRecommender(final DataModel dataModel) { 23 | final Map similaritySettings = SettingsUtils.get( 24 | rootSettings, "similarity", new HashMap()); 25 | similaritySettings.put(DATA_MODEL_ATTR, dataModel); 26 | final UserSimilarity similarity = createSimilarity(similaritySettings); 27 | 28 | final Map neighborhoodSettings = SettingsUtils.get( 29 | rootSettings, "neighborhood", new HashMap()); 30 | neighborhoodSettings.put(DATA_MODEL_ATTR, dataModel); 31 | neighborhoodSettings.put(USER_SIMILARITY_ATTR, similarity); 32 | final UserNeighborhood neighborhood = createUserNeighborhood(neighborhoodSettings); 33 | 34 | return new GenericUserBasedRecommender(dataModel, neighborhood, 35 | similarity); 36 | } 37 | 38 | protected UserNeighborhood createUserNeighborhood( 39 | final Map neighborhoodSettings) { 40 | final String factoryName = SettingsUtils 41 | .get(neighborhoodSettings, "factory", 42 | "org.codelibs.elasticsearch.taste.neighborhood.NearestNUserNeighborhoodFactory"); 43 | try { 44 | final Class clazz = Class.forName(factoryName); 45 | final UserNeighborhoodFactory userNeighborhoodFactory = (UserNeighborhoodFactory) clazz 46 | .newInstance(); 47 | userNeighborhoodFactory.init(neighborhoodSettings); 48 | return userNeighborhoodFactory.create(); 49 | } catch (ClassNotFoundException | InstantiationException 50 | | IllegalAccessException e) { 51 | throw new TasteException("Could not create an instance of " 52 | + factoryName, e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/Evaluator.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.eval; 2 | 3 | import org.codelibs.elasticsearch.taste.model.DataModel; 4 | import org.codelibs.elasticsearch.taste.writer.ResultWriter; 5 | 6 | public interface Evaluator { 7 | /** 8 | *

9 | * Evaluates the quality of a {@link org.codelibs.elasticsearch.taste.recommender.Recommender}'s recommendations. 10 | * The range of values that may be returned depends on the implementation, but lower values must 11 | * mean better recommendations, with 0 being the lowest / best possible evaluation, meaning a perfect match. 12 | * This method does not accept a {@link org.codelibs.elasticsearch.taste.recommender.Recommender} directly, but 13 | * rather a {@link RecommenderBuilder} which can build the 14 | * {@link org.codelibs.elasticsearch.taste.recommender.Recommender} to test on top of a given {@link DataModel}. 15 | *

16 | * 17 | *

18 | * Implementations will take a certain percentage of the preferences supplied by the given {@link DataModel} 19 | * as "training data". This is typically most of the data, like 90%. This data is used to produce 20 | * recommendations, and the rest of the data is compared against estimated preference values to see how much 21 | * the {@link org.codelibs.elasticsearch.taste.recommender.Recommender}'s predicted preferences match the user's 22 | * real preferences. Specifically, for each user, this percentage of the user's ratings are used to produce 23 | * recommendations, and for each user, the remaining preferences are compared against the user's real 24 | * preferences. 25 | *

26 | * 27 | *

28 | * For large datasets, it may be desirable to only evaluate based on a small percentage of the data. 29 | * {@code evaluationPercentage} controls how many of the {@link DataModel}'s users are used in 30 | * evaluation. 31 | *

32 | * 33 | *

34 | * To be clear, {@code trainingPercentage} and {@code evaluationPercentage} are not related. They 35 | * do not need to add up to 1.0, for example. 36 | *

37 | * 38 | * @param recommenderBuilder 39 | * object that can build a {@link org.codelibs.elasticsearch.taste.recommender.Recommender} to test 40 | * @param dataModel 41 | * dataset to test on 42 | * @param config 43 | * configuration for an evaluation 44 | * @return a evaluation result 45 | */ 46 | Evaluation evaluate(RecommenderBuilder recommenderBuilder, 47 | DataModel dataModel, EvaluationConfig config); 48 | 49 | void setResultWriter(ResultWriter resultWriter); 50 | 51 | void setId(String id); 52 | 53 | String getId(); 54 | 55 | void interrupt(); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/FixedRunningAverage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | import java.io.Serializable; 21 | 22 | /** 23 | *

24 | * A simple class that represents a fixed value of an average and count. This is useful 25 | * when an API needs to return {@link RunningAverage} but is not in a position to accept 26 | * updates to it. 27 | *

28 | */ 29 | public class FixedRunningAverage implements RunningAverage, Serializable { 30 | 31 | /** 32 | * 33 | */ 34 | private static final long serialVersionUID = 1L; 35 | 36 | private final double average; 37 | 38 | private final int count; 39 | 40 | public FixedRunningAverage(final double average, final int count) { 41 | this.average = average; 42 | this.count = count; 43 | } 44 | 45 | /** 46 | * @throws UnsupportedOperationException 47 | */ 48 | @Override 49 | public synchronized void addDatum(final double datum) { 50 | throw new UnsupportedOperationException(); 51 | } 52 | 53 | /** 54 | * @throws UnsupportedOperationException 55 | */ 56 | @Override 57 | public synchronized void removeDatum(final double datum) { 58 | throw new UnsupportedOperationException(); 59 | } 60 | 61 | /** 62 | * @throws UnsupportedOperationException 63 | */ 64 | @Override 65 | public synchronized void changeDatum(final double delta) { 66 | throw new UnsupportedOperationException(); 67 | } 68 | 69 | @Override 70 | public synchronized int getCount() { 71 | return count; 72 | } 73 | 74 | @Override 75 | public synchronized double getAverage() { 76 | return average; 77 | } 78 | 79 | @Override 80 | public RunningAverage inverse() { 81 | return new InvertedRunningAverage(this); 82 | } 83 | 84 | @Override 85 | public synchronized String toString() { 86 | return String.valueOf(average); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/StatsCallable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import java.util.concurrent.Callable; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | 23 | import org.codelibs.elasticsearch.taste.common.RunningAverageAndStdDev; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | final class StatsCallable implements Callable { 28 | 29 | private static final Logger log = LoggerFactory 30 | .getLogger(StatsCallable.class); 31 | 32 | private final Callable delegate; 33 | 34 | private final boolean logStats; 35 | 36 | private final RunningAverageAndStdDev timing; 37 | 38 | private final AtomicInteger noEstimateCounter; 39 | 40 | StatsCallable(final Callable delegate, final boolean logStats, 41 | final RunningAverageAndStdDev timing, 42 | final AtomicInteger noEstimateCounter) { 43 | this.delegate = delegate; 44 | this.logStats = logStats; 45 | this.timing = timing; 46 | this.noEstimateCounter = noEstimateCounter; 47 | } 48 | 49 | @Override 50 | public Void call() throws Exception { 51 | final long start = System.currentTimeMillis(); 52 | delegate.call(); 53 | final long end = System.currentTimeMillis(); 54 | timing.addDatum(end - start); 55 | if (logStats) { 56 | final Runtime runtime = Runtime.getRuntime(); 57 | final int average = (int) timing.getAverage(); 58 | log.info("Average time per recommendation: {}ms", average); 59 | final long totalMemory = runtime.totalMemory(); 60 | final long memory = totalMemory - runtime.freeMemory(); 61 | log.info("Approximate memory used: {}MB / {}MB", memory / 1000000L, 62 | totalMemory / 1000000L); 63 | log.info("Unable to recommend in {} cases", noEstimateCounter.get()); 64 | } 65 | return null; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/UncenteredCosineSimilarity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity; 19 | 20 | import org.codelibs.elasticsearch.taste.common.Weighting; 21 | import org.codelibs.elasticsearch.taste.model.DataModel; 22 | 23 | import com.google.common.base.Preconditions; 24 | 25 | /** 26 | *

27 | * An implementation of the cosine similarity. The result is the cosine of the angle formed between 28 | * the two preference vectors. 29 | *

30 | * 31 | *

32 | * Note that this similarity does not "center" its data, shifts the user's preference values so that each of their 33 | * means is 0. For this behavior, use {@link PearsonCorrelationSimilarity}, which actually is mathematically 34 | * equivalent for centered data. 35 | *

36 | */ 37 | public final class UncenteredCosineSimilarity extends AbstractSimilarity { 38 | 39 | /** 40 | * @throws IllegalArgumentException if {@link DataModel} does not have preference values 41 | */ 42 | public UncenteredCosineSimilarity(final DataModel dataModel) { 43 | this(dataModel, Weighting.UNWEIGHTED); 44 | } 45 | 46 | /** 47 | * @throws IllegalArgumentException if {@link DataModel} does not have preference values 48 | */ 49 | public UncenteredCosineSimilarity(final DataModel dataModel, 50 | final Weighting weighting) { 51 | super(dataModel, weighting, false); 52 | Preconditions.checkArgument(dataModel.hasPreferenceValues(), 53 | "DataModel doesn't have preference values"); 54 | } 55 | 56 | @Override 57 | double computeResult(final int n, final double sumXY, final double sumX2, 58 | final double sumY2, final double sumXYdiff2) { 59 | if (n == 0) { 60 | return Double.NaN; 61 | } 62 | final double denominator = Math.sqrt(sumX2) * Math.sqrt(sumY2); 63 | return denominator < 0.0 || denominator > 0.0 ? sumXY / denominator 64 | : Double.NaN; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/RelevantItemsDataSplitter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import org.codelibs.elasticsearch.taste.common.FastByIDMap; 21 | import org.codelibs.elasticsearch.taste.common.FastIDSet; 22 | import org.codelibs.elasticsearch.taste.model.DataModel; 23 | import org.codelibs.elasticsearch.taste.model.PreferenceArray; 24 | 25 | /** 26 | * Implementations of this interface determine the items that are considered relevant, 27 | * and splits data into a training and test subset, for purposes of precision/recall 28 | * tests as implemented by implementations of {@link RecommenderIRStatsEvaluator}. 29 | */ 30 | public interface RelevantItemsDataSplitter { 31 | 32 | /** 33 | * During testing, relevant items are removed from a particular users' preferences, 34 | * and a model is build using this user's other preferences and all other users. 35 | * 36 | * @param at Maximum number of items to be removed 37 | * @param relevanceThreshold Minimum strength of preference for an item to be considered 38 | * relevant 39 | * @return IDs of relevant items 40 | */ 41 | FastIDSet getRelevantItemsIDs(long userID, int at, 42 | double relevanceThreshold, DataModel dataModel); 43 | 44 | /** 45 | * Adds a single user and all their preferences to the training model. 46 | * 47 | * @param userID ID of user whose preferences we are trying to predict 48 | * @param relevantItemIDs IDs of items considered relevant to that user 49 | * @param trainingUsers the database of training preferences to which we will 50 | * append the ones for otherUserID. 51 | * @param otherUserID for whom we are adding preferences to the training model 52 | */ 53 | void processOtherUser(long userID, FastIDSet relevantItemIDs, 54 | FastByIDMap trainingUsers, long otherUserID, 55 | DataModel dataModel); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/LongPair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | import java.io.Serializable; 21 | 22 | import com.google.common.primitives.Longs; 23 | 24 | /** A simple (ordered) pair of longs. */ 25 | public final class LongPair implements Comparable, Serializable { 26 | 27 | /** 28 | * 29 | */ 30 | private static final long serialVersionUID = 1L; 31 | 32 | private final long first; 33 | 34 | private final long second; 35 | 36 | public LongPair(final long first, final long second) { 37 | this.first = first; 38 | this.second = second; 39 | } 40 | 41 | public long getFirst() { 42 | return first; 43 | } 44 | 45 | public long getSecond() { 46 | return second; 47 | } 48 | 49 | public LongPair swap() { 50 | return new LongPair(second, first); 51 | } 52 | 53 | @Override 54 | public boolean equals(final Object obj) { 55 | if (!(obj instanceof LongPair)) { 56 | return false; 57 | } 58 | final LongPair otherPair = (LongPair) obj; 59 | return first == otherPair.getFirst() && second == otherPair.getSecond(); 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | final int firstHash = Longs.hashCode(first); 65 | // Flip top and bottom 16 bits; this makes the hash function probably different 66 | // for (a,b) versus (b,a) 67 | return (firstHash >>> 16 | firstHash << 16) ^ Longs.hashCode(second); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return '(' + String.valueOf(first) + ',' + second + ')'; 73 | } 74 | 75 | @Override 76 | public int compareTo(final LongPair o) { 77 | if (first < o.getFirst()) { 78 | return -1; 79 | } else if (first > o.getFirst()) { 80 | return 1; 81 | } else { 82 | return second < o.getSecond() ? -1 : second > o.getSecond() ? 1 : 0; 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/NullRescorer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import org.codelibs.elasticsearch.taste.common.LongPair; 21 | 22 | /** 23 | *

24 | * A simple {@link Rescorer} which always returns the original score. 25 | *

26 | */ 27 | public final class NullRescorer implements Rescorer, IDRescorer { 28 | 29 | private static final IDRescorer USER_OR_ITEM_INSTANCE = new NullRescorer(); 30 | 31 | private static final Rescorer ITEM_ITEM_PAIR_INSTANCE = new NullRescorer<>(); 32 | 33 | private static final Rescorer USER_USER_PAIR_INSTANCE = new NullRescorer<>(); 34 | 35 | private NullRescorer() { 36 | } 37 | 38 | public static IDRescorer getItemInstance() { 39 | return USER_OR_ITEM_INSTANCE; 40 | } 41 | 42 | public static IDRescorer getUserInstance() { 43 | return USER_OR_ITEM_INSTANCE; 44 | } 45 | 46 | public static Rescorer getItemItemPairInstance() { 47 | return ITEM_ITEM_PAIR_INSTANCE; 48 | } 49 | 50 | public static Rescorer getUserUserPairInstance() { 51 | return USER_USER_PAIR_INSTANCE; 52 | } 53 | 54 | /** 55 | * @param thing 56 | * to rescore 57 | * @param originalScore 58 | * current score for item 59 | * @return same originalScore as new score, always 60 | */ 61 | @Override 62 | public double rescore(final T thing, final double originalScore) { 63 | return originalScore; 64 | } 65 | 66 | @Override 67 | public boolean isFiltered(final T thing) { 68 | return false; 69 | } 70 | 71 | @Override 72 | public double rescore(final long id, final double originalScore) { 73 | return originalScore; 74 | } 75 | 76 | @Override 77 | public boolean isFiltered(final long id) { 78 | return false; 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return "NullRescorer"; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/precompute/SimilarItems.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity.precompute; 19 | 20 | import java.util.List; 21 | import java.util.NoSuchElementException; 22 | 23 | import org.codelibs.elasticsearch.taste.recommender.RecommendedItem; 24 | 25 | import com.google.common.collect.UnmodifiableIterator; 26 | 27 | /** 28 | * Compact representation of all similar items for an item 29 | */ 30 | public class SimilarItems { 31 | 32 | private final long itemID; 33 | 34 | private final long[] similarItemIDs; 35 | 36 | private final double[] similarities; 37 | 38 | public SimilarItems(final long itemID, 39 | final List similarItems) { 40 | this.itemID = itemID; 41 | 42 | final int numSimilarItems = similarItems.size(); 43 | similarItemIDs = new long[numSimilarItems]; 44 | similarities = new double[numSimilarItems]; 45 | 46 | for (int n = 0; n < numSimilarItems; n++) { 47 | similarItemIDs[n] = similarItems.get(n).getItemID(); 48 | similarities[n] = similarItems.get(n).getValue(); 49 | } 50 | } 51 | 52 | public long getItemID() { 53 | return itemID; 54 | } 55 | 56 | public int numSimilarItems() { 57 | return similarItemIDs.length; 58 | } 59 | 60 | public Iterable getSimilarItems() { 61 | return () -> new SimilarItemsIterator(); 62 | } 63 | 64 | private class SimilarItemsIterator extends 65 | UnmodifiableIterator { 66 | 67 | private int index = 0; 68 | 69 | @Override 70 | public boolean hasNext() { 71 | return index < similarItemIDs.length - 1; 72 | } 73 | 74 | @Override 75 | public SimilarItem next() { 76 | if (!hasNext()) { 77 | throw new NoSuchElementException(); 78 | } 79 | index++; 80 | return new SimilarItem(similarItemIDs[index], similarities[index]); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/IDMigrator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.model; 19 | 20 | import org.codelibs.elasticsearch.taste.common.Refreshable; 21 | 22 | /** 23 | *

24 | * Mahout 0.2 changed the framework to operate only in terms of numeric (long) ID values for users and items. 25 | * This is, obviously, not compatible with applications that used other key types -- most commonly 26 | * {@link String}. Implementation of this class provide support for mapping String to longs and vice versa in 27 | * order to provide a smoother migration path to applications that must still use strings as IDs. 28 | *

29 | * 30 | *

31 | * The mapping from strings to 64-bit numeric values is fixed here, to provide a standard implementation that 32 | * is 'portable' or reproducible outside the framework easily. See {@link #toLongID(String)}. 33 | *

34 | * 35 | *

36 | * Because this mapping is deterministically computable, it does not need to be stored. Indeed, subclasses' 37 | * job is to store the reverse mapping. There are an infinite number of strings but only a fixed number of 38 | * longs, so, it is possible for two strings to map to the same value. Subclasses do not treat this as an 39 | * error but rather retain only the most recent mapping, overwriting a previous mapping. The probability of 40 | * collision in a 64-bit space is quite small, but not zero. However, in the context of a collaborative 41 | * filtering problem, the consequence of a collision is small, at worst -- perhaps one user receives another 42 | * recommendations. 43 | *

44 | * 45 | * @since 0.2 46 | */ 47 | public interface IDMigrator extends Refreshable { 48 | 49 | /** 50 | * @return the top 8 bytes of the MD5 hash of the bytes of the given {@link String}'s UTF-8 encoding as a 51 | * long. 52 | */ 53 | long toLongID(String stringID); 54 | 55 | /** 56 | * @return the string ID most recently associated with the given long ID, or null if doesn't exist 57 | */ 58 | String toStringID(long longID); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/LongPrimitiveArrayIterator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | import java.util.NoSuchElementException; 21 | 22 | import com.google.common.base.Preconditions; 23 | 24 | /** 25 | * While long[] is an Iterable, it is not an Iterable<Long>. This adapter class addresses that. 26 | */ 27 | public final class LongPrimitiveArrayIterator implements LongPrimitiveIterator { 28 | 29 | private final long[] array; 30 | 31 | private int position; 32 | 33 | private final int max; 34 | 35 | /** 36 | *

37 | * Creates an {@link LongPrimitiveArrayIterator} over an entire array. 38 | *

39 | * 40 | * @param array 41 | * array to iterate over 42 | */ 43 | public LongPrimitiveArrayIterator(final long[] array) { 44 | this.array = Preconditions.checkNotNull(array); // yeah, not going to copy the array here, for performance 45 | position = 0; 46 | max = array.length; 47 | } 48 | 49 | @Override 50 | public boolean hasNext() { 51 | return position < max; 52 | } 53 | 54 | @Override 55 | public Long next() { 56 | return nextLong(); 57 | } 58 | 59 | @Override 60 | public long nextLong() { 61 | if (position >= array.length) { 62 | throw new NoSuchElementException(); 63 | } 64 | return array[position++]; 65 | } 66 | 67 | @Override 68 | public long peek() { 69 | if (position >= array.length) { 70 | throw new NoSuchElementException(); 71 | } 72 | return array[position]; 73 | } 74 | 75 | /** 76 | * @throws UnsupportedOperationException 77 | */ 78 | @Override 79 | public void remove() { 80 | throw new UnsupportedOperationException(); 81 | } 82 | 83 | @Override 84 | public void skip(final int n) { 85 | if (n > 0) { 86 | position += n; 87 | } 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return "LongPrimitiveArrayIterator"; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/eval/LoadEvaluator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.eval; 19 | 20 | import java.util.Collection; 21 | import java.util.concurrent.Callable; 22 | import java.util.concurrent.atomic.AtomicInteger; 23 | 24 | import org.codelibs.elasticsearch.taste.common.FullRunningAverageAndStdDev; 25 | import org.codelibs.elasticsearch.taste.common.LongPrimitiveIterator; 26 | import org.codelibs.elasticsearch.taste.common.RunningAverageAndStdDev; 27 | import org.codelibs.elasticsearch.taste.common.SamplingLongPrimitiveIterator; 28 | import org.codelibs.elasticsearch.taste.model.DataModel; 29 | import org.codelibs.elasticsearch.taste.recommender.Recommender; 30 | 31 | import com.google.common.collect.Lists; 32 | 33 | /** 34 | * Simple helper class for running load on a Recommender. 35 | */ 36 | public final class LoadEvaluator { 37 | 38 | private LoadEvaluator() { 39 | } 40 | 41 | public static LoadStatistics runLoad(final Recommender recommender) { 42 | return runLoad(recommender, 10); 43 | } 44 | 45 | public static LoadStatistics runLoad(final Recommender recommender, 46 | final int howMany) { 47 | final DataModel dataModel = recommender.getDataModel(); 48 | final int numUsers = dataModel.getNumUsers(); 49 | final double sampleRate = 1000.0 / numUsers; 50 | final LongPrimitiveIterator userSampler = SamplingLongPrimitiveIterator 51 | .maybeWrapIterator(dataModel.getUserIDs(), sampleRate); 52 | recommender.recommend(userSampler.next(), howMany); // Warm up 53 | final Collection> callables = Lists.newArrayList(); 54 | while (userSampler.hasNext()) { 55 | callables.add(new LoadCallable(recommender, userSampler.next())); 56 | } 57 | final AtomicInteger noEstimateCounter = new AtomicInteger(); 58 | final RunningAverageAndStdDev timing = new FullRunningAverageAndStdDev(); 59 | AbstractDifferenceRecommenderEvaluator.execute(callables, 60 | noEstimateCounter, timing); 61 | return new LoadStatistics(timing); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/writer/AbstractWriter.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.writer; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | 6 | import org.codelibs.elasticsearch.taste.TasteConstants; 7 | import org.codelibs.elasticsearch.taste.exception.TasteException; 8 | import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; 9 | import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse; 10 | import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; 11 | import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; 12 | import org.elasticsearch.client.Client; 13 | import org.elasticsearch.common.xcontent.XContentBuilder; 14 | 15 | public abstract class AbstractWriter implements Closeable { 16 | 17 | protected Client client; 18 | 19 | protected String index; 20 | 21 | protected String type; 22 | 23 | protected String timestampField = TasteConstants.TIMESTAMP_FIELD; 24 | 25 | protected XContentBuilder mappingBuilder; 26 | 27 | public AbstractWriter(final Client client, final String index, 28 | final String type) { 29 | this.client = client; 30 | this.index = index; 31 | this.type = type; 32 | } 33 | 34 | public void open() { 35 | final IndicesExistsResponse existsResponse = client.admin().indices() 36 | .prepareExists(index).execute().actionGet(); 37 | if (!existsResponse.isExists()) { 38 | final CreateIndexResponse createIndexResponse = client.admin() 39 | .indices().prepareCreate(index).execute().actionGet(); 40 | if (!createIndexResponse.isAcknowledged()) { 41 | throw new TasteException("Failed to create " + index 42 | + " index."); 43 | } 44 | } 45 | 46 | if (mappingBuilder != null) { 47 | final GetMappingsResponse response = client.admin().indices() 48 | .prepareGetMappings(index).setTypes(type).execute() 49 | .actionGet(); 50 | if (response.mappings().isEmpty()) { 51 | final PutMappingResponse putMappingResponse = client.admin() 52 | .indices().preparePutMapping(index).setType(type) 53 | .setSource(mappingBuilder).execute().actionGet(); 54 | if (!putMappingResponse.isAcknowledged()) { 55 | throw new TasteException("Failed to create a mapping of" 56 | + index + "/" + type); 57 | } 58 | } 59 | } 60 | } 61 | 62 | @Override 63 | public abstract void close() throws IOException; 64 | 65 | public void setTimestampField(final String timestampField) { 66 | this.timestampField = timestampField; 67 | } 68 | 69 | public void setMapping(final XContentBuilder builder) { 70 | mappingBuilder = builder; 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/neighborhood/AbstractUserNeighborhood.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.neighborhood; 19 | 20 | import java.util.Collection; 21 | 22 | import org.codelibs.elasticsearch.taste.common.RefreshHelper; 23 | import org.codelibs.elasticsearch.taste.common.Refreshable; 24 | import org.codelibs.elasticsearch.taste.model.DataModel; 25 | import org.codelibs.elasticsearch.taste.similarity.UserSimilarity; 26 | 27 | import com.google.common.base.Preconditions; 28 | 29 | /** 30 | *

31 | * Contains methods and resources useful to all classes in this package. 32 | *

33 | */ 34 | abstract class AbstractUserNeighborhood implements UserNeighborhood { 35 | 36 | private final UserSimilarity userSimilarity; 37 | 38 | private final DataModel dataModel; 39 | 40 | private final double samplingRate; 41 | 42 | private final RefreshHelper refreshHelper; 43 | 44 | AbstractUserNeighborhood(final UserSimilarity userSimilarity, 45 | final DataModel dataModel, final double samplingRate) { 46 | Preconditions.checkArgument(userSimilarity != null, 47 | "userSimilarity is null"); 48 | Preconditions.checkArgument(dataModel != null, "dataModel is null"); 49 | Preconditions.checkArgument(samplingRate > 0.0 && samplingRate <= 1.0, 50 | "samplingRate must be in (0,1]"); 51 | this.userSimilarity = userSimilarity; 52 | this.dataModel = dataModel; 53 | this.samplingRate = samplingRate; 54 | refreshHelper = new RefreshHelper(null); 55 | refreshHelper.addDependency(this.dataModel); 56 | refreshHelper.addDependency(this.userSimilarity); 57 | } 58 | 59 | final UserSimilarity getUserSimilarity() { 60 | return userSimilarity; 61 | } 62 | 63 | final DataModel getDataModel() { 64 | return dataModel; 65 | } 66 | 67 | final double getSamplingRate() { 68 | return samplingRate; 69 | } 70 | 71 | @Override 72 | public final void refresh(final Collection alreadyRefreshed) { 73 | refreshHelper.refresh(alreadyRefreshed); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/writer/ObjectWriter.java: -------------------------------------------------------------------------------- 1 | package org.codelibs.elasticsearch.taste.writer; 2 | 3 | import java.io.IOException; 4 | import java.util.Date; 5 | import java.util.Map; 6 | import java.util.concurrent.ArrayBlockingQueue; 7 | 8 | import org.codelibs.elasticsearch.taste.exception.TasteException; 9 | import org.elasticsearch.action.ActionListener; 10 | import org.elasticsearch.action.index.IndexResponse; 11 | import org.elasticsearch.client.Client; 12 | import org.elasticsearch.common.logging.ESLogger; 13 | import org.elasticsearch.common.logging.Loggers; 14 | 15 | public class ObjectWriter extends AbstractWriter { 16 | private static final ESLogger logger = Loggers.getLogger(ItemWriter.class); 17 | 18 | private ArrayBlockingQueue queue; 19 | 20 | public ObjectWriter(final Client client, final String index, 21 | final String type, final int capacity) { 22 | super(client, index, type); 23 | queue = new ArrayBlockingQueue<>(capacity); 24 | } 25 | 26 | public void write(final Map rootObj) { 27 | countUp(); 28 | try { 29 | rootObj.put(timestampField, new Date()); 30 | 31 | client.prepareIndex(index, type).setSource(rootObj) 32 | .execute(new ActionListener() { 33 | @Override 34 | public void onResponse(final IndexResponse response) { 35 | if (logger.isDebugEnabled()) { 36 | logger.debug( 37 | "Response: {}/{}/{}, Created: {}, Version: {}", 38 | response.getIndex(), response.getType(), 39 | response.getId(), response.isCreated(), 40 | response.getVersion()); 41 | } 42 | countDown(); 43 | } 44 | 45 | @Override 46 | public void onFailure(final Throwable e) { 47 | logger.error("Failed to write " + rootObj, e); 48 | countDown(); 49 | } 50 | }); 51 | } catch (final Throwable t) { 52 | countDown(); 53 | throw new TasteException(t); 54 | } 55 | } 56 | 57 | @Override 58 | public void close() throws IOException { 59 | for (int i = 0; i < 60; i++) { 60 | if (queue.isEmpty()) { 61 | break; 62 | } 63 | try { 64 | Thread.sleep(1000L); 65 | } catch (InterruptedException e) { 66 | break; 67 | } 68 | } 69 | } 70 | 71 | protected void countUp() { 72 | try { 73 | queue.put(Boolean.TRUE); 74 | } catch (InterruptedException e) { 75 | throw new TasteException(e); 76 | } 77 | } 78 | 79 | protected void countDown() { 80 | queue.poll(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/common/BitSet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.common; 19 | 20 | import java.io.Serializable; 21 | import java.util.Arrays; 22 | 23 | /** A simplified and streamlined version of {@link java.util.BitSet}. */ 24 | final class BitSet implements Serializable, Cloneable { 25 | 26 | /** 27 | * 28 | */ 29 | private static final long serialVersionUID = 1L; 30 | 31 | private final long[] bits; 32 | 33 | BitSet(final int numBits) { 34 | int numLongs = numBits >>> 6; 35 | if ((numBits & 0x3F) != 0) { 36 | numLongs++; 37 | } 38 | bits = new long[numLongs]; 39 | } 40 | 41 | private BitSet(final long[] bits) { 42 | this.bits = bits; 43 | } 44 | 45 | boolean get(final int index) { 46 | // skipping range check for speed 47 | return (bits[index >>> 6] & 1L << (index & 0x3F)) != 0L; 48 | } 49 | 50 | void set(final int index) { 51 | // skipping range check for speed 52 | bits[index >>> 6] |= 1L << (index & 0x3F); 53 | } 54 | 55 | void clear(final int index) { 56 | // skipping range check for speed 57 | bits[index >>> 6] &= ~(1L << (index & 0x3F)); 58 | } 59 | 60 | void clear() { 61 | final int length = bits.length; 62 | for (int i = 0; i < length; i++) { 63 | bits[i] = 0L; 64 | } 65 | } 66 | 67 | @Override 68 | public BitSet clone() { 69 | return new BitSet(bits.clone()); 70 | } 71 | 72 | @Override 73 | public int hashCode() { 74 | return Arrays.hashCode(bits); 75 | } 76 | 77 | @Override 78 | public boolean equals(final Object o) { 79 | if (!(o instanceof BitSet)) { 80 | return false; 81 | } 82 | final BitSet other = (BitSet) o; 83 | return Arrays.equals(bits, other.bits); 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | final StringBuilder result = new StringBuilder(64 * bits.length); 89 | for (final long l : bits) { 90 | for (int j = 0; j < 64; j++) { 91 | result.append((l & 1L << j) == 0 ? '0' : '1'); 92 | } 93 | result.append(' '); 94 | } 95 | return result.toString(); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/model/PlusAnonymousUserLongPrimitiveIterator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.model; 19 | 20 | import org.codelibs.elasticsearch.taste.common.AbstractLongPrimitiveIterator; 21 | import org.codelibs.elasticsearch.taste.common.LongPrimitiveIterator; 22 | 23 | final class PlusAnonymousUserLongPrimitiveIterator extends 24 | AbstractLongPrimitiveIterator { 25 | 26 | private final LongPrimitiveIterator delegate; 27 | 28 | private final long extraDatum; 29 | 30 | private boolean datumConsumed; 31 | 32 | PlusAnonymousUserLongPrimitiveIterator( 33 | final LongPrimitiveIterator delegate, final long extraDatum) { 34 | this.delegate = delegate; 35 | this.extraDatum = extraDatum; 36 | datumConsumed = false; 37 | } 38 | 39 | @Override 40 | public long nextLong() { 41 | if (datumConsumed) { 42 | return delegate.nextLong(); 43 | } else { 44 | if (delegate.hasNext()) { 45 | final long delegateNext = delegate.peek(); 46 | if (extraDatum <= delegateNext) { 47 | datumConsumed = true; 48 | return extraDatum; 49 | } else { 50 | return delegate.next(); 51 | } 52 | } else { 53 | datumConsumed = true; 54 | return extraDatum; 55 | } 56 | } 57 | } 58 | 59 | @Override 60 | public long peek() { 61 | if (datumConsumed) { 62 | return delegate.peek(); 63 | } else { 64 | if (delegate.hasNext()) { 65 | final long delegateNext = delegate.peek(); 66 | if (extraDatum <= delegateNext) { 67 | return extraDatum; 68 | } else { 69 | return delegateNext; 70 | } 71 | } else { 72 | return extraDatum; 73 | } 74 | } 75 | } 76 | 77 | @Override 78 | public boolean hasNext() { 79 | return !datumConsumed || delegate.hasNext(); 80 | } 81 | 82 | @Override 83 | public void remove() { 84 | throw new UnsupportedOperationException(); 85 | } 86 | 87 | @Override 88 | public void skip(final int n) { 89 | for (int i = 0; i < n; i++) { 90 | nextLong(); 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/neighborhood/CachingUserNeighborhood.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.neighborhood; 19 | 20 | import java.util.Collection; 21 | import java.util.List; 22 | 23 | import org.codelibs.elasticsearch.taste.common.Cache; 24 | import org.codelibs.elasticsearch.taste.common.RefreshHelper; 25 | import org.codelibs.elasticsearch.taste.common.Refreshable; 26 | import org.codelibs.elasticsearch.taste.common.Retriever; 27 | import org.codelibs.elasticsearch.taste.model.DataModel; 28 | import org.codelibs.elasticsearch.taste.recommender.SimilarUser; 29 | 30 | import com.google.common.base.Preconditions; 31 | 32 | /** A caching wrapper around an underlying {@link UserNeighborhood} implementation. */ 33 | public final class CachingUserNeighborhood implements UserNeighborhood { 34 | 35 | private final UserNeighborhood neighborhood; 36 | 37 | private final Cache> neighborhoodCache; 38 | 39 | public CachingUserNeighborhood(final UserNeighborhood neighborhood, 40 | final DataModel dataModel) { 41 | Preconditions.checkArgument(neighborhood != null, 42 | "neighborhood is null"); 43 | this.neighborhood = neighborhood; 44 | final int maxCacheSize = dataModel.getNumUsers(); // just a dumb heuristic for sizing 45 | neighborhoodCache = new Cache<>( 46 | new NeighborhoodRetriever(neighborhood), maxCacheSize); 47 | } 48 | 49 | @Override 50 | public List getUserNeighborhood(final long userID) { 51 | return neighborhoodCache.get(userID); 52 | } 53 | 54 | @Override 55 | public void refresh(final Collection alreadyRefreshed) { 56 | neighborhoodCache.clear(); 57 | final Collection refreshed = RefreshHelper 58 | .buildRefreshed(alreadyRefreshed); 59 | RefreshHelper.maybeRefresh(refreshed, neighborhood); 60 | } 61 | 62 | private static final class NeighborhoodRetriever implements 63 | Retriever> { 64 | private final UserNeighborhood neighborhood; 65 | 66 | private NeighborhoodRetriever(final UserNeighborhood neighborhood) { 67 | this.neighborhood = neighborhood; 68 | } 69 | 70 | @Override 71 | public List get(final Long key) { 72 | return neighborhood.getUserNeighborhood(key); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/recommender/GenericBooleanPrefItemBasedRecommender.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.recommender; 19 | 20 | import org.codelibs.elasticsearch.taste.model.DataModel; 21 | import org.codelibs.elasticsearch.taste.model.PreferenceArray; 22 | import org.codelibs.elasticsearch.taste.similarity.ItemSimilarity; 23 | 24 | /** 25 | * A variant on {@link GenericItemBasedRecommender} which is appropriate for use when no notion of preference 26 | * value exists in the data. 27 | * 28 | * @see org.codelibs.elasticsearch.taste.recommender.GenericBooleanPrefUserBasedRecommender 29 | */ 30 | public final class GenericBooleanPrefItemBasedRecommender extends 31 | GenericItemBasedRecommender { 32 | 33 | public GenericBooleanPrefItemBasedRecommender(final DataModel dataModel, 34 | final ItemSimilarity similarity) { 35 | super(dataModel, similarity); 36 | } 37 | 38 | public GenericBooleanPrefItemBasedRecommender( 39 | final DataModel dataModel, 40 | final ItemSimilarity similarity, 41 | final CandidateItemsStrategy candidateItemsStrategy, 42 | final MostSimilarItemsCandidateItemsStrategy mostSimilarItemsCandidateItemsStrategy) { 43 | super(dataModel, similarity, candidateItemsStrategy, 44 | mostSimilarItemsCandidateItemsStrategy); 45 | } 46 | 47 | /** 48 | * This computation is in a technical sense, wrong, since in the domain of "boolean preference users" where 49 | * all preference values are 1, this method should only ever return 1.0 or NaN. This isn't terribly useful 50 | * however since it means results can't be ranked by preference value (all are 1). So instead this returns a 51 | * sum of similarities. 52 | */ 53 | @Override 54 | protected float doEstimatePreference(final long userID, 55 | final PreferenceArray preferencesFromUser, final long itemID) { 56 | final double[] similarities = getSimilarity().itemSimilarities(itemID, 57 | preferencesFromUser.getIDs()); 58 | boolean foundAPref = false; 59 | double totalSimilarity = 0.0; 60 | for (final double theSimilarity : similarities) { 61 | if (!Double.isNaN(theSimilarity)) { 62 | foundAPref = true; 63 | totalSimilarity += theSimilarity; 64 | } 65 | } 66 | return foundAPref ? (float) totalSimilarity : Float.NaN; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "GenericBooleanPrefItemBasedRecommender"; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/codelibs/elasticsearch/taste/similarity/AveragingPreferenceInferrer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.codelibs.elasticsearch.taste.similarity; 19 | 20 | import java.util.Collection; 21 | 22 | import org.codelibs.elasticsearch.taste.common.Cache; 23 | import org.codelibs.elasticsearch.taste.common.FullRunningAverage; 24 | import org.codelibs.elasticsearch.taste.common.Refreshable; 25 | import org.codelibs.elasticsearch.taste.common.Retriever; 26 | import org.codelibs.elasticsearch.taste.common.RunningAverage; 27 | import org.codelibs.elasticsearch.taste.model.DataModel; 28 | import org.codelibs.elasticsearch.taste.model.PreferenceArray; 29 | 30 | /** 31 | *

32 | * Implementations of this interface compute an inferred preference for a user and an item that the user has 33 | * not expressed any preference for. This might be an average of other preferences scores from that user, for 34 | * example. This technique is sometimes called "default voting". 35 | *

36 | */ 37 | public final class AveragingPreferenceInferrer implements PreferenceInferrer { 38 | 39 | private static final Float ZERO = 0.0f; 40 | 41 | private final DataModel dataModel; 42 | 43 | private final Cache averagePreferenceValue; 44 | 45 | public AveragingPreferenceInferrer(final DataModel dataModel) { 46 | this.dataModel = dataModel; 47 | final Retriever retriever = new PrefRetriever(); 48 | averagePreferenceValue = new Cache<>(retriever, 49 | dataModel.getNumUsers()); 50 | refresh(null); 51 | } 52 | 53 | @Override 54 | public float inferPreference(final long userID, final long itemID) { 55 | return averagePreferenceValue.get(userID); 56 | } 57 | 58 | @Override 59 | public void refresh(final Collection alreadyRefreshed) { 60 | averagePreferenceValue.clear(); 61 | } 62 | 63 | private final class PrefRetriever implements Retriever { 64 | 65 | @Override 66 | public Float get(final Long key) { 67 | final PreferenceArray prefs = dataModel.getPreferencesFromUser(key); 68 | final int size = prefs.length(); 69 | if (size == 0) { 70 | return ZERO; 71 | } 72 | final RunningAverage average = new FullRunningAverage(); 73 | for (int i = 0; i < size; i++) { 74 | average.addDatum(prefs.getValue(i)); 75 | } 76 | return (float) average.getAverage(); 77 | } 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return "AveragingPreferenceInferrer"; 83 | } 84 | 85 | } 86 | --------------------------------------------------------------------------------