/* * Seldon -- open source prediction engine * ======================================= * * Copyright 2011-2015 Seldon Technologies Ltd and Rummble Ltd (http://www.seldon.io/) * * ******************************************************************************************** * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ******************************************************************************************** */ package io.seldon.clustering.recommender; import io.seldon.api.state.options.DefaultOptions; import io.seldon.recommendation.AlgorithmStrategy; import io.seldon.recommendation.ItemFilter; import io.seldon.recommendation.ItemIncluder; import io.seldon.recommendation.filters.FilteredItems; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.BooleanUtils; import org.apache.log4j.Logger; /** * * Information that the Item Recommendation algorithms require to calculate what they should recommend. The main concept * is the 'mode'. Inclusion mode means that the context items are a set of items to recommend from, where as exclusion * mode means that they context items are a set of items to exclude from the recommendations. * * @author firemanphil * Date: 25/11/14 * Time: 16:06 */ public class RecommendationContext { private static final String ITEMS_PER_INCLUDER_OPTION_NAME = "io.seldon.algorithm.inclusion.itemsperincluder"; private static Logger logger = Logger.getLogger(RecommendationContext.class.getName()); private final String lastRecListUUID; private final OptionsHolder optsHolder; public enum MODE { INCLUSION, EXCLUSION, NONE } private final MODE mode; private final Set<Long> contextItems; private final Set<Long> exclusionItems; private final List<String> inclusionKeys; private final Long currentItem; public RecommendationContext(MODE mode, Set<Long> contextItems, Set<Long> exclusionItems, List<String> inclusionKeys, Long currentItem, String lastRecListUUID, OptionsHolder optsHolder) { if (logger.isDebugEnabled()) logger.debug("Built new rec context object in mode " +mode.name()); this.mode = mode; this.contextItems = contextItems; this.exclusionItems = exclusionItems; this.currentItem = currentItem; this.inclusionKeys = inclusionKeys; this.lastRecListUUID = lastRecListUUID; this.optsHolder = optsHolder; } public String getLastRecListUUID() { return lastRecListUUID; } public MODE getMode() { return mode; } public Set<Long> getContextItems() { return contextItems; } public Set<Long> getExclusionItems() { return exclusionItems; } public List<String> getInclusionKeys() { return inclusionKeys; } public Long getCurrentItem() { return currentItem; } public OptionsHolder getOptsHolder() { return optsHolder; } public static RecommendationContext buildContext(String client, AlgorithmStrategy strategy, Long user, String clientUserId, Long currentItem, Set<Integer> dimensions, String lastRecListUUID, int numRecommendations, DefaultOptions defaultOptions, FilteredItems includedItems){ OptionsHolder optsHolder = new OptionsHolder(defaultOptions, strategy.config); List<String> inclusionKeys = new ArrayList<String>(); Set<Long> contextItems = new HashSet<>(); if (includedItems != null) { contextItems.addAll(includedItems.getItems()); inclusionKeys.add(includedItems.getCachingKey()); return new RecommendationContext(MODE.INCLUSION, contextItems, Collections.<Long>emptySet(), inclusionKeys, currentItem, lastRecListUUID,optsHolder); } Set<ItemIncluder> inclusionProducers = strategy.includers; Set<ItemFilter> itemFilters = strategy.filters; if(inclusionProducers == null || inclusionProducers.size() ==0){ if (itemFilters==null || itemFilters.size() == 0){ logger.warn("No filters or includers present in strategy"); return new RecommendationContext(MODE.NONE, Collections.<Long>emptySet(), Collections.<Long>emptySet(), Collections.<String>emptyList(),currentItem, lastRecListUUID,optsHolder); } for (ItemFilter filter : itemFilters){ contextItems.addAll(filter.produceExcludedItems(client, user,clientUserId, optsHolder,currentItem, lastRecListUUID, numRecommendations)); } return new RecommendationContext(MODE.EXCLUSION, contextItems, contextItems, inclusionKeys, currentItem, lastRecListUUID,optsHolder); } Integer itemsPerIncluder = optsHolder.getIntegerOption(ITEMS_PER_INCLUDER_OPTION_NAME); if(itemFilters == null || itemFilters.size() ==0) { for (ItemIncluder producer : inclusionProducers){ FilteredItems filteredItems = producer.generateIncludedItems(client, dimensions,itemsPerIncluder); contextItems.addAll(filteredItems.getItems()); inclusionKeys.add(filteredItems.getCachingKey()); } return new RecommendationContext(MODE.INCLUSION, contextItems, Collections.<Long>emptySet(), inclusionKeys, currentItem, lastRecListUUID,optsHolder); } Set<Long> included = new HashSet<>(); Set<Long> excluded = new HashSet<>(); for (ItemFilter filter : itemFilters){ excluded.addAll(filter.produceExcludedItems(client, user,clientUserId,optsHolder, currentItem, lastRecListUUID, numRecommendations)); } for (ItemIncluder producer : inclusionProducers){ FilteredItems filteredItems = producer.generateIncludedItems(client, dimensions,itemsPerIncluder); included.addAll(filteredItems.getItems()); inclusionKeys.add(filteredItems.getCachingKey()); } included.removeAll(excluded); // ok to do this as the excluded items that weren't in "included" will never // be recommended return new RecommendationContext(MODE.INCLUSION, included, excluded, inclusionKeys,currentItem, lastRecListUUID,optsHolder); } public static class OptionsHolder{ private final DefaultOptions defaultOptions; private final Map<String, String> options; public OptionsHolder(DefaultOptions options, Map<String, String > perStrategyOptions){ this.defaultOptions = options; this.options = perStrategyOptions; } public String getStringOption(String optionName){ if(options.containsKey(optionName)) return options.get(optionName); return defaultOptions.getOption(optionName); } public boolean getBooleanOption(String optionName){ if(options.containsKey(optionName)) return BooleanUtils.toBoolean(options.get(optionName)); else return BooleanUtils.toBoolean(defaultOptions.getOption(optionName)); } public Double getDoubleOption(String optionName){ try { if(options.containsKey(optionName)) return Double.parseDouble(options.get(optionName)); else return Double.parseDouble(defaultOptions.getOption(optionName)); } catch (NumberFormatException | NullPointerException e){ logger.error("Couldn't get algorithm option "+optionName,e); return 0.0D; } } public Integer getIntegerOption(String optionName){ try { if(options.containsKey(optionName)) return Integer.parseInt(options.get(optionName)); else return Integer.parseInt(defaultOptions.getOption(optionName)); } catch (NumberFormatException | NullPointerException e){ logger.error("Couldn't get algorithm option "+optionName,e); return 0; } } } }