集合知プログラミング 2章 似ている製品

前回までは「評者」に対し「アイテム」と、その「評価点」を元に「評者同士の類似度」の計算と、それを用いた「未評価のアイテムの推薦」をやってきた。
これに対し、評者とアイテムを逆にすることで違う視点からの計算ができる。
つまり、「アイテム」に対し「評者」とその「評価点」を元に計算することで、「アイテム同士の類似度」を得ることができる。
また同様に「未評価の評者の推薦」もできる。これはなかなか使いどころはなさそうだが…アイテムを売る側からすれば必要な情報かも知れない。


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