2014年2月23日日曜日

Solaris11 + nginx + Lua + Redis で動的リバースプロキシサーバーを作ってみた

nginxによる動的リバースプロキシサーバー

以前、LXCの勉強会で知ったNginxとRedisを使った動的なプロキシサーバーに興味をそそられて、「これは素晴らしい!」といまさら影響を受け、この辺の資料や記事を漁ってみたもののLinux系の情報は多いのですがなかなかSolaris系の情報が少なかったため、「では、Solarisでやってみましょう」ということでやってみました

やりたいこと

今回のサンプル環境でやりたいことは、URLに含まれるディレクトリが、Redisへ登録されている場合だけ静的コンテンツを表示するwebサーバーへ(server3)転送し、それ以外は動的コンテンツを表示するwebサーバー(server1、server2)へ転送するという処理をnginxで処理させます。

構成

環境として、愛しのGentoo Linuxへインストールしたvmware player上のSolaris11で構築しました、こんな感じです

 

それぞれ5つのzoneを作成して必要なアプリケーションをインストールします。

作成するZone

  • ngx
    • nginx + Lua + Redis
  • server1
  • server2
    • apache + PHP + ZendFramework(動的コンテンツ出力用)
  • server3
    • apache(静的コンテンツ出力用) 
  • iichiko-spec
    • パッケージサーバー(IPS)
以上のZoneをSolaris11上へ作成します

インストール

まず、全部パッケージをビルドするのは面倒なので、パッケージサーバーを作成しそこからインストールします。このパッケージは私がビルドしたものなので、ご利用の際は自己責任でお願いします、検証のために作成したためテストなどは一切行っておりませんのでご注意ください。

iichiko-specゾーンの作成

パッケージインストール用zoneの作成、これは他のzoneで代用しても構いません。

iichiko-spec.cfg
sol11 ~ # cat iichiko-spec.cfg
create -b
set brand=solaris
set zonepath=/rpool/zones/iichiko-spec
set autoboot=false
set ip-type=shared
add net
set address=192.168.254.102/24
set configure-allowed-address=true
set physical=net0
end
iichiko-specゾーンの構築
sol11 ~ # zonecfg -z iichiko-spec -f iichiko-spec.cfg
sol11 ~ # zoneadm -z iichiko-spec install
sol11 ~ # zoneadm -z iichiko-spec boot
sol11 ~ # zlogin -C iichiko-spec
パッケージサーバーを設定
こでで、iichiko-specからnginxとredisなどの必要パッケージをインストールすることが出きるようになります。
root@iichiko-spec:~# zfs create -p -o mountpoint=/var/pkglocal rpool/pkglocal
root@iichiko-spec:~# svccfg -s application/pkg/server setprop pkg/inst_root=/var/pkglocal
root@iichiko-spec:~# pkgsend -s file:///var/pkglocal create-repository --set-property publisher.prefix=iichiko-spec
root@iichiko-spec:~# svccfg -s pkg/server setprop pkg/port=80
root@iichiko-spec:~# svccfg -s svc:/application/pkg/server setprop pkg/readonly=true
root@iichiko-spec:~# svcadm refresh pkg/server
root@iichiko-spec:~# wget http://www.karky7.com/files/ips-2014.02.22.zfs.img.gz
root@iichiko-spec:~# gunzip < ips-2014.02.22.zfs.img.gz | zfs recv -F rpool/pkglocal
root@iichiko-spec:~# svcadm enable pkg/server

http://192.168.254.102にアクセスしてこんな画面がでればOKです


nginxをソースからビルドする場合

今回のnginxへ組み込んだモジュールは
  • nginx-1.5.8
  • ngx_http_redis-0.3.7
  • lua_nginx_module-0.9.5rc2
です、ソースからインストールする方は、configureにモジュールを展開したディレクトリを指定してください

$ wget http://nginx.org/download/nginx-1.5.8.tar.gz
$ tar -xzvf nginx-1.5.8.tar.gz
$ cd nginx-1.5.8
$ ./condifgure \
--add-module=../ngx_http_redis-0.3.7 \
--add-module=../lua_nginx_module-0.9.5rc2
...
...


ngxゾーンの構築

ngx.cfg
create -b
set brand=solaris
set zonepath=/rpool/zones/ngx
set autoboot=false
set ip-type=shared
add net
set address=192.168.254.200/24
set configure-allowed-address=true
set physical=net0
end
ngxゾーンのインストール
sol11 ~ # zonecfg -z ngx -f ngx.cfg
sol11 ~ # zoneadm -z ngx install
ngxゾーンへアプリケーションのインストール
sol11 ~ # zlogin ngx
root@ngx:~# pkg set-publisher -g http://192.168.254.102 iichiko-spec
必要なアプリケーションをインストール

root@ngx:~# pkg install pkg://iichiko-spec/service/redis-28
root@ngx:~# pkg install pkg://iichiko-spec/web/server/nginx
root@ngx:~# pkg install pkg://iichiko-spec/library/lua/resty-redis
root@ngx:~# pkg install pkg://iichiko-spec/library/lua/jit
root@ngx:~# pkg install runtime/lua
root@ngx:~# svcadm enable svc:/application/database/redis_28:default_64bit

nginxからRedisへアクセスする設定

Redisへサンプルとして使うデータをセットする、イメージ的にはこんな感じです
「static1」、「static2」、「static3」へアクセスした場合だけ、静的なコンテンツを出力するwebサーバーへ転送します。

root@ngx:~# redis-cli
127.0.0.1:6379>

127.0.0.1:6379> RPUSH "www.samohan.jp" "static1"
(integer) 1
127.0.0.1:6379> RPUSH "www.samohan.jp" "static2"
(integer) 2
127.0.0.1:6379> RPUSH "www.samohan.jp" "static3"
(integer) 3
127.0.0.1:6379>

nginx + luaの設定を作成

nginxからlua経由でRedisへアクセスするための設定をnginx.confへ書いておきます。
細かい設定は調整してください、私自身も細かいところまで解ってません 笑...
upstream の部分がzendが動いているserver1、server2へ振る設定です、access_by_luaの部分がluaの部分でRedisへアクセスするコードです。luaがライブラリにアクセス出きるようにlua_package_pathの設定を忘れずに書いておいてください。
# /etc/nginx/nginx.conf
#user  nobody;
worker_processes  1;

# error_log  logs/error.log;
#error_log  logs/error.log  notice;

# ここをコメントアウトするとINFOレベルのログの出力が止まります
error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    lua_package_path "/usr/lib/lua/?.lua;;";

    upstream zend {
        server 192.168.254.201:80;
        server 192.168.254.202:80;
    }

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
        set $target '';
        access_by_lua '
            local domain_name = ngx.var.host
            local path = ngx.var.uri
            ngx.log(ngx.INFO, "Get domain_name: ", domain_name)
            ngx.log(ngx.INFO, "Get path: ", path)

            local redis = require "resty.redis"
            local red = redis:new()

            red:set_timeout(1000) -- 1 second

            local ok, err = red:connect("127.0.0.1", 6379)
            if not ok then
                ngx.log(ngx.ERR, "failed to connect to redis: ", err)
                return ngx.exit(500)
            end

            local klen, err = red:llen(domain_name)
            if klen == 0 then
                ngx.log(ngx.ERR, "failed to llen redis key: ", err)
                return ngx.exit(500)
            end
            local maxindex = klen - 1
            ngx.log(ngx.INFO, "Get maxindex: ", maxindex)
                
            local t = {}
            local find = false
            local m = nil
            for i = 0, maxindex do
                t[i] = red:lindex(domain_name, i)
                m = string.match(path, "^\/" .. t[i] .. "\/")
                if m ~= nil then
                    ngx.log(ngx.INFO, "Found : ", m)
                    find = true
                    break
                end
                ngx.log(ngx.INFO, "Get data: ", t[i])
            end

            if find == true then
                ngx.var.target = "192.168.254.203"
            else
                ngx.var.target = "zend"
            end
        ';

        proxy_pass http://$target;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}
設定ができましたらnginxを再起動してください
root@ngx:~# svcadm enable nginx
root@ngx:~# svcs -xv
root@ngx:~#


server1、server2、server3ゾーンの構築

server1とserver2は同じものを作成する、動的なページの生成で利用する、server3は静的ページを表示するために利用。
server1.cfg
sol11 ~ # cat server1.cfg
create -b
set brand=solaris
set zonepath=/rpool/zones/server1
set autoboot=false
set ip-type=shared
add net
set address=192.168.254.201/24
set configure-allowed-address=true
set physical=net0
end
server2.cfg
sol11 ~ # cat server2.cfg
create -b
set brand=solaris
set zonepath=/rpool/zones/server2
set autoboot=false
set ip-type=shared
add net
set address=192.168.254.202/24
set configure-allowed-address=true
set physical=net0
end
server3.cfg
sol11 ~ # cat server3.cfg
create -b
set brand=solaris
set zonepath=/rpool/zones/server3
set autoboot=false
set ip-type=shared
add net
set address=192.168.254.203/24
set configure-allowed-address=true
set physical=net0
end
ゾーン作成
sol11 ~ # zonecfg -z server1 -f server1.cfg
sol11 ~ # zonecfg -z server2 -f server2.cfg
sol11 ~ # zonecfg -z server3 -f server3.cfg
sol11 ~ # zoneadm -z server1 install 
sol11 ~ # zoneadm -z server2 clone server1
sol11 ~ # zoneadm -z server3 clone server1
最終的なゾーンはこの様な形になります
sol11 ~ # zoneadm -z list -vc
  ID NAME             STATUS     PATH                           BRAND    IP
   0 global           running    /                              solaris  shared
  10 iichiko-spec     running    /rpool/zones/iichiko-spec      solaris  shared
  14 ngx              running    /rpool/zones/ngx               solaris  shared
  15 server1          running    /rpool/zones/server1           solaris  shared
  16 server2          running    /rpool/zones/server2           solaris  shared
  17 server3          running    /rpool/zones/server3           solaris  shared

server1、server2へZendFrameworkをインストール

server2も同じ構成でインストールしてください
sol11 ~ # zlogin server1
root@server1:~# pkg install pkg:/web/server/apache-22
root@server1:~# pkg install pkg:/web/server/apache-22/module/apache-php52
root@server1:~# wget http://packages.zendframework.com/releases/ZendFramework-1.12.3/ZendFramework-1.12.3.tar.gz
root@server1:~# mkdir -p /usr/local/share/php
root@server1:~# tar -xzvf ZendFramework-1.12.3.tar.gz
root@server1:~# cp -rf ZendFramework-1.12.3/library/Zend/ /usr/local/share/php/
php.iniの設定
root@server1:~# emacs /etc/php/5.2/php.ini
include_path = ".:/var/php/5.2/pear:/usr/local/share/php"
ZendFrameworkの設定
server1とserver2へ展開してください
root@server1:~# cd /
root@server1:/# wget http://www.karky7.com/files/zend_sample.tar.gz
root@server1:/# tar -xzvf zend_sample.tar.gz
root@server1:/# chown -R webservd:webservd web
root@server1:/# tree /web
/web
├── application
│   ├── controllers
│   │   ├── ErrorController.php
│   │   ├── IndexController.php
│   │   └── SampleController.php
│   └── views
│       ├── index
│       │   └── index.phtml
│       └── sample
│           └── sample.phtml
└── www
    └── index.
root@server1:/#
続いてhttpd.confの設定
root@server1:~# diff /etc/apache2/2.2/httpd.conf /etc/apache2/2.2/httpd.conf.ORG
113c113
< DocumentRoot "/web/www"
---
> DocumentRoot "/var/apache2/2.2/htdocs"
140c140
< <Directory "/web/www">
---
> <Directory "/var/apache2/2.2/htdocs">
153c153
<     Options All
---
>     Options Indexes FollowSymLinks
160c160
<     AllowOverride All
---
>     AllowOverride None

server3の構築

server3は普通の静的ファイルを返すだけのapacheサーバーとして構築する
sol11 ~ # zlogin server3
root@server3:~# pkg install pkg:/web/server/apache-22

アクセスしてみる

最後に、ドメイン経由でブラウザからアクセスしたいので /etc/hostsへドメインを追加する(DNS立てれないからね)、ちなみにここで言うhostsファイルは、ブラウザでアクセスするホストの奴です、僕で言うとGentooのhostsフィルです。
192.168.254.200はngxゾーンのIPです。
192.168.254.200  www.samohan.jp
ブラウザから以下のURLへアクセスしてみますと、Redisへ登録されているstatic1、static2、static3へのアクセスはserver3へ転送され、それ以外はserver1、server2へ転送される。
  • http://www.samohan.jp/ ・・・ zendサーバーへ、server1、server2
  • http://www.samohan.jp/static1/ ・・・ server3へ
  • http://www.samohan.jp/static2/ ・・・ server3へ
  • http://www.samohan.jp/static3/ ・・・ server3へ
  • http://www.samohan.jp/static4/ ・・・ zendサーバーへ転送されErrorControllerで捕まる

こんな感じで、Redisに保存されている情報を元に色々振り分ける事ができます。
これは素晴らしい
この他にもnginxって色々使えそうでビックリしてる所です、今後、適材適所でnginxを利用していこう思います。

書きすぎでまとまりがありませんが、間違いがありましたら指摘していただければ幸です

最後に謝辞


この度、パッケージングで作成したnginxは @ftnk 先生のspecをカスタマイズして利用させていただきました、ブログからで申し訳ございませんがお礼を申し上げます、ありがとうございました。また色々お世話になると思いますのでよろしくお願いします。


2014年2月11日火曜日

ピアソン相関で類似性の計算

ピアソン相関によるスコア計算


ピアソン相関係数の計算式は

です、値は-1から1の間をとり、完全に相関する場合は1、相関がない場合0、逆相関の場合は-1となる模様

Pythonのコード


映画の評価データは前回と同じものを利用していますのでそちらを参照してください
#!/usr/bin/python
# -*- coding: utf-8 -*-

from MovieEval import critics
from math import sqrt

def sim_pearson(prefs, person1, person2):
    si = {}
    for item in prefs[person1]:
        if item in prefs[person2]:
            si[item] = 1
    n = len(si)
    if n == 0:
        return 0

    sum1 = sum([prefs[person1][it] for it in si])
    sum2 = sum([prefs[person2][it] for it in si])

    sum1Sq = sum([pow(prefs[person1][it], 2) for it in si])
    sum2Sq = sum([pow(prefs[person2][it], 2) for it in si])

    pSum = sum([prefs[person1][it] * prefs[person2][it] for it in si])

    num = pSum - (sum1 * sum2 / n)
    den = sqrt((sum1Sq - pow(sum1, 2) / n) * (sum2Sq - pow(sum2, 2) / n))
    if den == 0:
        return 0
    r = num / den
    return r

def calcScore(name, prefs):
    print("%s:" % name)
    for (nm, val) in prefs.items():
        print("   %20s    %20f" % (nm, sim_pearson(critics, name, nm)))
    print("--------------------------------------------------")

if __name__ == '__main__':
    for (name, ev) in critics.items():
        calcScore(name, critics)
これを実行すると
cuomo@karky7 ~ $ python pearson.py
Jack Matthews:
          Jack Matthews                1.000000
           Mick LaSalle                0.211289
           Claudia Puig                0.028571
              Lisa Rose                0.490990
                   Toby                0.662849
           Gene Seymour                0.963796
       Michael Phillips                0.134840
--------------------------------------------------
Mick LaSalle:
          Jack Matthews                0.211289
           Mick LaSalle                1.000000
           Claudia Puig                0.566947
              Lisa Rose                0.585491
                   Toby                0.924473
           Gene Seymour                0.411765
       Michael Phillips               -0.258199
--------------------------------------------------
Claudia Puig:
          Jack Matthews                0.028571
           Mick LaSalle                0.566947
           Claudia Puig                1.000000
              Lisa Rose                0.883883
                   Toby                0.893405
           Gene Seymour                0.314970
       Michael Phillips                1.000000
--------------------------------------------------
Lisa Rose:
          Jack Matthews                0.490990
           Mick LaSalle                0.585491
           Claudia Puig                0.883883
              Lisa Rose                1.000000
                   Toby                0.991241
           Gene Seymour                0.315264
       Michael Phillips                0.774597
--------------------------------------------------
Toby:
          Jack Matthews                0.662849
           Mick LaSalle                0.924473
           Claudia Puig                0.893405
              Lisa Rose                0.991241
                   Toby                1.000000
           Gene Seymour                0.381246
       Michael Phillips               -1.000000
--------------------------------------------------
Gene Seymour:
          Jack Matthews                0.963796
           Mick LaSalle                0.411765
           Claudia Puig                0.314970
              Lisa Rose                0.315264
                   Toby                0.381246
           Gene Seymour                1.000000
       Michael Phillips                0.204598
--------------------------------------------------
Michael Phillips:
          Jack Matthews                0.134840
           Mick LaSalle               -0.258199
           Claudia Puig                1.000000
              Lisa Rose                0.774597
                   Toby               -1.000000
           Gene Seymour                0.204598
       Michael Phillips                1.000000
--------------------------------------------------
cuomo@karky7 ~ $


Haskellでやると

評価データは前回のものを利用しています

import qualified Data.Map as M
import MovieEval
import Control.Monad (forM_)
import Text.Printf

sim_distance :: M.Map String [Eval] -> String -> String -> Double
sim_distance perfs person1 = \person2 -> sim_pearson p1 (M.lookup person2 perfs)
  where
    p1 = M.lookup person1 perfs

sim_pearson :: Maybe [Eval] -> Maybe [Eval] -> Double
sim_pearson (Just p1) (Just p2) = if den /= 0
                                 then num / den
                                 else 0
  where
    ev = map (\(v, w) -> (movEv v, movEv w)) [(x, y) |
                                              x <- p1,
                                              y <- p2,
                                              movName x == movName y]
    n = length ev
    xs = map fst ev
    ys = map snd ev
    sumx = sum xs
    sumy = sum ys
    sumxSq = sum[x*x | x <- xs]
    sumySq = sum[y*y | y <- ys]
    pSum = sum[x*y | (x, y) <- ev]
    num = pSum - (sumx * sumy / realToFrac n)
    den = sqrt ((sumxSq-(sumx*sumx) / realToFrac n)*(sumySq-(sumy*sumy) / realToFrac n))
sim_pearson _ _ = 0.0

calcScore :: String -> [String] -> (String -> String -> Double) -> IO()
calcScore name names f = do
  putStrLn $ name ++ ":"
  forM_ names $ \nm -> do
    printf "   %20s    %20f\n" nm (f name nm)
  putStrLn "--------------------------------------------------\n"

main :: IO()
main = do
  let perfs = getCritics
      names = M.keys perfs
  mapM_ (\name -> calcScore name names (sim_distance perfs)) names
cuomo@karky7 ~ $ runhaskell peason.hs
Claudia Puig:
           Claudia Puig                     1.0
           Gene Seymour     0.31497039417435607
          Jack Matthews     0.02857142857142857
              Lisa Rose       0.883883476483186
       Michael Phillips                     1.0
           Mick LaSalle      0.5669467095138411
                   Toby      0.8934051474415647
--------------------------------------------------

Gene Seymour:
           Claudia Puig     0.31497039417435607
           Gene Seymour                     1.0
          Jack Matthews       0.963795681875635
              Lisa Rose     0.31526414437773115
       Michael Phillips     0.20459830184114206
           Mick LaSalle     0.41176470588235276
                   Toby     0.38124642583151164
--------------------------------------------------

Jack Matthews:
           Claudia Puig     0.02857142857142857
           Gene Seymour       0.963795681875635
          Jack Matthews                     1.0
              Lisa Rose     0.49099025303098176
       Michael Phillips     0.13483997249264842
           Mick LaSalle     0.21128856368212925
                   Toby        0.66284898035987
--------------------------------------------------

Lisa Rose:
           Claudia Puig       0.883883476483186
           Gene Seymour     0.31526414437773115
          Jack Matthews     0.49099025303098176
              Lisa Rose                     1.0
       Michael Phillips      0.7745966692414834
           Mick LaSalle      0.5854905538443589
                   Toby      0.9912407071619299
--------------------------------------------------

Michael Phillips:
           Claudia Puig                     1.0
           Gene Seymour     0.20459830184114206
          Jack Matthews     0.13483997249264842
              Lisa Rose      0.7745966692414834
       Michael Phillips                     1.0
           Mick LaSalle     -0.2581988897471611
                   Toby                    -1.0
--------------------------------------------------

Mick LaSalle:
           Claudia Puig      0.5669467095138411
           Gene Seymour     0.41176470588235276
          Jack Matthews     0.21128856368212925
              Lisa Rose      0.5854905538443589
       Michael Phillips     -0.2581988897471611
           Mick LaSalle                     1.0
                   Toby      0.9244734516419049
--------------------------------------------------

Toby:
           Claudia Puig      0.8934051474415647
           Gene Seymour     0.38124642583151164
          Jack Matthews        0.66284898035987
              Lisa Rose      0.9912407071619299
       Michael Phillips                    -1.0
           Mick LaSalle      0.9244734516419049
                   Toby                     1.0
--------------------------------------------------
cuomo@karky7 ~ $
こんな感じで2人の間の類似性が簡単な計算で求められたりするとなかなかおもしろいですね。チョット気になるのが相関値が同一人物でないのに1.0で出てるのが、計算の誤差なんでしょうかね?....pythonでやってもhaskellでやっても1.0なのでいいとは思うのですが。

後で得意の電卓でやってみます 笑...


2014年2月9日日曜日

ユークリッド距離によるスコアをHaskellで書いてみた

暇なのでPythonのサンプルをHaskellに移植してみました


「集合知プログラミング」のPythonのサンプルをHaskellで書いてみました。最近、協調フィルタリングのお勉強をしているのですが、コードを読んでいるだけではなかなかしっくりこないので、実際に書いてみました。

まず、Pythonのコード


映画の評価データ、Lisa Roseさんはそれぞれの映画を、2.5、3.5、3.0...で評価しているというデータ
#!/usr/bin/python
#! -*- coding: utf-8 -*-

critics = {
    'Lisa Rose': {
        'Lady in the Water': 2.5,
        'Snake on a Plane': 3.5,
        'Just My Luck': 3.0,
        'Superman Returns': 3.5,
        'You, Me and Dupree': 2.5,
        'The Night Listener': 3.5
    },
    'Gene Seymour': {
        'Lady in the Water': 3.0,
        'Snake 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': {
        'Lady in the Water': 2.5,
        'Snake on a Plane': 3.0,
        'Superman Returns': 3.5,
        'The Night Listener': 4.0
    },
    'Claudia Puig': {
        'Snake 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': {
        'Lady in the Water': 3.0,
        'Snake 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': {
        'Lady in the Water': 3.0,
        'Snake on a Plane': 4.0,
        'The Night Listener': 3.0,
        'Superman Returns': 5.0,
        'You, Me and Dupree': 3.5
    },
    'Toby': {
        'Snake on a Plane': 4.5,
        'You, Me and Dupree': 1.0,
        'Superman Returns': 4.0
    }
}
で、その評価データを利用して計算するmainコード、本に掲載されているとおり
#!/usr/bin/python
#! -*- coding: utf-8 -*-

from MovieEval import critics

def sim_distance(prefs, person1, person2):
    si = {}
    for item in prefs[person1]:
        if item in prefs[person2]:
            si[item] = 1

    if len(si) == 0: return 0

    sum_of_squere = sum([
        pow(prefs[person1][item] - prefs[person2][item], 2)
        for item in prefs[person1] if item in prefs[person2]
    ])
    return 1 / (1 + sum_of_squere)


def calcScore(name, prefs):
    print("%s:" % name)
    for (nm, val) in prefs.items():
        print("   %20s    %20f" % (nm, sim_distance(critics, name, nm)))
    print("--------------------------------------------------")


if __name__ == '__main__':
    for (name, ev) in critics.items():
        calcScore(name, critics)
これを実行すると
cuomo@karky7 ~ $ python recommendations.py
Jack Matthews:
          Jack Matthews                1.000000
           Mick LaSalle                0.137931
           Claudia Puig                0.181818
              Lisa Rose                0.200000
                   Toby                0.117647
           Gene Seymour                0.800000
       Michael Phillips                0.181818
--------------------------------------------------
Mick LaSalle:
          Jack Matthews                0.137931
           Mick LaSalle                1.000000
           Claudia Puig                0.173913
              Lisa Rose                0.307692
                   Toby                0.307692
           Gene Seymour                0.129032
       Michael Phillips                0.285714
--------------------------------------------------
Claudia Puig:
          Jack Matthews                0.181818
           Mick LaSalle                0.173913
           Claudia Puig                1.000000
              Lisa Rose                0.444444
                   Toby                0.235294
           Gene Seymour                0.133333
       Michael Phillips                0.571429
--------------------------------------------------
Lisa Rose:
          Jack Matthews                0.200000
           Mick LaSalle                0.307692
           Claudia Puig                0.444444
              Lisa Rose                1.000000
                   Toby                0.222222
           Gene Seymour                0.142857
       Michael Phillips                0.666667
--------------------------------------------------
Toby:
          Jack Matthews                0.117647
           Mick LaSalle                0.307692
           Claudia Puig                0.235294
              Lisa Rose                0.222222
                   Toby                1.000000
           Gene Seymour                0.108108
       Michael Phillips                0.285714
--------------------------------------------------
Gene Seymour:
          Jack Matthews                0.800000
           Mick LaSalle                0.129032
           Claudia Puig                0.133333
              Lisa Rose                0.142857
                   Toby                0.108108
           Gene Seymour                1.000000
       Michael Phillips                0.210526
--------------------------------------------------
Michael Phillips:
          Jack Matthews                0.181818
           Mick LaSalle                0.285714
           Claudia Puig                0.571429
              Lisa Rose                0.666667
                   Toby                0.285714
           Gene Seymour                0.210526
       Michael Phillips                1.000000
--------------------------------------------------
cuomo@karky7 ~ $ 
Michael PhillipsさんとMichael Phillipsさんの評価はまったく同じなので、スコアは1.0になるわけで、当たり前ですよね。1に近ければ近いほど評価が同じということ。

これをHaskellで書いてみた

基本的には同じ処理です、まず評価データ
module MovieEval (
  Eval,
  movName,
  movEv,
  getCritics
) where

import qualified Data.Map as M

data Eval = Eval {
  movName :: String,
  movEv :: Double
} deriving(Show, Eq)

getCritics :: M.Map String [Eval]
getCritics = M.fromList $
             [ (
                 "Lisa Rose",
                 [
                   Eval "Lady in the Water"  2.5,
                   Eval "Snake on a Plane"   3.5,
                   Eval "Just My Luck"       3.0,
                   Eval "Superman Returns"   3.5,
                   Eval "You, Me and Dupree" 2.5,
                   Eval "The Night Listener" 3.5
                 ]
               ),
               (
                 "Gene Seymour",
                 [
                   Eval "Lady in the Water"  3.0,
                   Eval "Snake on a Plane"   3.5,
                   Eval "Just My Luck"       1.5,
                   Eval "Superman Returns"   5.0,
                   Eval "The Night Listener" 3.0,
                   Eval "You, Me and Dupree" 3.5
                 ]
               ),
               (
                 "Michael Phillips",
                 [
                   Eval "Lady in the Water"  2.5,
                   Eval "Snake on a Plane"   3.0,
                   Eval "Superman Returns"   3.5,
                   Eval "The Night Listener" 4.0
                 ]
               ),
               (
                 "Claudia Puig",
                 [
                   Eval "Snake on a Plane"   3.5,
                   Eval "Just My Luck"       3.0,
                   Eval "The Night Listener" 4.5,
                   Eval "Superman Returns"   4.0,
                   Eval "You, Me and Dupree" 2.5
                 ]
               ),
               (
                 "Mick LaSalle",
                 [
                   Eval "Lady in the Water"  3.0,
                   Eval "Snake on a Plane"   4.0,
                   Eval "Just My Luck"       2.0,
                   Eval "Superman Returns"   3.0,
                   Eval "The Night Listener" 3.0,
                   Eval "You, Me and Dupree" 2.0
                 ]
               ),
               (
                 "Jack Matthews",
                 [
                   Eval "Lady in the Water"  3.0,
                   Eval "Snake on a Plane"   4.0,
                   Eval "The Night Listener" 3.0,
                   Eval "Superman Returns"   5.0,
                   Eval "You, Me and Dupree" 3.5
                 ]
               ),
               (
                 "Toby",
                 [
                   Eval "Snake on a Plane"   4.5,
                   Eval "You, Me and Dupree" 1.0,
                   Eval "Superman Returns"   4.0
                 ]
               )
             ] -- End of List
で、評価の計算コード
import qualified Data.Map as M
import MovieEval
import Control.Monad (forM_)
import Text.Printf
import Debug.Trace

sim_distance :: M.Map String [Eval] -> String -> String -> Double
sim_distance perfs person1 = \person2 -> sum_of_square p1 (M.lookup person2 perfs)
  where
    p1 = M.lookup person1 perfs

sum_of_square :: Maybe [Eval] -> Maybe [Eval] -> Double
sum_of_square (Just p1) (Just p2) = 1 / (1 + total)
  where
    ev = map (\(v, w) -> (movEv v, movEv w)) [(x, y) |
                                              x <- p1,
                                              y <- p2,
                                              movName x == movName y]
    total = sum $ map (\(p1e, p2e) -> realToFrac (p1e - p2e) ^ 2) ev
sum_of_square _ _ = 0.0

calcScore :: String -> [String] -> (String -> String -> Double) -> IO()
calcScore name names f = do
  putStrLn $ name ++ ":"
  forM_ names $ \nm -> do
    printf "   %20s    %20f\n" nm (f name nm)
  putStrLn "--------------------------------------------------\n"

main :: IO()
main = do
  let perfs = getCritics
      names = M.keys perfs
  mapM_ (\name -> calcScore name names (sim_distance perfs)) names
これをHaskellで実行すると
cuomo@karky7 ~ $ runhaskell recommendations.hs
Claudia Puig:
           Claudia Puig                     1.0
           Gene Seymour     0.13333333333333333
          Jack Matthews     0.18181818181818182
              Lisa Rose      0.4444444444444444
       Michael Phillips      0.5714285714285714
           Mick LaSalle     0.17391304347826086
                   Toby     0.23529411764705882
--------------------------------------------------

Gene Seymour:
           Claudia Puig     0.13333333333333333
           Gene Seymour                     1.0
          Jack Matthews                     0.8
              Lisa Rose     0.14285714285714285
       Michael Phillips     0.21052631578947367
           Mick LaSalle     0.12903225806451613
                   Toby     0.10810810810810811
--------------------------------------------------

Jack Matthews:
           Claudia Puig     0.18181818181818182
           Gene Seymour                     0.8
          Jack Matthews                     1.0
              Lisa Rose                     0.2
       Michael Phillips     0.18181818181818182
           Mick LaSalle     0.13793103448275862
                   Toby     0.11764705882352941
--------------------------------------------------

Lisa Rose:
           Claudia Puig      0.4444444444444444
           Gene Seymour     0.14285714285714285
          Jack Matthews                     0.2
              Lisa Rose                     1.0
       Michael Phillips      0.6666666666666666
           Mick LaSalle      0.3076923076923077
                   Toby      0.2222222222222222
--------------------------------------------------

Michael Phillips:
           Claudia Puig      0.5714285714285714
           Gene Seymour     0.21052631578947367
          Jack Matthews     0.18181818181818182
              Lisa Rose      0.6666666666666666
       Michael Phillips                     1.0
           Mick LaSalle      0.2857142857142857
                   Toby      0.2857142857142857
--------------------------------------------------

Mick LaSalle:
           Claudia Puig     0.17391304347826086
           Gene Seymour     0.12903225806451613
          Jack Matthews     0.13793103448275862
              Lisa Rose      0.3076923076923077
       Michael Phillips      0.2857142857142857
           Mick LaSalle                     1.0
                   Toby      0.3076923076923077
--------------------------------------------------

Toby:
           Claudia Puig     0.23529411764705882
           Gene Seymour     0.10810810810810811
          Jack Matthews     0.11764705882352941
              Lisa Rose      0.2222222222222222
       Michael Phillips      0.2857142857142857
           Mick LaSalle      0.3076923076923077
                   Toby                     1.0
--------------------------------------------------

cuomo@karky7 ~ $ 
Pythonって直感的に書いていけるところはやはり早しいいなって思うところデスが、Haskellって関数を渡せるところが凄くいけてる感じがしますね。評価元となる人のパラメータで関数を一時的に保存しておいて、後から評価対象の人を渡すだけでグリグリ回せちゃうとこなんか感心してしまいます。チョットmapを多用し過ぎ感があったり、Applicative辺りを利用すればもっと綺麗に書けそうな気がしてならないのですが、ただいまHaskell勉強中なので、あまり突っ込まないでください。
でも、こういう書き方もあるよっていうのがあったら知りたい次第でござりまする、では


2014年2月8日土曜日

ご冗談でしょGoDaddyさん

ドメインを失効した人の気持ちがわりました

再三にわたるDoレジさんからの忠告に気づかずそのままkarky7.netを失効してしまい、

「まぁ、使えるようになったら新規で取得すればいいか...」

と思っていた矢先に、いつまで立ってもドメインが使えない...
なんと!、わたくしのドメインを奪っている輩にキズイタ時にはもう遅いですね。
karky7.net買い戻しにお金を払えと言うんですよ、このわたしに、飼い犬に噛まれたような違和感が....

そんな人たちに払うお金は...

ございません!

残念ですが、諦めてください、僕はどんな事があってもお金は払いませんから 笑...


こいう商売(?)があったのですね、いままで知りませんでした。
これから新しいドメインでまたブログを更新していきますので皆様よろしくお願いします。