2010年1月1日 星期五

Wretch 無名小站 API - 申請、使用完整教學

第一次使用時,因為我沒 public ip 的機器,所以就嘗試架在 EC2 上,奢侈啊。過了一陣子,我發表一篇文章紀錄:Wretch 無名小站 API - Yahoo! Developer,然後有人留言請教,於是我找了另一台機器,但我也發現碰到一樣的問題,但礙於時間關係,所以遲遲沒有幫忙,隨意 Google 關鍵字,可能是在做專題吧?!真是抱歉,現在我有空了,就在仔細紀錄一下吧!


關於 Yahoo API ,我只嘗試 無名小站 API ,主因是以前我自己曾使用 PHP 與 cURL 來模擬瀏覽器,以程式自動化完成登入,並且發表文章到無名網誌,但這種客制化的行為真的不如使用官方的 API 啦,因此還滿興奮有這個東西的出現,然而,我還是比較希望它提供 XMLRPC 。


扯遠了,現在開始教學吧!首先,一定要有一台 Public IP 供你使用,接著你需要去註冊一個 Domain Name 來使用,網路上很多免費的,像我就是使用 TWBBS.org ,但要留意的,並不是馬上申請就可以用,要等一陣子才行,可以試著用 "nslooup your_name.twbbs.org" 來試試,但最主要並不是你所在的機器能找到,而是 Yahoo 那邊的驗證機器能找到它。接著,請連到 http://tw.developer.yahoo.com/http://developer.yahoo.com/ 登入,在此以英文介面當作範例。


登入完後,請點選 My Project 並且在該頁面點選 New Project 。接著選一下有 "Wretch" 關鍵字吧!


2010/01/01 YDN - New Project


接著就是填寫一些資訊,比較重要的就是你的 Application 的網頁位置,此例是使用 Web-Based 模式


2010/01/01 YDN - New Project


記得把 Access Scopes 選則有關 private user data 囉,因為 Wretch API 是歸類在這部份的。並且在最後一項中選擇 Wretch API 啦


2010/01/01 YDN - New Project


接著就是關鍵的驗證部分,這也是為何我說要有一個 public ip 的機器,以及一個 domain name 啦,此驗證過程就是依照上頭的規定,在 Web Server 的目錄下擺放一個特定檔名的檔案,讓 Yahoo 那邊的機器去存取驗證,如果取得到,那就可以通過啦!


2010/01/01 YDN - New Project


在驗證過程中,可能會碰到一些問題,一種是你的檔名沒建好,所以它找不到,另一種比較神秘,它直接告訴你


Domain Verification Failed
There was an internal error while an application is being created. Please try agin.


像這個問題在一些討論區有提到,有可能 Applocation Name 已存在,或是 Description 沒填等等的問題,但我碰到的真正問題,其實是 Favicon URL 那欄!請為它建一個檔案吧,看起來它也會去抓這個位置來驗證,還真有點怪,它上頭明明寫 Optional !驗證成功則會顯示相關資訊囉


2010/01/01 YDN - New Project


這邊所要留意的有 Application ID 、Consumer Key 和 Consumer Secret ,其中以後兩者最為重要。緊接著,在你的 Web Server 擺上相關程式碼吧!讓我們來測測看啦!接著也可參考 Wretch 無名小站 API - Yahoo! Developer 這篇囉,上頭有其他測試的程式碼。



  • Wretch API 文件

  • 下載 PHP Library:Using Yahoo! Social SDK for PHP

    • 今天我用的版本 yahoo-yos-social-php-12c4a50



  • 讓你的 Web Server 支援 PHP - cURL ,在此就不教怎樣安裝 PHP5 了,以下動作別忘了重開 Web Server。


    • Ubuntu


      • # sudo apt-get install php5-curl



    • FreeBSD

      • # cd /usr/ports/lang/php5-extensions/ ; 

      • # make config ; // 請將 CURL support 勾選

      • # make install clean



    • Windows

      • 通常從官網下載的包裝中已經有囉,只差設定好位置以及啟用 cURL Library 啦

      • 編輯 php.ini (可能在 C:\Windows 目錄中 )

        • 設定好 extension_dir 位置

        • 把 extension=php_curl.dll 前面的 # 拿掉,以此啟用它








接著,就是把相關程式碼擺定位,以下以 Windows 平台描述



  • Yahoo Library

    • C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\wretch\yahoo-yos-social-php-12c4a50



  • 測試程式碼

    • C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\wretch\index.php

    • <?php
      require("yahoo-yos-social-php-12c4a50/lib/Yahoo.inc");  

      // Your Consumer Key (API Key) goes here.  
      define('CONSUMER_KEY', "######");

      // Your Consumer Secret goes here.  
      define('CONSUMER_SECRET', "######");

      // Your application ID goes here.  
      define('APPID', "######");  

      //$GLOBAL_YAHOO_LOGGER_DEBUG = true;
      //$GLOBAL_YAHOO_LOGGER_DEBUG_DESTINATION = "CONSOLE";

      $containerSession = YahooSession::requireSession(CONSUMER_KEY,CONSUMER_SECRET);
      $userSession = $containerSession->getSessionedUser();
      ?>




透過瀏覽器連過去你設定好的位置,若正常成功的話,它會蹦出要你輸入帳密的畫面囉,這個流程是將你導到 Yahoo 登入畫面


2010/01/01 YDN - New Project


並且完成登入後會詢問你是否允許這支程式存取你帳號的資料


2010/01/01 YDN - New Project


當然自己寫的程式比較 OK ,就允許吧!不然也沒得測。


只不過,以上的過程並沒有那麼寫意輕鬆,因為我還是跟之前一樣,卡在這個錯誤訊息


Call to a member function getSessionedUser() on a non-object


逛了幾個網站,才知道可以把 debug mode 打開,就是在引入 Yahoo.inc 後,加上片段程式


$GLOBAL_YAHOO_LOGGER_DEBUG = true;
$GLOBAL_YAHOO_LOGGER_DEBUG_DESTINATION = "CONSOLE";


此時,錯誤訊息


ERROR - Error making libcurl request(https://api.login.yahoo.com/oauth/v2/get_request_token): SSL certificate problem, verify that the CA cert is OK. Details: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed ERROR - OAuth call to get request token failed ERROR - Failed to create request token ERROR - Failed to create authorization URLs


喔耶,至少我看得懂了,當初別人問我時,還傻傻地去 trace code 東改西改,改到後面還是沒搞定,自以為是 Yahoo API 沒做好,畢竟你用 "Wretch API" 去 Google 的第一筆結果就是在抱怨,哈。關於這個問題,恰好我有用過 PHP cURL ,所以知道可以設定一個選項來避開它,但這個動作必須修改 Yahoo Library 程式碼,說真的我也很不願意,但至少這樣做可以搞定。 


@Yahoo.inc:1811
+curl_setopt( $ch , CURLOPT_SSL_VERIFYPEER , false );


以上就是去 Yahoo.inc 這個檔案,我使用 curl_init 去搜尋,發現只有一處,很好,那我只要更新一處就行,在 curl_init 後面加上 curl_setopt( $ch , CURLOPT_SSL_VERIFYPEER , false ); 就可以了。


以上是我在 Windows 平台得到的訊息以及解決方式,然而,我在 FreeBSD 機器上時,得到的錯誤訊息則不一樣了


DEBUG - HTTP request details
DEBUG OBJECT - Array
(
    [method] => POST
    [url] => https://api.login.yahoo.com/oauth/v2/get_request_token
    [code] => 401
    [requestHeaders] => Array
        (
            [0] => Accept: application/json
            [1] => Content-Type: application/x-www-form-urlencoded
        )

    [requestBody] => ######
    [responseHeaders] => Array
        (
            [Date] => ######
            [P3P] => ######
            [WWW-Authenticate] => OAuth oauth_problem=signature_invalid
            [Connection] => close
            [Transfer-Encoding] => chunked
            [Content-Type] => application/x-www-form-urlencoded
        )

    [responseBody] => oauth_problem=signature_invalid
)

ERROR - Failed to create request token: signature_invalid
ERROR - Failed to create request token
ERROR - Failed to create authorization URLs


那你在使用 YahooSession::requireSession(CONSUMER_KEY,CONSUMER_SECRET); 時,請改成加上 APPID 和當初認證的 Domain Name 應該就能解決囉


<?php

$containerSession = YahooSession::requireSession(CONSUMER_KEY,CONSUMER_SECRET , APPID , 'http://your_name.twbbs.org/' );
$userSession = $containerSession->getSessionedUser();

?>


最後,可以在把 debug mode 拿掉,完成轉址到 Yahoo 登入頁面囉!


以下是簡單的列出使用者在無名小站的個人資料、網誌文章和相簿列表,詳細部分請參考 Wretch API 文件


<?php

//error_reporting(E_ALL); ini_set("display_errors", 1);
require("yahoo-yos-social-php-12c4a50/lib/Yahoo.inc");  
// Your Consumer Key (API Key) goes here.  
define('CONSUMER_KEY', "######");
// Your Consumer Secret goes here.  
define('CONSUMER_SECRET', "######");
// Your application ID goes here.  
define('APPID', "######");  

//$GLOBAL_YAHOO_LOGGER_DEBUG = true;
//$GLOBAL_YAHOO_LOGGER_DEBUG_DESTINATION = "CONSOLE";

$session = YahooSession::requireSession( CONSUMER_KEY, CONSUMER_SECRET );
$yahoo_user = $session->getSessionedUser();

// Profile
$url = 'http://wretch.yahooapis.com/v1/profileService/'.$yahoo_user->guid;
$response = $session->client->get($url);
echo "[REQUEST] $url\n";
echo "<pre>";
print_r($response);
echo "</pre>";

// Blog
//$url = 'http://wretch.yahooapis.com/v1/blogService/'.$yahoo_user->guid.'/articles';
$response = $session->client->get($url);
echo "[REQUEST] $url\n";
echo "<pre>";
print_r($response);
echo "</pre>";

// Albums
$url = 'http://wretch.yahooapis.com/v1/albumService/'.$yahoo_user->guid.'/albums';
$response = $session->client->get($url);
echo "[REQUEST] $url\n";
echo "<pre>";
print_r($response);
echo "</pre>";

?>


以上回傳的格式(以 Profile 為例):


Array
(
[method] => GET
[url] => http://wretch.yahooapis.com/v1/profileService/####
[code] => 200
[requestHeaders] => Array
(
[0] => Accept: application/json
[1] => Authorization: OAuth realm="yahooapis.com",oauth_version="1.0",oauth_nonce="######",oauth_timestamp="######",oauth_consumer_key="######",oauth_token="######",oauth_signature_method="HMAC-SHA1",oauth_signature="######"
)

[requestBody] =>
[responseHeaders] => Array
(
[Date] => Fri, 01 Jan 2010 14:05:24 GMT
[P3P] => policyref="http://info.yahoo.com/w3c/p3p.xml", CP="######"
[Set-Cookie] => lang=zh-tw; expires=Mon, 27-Dec-2010 14:05:24 GMT; path=/; domain=.wretch.cc
[cache-control] => private,must-revalidate
[content-language] => en-US
[content-location] => http://www.wretch.cc/user/######
[content-type] => application/json; charset=UTF-8
[Vary] => Accept-Encoding
[Connection] => close
[Transfer-Encoding] => chunked
[Content-Encoding] => gzip
)

[responseBody] => {"profile":{"title":"######","desc":"######","nick":"######","birthday":null,"intro":"######","sex":"######","lang":"en-US","uri":"######"}}
)

其中 code 為 200 代表正常。


此次測試,是以一個新帳號測試並且完成開通無名帳號,只是一開始回傳的還是 302 或 404 ,猜測可能是資料還未同步好,過了一陣子再試的結果就是 200 囉。以上提供給各位參考囉。


其他相關參考



用一首歌迎接 2010 年‧SMAP - 世界に一つだけの花 (世界中唯一僅有的花)








希望 2010 年,事事順利,能找到人生的目標而努力邁進,並與家人分享更多的幸福。


無法成為 No.1 也好
原本就是最特別的 Only one

排列在花店門口
看著各式各業的花
雖然人的喜好各有不同
但是每一朵都很漂亮呢

在這當中誰最美麗
沒有如此的紛爭
在桶中誇耀般的
綻開著挺起胸膛

即使如此為何我們人類
卻要如此互相比較呢
每人皆不盡相同
卻在那之中想成為第一名

是啊 我們皆是
世界中唯一僅有的花
每個人都擁有著不同品種
單純地為了讓那朵花盛開
而努力著就好

有著一邊困擾著笑著
還迷惑著的人
努力綻放的花朵無論哪一朵
都那麼美麗  所以難以抉擇

終於走出了店外
那人擁抱著
色彩豔麗的花束
以及欣喜的側臉

雖然不知道你的名字
但給了那一日的我一個笑容
有如在誰都不層察覺的場所
盛開著的花兒一般

是啊  我們皆是
世界中唯一僅有的花
每個人都擁有著不同品種
單純地為了讓那朵花盛開
而努力著就好

無論小花或是大花
都不是相同之物
無法成為 No.1 也好
原本就是最特別的 Only one


2009年12月27日 星期日

2009 Taiwan Presentation @ Pardubice

非常非常棒的影片,影片來源 [雪兒] 第一次覺得台灣竟然這麼美 @ 批踢踢 Share 板Ptt Web BBS - 文章






 






























2009年12月24日 星期四

[Java] Pairwise Vector Similarity by Cosine Similarity @ Hadoop 0.20.1

Source: http://en.wikipedia.org/wiki/Cosine_similarity

source: http://en.wikipedia.org/wiki/Cosine_similarity


前陣子在 「台灣區 Hadoop 使用者社群會議」看到 Image Selection for Large-Scale Flickr Photos using Hadoop 中,使用 Cosine Similarity 計算向量的相似程度,才想起原來 MapReduce 的架構也可以用這啊!第一次聽到 Cosine Similarity 可能是大一的線性代數或是微積分,但第一次專題用到的實做,則是在大四 Datamining 的課程上,當時我們正在作一個找出相似遊戲玩家的專題,因此使用 Vector 跟 Cosine 來計算相似程度,但那次並不是我實做這類東西,我只負責用 Perl 去撈別人家的資料庫,哈。


最近讀 paper 有點悶,就先自己想了一下 Cosine Similarity 實作方式,發現有些地方卡卡的,另外,覺得做出來的還是要負擔一次把所有資料讀進記憶體的花費,這應該不是好的解法,所以就使用 "Cosine Similarity" 跟 "Hadoop" 或 "MapReduce" 關鍵字來搜尋一下,就找到以下幾篇:



當我了解實做上採用的演算法時,不禁感嘆這演算法的美好,所以決定來實做一下。另外,有個插曲是我請教強者同事關於連續做兩個有相依性的 job 問題時,發現他也實做過這個演算法 :D 還好我也先實做完才問,不然應該會很偷懶吧,哈



然後我有點龜毛地跟同事閒聊為什摩上頭提到的 Document Similarity 的計算都沒有完整做完 Cosine Similarity 的動作,就只是作向量的內積部份( Dot product , Inner product space ),後來討論時也才想到,其實 Document Similarity 重點在於 keyword 是否有 match 到,因此向量內積的算法,恰巧可以用這樣個觀點上,只是我還是覺得些不妥,因為呈現的數值沒有正規劃,假設有兩組向量的比較



  1. ( 1 , 1 ) , ( 1 , 100 )

  2. ( 1 , 1 ) , ( 1 , 1 )


以單純向量內積結果,第一組數值是 101 , 第二組是 2 ,但我覺得相似程度應該是第二組最好,完全 match 啊,以 Cosine Similarity 的作法,就會變成 1 ~ -1 之間,即 0.714142143 和 1 ,就可以很輕易看出第二組最好囉。只是實用上,可能計算量大而不適用!但我還是打算實做完 Cosine Similarity 啦,若單純想用 Document Similarity 可以到 Pairwise Document Similarity in Large Collections with MapReduce 逛逛,上頭有完整的 Java 程式囉!另外,若又想要降低計算量,又想計算 Cosine Similarity ,那可以先將向量正規化,讓他們的長度都為 1 ,接著再用 Document Similarity 算,就可以是正解啦!


對於 Cosine Similarity 的實做,採用 Sparse Matrix/Vector 的方式紀錄各物件的屬性,流程如下:


物件 (Source) -> 取出特性 (1st Map) -> 依此特性進行收集 (1st Reduce) => 以特性為主體,決定有多少物件要計算內積 (2nd Map) -> 以內積項目進行收集與累積數值 (2nd Reduce)


第一次是以屬性(feature)作為丟給 Reducer 的分群,第二次則以兩內積作為丟給 Reducer 的分群依據。


透過這樣的架構,一開始在對物件取資料時,就可以用多個 Mapper 同時取資料做事,至於缺點部份,則是第二次 Map 在決定有多少項目要作內積相乘時,必須一次將資料讀完才行,以 MapReduce 的架構,就是指一列資料。這是因為在作兩兩向量內積時,需要產生 N 階乘的項目,必須一口氣先得知有幾個項目才行,假設共有一千萬的項目,結果共有 900,000,000 的項目同樣擁有某個屬性時(feature),這將導致第一次 Reduce 產生會有一列擁有 900,000,000 的資料,並且在第二次 Map 時,記憶體一開始得讀入九百萬的項目,接著在產生 900,000,000! 等待相乘計算內積的數量。


只是,如果資料真的是這樣時,可能要去想想那個 Feature 是不是要捨棄掉,或是反向去建新的屬性,以此例來說,乾脆去考慮為剩下的一百萬項目建立一個屬性,這樣要作內積的計算也就從 900,000,000 ! 降到 100,000,000! 囉。


以下是我自己寫得粗略程式碼 XD


Makefile (太習慣寫 C 了, 這也是當初比賽前弄的架構, 只是最後改用 streaming 實做啦):


CC = javac
HADOOP = /usr/local/hadoop/bin/hadoop
CFLAGS =
CURR_DIR = $(.CURDIR)
SRC_BASE = .
OUT_BASE = .

PATH_CLASSPATH = /usr/local/hadoop
PATH_SRC = .
PATH_BIN = .

OBJ_DIR = $(OUT_BASE)/out
EXE = out.jar
LIB = $(PATH_CLASSPATH)/hadoop-0.20.1-core.jar
SRC = CosineSimilarity.java
OBJ = $(SRC:.java=.class)

HADOOP_INPUT = tmp_input
HADOOP_TEMP = tmp_tmp
HADOOP_OUTPUT = tmp_output

.SUFFIXES: .java .class

.java.class:
        $(CC) -classpath $(LIB) -d $(OBJ_DIR) $(PATH_SRC)/$<

all: $(OBJ_DIR) $(OBJ)

jar: $(OBJ_DIR) $(OBJ) $(EXE)

$(OBJ_DIR):
        @rm -rf $(OBJ_DIR) ;
        @mkdir $(OBJ_DIR) ;

hadoop: jar
        $(HADOOP) dfs -mkdir $(HADOOP_INPUT)
        $(HADOOP) dfs -put test.data $(HADOOP_INPUT)/
        $(HADOOP) jar $(EXE) org.changyy.CosineSimilarity $(HADOOP_INPUT) $(HADOOP_TEMP) $(HADOOP_OUTPUT)
        $(HADOOP) dfs -get $(HADOOP_OUTPUT)/part-* . ;

$(EXE):
        jar -cvf $(EXE) -C $(OBJ_DIR) $(OUT_BASE) ;

clean:
        @rm -rf $(OBJ_DIR) ;
        @rm -rf $(OUT_BASE)/$(EXE) ;
        @rm -rf part-*;
        @$(HADOOP) dfs -rmr $(HADOOP_INPUT) $(HADOOP_TEMP) $(HADOOP_OUTPUT) || echo "Pass : $(HADOOP) dfs -rmr $(HADOOP_INPUT) $(HADOOP_TEMP) $(HADOOP_OUTPUT)"


程式碼(其中在 main 裡連續作兩個 job 我是照抄同事的程式碼 XD Pairwise Document Similarity in Large Collections with MapReduce ):


package org.changyy;
        
import java.io.IOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.util.*;
import java.lang.Math;
        
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.conf.*;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapred.*;
import org.apache.hadoop.util.*;
import org.apache.hadoop.mapred.jobcontrol.Job;
import org.apache.hadoop.mapred.jobcontrol.JobControl;


public class CosineSimilarity extends Configured implements Tool
{
    public static class ExtractFeature_Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, Text>
    {
        // Src:    node_name(str)    feature(str)    value(str)    ..
        public void map(LongWritable key, Text value, OutputCollector<Text, Text> output, Reporter reporter) throws IOException
        {
            String tmp_key = null;
            String line = value.toString();
            StringTokenizer tokenizer = new StringTokenizer(line);
            String tmp_feature = null , tmp_value = null;

            List<String> feature_list = new ArrayList<String>();
            List<String> value_list = new ArrayList<String>();

            boolean switch_flag = true;
            double length_calculate = 0 , tmp_double = 0;
            if( tokenizer.hasMoreTokens() )
                tmp_key = tokenizer.nextToken();
            while (tokenizer.hasMoreTokens())
            {
                if( switch_flag )
                {
                    tmp_feature = tokenizer.nextToken();
                    switch_flag = false;
                }
                else
                {
                    tmp_value = tokenizer.nextToken();
                    value.set( tmp_value );
                    switch_flag = true;
                    try
                    {
                        tmp_double = Double.parseDouble( tmp_value );
                    }
                    catch( Exception e )
                    {
                        tmp_double = 0;
                    }
                    // to list
                    if( tmp_double != 0 )
                    {
                        length_calculate += tmp_double*tmp_double;
                        feature_list.add( tmp_feature );
                        value_list.add( ( new Double( tmp_double ) ).toString() );
                    }
                }
            }
            if( length_calculate > 0 )
            {
                Text out_key = new Text();
                Text out_value = new Text();
                String length = ( new Double( Math.sqrt( length_calculate ) ) ).toString();
                Iterator iterator_feature = feature_list.iterator();
                Iterator iterator_value = value_list.iterator();
                while( iterator_feature.hasNext() ) // && iterator_value.hasNext() ) // for performance
                {
                    out_key.set( (String) iterator_feature.next() );
                    out_value.set( (String) iterator_value.next() + "\t" + tmp_key + "\t" + length  );

                    // Out: Feature \t Value \t Node \t Length
                    output.collect( out_key , out_value );
                }
            }
        }
    }
        
    public static class ExtractFeature_Reduce extends MapReduceBase implements Reducer<Text, Text, Text, Text>
    {
        // Out: Feature \t Value \t Node \t Length
        public void reduce(Text key, Iterator<Text> values, OutputCollector<Text, Text> output, Reporter reporter) throws IOException
        {
            Text out_value = new Text();
            String tmp_value = new String();
            if( values.hasNext() )
                tmp_value += values.next();
            while (values.hasNext())
                tmp_value += "\t" + values.next();

            out_value.set( tmp_value );
            output.collect( key, out_value );
            //int sum = 0;
            //while (values.hasNext())
            //    sum += values.next().get();
            //output.collect(key, new IntWritable(sum));
        }
    }

    public static class InnerProduct_Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, Text>
    {
        // Src:    feature \t value \t node1 \t length \t value \t node2 \t lenght ...
        public void map(LongWritable key, Text value, OutputCollector<Text, Text> output, Reporter reporter) throws IOException
        {
            StringTokenizer tokenizer = new StringTokenizer( (String) value.toString() );

            List<String> node_list = new ArrayList<String>();
            List<Double> value_list = new ArrayList<Double>();
            List<Double> length_list = new ArrayList<Double>();

            int switch_flag = 0;
            String tmp_string ;
            if( tokenizer.hasMoreTokens() )
                 tmp_string = tokenizer.nextToken();     // skip the first element

            double tmp_double = 0;
            while (tokenizer.hasMoreTokens())
            {
                switch( switch_flag )
                {
                    case 0:
                        try
                        {
                            tmp_double = Double.parseDouble( (String) tokenizer.nextToken() );
                        }
                        catch( Exception e )
                        {
                            tmp_double = 1;
                        }
                        value_list.add( new Double( tmp_double ) );
                        switch_flag = 1;
                        break;
                    case 1:
                        node_list.add( tokenizer.nextToken() );
                        switch_flag = 2;
                        break;
                    case 2:
                        try
                        {
                            tmp_double = Double.parseDouble( (String) tokenizer.nextToken() );
                        }
                        catch( Exception e )
                        {
                            tmp_double = 1;
                        }
                        length_list.add( new Double( tmp_double ) );
                        switch_flag = 0;
                        break;
                }
            }
            Text out_key = new Text();
            Text out_value = new Text();
            String tmp_string_2 = null;
            for( int i=0 , j=0 , size=node_list.size() ; i<size ; ++i )
            {
                for( j=i+1; j<size ; ++j )
                {
                    tmp_string = node_list.get(i);
                    tmp_string_2 = node_list.get(j);
                    
                    if( tmp_string.compareTo( tmp_string_2 ) < 0)
                        tmp_string += "," + tmp_string_2;
                    else
                        tmp_string = tmp_string_2 + "," + tmp_string;

                    out_value.set( ""+ ( value_list.get(i)*value_list.get(j) / ( length_list.get(i)*length_list.get(j) ) ) );
                    out_key.set( "("+tmp_string+")" );

                    // Out: Node1_Node2 \t Value
                    output.collect( out_key , out_value );
                }
            }
        }
    }
        
    public static class InnerProduct_Reduce extends MapReduceBase implements Reducer<Text, Text, Text, Text>
    {
        // Out: (Node1,Node2) \t Value
        public void reduce(Text key, Iterator<Text> values, OutputCollector<Text, Text> output, Reporter reporter) throws IOException
        {
            double tmp_value=0 ;
            while (values.hasNext())
                tmp_value += Double.parseDouble( (String) values.next().toString() );

            Text out_value = new Text();
            out_value.set( ( new Double( tmp_value ) ).toString() );
            output.collect( key, out_value );
        }
    }

    public int run(String[] args) throws Exception
    {
        //JobConf conf = new JobConf(CosineSimilarity.class);
        JobConf conf = new JobConf( getConf() , CosineSimilarity.class);
        conf.setJobName("CosineSimilarity_ExtractFeature");
        
        conf.setOutputKeyClass(Text.class);
        conf.setOutputValueClass(Text.class);
        
        conf.setMapperClass(ExtractFeature_Map.class);
        conf.setCombinerClass(ExtractFeature_Reduce.class);
        conf.setReducerClass(ExtractFeature_Reduce.class);
        
        conf.setInputFormat(TextInputFormat.class);
        conf.setOutputFormat(TextOutputFormat.class);
        
        FileInputFormat.setInputPaths(conf, new Path(args[0]));
        FileOutputFormat.setOutputPath(conf, new Path(args[1]));
        
        Job job = new Job(conf);

        //JobConf conf2 = new JobConf(CosineSimilarity.class);
        JobConf conf2 = new JobConf( getConf() , CosineSimilarity.class);
        conf2.setJobName("CosineSimilarity_InnerProduct");
        
        conf2.setOutputKeyClass(Text.class);
        conf2.setOutputValueClass(Text.class);
        
        conf2.setMapperClass(InnerProduct_Map.class);
        conf2.setCombinerClass(InnerProduct_Reduce.class);
        conf2.setReducerClass(InnerProduct_Reduce.class);
        
        conf2.setInputFormat(TextInputFormat.class);
        conf2.setOutputFormat(TextOutputFormat.class);
        
        FileInputFormat.setInputPaths(conf2, new Path(args[1]+"/part*"));
        FileOutputFormat.setOutputPath(conf2, new Path(args[2]));
        
        Job job2 = new Job(conf2);

        job2.addDependingJob(job);
        JobControl controller = new JobControl("CosineSimilarity");
        controller.addJob(job);
        controller.addJob(job2);

        
        new Thread(controller).start();

        while (!controller.allFinished())
        {
            System.out.println("Jobs in waiting state: "+ controller.getWaitingJobs().size());
            System.out.println("Jobs in ready state: "+ controller.getReadyJobs().size());
            System.out.println("Jobs in running state: "+ controller.getRunningJobs().size());
            System.out.println("Jobs in success state: "+ controller.getSuccessfulJobs().size());
            System.out.println("Jobs in failed state: "+ controller.getFailedJobs().size());
            System.out.println();

            try
            {
                Thread.sleep(20000);
            } catch (Exception e)
            {
                e.printStackTrace();
            }
        }

        //JobClient.runJob(conf2);
        return 0;
    }
    public static void main(String[] args) throws Exception
    {
        int status = ToolRunner.run(new Configuration(), new CosineSimilarity(), args);
        System.exit( status );
    }
}


測資(test.data):


node1   f1      10      f2      1       f3      4       f5      2
node2   f2      50      f4      30      f5      10
node3   f1      50      f3      10
node4   f5      20      f1      10
node5   f6      1       f3      10
node6   f6      20
node7   f6      20


執行:


# make clean
# make hadoop

其中會使用三個目錄 tmp_input , tmp_output , tmp_tmp ,第一步就是先清掉,接著 make hadoop 就是編譯成 jar 檔以及執行, 最後再從 tmp_output 撈 part* 出來

觀看結果
# cat part*


原先也想寫個 Writable 等等的東西,但寫好了卻編譯有問題,就是找不到 Orz 可能是我對 jar 檔架構不熟,因此最後就改用字串啦 :P 就像寫 streaming 一樣,全部都用字串來處理。有機會再來慢慢熟悉 jar 環境了!


2009年12月21日 星期一