前回までは「評者」に対し「アイテム」と、その「評価点」を元に「評者同士の類似度」の計算と、それを用いた「未評価のアイテムの推薦」をやってきた。
これに対し、評者とアイテムを逆にすることで違う視点からの計算ができる。
つまり、「アイテム」に対し「評者」とその「評価点」を元に計算することで、「アイテム同士の類似度」を得ることができる。
また同様に「未評価の評者の推薦」もできる。これはなかなか使いどころはなさそうだが…アイテムを売る側からすれば必要な情報かも知れない。
Critics.java 逆の評価表を得るようgetTransformedMapを追加
package chapter2; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class Critics { private Map<String, Map<String, Double>> data = new HashMap<String, Map<String, Double>>(); /** * データ読み込みによる初期化 */ public Critics() { 略 } /** * @return the data */ public Map<String, Map<String, Double>> getData() { return new HashMap<String, Map<String, Double>>(data); } /** * @return 評価者と評価されるアイテムを逆転させたMapを作成し、返す */ public Map<String, Map<String, Double>> getTransformData() { Map<String, Map<String, Double>> transformedMap = new HashMap<String, Map<String, Double>>(); // すべての第1キーについて探索 Iterator<String> it1 = data.keySet().iterator(); while (it1.hasNext()) { String key1 = it1.next(); Map<String, Double> dataMap = data.get(key1); // すべての第2キーについて探索 Iterator<String> it2 = dataMap.keySet().iterator(); while (it2.hasNext()) { String key2 = it2.next(); // 第2キーを新たに第1キーとして第2キーを持つMapを作成し、格納 Map<String, Double> map = transformedMap.get(key2); if (map == null) { map = new HashMap<String, Double>(); } map.put(key1, dataMap.get(key2)); transformedMap.put(key2, map); } } return transformedMap; } }
Main.java
package chapter2; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; public class Main { /** * @param targetName * @param simcal * @return simcalを使って計算した結果で、targetNameと類似度が高い順にソートされたリスト */ public List<ScoredThing> topMatches(Map<String, Map<String, Double>> preferenceMap, String target, SimilarityCalculator simcal) { List<ScoredThing> maches = new LinkedList<ScoredThing>(); // targetName以外の人との類似度を計算する Iterator<String> iterator = preferenceMap.keySet().iterator(); while (iterator.hasNext()) { String name = iterator.next(); if (name.equals(target)) { continue; } double similarity = simcal.getSimilarity(preferenceMap, name, target); maches.add(new ScoredThing(name, similarity)); } // 類似度順にソート Collections.sort(maches); return maches; } /** * @param targetName * @param simcal * @return simcalを使って計算した結果で、targetNameが未評価のもので推薦度が高い順にソートされたリスト */ public List<ScoredThing> getRecommendations(Map<String, Map<String, Double>> preferenceMap, String target, SimilarityCalculator simcal) { List<ScoredThing> recommendations = new LinkedList<ScoredThing>(); // targetの評価 Map<String, Double> targetMap = preferenceMap.get(target); // 一時的な合計を保存する Map<String, Double> totalsMap = new HashMap<String, Double>(); Map<String, Double> simSumMap = new HashMap<String, Double>(); Iterator<String> iterName = preferenceMap.keySet().iterator(); while (iterName.hasNext()) { String name = iterName.next(); // 自分自身とは比較しない if (name.equals(target)) { continue; } // 0以下のスコアは無視する double similarity = simcal.getSimilarity(preferenceMap, name, target); if (similarity < 0.0) { continue; } // まだ評価していないアイテムを探す Map<String, Double> mapCritique = preferenceMap.get(name); Iterator<String> iterKey = mapCritique.keySet().iterator(); while (iterKey.hasNext()) { String item = iterKey.next(); // 既に評価済みのアイテムは無視 if (targetMap.containsKey(item)) { continue; } double rating = mapCritique.get(item).doubleValue(); double currentTotals, currentSimSum; if (!totalsMap.containsKey(item)) { currentTotals = 0.0; currentSimSum = 0.0; } else { currentTotals = totalsMap.get(item).doubleValue(); currentSimSum = simSumMap.get(item).doubleValue(); } // 類似度×スコアを合計していく totalsMap.put(item, Double.valueOf(rating * similarity + currentTotals)); // 類似度を合計していく simSumMap.put(item, Double.valueOf(similarity + currentSimSum)); } } // 正規化したリストを作る Iterator<String> iterItems = totalsMap.keySet().iterator(); while (iterItems.hasNext()) { String item = iterItems.next(); if (simSumMap.containsKey(item)) { double score = totalsMap.get(item).doubleValue() / simSumMap.get(item).doubleValue(); recommendations.add(new ScoredThing(item, score)); } } // スコア順にソート Collections.sort(recommendations); return recommendations; } /** * */ public Main() { /* 評者をランキングする List<ScoredThing> results = topMatches(new Critics().getData(), Common.Toby, new PearsonCalculator()); */ /* アイテムを推薦する List<ScoredThing> results = getRecommendations(new Critics().getData(), Common.Toby, new PearsonCalculator()); */ /* 似ている製品をランキングする */ List<ScoredThing> results = topMatches(new Critics().getTransformData(), Common.SupermanReturns, new PearsonCalculator()); /* 評者を推薦する List<ScoredThing> results = getRecommendations(new Critics().getTransformData(), Common.JustMyLuck, new PearsonCalculator()); */ Iterator<ScoredThing> iterator = results.iterator(); while (iterator.hasNext()) { ScoredThing st = iterator.next(); System.out.println(st.getName() + ":" + st.getScore()); } } /** * @param args */ public static void main(String[] args) { new Main(); } }
実行結果
You, Me and Dupree:0.6579516949597695 Lady in the Water:0.4879500364742689 Snakes on a plane:0.11180339887498941 The Night Listener:-0.1798471947990544 Just My Luck:-0.42289003161103106