ユークリッド距離の他に類似度を決定する手法としてピアソン相関係数が紹介されている。
これはデータが正規化されていない場合にその偏りをある程度補正してくれることになるようだ。
ピアソン相関係数は以下の式から求められる。
PearsonCalculator.java
package chapter2; import java.util.Iterator; import java.util.List; import java.util.Map; public class PearsonCalculator extends SimilarityCalculator { /* (non-Javadoc) * @see chapter2.SimilarityCalculator#getSimilarity(java.lang.String, java.lang.String) */ @Override double getSimilarity(String lhs, String rhs) { Map<String, Double> lhsMap = data.getMap(lhs); Map<String, Double> rhsMap = data.getMap(rhs); // 共通keyのリストを取得 List<String> commonKeyList = Common.commonKeyList(lhsMap, rhsMap); // 共通のものがなければ0.0を返す if (commonKeyList == null) { return 0.0; } double sumLhs = 0.0, sumRhs = 0.0; // すべての点数の合計 double sumSqrLhs = 0.0, sumSqrRhs = 0.0; // すべての点数の平方の合計 double sumProduct = 0.0; // それぞれの点数の積の合計 Iterator<String> iterList = commonKeyList.iterator(); while (iterList.hasNext()) { String key = iterList.next(); double valueLhs = lhsMap.get(key).doubleValue(); double valueRhs = rhsMap.get(key).doubleValue(); sumLhs += valueLhs; sumRhs += valueRhs; sumSqrLhs += valueLhs * valueLhs; sumSqrRhs += valueRhs * valueRhs; sumProduct += valueLhs * valueRhs; } double n = (double)(commonKeyList.size()); // 分子 double numerator = sumProduct - (sumLhs * sumRhs / n); // 分母 double denominator = Math.sqrt((sumSqrLhs - sumLhs * sumLhs / n) * (sumSqrRhs - sumRhs * sumRhs / n)); if (denominator == 0.0) { return 0.0; } else { return numerator / denominator; } } }
Common.javaを修正
package chapter2; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; public class Common { final static String LisaRose = "Lisa Rose"; final static String GeneSeymour = "Gene Seymour"; final static String MichaelPhillips = "Michael Phillips"; final static String ClaudiaPuig = "Claudia Puig"; final static String MickLassalle = "Mick Lassalle"; final static String JackMatthews = "Jack Matthews"; final static String Toby = "Toby"; final static String LadyInTheWater = "Lady in the Water"; final static String SnakesOnAPlane = "Snakes on a plane"; final static String JustMyLuck = "Just My Luck"; final static String SupermanReturns = "Superman Returns"; final static String YouMeAndDupree = "You, Me and Dupree"; final static String TheNightListener = "The Night Listener"; /** * @param lhsMap * @param rhsMap * @return 両方のMapに共通しているkeyをListにして返す */ static List<String> commonKeyList(Map<String, Double> lhsMap, Map<String, Double> rhsMap) { // どちらかでも存在していなければnullを返す if ((lhsMap == null) || (rhsMap == null)) { return null; } // 共通しているkeyを返す // 共通のものが一つもなければnullを返す List<String> commonList = new LinkedList<String>(); Iterator<String> iterKey = lhsMap.keySet().iterator(); while (iterKey.hasNext()) { String key = iterKey.next(); if (rhsMap.containsKey(key)) { commonList.add(key); } } if (commonList.size() == 0) { return null; } return commonList; } }
Main.java
package chapter2; public class Main { /** * @param args */ public static void main(String[] args) { SimilarityCalculator simcal; simcal = new DistanceCalculator(); System.out.println(simcal.getSimilarity(Common.LisaRose, Common.GeneSeymour)); simcal = new PearsonCalculator(); System.out.println(simcal.getSimilarity(Common.LisaRose, Common.GeneSeymour)); } }
実行結果。
0.14814814814814814 0.39605901719066977
結構ユークリッド距離と比べると値が変わるんだなぁ。
この場合はユークリッド距離的には類似してなさそうだけど偏りを補正するといくらか近いよ、ということになるのかな。