Hatena::ブログ(Diary)

かがぴーの雑記帳 このページをアンテナに追加 RSSフィード

 普段の日記とか研究めもとか (2011/06〜 デザイン変わりました)
 ついった発言まとめ(個人的)とかクライアント作成はどこかへいきました。

2011-06-27

XML::Simple

 同期にXML解析について聞かれたのでその時に用意したドキュメントをそのままぺたり。

 どっちかと言うとリファレンスの利用方法とかそっち系が強い気がする。意外と、あるデータ構造が与えられてそのデータ構造をどうやって扱うのとかそういうクリティカルな情報って無いもんだなーと思った次第。

 XML::Simpleについて基本的な使い方は以下を参考に。 

  http://d.hatena.ne.jp/perlcodesample/20100424/1270894115

  http://search.cpan.org/~grantm/XML-Simple-2.18/lib/XML/Simple.pm

 とりあえずサンプルコードを用意したのでそれを眺めてみよー

#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;
use XML::Simple;

my $xml = XML::Simple->new;
my $data = $xml -> XMLin('conf.xml');

# 素のXML全て表示
warn Dumper ($data);
print "============", "\n";

# log-types以下の内容全て表示
warn Dumper ($data->{'log-types'});
print "============", "\n";


# XMLのlib-dirのみ表示
print $data->{'lib-dir'}, "\n";
print "============", "\n";

# XMLの上層タグ一覧表示みたいな
foreach my $key ( keys %$data){
    print $key, "\n";
}
print "============", "\n";

# log-typesタグの中身を表示しさらにその中も表示
foreach my $key (keys %{$data->{'log-types'}}){
    print $key, "\n";

    foreach my $key2 (keys %{$data->{'log-types'}{$key}}){
        print "\t", $key2, "\t", $data->{'log-types'}{$key}{$key2}, "\n";
    }
}
print "============", "\n";

 どんなデータ構造があるか調べるには? (実際に使うことはあまりないけど参考にするには?)

  → Data::Dumper を利用 (上2つのprint文あたりがその例)


 下3つのprint文あたりはリファレンスに保管されてるXMLのデータ構造をどうやって取り出すかみたいな一例。

 Perlリファレンスなにそれおいしいの?な人はぐぐる推奨。

 多分大体これだけ知ってればXMLの中から必要な情報だけ取り出すとかそういうことはできそーです


 いじょ。

 

2011-06-09

pLSA(pLSI)用共起行列の作成

 既に2年前にやったことですが、今回はpLSA(pLSI)というものに焦点を。

 pLSA(なんと99年に発表された論文)は言語処理とかそういう分野に限定せず色々なところで応用の効く手法ですが、言語処理の場合、共起行列(単語文書行列)と呼ばれるものを前処理として作成しておく必要があります。なのでその部分のプログラムを。

 その前に : 共起行列(単語文書行列) なんじゃらほい?

 例えばある文書ファイルに、

   こんにちは
   今日の天気は
   明日の天気は

 という感じで書いてあったとしましょう。

 したら以下みたいな数字の羅列が共起行列になります。

   0:1
   1:1 2:1 3:1 4:1
   5:1 2:1 3:1 4:1

 大雑把すぎますが、各行がそのまま元のファイルの行に対応してます。注目すべきは2行目と三行目。「今日の天気は」という表現は「今日/の/天気/は」というように形態素解析されます。各単語にIDみたいな数字が割り振られ、その行に出現した回数が記録される・・・ので、「ID : 出現回数」という固まりがずらっと並びます。「明日/の/天気/は」のうち3単語は直前で出てるので、以前使った単語IDが使われてるわけですね。

 さてさて。

 形態素解析を行う必要があるのでまずはmecabインストールしないといけません。(ちなみにやるならChasenでも可。その場合適宜プログラムを改変すれば動くと思います)

 とりあえず、家PCはMacになってるので、とりあえず家PCにもmecabさんを入れてみよーと思います。


 1. http://mecab.sourceforge.net/ 

    ここから、mecab-*.**.tar.gz、mecab-ipadic-*.**.tar.gzをダウンロード

 2. ターミナル起動

 3. DLしてきたやつを展開。まずはmecab本体のインストールから。

     $ tar zxvf mecab-0.98.tar.gz
     $ cd mecab-0.98
     $ ./configure --enable-utf8-only
       ※mecabの標準がEUC-JPなので文字コードを設定
     $ make
     $ sudo make install

 4. お次にmecab用の辞書をインストール

     $ tar zxvf mecab-ipadic-2.7.0-20070801.tar.gz
     $ cd mecab-ipadic-2.7.0-20070801
     $ ./configure --with-charset=utf8
       ※これまた文字コード指定
     $ make
     $ sudo make install

 以上の手順でmecabインストールは終わり。あとは適当に、

     $ mecab

 で起動できます。適当な言葉を入れたら形態素解析結果が出たりと。細かいオプションとかは本家サイトでも参照してください。


 で、ここまでが長い前置きです。

 この形態素解析mecabを使って、pLSA用の共起行列(単語文書行列)を作成するのが本題です。

 てことで以下コードを。

#!/usr/bin/perl

use strict;
use warnings;

my $mem;
my %hash = ();
my %hashword = ();
my $wordnumber = 0;

open(IN, "test.txt");

# 各文をキーとしたハッシュを作成するので変数に保管
while(my $hashtitle = <IN>){
    chomp $hashtitle;

    # mecabにより形態素解析結果を得る
    open(MCB, "echo $hashtitle | mecab | ");
    while(<MCB>){
	# 単語のみ必要なので切り分ける
	my @data = split(/\t/, $_);

	# EOS及び記号以外の単語をハッシュへ保管
	if($data[0] !~ m/EOS/ &&
	   $data[1] !~ m/^記号/){

	    # 行->単語->出現回数 のようなハッシュを作る準備
	    my $subkey = $data[0];

	    # 単語の出現回数を格納
	    if( exists ($hash{$hashtitle}{$subkey}) ){
		$hash{$hashtitle}{$subkey}++;
	    }else{
		$hash{$hashtitle}{$subkey} = 1;
	    }

	    # 単語IDを放り込む
	    if(exists ($hashword{$subkey}) ){
		# 単語が存在していない場合はIDの付与を行わない
	    }else{
		$hashword{$subkey} = $wordnumber;
		$wordnumber++;
	    }
	}
    }
    close(MCB);

    # 共起行列を保管(処理が終わった行から保管)
    foreach my $key (keys %{$hash{$hashtitle}}){
	$mem = $mem."$hashword{$key}:$hash{$hashtitle}{$key} ";
    }
    $mem = $mem."\n";
}
close(IN);

# 共起行列を出力
print $mem;

 こんな感じ。

 Perlハッシュを2つ利用して作っている感じ。1つは 行->単語->出現回数のハッシュ。もう一つは単語->IDのハッシュ。この2つを作って保存するようにしていけば基本的に出来上がりです。

 以下のファイル(test.txt)をこのスクリプトに与えてみましょ。

   こんにちは
   今日の天気はどうですか
   明日の天気は
   いらっしゃいませ
   さようなら
   この大学について教えてください
   広島市の名産は
   広島について教えてください

 で、実行結果は以下の通り。

   $ perl kyoki.pl
   > 0:1 
   > 6:1 4:1 2:1 3:1 1:1 7:1 5:1 
   > 4:1 2:1 8:1 3:1 
   > 10:1 9:1 
   > 11:1 
   > 15:1 13:1 17:1 16:1 14:1 12:1 
   > 4:1 2:1 20:1 18:1 19:1 
   > 15:1 17:1 18:1 16:1 14:1 

 あとはこの共起行列を使ってpLSAの計算を・・・という感じです。

 ちなみに、LSAというのもあって、それを確率的に与えたのがpLSAで。更に改良したのがLDAとかありますが・・・そのへんの詳細と利用法はまた後日にでも。

2011-06-05

Webスクレイピング

 備忘録的にまとめていこうシリーズ、今回はWebスクレイピングの話題を。

 超今更感のあるスクレイピングです。例によってPerlです。

 スクレイピング=必要な情報を毟り取ってくること

 ところで、スクレイピングってAPIでとってくるのと違うらしいんだけど何が違ったっけ。返ってくるデータが素のデータなだけかな? まぁいいやー

 でもこれ、ついった初期の頃は凄い使われてましたね。あの頃はスクレイピングでほいほいとってくるのが楽しかった記憶が。今は確か規約で禁止されてるんでしたっけ。(スクレイピングは場所によっては規約で禁止されてるらしーーので一応処理する前に確認を−)




 さてさて本題です。

 今回なんでスクレイピングしたのかというと某所から価格情報を定期的にとってきたかったから、ですです。あまぞんさんではありません。

 検索対象の語句をある別ファイルに保存していたとして、そいつを読み込んで各用語ごとにスクレイピングで情報取得、という流れです。

 そんなのを金曜日にふと思いついて構想から完成まで10分。コードが一部おかしいのかなーという気もするけどPerlってきちんと学んだことない。プロの方いないかなぁ?

 さてコードです。

#!/usr/bin/perl

use warnings;
use strict;
use URI;
use LWP::UserAgent;

# 検索対象の語句を保管したファイル
# 検索対象1つごとに改行してる
open(LIST, "search.txt");

while(my $target = <LIST>){
    # 仕込むサイト
    my $uri = URI->new('http://www.sample.co.jp');
    # ここのクエリの付け方は各サイトに準じる
    $uri->query_form( kw=> $target);

    # リクエストとか    
    my $ua = LWP::UserAgent->new;
    my $res = $ua->get($uri);
    
    # 保管
    my $data = res->content

    #############################
    # 欲しいデータに合わせて正規表現ごりごり #
    #############################
   
 }

 大体こんな感じ。基本的には、

  1.検索クエリを指定してリクエストを投げる

  2.レスポンスに対して正規表現でデータを処理

 という感じですね。結構基本。(今更記事にしてるのもあれですがまぁ)

 LWP::UserAgentはWebデータをとってくるのに利用できるモジュールですね。大体こんな感じのテンプレで利用できて、API叩く時もこれ使う場合も(もちろんサイトによるけど)

 ただ、今回はとってくるだけなのでわざわざこのモジュール使わなくてもとれたりする(というか単にget叩けば)

 で、まぁ、返ってきたデータは$res->content に入っているのでそれを処理させるのに一旦変数$dataに保存してる、という感じですです。この処理するデータのどの部分が欲しいか探すのに、一回print $dataしても良いし、サイトの方に戻って実際に自分で検索かけて出てきたページのソースを眺めるのも一つの手ですです。

 あと、場合によっては、HTML::TagParserってモジュールを使ってとってきた方が楽な場合もあるので、欲しいデータに合わせてとってくる、っと。


 ・・・公開されてるAPIが多い昨今、スクレイピングをしてまで欲しい情報を取得する機会ってあまりないかもですね。

 今度はAPI関係の処理をまとめようかなぁ

 最近、備忘録的な感じでちょこちょこ記事にめもるのが楽しいです。

2011-06-02

Google APIについて

 後輩くんの研究のお手伝いで今日利用したGoogle API(特にMap)。

 自分の備忘録も兼ねてまとめておこうかと。(研究室向け的な意味合いも兼ねて)

 

Google Maps API

 リクエスト数に制限は無し

 ジオコードリクエストは上限あり(1日あたり2500件)

 他のAPI同様キーの取得が必要


●基本的な使い方

 以下のようなコードをhtmlファイルに記述。

 このプログラムでは東京駅を中心とした地図のみを表示。

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Google Maps JavaScript API Example</title>
    <script src="http://maps.google.co.jp/maps?file=api&hl=ja&v=2&key=[Your Google Maps Key]" type="text/javascript"></script>
    <script type="text/javascript">
    //<![CDATA[

    function load() {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        map.setCenter(new GLatLng(35.658613,139.745525), 17);
      }
    }
    GEvent.addDomListener(window, "load", load);
    GEvent.addDomListener(window, "unload", GUnload);

    //]]>
    </script>
  </head>
  <body>
    <div id="map" style="width: 500px; height: 500px"></div>
  </body>
</html>

 HTMLのコードがおかしいとかそういう所は気にしない方向で。

 scriptタグの中が重要で、この部分を変更していけば簡単に修正できる。

 このプログラムテンプレみたいな感じで流用していけば良い。必要なところは後から足していけばいくらでもGoogle Mapはいじくれるので。

 [Your Google Maps Key] のところは自分で登録して取得したキーを入力する。

if (GBrowserIsCompatible()) {
    var map = new GMap2(document.getElementById("map"));
    map.setCenter(new GLatLng(35.658613,139.745525), 17);
}

 ここはどこを中心にした地図を生成するか、みたいなところ。

var map = new GMap2(document.getElementById("map"));

 ここはこれから生成するGoogle MapにIDを付与するというか、後から呼び出すためのIDを設定している感じ。

map.setCenter(new GLatLng(35.658613,139.745525), 17);

 ここで座標をしていると思えば。緯度と経度。北緯東経の順番。ここいじくったら中心にくる座標が変わる感じ。

 で、ここまではscriptタグの話。肝心のhtmlの方はどーするの、ってことで、単純にGoogle Mapを表示するだけならbodyタグ内に以下みたいなのを書く。

<div id="map" style="width: 500px; height: 500px"></div>

 さっきIDを決めたと思うけど、div要素のidのとこにそれを指定してやれば、この部分にGoogle Mapが表示されるようになる。

 あとは適当なサーバに放り込んで開いてみたらちゃんと表示されると思う。

 まったく何も表示されない場合、ファイルがUTF-8になっていない可能性があるので、注意。


●このままだと面白くもなんともないので。。。

 さっき生成したGoogle Mapにピンを突き立ててみる。

var marker = new GMarker(new GLatLng(35.658613,139.745525));
map.addOverlay(marker);

 さっきのプログラムの、map.setCenterの後にでも書き加えてみる。

 一行目でどこにピンをたてるかを記述して二行目でmapに加えてるとかそんなイメージ。

 これで東京駅ど真ん中にピンが突き刺さったGoogle Mapが生成される。


●じゃあ緯度とか経度とかどーやって取得するの?

 GClientGeocoderとかいうAPIを使う。

 とりあえずfunction_load()の中に以下のコードを追加。

geocoder = new GClientGeocoder();

 これでGeocoderの初期化かんりょ。

 で、実際に住所から緯度経度を取得するには、javascript中に処理を埋め込んでbodyタグ中で呼び出して取得するという感じ。

 ただまぁそのへんは私がjavascriptメインに使いたいという人じゃない(というかjavascriptはここ数年触ってないような・・・)のでAPIの呼び出し部分だけjavascriptで書いてみる。したら以下みたいな感じ。

geocoder.getLatLng(
    address,function(point) {
        if (!point) {
            alert(address + " not found");
        } else {
            map.setCenter(point, 13);
            var marker = new GMarker(point);
            map.addOverlay(marker);
        }
    }
);

 こんな感じ。addressの部分に住所(文字列)をぽいっちょすればpointってとこに緯度経度の情報が入ってるので、あとはいつもの処理を入れたらはいかんせー。

marker.openInfoWindowHtml("ぶええ");

 ちなみにこれピンを立てたところに吹き出しを作るので、ちょっとした捕捉説明を書き加えたい時に使えると思う(観光情報を入れるとかしたらどうだろう)

 ところで一部動作確認とってないので実際に実装してみるといいと思うんだー


●で、他のAPIどーやって使うの?

●そもそもPerlで実装しないの?

 後日。

2011-05-29

htmlのコンテンツ抽出

私の研究内容とは離れますが、後輩くんの研究のお手伝い。

 今回は"ExtractUniqueBlock"を使ってみた。

 htmlソースから本文を抽出するにはいくつかの方法があるけど、朝日新聞とかのニュース記事から本文のみを抽出するってなると正規表現を駆使しても結構きつかったり、何より汎用性が無かったりする。なのでこれを使うことに。

 必要なファイルをダウンロードして(モジュールで提供されてるから便利)、サンプル通りにまずは実行してみると、まぁずらーーーっと出てくるからちょっと謎。というか変なところで改行入れられたりで少し扱い辛い印象。

 てことで基本はサンプル通りのプログラムで、一度改行を除いた上でメモリ(変数)に保存。

 で、コンテンツの部分は、 タグというわけではないけどそれっぽいのでくくられている。けど、コンテンツの途中で改行されたりするから、改行を除いた上でメモリに保存する必要がある感じ。

 で、実行してみた。

 朝日新聞毎日新聞 : 割とよく取れる

 読売新聞      : むり

 という感じ。細かいところでミスが出てくるのは仕方ないので、これはちまちまと正規表現をかけて取り除いていけば良い感じ。特に後輩くんは新聞のニュースサイト限定なのでそこまでではないかなーと(頑張ってもらうけど。。。


 で、今回これを勉強してる過程で、CETRによるコンテンツ抽出もあった。これはめも。いずれちょっと色々いじってみよう



 自分の研究分野とは結構離れてるけど、根っこは同じNLPなので勉強になりますね。