2013年10月5日土曜日

Underscore.jsのメソッドまとめ

_.shuffle() : 配列をシャッフル

_.each() : 要素のそれぞれに処理を行う


            _.each ([2, 5, 8], function(num) {
                console.log(num*2);
             });
配列のそれぞれの要素がfunctionの引数に入ってきて処理を行う


_.map() : 要素のそれぞれに処理を行って、returnしたものを配列として返す
            x = _.map ([2, 5, 8], function(num) {
                 return num*2;
             });
           
x = [4, 10, 8]という風に返ってくる

find : 条件に最初に合うものを探して返す


              var a = [2, 5, 8, 42, 12];
              var x;
 
              x = _.find(a, function(num) {
                  return num > 5;
              });
    //aの各要素がnumに入って、num>5ならその値をxに入れて終了。
     最初に条件に一致したものを返す


filter :条件に合うもの全てを配列として返す、挙動はほぼfindとおなじ。
contains : 配列にある値があるかないかを判定する


            var a = [2, 5, 8, 42, 12];
              var x;
 
              x = _.contains(a, 8);

配列aに8という要素があればtrueなければfalseを返す

groupBy : 条件に併せてグルーピングして配列で返す

x = _.groupBy(a, function(num) {
return num % 3;
});
この場合だと3でわって余りが1のグループ、2のグループ、0のグループ
の配列を返す

countBy : 条件ごとに要素数をカウント

x = _.countBy(a, function(num){
  return num % 2 == 0 ? 'even' : 'odd';});条件に合う要素数をevenとして、合わない元をoddとして返す

sortBy:演算結果の大きさでソートする

x = _.sortBy(a, function(num) {
return Math.sin(num);
});

この場合は各要素のsin値の大きさでソートして配列を返す
x = _.sortBy(["me", "i", "and"], 'length');
このようにプロパティーを第二引数にとってそのプロパティーでソートする

集合演算子計

var a = [1, 2, 5];
var b = [5, 2, 8];
var x;
_.union : 複数の配列の重複を削除して和集合をとる

 x = _.union(a, b);
結果はx = [1, 2, 5, 8]

_.intersection : 共通部部分を探す

x = _.intersection(a, b);
結果はx = [2, 5]

_.difference : 差分を求める(第二引数の要素には無い第一引数の要素)
x = _.difference(a, b);


_.uniq:重複がないものだけ引っ張ってくる
x = _.uniq([2, 5, 2, 10, 5]);
結果はx = [2, 5, 10]

オブジェクトに関する関数

var x;
var user = {
 name: 'hotondo',
 score: 80,
 web: 'http://hotondo-kojin.com'
};

_.keys : オブジェクトのキーを返してくれる
x = _.keys(user);
結果
x = [name, score, web]

_.values : オブジェクトの値のみとってくる
x = _.values(user);

_.invert : value とkeyを逆転する
x = _.invert(user);

_.has : keyを持つか判定
x = _.has(user, "name");
結果
x = true;

あとはisEmptyとかisString, isNull, isNumberとかもある

生成系

_.range([start], stop, [step]) : startからstopまでの連続し配列を生成、stepに値をいれると飛び飛びになる

_.range(10); 
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

_.range(1, 11); 
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

_.random([max]):[max]までの数字でランダムな数字を生成

_.escape(”文字列”) : 文字列を実体参照した形で表示する

_.times(回数, function()) : 関数の処理を回数分行う

_(回数).times(function()):処理される内容は”_.times(回数, function())”

繋げて書くことが出来るメソッド

_.chain(”処理対象”).メソッド.メソッド.メソッド.value() : このようにchainとvalueの間に複数のメソッドをつないで処理することができる

テンプレート機能
_.template:テンプレートを作成してその中にオブジェクトを当て込む機能です
var x;
var user = {
 name: 'hotondo',
 score: 80,
 web: 'http://hotondo-kojin.com'
};
var tpl = "<% console.log('hello from tpl'); %><li><%- name %> (<%- score %>)</li>";
x = _.template(tpl, user);
こうすることでtplのなかのnameとscoreにuserのオブジェクトが代入された形で出力されます。
代入する際に使われる括りが3種類あって
<% %> :内部にjavascriptの命令を書き込める
<%- %> :値をそのまま出力
<%= %> :値をエスケープして出力

2013年10月4日金曜日

haml メモ

属性の付け方
%html{:lang => "ja"}
%meta(charset="UTF-8")
の2種類がある

コメントの書き方
htmlにも反映される書き方 : /
htmlには反映されない書き方: -#
 *字下げすればコメントでくくられるので複数行かけるよ

属性の書き方いろいろ
<div class="myClass" id="main"></div>
と表示させたい場合


  1. %div{:id => "main", :class => "myClass"}
  2. %div(id="main" class="myClass")
  3. %div#main.myClass

上の1〜3どれでも同じように出力される

フィルターいろいろ

  • :css
  • :javascript
  • :escaped

  1. :css
  2. .myStyle {
  3. color: red;
  4. }
のように
:フィルター名
の後にインデントを下げて普通に内容を書けば
:cssなら<style>, :javascriptなら<script>, :escapedなら実体参照で出力される

Rubyの表現
#{}
カッコの中身ではルビーを実行できる、その結果がテキストとして出力される

%p total is #{5 * 3}
結果
<p>total is 15</p>

=
イコールの後にルビーの命令を入力することもできる
%p= Time.now
結果
<p>2012-11-14 19:47:53 +0900</p>

-
ただ単にルビーを実行したいときに使う
- (1..10).each do |i|
%p{:id => "item_#{i}"} #{i}

結果
<p id="item_1">1</p>
<p id="item_2">2</p>
<p id="item_3">3</p>
<p id="item_4">4</p>
<p id="item_5">5</p>
<p id="item_6">6</p>
<p id="item_7">7</p>
<p id="item_8">8</p>
<p id="item_9">9</p>
<p id="item_10">10</p>



2013年7月17日水曜日

PHPで学ぶ「集合知プログラミング」〜最適化〜 3

今回は学生寮の最適化問題をPHPで書いてみます。
まずは$dormsと$prefで学生寮のリストと各学生の希望のリストを作ります


$dorms = array('Zeus', 'Athena', 'Hercules', 'Bacchus', 'Plute');
$prefs = array(array('Toby', array('Bacchus', 'Hercules')), array('Steve', array('Zeus', 'Plute')), array('Andrea', array('Athena', 'Zeus')),array('Sarah', array('Zeus', 'Plute')), array('Dave', array('Athena', 'Baccus')), array('Jeff', array('Hercules', 'Plute')), array('Fred',  array('Hercules', 'Plute')), array('Suzie', array('Bacchus', 'Hercules')), array('Laura', array('Bacchus', 'Hercules')), array('Neil', array('Hercules', 'Athena')));

学生に寮を割り当てるたびに寮の候補が減っていくので
今回の$domainは順番に範囲が狭くなるように設定しています


$domain = array();
var_dump($prefs);
for ($i = 0; $i <count($dorms) * 2; $i++) {
    $domain[] = array(0, count($dorms)*2 -1 - $i);
}

各生徒への寮の割り当て配列$vecが与えられた時に寮の割り当て表を出力する関数を定義します。


function printsolution($vec, $dorms, $prefs) {
    $slots = array();
    for ( $i = 0; $i<count($dorms); $i++) {
        $slots[] = $i;
        $slots[] = $i;
    }

    for($i = 0; $i < count($vec); $i++) {
        $x = $vec[$i];
        $dorm = $dorms[$slots[$x]];
        echo $prefs[$i][0] . ',' . $dorm . '<br>';
        array_slice($slots, $x, 1);
    }
}
$vec = array(0,0,0,0,0,0,0,0,0,0);
var_dump(printsolution($vec, $dorms, $prefs));

寮の割り当てに対するコスト関数の定義を行います。


function dormcost($vec, $dorms, $prefs){
    $cost = 0;
    $slots = array(0,0,1,1,2,2,3,3,4,4);

    for($i = 0; $i < count($vec); $i++) {
        $x = $vec[$i];
        $dorm = $dorms[$slots[$x]];
        $pref = $prefs[$i][1];
        if($pref[0] == $dorm) {
            $cost = $cost + 0;
        } elseif($pref[1] == $dorm) {
            $cost = $cost + 1;
        } else {
            $cost = $cost + 3;
        }
        array_slice($slots, $x, 1);
    }
    return $cost;
}

ランダムオプティマイゼーションで寮の割り当て問題の最適化をしてみます。


function dormcostrandomoptimize($domain, $dorms, $prefs) {
    $best = 999999999;
    $bestr = 'null';
    for($i = 0; $i < 10000; $i++) {
        $r = array();
        for($s = 0; $s < count($domain); $s++) {
            $r[] = rand($domain[$s][0], $domain[$s][1]);
        }
        $cost = dormcost($r, $dorms, $prefs);

        if ($cost < $best) {
            $best = $cost;
            $bestr = $r;
        }
    }
    return $bestr;
}

$s = dormcostrandomoptimize($domain, $dorms, $prefs);
var_dump(dormcost($s, $dorms, $prefs));
var_dump(printsolution($s, $dorms, $prefs));

2013年7月15日月曜日

PHPで学ぶ「集合知プログラミング」〜最適化〜 2

ランダムサーチによる最小コストの旅程を求める関数。
コスト関数を入れ替えれば旅程以外の問題でも最小、最大をランダムサーチによって求めることができる。
function randomoptimize($domain, $origin, $people, $flights, $destination) {
$best = 999999999;
$bestr = 'null';
for($i = 0; $i < 10000; $i++) {
$r = array();
for($s = 0; $s < count($domain); $s++) {
$r[] = rand($domain[$s][0], $domain[$s][1]);
}
$cost = schedulecost($r, $origin, $people, $flights, $destination);
if ($cost < $best) {
$best = $cost;
$bestr = $r;
}
}
return $bestr;
}
$domain = array();
for ($i = 0; $i < count($people)*2; $i++) {
$domain[] = array(0, 9);
}
$result = randomoptimize($domain, $origin, $people, $flights, $destination);
$schedule = printschedule($result, $people, $flights, $destination);

ヒルクライムによる最小コストを求める関数。
コスト関数を入れ替えれば、他の問題においても最小、最大値を求めることができる。
function hillclimb($domain, $origin, $people, $flights, $destination) {
$r = array();
for($s = 0; $s < count($domain); $s++) {
$r[] = rand($domain[$s][0], $domain[$s][1]);
}
while(1){
$neighbors = array();
for ($k = 0; $k < count($domain); $k++) {
if($r[$k] > $domain[$k][0]) {
$neighbors[] = array_replace($r, array($k => $r[$k]-1));
}
if($r[$k] < $domain[$k][1]) {
$neighbors[] = array_replace($r, array($k => $r[$k]+1));
}
}
$current = schedulecost($r, $origin, $people, $flights, $destination);
$best = $current;
for ($p = 0; $p < count($neighbors); $p++) {
$cost = schedulecost($neighbors[$p], $origin, $people, $flights, $destination);
if ( $cost < $best ) {
$best = $cost;
$r = $neighbors[$p];
}
}
if( $best == $current ) {
break;
}
}
return $r;
}
$result = hillclimb($domain, $origin, $people, $flights, $destination);
var_dump($result);
$cost = schedulecost($result, $origin, $people, $flights, $destination);
var_dump($cost);
$schedule = printschedule($result, $people, $flights, $destination);

擬似アニーリングによる最適化関数。
ここでは、旅程のコストの最適値を求めているが、コスト関数を入れ替えれば他の問題にも適用できます。
function annealingoptimize($domain, $origin, $people, $flights, $destination, $T=10000.0, $cool=0.95, $step=1) {
$vec = array();
for($s = 0; $s < count($domain); $s++) {
$vec[] = rand($domain[$s][0], $domain[$s][1]);
}
while ( $T > 0.1 ) {
$i = rand(0, (count($domain) - 1) );
$dir = rand( -$step, $step);
$vecb = $vec;
$vecb[$i] += $dir;
if($vecb[$i] < $domain[$i][0]){
$vecb[$i] = $domain[$i][0];
} elseif ($vecb[$i] > $domain[$i][1]) {
$vecb[$i] = $domain[$i][1];
}
$ea = schedulecost($vec, $origin, $people, $flights, $destination);
$eb = schedulecost($vecb, $origin, $people, $flights, $destination);
$p = exp(-abs($eb-$ea)/$T);
$rand = rand(1,10000)/10000;
if (($eb < $ea) || ($rand < $p)) {
var_dump('A');
$vec = $vecb;
}
$T = $T*$cool;
}
return $vec;
}
$result = annealingoptimize($domain, $origin, $people, $flights, $destination, $T=10000.0, $cool=0.95, $step=1);
var_dump($result);
$cost = schedulecost($result, $origin, $people, $flights, $destination);
var_dump($cost);
$schedule = printschedule($result, $people, $flights, $destination);

遺伝アルゴリズムを用いた最適化関数。
ここでは旅程問題の最小コストの最適化を試みていますが、他の問題にも応用できます。
かなり強力なアルゴリズムなのでオススメです。
肌感としては、世代数を増やすよりも人口(ポピュレーション)を増やしたほうが良い解に辿り着ける可能性が高い気がします。
function geneticoptimize($domain, $origin, $people, $flights, $destination, $popsize = 50, $step=1, $mutprob=0.2, $elite=0.2, $maxiter = 100) {
function mutate($vec, $domain, $step) {
$i = rand(0, (count($domain) - 1));
$rand = rand(1,10000)/10000;
if(($rand < 0.5) && ($vec[$i] > $domain[$i][0])) {
$vec[$i] = $vec[$i] - $step;
return $vec;
} elseif($vec[$i] < $domain[$i][1]) {
$vec[$i] = $vec[$i] + $step;
return $vec;
}
}
function crossover($r1, $r2, $domain) {
$i = rand(1, (count($domain) - 2));
for($k = 0; $k < $i; $k++){
$r2[$k] = $r1[$k];
}
return $r2;
}
$pop = array();
for($y = 0; $y < $popsize; $y++){
$vec = array();
for($s = 0; $s < count($domain); $s++) {
$vec[] = rand($domain[$s][0], $domain[$s][1]);
}
$pop[] = $vec;
}
$topelite = floor($elite*$popsize);
for($g=0; $g<$maxiter; $g++){
$scores = array();
foreach($pop as $key => $v){
if(!empty($v)){
$scores[]=array(schedulecost($v, $origin, $people, $flights, $destination), $v);
}
}
sort($scores);
$pop = $ranked = array();
foreach($scores as $key => $v) {
$ranked[] = $v[1];
}
foreach($ranked as $v){
$pop[] = $v;
if(count($pop) == $topelite) {
break;
}
}
while(count($pop) <= $popsize) {
$randrand = rand(1,10000)/10000;
if($randrand < $mutprob){
$c = rand(0, $topelite);
$pop[] = mutate($ranked[$c], $domain, $step);
}else{
$c1 = rand(0, $topelite);
$c2 = rand(0, $topelite);
$pop[] = crossover($ranked[$c1], $ranked[$c2], $domain);
}
}
print $scores[0][0] . '<br />';
}
return $scores[0][1];
}
$result = geneticoptimize($domain, $origin, $people, $flights, $destination, $popsize = 50, $step=1, $mutprob=0.2, $elite=0.2, $maxiter = 100);
var_dump($result);
die();
$cost = schedulecost($result, $origin, $people, $flights, $destination);
var_dump($cost);
$schedule = printschedule($result, $people, $flights, $destination);
var_dump($schedule);
遺伝的アルゴリズムはpythonコードをphp化するのに苦労しました。
もとのpythonコードにミスがあったので苦戦していたのですが、<『集合知プログラミング』 解体新書/a>が非常に参考になりました。

2013年7月14日日曜日

PHPで学ぶ「集合知プログラミング」〜最適化〜 1

グループ旅行問題を特にあたり$peopleのarrayに人名と出発地を入れる。
そして、全ての人達の目的地としてLGAを設定する。
サンプルのフライトデータとしてhttp://kiwitobes.com/optimize/schedule.txtが用意されているのでダウンロードして同じディレクトリに入れておく。
そして、schedule.txtからoriginとdestをキーとしたフライトリストを作る。
さらに、HH:MMの形式で表された時間を午前0時0分から何分経ったかに変換するgetminutes関数を定義する

$people = array(array('Symour', 'BOS'), array('Franny', 'DAL'), array('Zooey', 'CAK'), array('Walt', 'MIA'), array('Buddy', 'ORD'), array('Les', 'OMA'));
$destination = 'LGA';
$flights = array();
$fp = fopen("schedule.txt", "r");
while($line = fgets($fp)) {
$line = rtrim($line);
$explode_line = explode(",",$line);
$origin = $explode_line[0];
$dest = $explode_line[1];
$depart = $explode_line[2];
$arrive = $explode_line[3];
$price = $explode_line[4];
if(!isset($flights[$origin][$dest])){
$flights[$origin][$dest] = array();
}
$flights[$origin][$dest][] = array($depart, $arrive, $price);
}
function getminutes($t) {
$x = strptime($t, '%H:%M');
return $x["tm_min"] + $x["tm_hour"]*60;
}
誰がどの便でLGAにやってくるかを$sで表現する。
各数字はその日の何便目に乗るか。そして、Seymourの行き・帰り・Frannyの行き・帰り・・・の順に並んでいる。
この数列から各人の旅程の情報を出力する関数を定義する(printschedule)

$s = array(1,4,3,2,7,3,6,3,2,4,5,3);
function printschedule($r, $people, $flights, $destination) {
$d = count($r)/2;
for($i = 0; $i < $d; $i++){
$name = $people[$i][0];
$origin = $people[$i][1];
$out = $flights[$origin][$destination][$r[($i*2)]];
$ret = $flights[$destination][$origin][$r[($i*2+1)]];
echo sprintf("%10s%10s %5s-%5s $%3s %5s-%5s $%3s" . PHP_EOL, $name, $origin, $out[0], $out[1], $out[2], $ret[0], $ret[1], $ret[2]);
}
}
コスト関数の定義。運賃総額と空港での待ち時間、レンタカーの追加料金を含めたコストを旅程ごとに算出する関数を定義する。

function schedulecost($sol, $origin, $people, $flights, $destination) {
$totalprice = 0;
$latestarrival = 0;
$earliestdep = 24*60;
$d = count($sol)/2;
for ($i = 0; $i < $d; $i++) {
$origin = $people[$i][1];
$outbound = $flights[$origin][$destination][$sol[$i*2]];
$returnf = $flights[$destination][$origin][$sol[$i*2+1]];
$totalprice += $outbound[2];
$totalprice += $returnf[2];
if ($latestarrival < getminutes($outbound[1])) {
$latestarrival = getminutes($outbound[1]);
}
if ($earliestdep > getminutes($returnf[0])) {
$earliestdep = getminutes($returnf[0]);
}
}
$totalwait = 0;
for ($i = 0; $i < $d; $i++) {
$origin = $people[$i][1];
$outbound = $flights[$origin][$destination][$sol[$i*2]];
$returnf = $flights[$destination][$origin][$sol[$i*2+1]];
$totalwait += $latestarrival - getminutes($outbound[1]);
$totalwait += getminutes($returnf[0]) - $earliestdep;
}
if ($latestarrival < $earliestdep) {
$totalprice += 50;
}
return $totalprice + $totalwait;
}
$totalcost = schedulecost($s, $origin, $people, $flights, $destination);
var_dump($totalcost);

2013年7月13日土曜日

PHPで学ぶ「集合知プログラミング」〜推薦を行う〜 2

transformPrefsでスコアのディクショナリをアイテムごとのディクショナリに変換して
アイテムごとにループを回して各アイテムに似ているアイテムをランキングでとってくる
各ユーザーの評価で映画のタイトルのスコアに重みづけをしてアイテムベースでユーザーにオススメをする

function calculateSimilarItems($critics, $n) {
$result = array();
$itemPrefs = transformPrefs($critics);
$c = 0;
foreach($itemPrefs as $item => $v) {
$c++;
if( $c%100 == 0){
echo sprintf("%d / %d", $c, count($itemPrefs));
}
$scores = topMatches($itemPrefs, $item, $n, 'distance');
$result[$item] = $scores;
}
return $result;
}
$n = 10;
$itemsim = calculateSimilarItems($critics, $n);
function getRecommendedItems($critics, $itemMatch, $user) {
$userRatings = $critics[$user];
$scores = array();
$totalSim = array();
foreach ($userRatings as $item => $rating) {
foreach ($itemMatch[$item] as $item2 => $similarity) {
if ( array_key_exists($item2, $userRatings)) {
continue;
}
if( !isset($scores[$item2]) ){
$scores[$item2] = $similarity*$rating;
}else{
$scores[$item2] = $scores[$item2] + $similarity*$rating;
}
if (!isset($totalSim[$item2])) {
$totalSim[$item2] = $similarity;
} else {
$totalSim[$item2] = $totalSim[$item2] + $similarity;
}
}
}
foreach ($scores as $item => $score) {
$rankings[$item] = $score/$totalSim[$item];
}
arsort($rankings);
return $rankings;
}
$recommendation = getRecommendedItems($critics, $itemsim, 'Toby');
var_dump($recommendation);
MovieLensのデータ・セットを使って商品の推薦を行います。

まず、loadMovieLens関数でMovieLensのデータをロードします。そして、getRecommendations関数を使ってユーザーベースの推薦を行い、
calculateSimilarItems関数を使ってアイテム間の相関を計算し、getRecommendedItems関数でアイテムベースの推薦を行います。

function loadMovieLens($path) {
$movies = array();
$fp = fopen($path . "/u.item", "r");
while($line = fgets($fp)){
$line_explode = explode("|", $line);
$movies[$line_explode[0]] = $line_explode[1];
}
fclose($fp);
$prefs = array();
$fp = fopen($path . "/u.data", "r");
while($line = fgets($fp)){
$data_explode = explode("\t",$line);
$user = $data_explode[0];
$movieid = $data_explode[1];
$rating = $data_explode[2];
$ts = $data_explode[3];
if(!isset($prefs[$user])) {
$prefs[$user] = array();
}
$prefs[$user][$movies[$movieid]]=$rating;
}
return $prefs;
}
$lines = loadMovieLens('MovieLensのデータを保存したフォルダ');
$movie_recommend = getRecommendations($lines, '87', 'pearson');
$itemsim = calculateSimilarItems($lines, 50);
$movie_recommend_item = getRecommendedItems($lines, $itemsim, '87');
var_dump($itemsim);
とここまで「集合知プログラミング」の2章をPHPで書きなおして来たわけですが、
実際にアイテムベースで推薦を行う場合のロジックはどのようなものなのでしょうか?
まず、アイテムベースの推薦で使っている関数は
calculateSimilarItems
transformPrefs
topMatches
sim_peason( or sim_distance)
でやっていることといえば
まず、ユーザーごとの各アイテムの評価のarrayをアイテムごとのユーザーによる評価のarrayに変換しています。
つまり、arrayの第一連想配列のキーはアイテム名で、第二連想配列のキーは評価者となります。
そして、sim_peasonを使ってアイテム間の相関スコアを取得します。
そのアイテムスコアを使ってtopMaches関数で各アイテムごとに、相関スコアが高い順にアイテムをランキング付けします。
たとえば、Superman Returnsに一番相関が高いのはYou Me and Dupreeでスコアは0.657
二番目に高いのはLady in the Waterでスコアは0.487といった具合です。
ここまでで、どのアイテムとどのアイテムが似ているのかがわかるようになったわけです。
最後にユーザーに対して推薦を行う場合は
ユーザーが既に評価した(購入した)アイテムとまだ購入していないアイテムの相関スコアに対して
評価したアイテムの評価で重み付けを行い、まだ購入していないアイテムそれぞれの推薦度スコアを計算します
そして推薦度スコアがもっとも高いものをユーザーに推薦します。
この章では一旦、ユーザーベースの推薦を行ったあとに、そのロジックを逆転させてアイテムベースの推薦を行っているので
どういうロジックでアイテムベースの推薦を行っているのかを理解するのがすこし難しいですね

2013年7月12日金曜日

PHPで学ぶ「集合知プログラミング」〜推薦を行う〜 1

「集合知プログラミング」 買いました!
データのシミュレーションや分析には興味があったので
といっても
ただ、エクセルで表とかグラフとか作るだけじゃつまんない
エンジニアとして
どや!!って言える仕事がしたい
なので、集合知プログラミングでエンジニアっぽい分析を習得しようと思います

この集合知プログラミングはPythonで書かれております。
僕は主にPHPを使って開発をしていますし
というか、PHPしかつかってないし。。
ただ単にPythonを写経するだけだと何にも身につかないと思うので
サンプルコードをPHPで書き直しながら進めていきたいと思っています。 

まずはデータ・セットの作成

$critics=array('Lisa Rose'=> array('Lady in the Water' =>2.5, 'Snakes on a Plane' =>3.5,
'Just My Luck'=> 3.0, 'Superman Returns'=> 3.5, 'You, Me and Dupree'=> 2.5,
'The Night Listener'=> 3.0),
'Gene Seymour' => array('Lady in the Water'=> 3.0, 'Snakes on a Plane'=> 3.5,
'Just My Luck'=> 1.5, 'Superman Returns'=> 5.0, 'The Night Listener'=> 3.0,
'You, Me and Dupree'=> 3.5),
'Michael Phillips'=> array('Lady in the Water'=> 2.5, 'Snakes on a Plane'=> 3.0,
'Superman Returns'=> 3.5, 'The Night Listener'=> 4.0),
'Claudia Puig'=> array('Snakes on a Plane'=> 3.5, 'Just My Luck'=> 3.0,
'The Night Listener'=> 4.5, 'Superman Returns'=> 4.0,
'You, Me and Dupree'=> 2.5),
'Mick LaSalle'=> array('Lady in the Water'=> 3.0, 'Snakes on a Plane'=> 4.0,
'Just My Luck'=> 2.0, 'Superman Returns'=> 3.0, 'The Night Listener'=> 3.0,
'You, Me and Dupree'=> 2.0),
'Jack Matthews'=> array('Lady in the Water'=> 3.0, 'Snakes on a Plane'=> 4.0,
'The Night Listener'=> 3.0, 'Superman Returns'=> 5.0, 'You, Me and Dupree'=> 3.5),
'Toby'=> array('Snakes on a Plane'=>4.5,'You, Me and Dupree'=>1.0,'Superman Returns'=>4.0));
二人の評価者のユークリッド距離によるスコアを算出するsim_distanceとピアソン相関によるスコアを算出するsim_peasonを定義
リストの中から最も好みの似ている評価者を選び出すtopMatchesを定義。
var_dump($result)でTobyに似ているユーザーをarrayで返してくれる
function sim_distance($person1, $person2){
$si = array();
foreach ($person1 as $k => $v) {
if (array_key_exists($k, $person2)) {
$si[$k] = 1;
}
}
if (count($si) == 0) {
return 0;
}
$squares = array();
foreach($si as $k => $v) {
$squares[] = pow(($person1[$k] - $person2[$k]), 2);
}
$sum_of_squares = array_sum($squares);
return 1/(1 + $sum_of_squares);
}
function sim_pearson($p1, $p2) {
$si = array();
foreach ($p1 as $k => $v) {
if (array_key_exists($k, $p2)) {
$si[$k] = 1;
}
}
if (count($si) == 0) {
return 0;
}
$sum1 = $sum2 = $sum1Sq = $sum2Sq = $pSum = 0;
foreach ($si as $k => $v) {
$sum1 = $sum1 + $p1[$k];
$sum2 = $sum2 + $p2[$k];
$sum1Sq = $sum1Sq + $p1[$k]*$p1[$k];
$sum2Sq = $sum2Sq + $p2[$k]*$p2[$k];
$pSum = $pSum + $p1[$k]*$p2[$k];
}
$num = $pSum -($sum1*$sum2/count($si));
$den=sqrt(($sum1Sq-pow($sum1, 2)/count($si))*($sum2Sq-pow($sum2, 2)/count($si)));
if($den == 0) {
return 0;
}
$r = $num / $den;
return $r;
}
function topMatches($array, $name1, $number, $type){
foreach($array as $key => $v){
if($key != $name1) {
$other = $key;
if ($type == 'pearson') {
$score[$name1][$other] = sim_pearson($array[$name1], $array[$other]);
} elseif ($tyep == 'distance') {
$score[$name1][$other] = sim_distance($array[$name1], $array[$other]);
}else{
echo '正しい評価方法を指定してください';
exit;
}
}
}
arsort($score[$name1]);
$output = array_slice($score[$name1], 0, $number, true);
return $output;
}
$result = topMatches($critics, 'Toby', 3, 'pearson');
var_dump($result);
評価者に重み付けを行い(自身とどの程度好みが似ているか)
その重みづけスコアと書く評価者の評価点を掛けあわせて映画のタイトルごとにスコアを算出する
もっともスコアの高い映画が、対象とするユーザーにオススメの映画となる
function getRecommendations($array, $person, $type) {
$totals = array();
$simSums = array();
$rankings = array();
foreach ($array as $other => $v) {
if ($other == $person ){
continue;
} elseif($type == 'pearson') {
$simulate = sim_pearson($array[$person], $array[$other]);
if($simulate <= 0) {
continue;
} else {
$sim = $simulate;
}
} elseif($type == 'distance'){
$simulate = sim_distance($array[$person], $array[$other]);
if($simulate <= 0) {
continue;
} else {
$sim = $simulate;
}
} else {
echo '正しい評価方法を指定してください';
exit;
}
foreach ($array[$other] as $item => $value) {
if(!isset($totals[$item])) {
$totals[$item] = 0;
}
if(!isset($simSums[$item])) {
$simSums[$item] = 0;
}
if (!array_key_exists($item, $array[$person]) || $array[$person][$item] == 0) {
$totals[$item] += $array[$other][$item]*$sim;
$simSums[$item] += $sim;
}
}
}
foreach ($totals as $item => $total) {
if($simSums[$item] != 0) {
$rankings[$item] = $total /$simSums[$item];
}
}
arsort($rankings);
return $rankings;
}
$result = getRecommendations($critics, 'Toby', 'pearson');
var_dump($result);
今まで用いてきた評価者ごとの映画の評価配列を映画ごとの評価配列に変換する
以前に定義したtopMatches関数を使って、似た属性をもつ映画をランキング形式で表示できる。
また、映画が誰に見られるべきかをgetRecommendationsを使って表示することができる
function transformPrefs($critics) {
$result = array();
foreach ($critics as $key => $person) {
foreach ($person as $item => $value) {
if(!isset($result[$item])){
$resutl[$item] = array();
}
$result[$item][$key] = $value;
}
}
return $result;
}
$movies = transformPrefs($critics);
$result = topMatches($movies, 'Superman Returns', 4, 'pearson');
var_dump($result);
$recomend_person = getRecommendations($movies, 'Just My Luck', 'pearson');
var_dump($recomend_person);