2007/06/26(火)
■[日記]近況報告

第五十一回
最近は研究が忙しいのと,RailsをせっかくさわってるんだからBlogもRailsアプリにするべきじゃない?という短絡的な思考でtypoでBlogを作ってみました.
acts_as_ludia や,acts_as_find_or_initialize_by といったプラグインのこともちょこっと書いてます.
私自身が非常に飽きっぽいのでいつまで続くか分かりませんが,お時間があればのぞいてあげてください.
新しく作ったBlogはもちろんtypoがベースになっていますが,実はRuby-GetText-Packageを利用して日本語化しようと目論んでいたりします.設定画面の一部などが今は日本語になってきています.その話もおいおい.
typoベースの私のブログ
2007/06/08(金)
■[Rails][Ludia]LudiaでWikipedia日本語版を対象にインデックスを構築するとかかる時間

第五十回
[myname@localhost wikipedia]$ rake db:migrate
(in /home/myname/rails/wikipedia)
== AddLudiaIndex: migrating ===================================================
-- execute("CREATE INDEX plain_text_index ON documents USING fulltext(plain_text);")
-> 3594.9286s
== AddLudiaIndex: migrated (3594.9307s) =======================================
環境とか
VMWareServer1.01上で仮想サーバを構築して試しました.
ホストOS
ゲストOS
- Fedora Core 5
- 割り当てメモリ 1GB
- MeCab 0.95
- senna 1.0.7
- PostgreSQL 8.1.8
- Ludia 1.1.0
データの詳細
- Wikipedia:データベースダウンロード - Wikipediaの最新版 jawiki-latest-pages-articles.xml.bz2 を利用
- HyperEstraierの平林さんが作られた wpxmltoest を参考にテキスト抽出してデータベースへ格納
- 文書数は373,666件(countにも結構時間がかかる)
感想とか
ほぼ1時間でインデックス作成ってのは早いのかなどうなのかな.ちなみにHyperEstraierでは,
早速、「estcmd gather -xl -cs 640 casket .」でインデクシングしてみたところ、45分2秒で完了し、さらに「estcmd gather -um casket」でMeCabの補助インデックスを作ったところ、11分3秒で完了した。
開発メモ
とのことらしいので,ちょっとだけHyperEstraierの方が早・・と思ったけどこの記事は2005/12/28の記事でした.同じ記事には以下のようにも書いてあって,
wpxmltoestは62分22秒で完了した。162030件(1527MB)の文書を抽出できた。1文書当たり9.65KBだから、ASCIIコードも少し混じっているから、各文書の文字数は平均でだいたい4000文字くらいかな。
開発メモ
ということなので,私は倍以上の文書に対してインデックスを構築したようです.
しかし,WikipediaのXMLファイルからテキストを抽出してデータベースへ格納するのにはだいたい丸一日くらいかかりました.Railsが遅いのでしょうか.ものっすごいメモリ使いましたし.(私の書いたコードが悪いといううわさもチラホラ(笑)
データベースへのデータ格納用Railsコード
xml2sql --postgresql というやりかたも考えたんですが,事前に必要なPostgreSQLのテーブル構成がいまいちよく分からなかったのでRailsのActiveRecordに頼りました.こういう点ではORMのメリットですよね.
以下にデータベースへWikipediaの文書を格納するコードを載せておきます.ここはこうした方が早くなるとかあれば教えてください.
app/model/document.rb
class Document < ActiveRecord::Base class << self def import(start_id = 1) file = Pathname.new(RAILS_ROOT) + "db/data/jawiki-latest-pages-articles.xml" list = MyListener.new(start_id) source = File.new(file) REXML::Document.parse_stream(source, list) end class MyListener include REXML::StreamListener BASEURL = "http://ja.wikipedia.org/wiki/" TEXTMINSIZE = 256 def initialize(start_id) @document = nil @buf = nil @start = start_id.to_s @flag = false @count = 0 end def tag_start(name, attrs) if name == "page" @document = nil @buf = StringIO.new end return nil if skip? @buf.write(%|<#{REXML::Text::normalize(name)}|) attrs.each do |pair| @buf.write(%| #{REXML::Text::normalize(pair[0])}="#{REXML::Text::normalize(pair[1])}"|) end @buf.write(">") end def text(text) return nil if skip? @buf.write(REXML::Text::normalize(text)) end def tag_end(name) return nil if skip? @buf.write(%|</#{REXML::Text::normalize(name)}>|) if name == "page" begin proc_document(Hpricot.XML(@buf.string)) rescue => e puts e return ensure @count +=1 end if @document.plain_text @document.wiki_text = @buf.string @document.save end @buf.close exit if @count == 2 end end private def proc_document(doc) id = doc.at(:id).inner_text title = doc.at(:title).inner_text raise "no title error" if title.blank? if @flag elsif id == @start @flag = true else raise "not modified: count = #{@count}" end raise "no indexing document" if title =~ /(Media|特別|Wikipedia|利用者|ノート|画像|Template|Category|Portal)(:|;|-|=|‐)/ @document = Document.find_or_new_by_entity_id(id) @document.created_time = Time.parse(doc.at(:timestamp).inner_text) raise "not modified: #{@document.id ? @document.id : ''}" if @document.created_time < (@document.updated_on || Time.parse('2001-05-20T00:00:00Z')) @document.title = title if author = doc.at(:username) || doc.at(:ip) @document.author = author.inner_text else @document.author = "anonymous" end text = doc.at(:text).inner_text @document.plain_text = trimming(REXML::Text::unnormalize(text)) @document.url = BASEURL + CGI.escape(@document.title) end def trimming(text) return nil if self.blank? || (text.size < TEXTMINSIZE) || (text =~ /^#REDIRECT/) text.gsub!(/^=+([^=]+)=+/){ $1 } text.gsub!(/<[^>]+>/, "") text.gsub!(/^\s*[\*#:|;-]+\s*/, "") text.gsub!(/\[\[[^\]\|]+\|([^\]]+)\]\]/){ $1 } text.gsub!(/\[\[([a-zA-Z-]+:)?([^\]]+)\]\]/){ $2 } text.gsub!(/\{\{([^\}\|]+)\|[^\}]+\}\}/){ $1 } text.gsub!(/\{\{([^\}]+)\}\}/){ $1 } text.gsub!(/\[http:[^ \]]+ ([^\]]+)\]/){ $1 } text.gsub!(/'{2,}/, "") text.gsub!(/^ *\{?|/, "") text.gsub!(/^ *[\!\|\}]/, "") text.gsub!(/^\*+/, "") text.gsub!(/[a-zA-Z]+=\"[^\"].*\"/, "") text.gsub!(/[a-z][a-z]+=[0-9]+/, "") text.gsub!(/.*border-style.*/, "") text.gsub!(/.*valign=.*/, "") text.gsub!(/\&[a-zA-Z]+;/, "") text.gsub!(/.*(利用者|会話|ノート):.*/, "") text.gsub!(/(Wikipedia|Category):/, "") text.gsub!(/.*語:/, "") text.gsub!(/^thumb\|/, "") text.gsub!(/画像:/, "") text.gsub!(/^[ +]*[\|]*/, "") text.gsub!(/\|\|/, " ") text.gsub!(/\s/, " ") return text end def skip? return true unless @buf return true if @buf.closed? end end end end
bluedemio
wikipediaのデータをDBに登録したいと思い、webで検索していて辿りつきました。
「document.rb」を使いたいと思っているのですが、おそらく私のスキル不足のため、正常に動作させることができません。いくつか疑問点があるので、お暇なときにでも返信がもらえると嬉しい限りです。
前提:
私はほとんどRubyについて知りませんが、「プログラミングRuby」と「RailsによるアジャイルWebアプリケーション開発」という本が手元にあるので、多少のことであれば調べられる環境にあります。ソースを解析しようと思っていますが、現状ではほとんど分かっていない状況です。
質問:
1. document.rb の使い方について教えてください。
2. テーブルの作成等は、document.rb内で行っているのでしょうか?
3. 読み込むxmlファイルの形式はどのようになっていますでしょうか?
質問の詳細:
1.
私の環境では、$ ruby document.rb としても動作しないので、ファイルの先頭に以下を追加し、読み込むファイルのパスを変更し、ファイルの末尾に以下を追加したところ、何かしらの動作が始まりました。結局、途中でParseExceptionが発生し、失敗に終わったのですが。。。
<ファイルの先頭>
require ’rubygems’
require_gem ’activerecord’
require ’rexml/streamlistener’
ActiveRecord::Base.establish_connection (
# データベースの設定
)
<ファイルの末尾>
Document.import()
2.
『事前に必要なPostgreSQLのテーブル構成がいまいちよく分からなかったのでRailsのActiveRecordに頼りました.』という記述から、おそらくdocument.rb内でテーブルを作成している部分があるのでしょうが、よく分かりませんでした。
3.
うまく動作しない原因は、利用しているファイルの違いにあるのかな?とも思っています。
私は、「jawiki-20070702-pages-articles.bz2」をダウンロードしてきて使おうしています。解凍して利用したところ、「#<Class:0xb7db2524>” is not a valid constant name!」というメッセージが次々に出力され、うまく動作しないようです。解凍せずに利用したところ、メッセージは出力されないのですが、前述のようにParseExceptionで止まってしまいました。40時間ほど経過したところでした。。。
bluedemio
完璧とは言えないまでも、DBに格納できるようになったので報告しておきます。
その中で気になったところがあったので、お時間のあるときにでも返答いただければ幸いです。
気になった点
find_or_new_by_entity_id の定義がないのですが、どんな感じになるのでしょうか?
「idがDBにあったら上書き、なかったら新規作成」みたいな処理にするのだと思うのですが。とりあえず、ここの部分をDocument.newとすることでしのぎました。
前から変更した点
1. railsのアプリケーションを作成し、app/models/以下にdocument.rbを置きました。でも、これはあまり意味がなさそう。
2. 私の環境では、Hpiricotがなかった(?)ようなので、$gem install hpricot でインストールし、document.rbの先頭に require ”rubygems” require ”hpricot” を追加しました。
uchiuchiyama
>> bluedemioさん
あああ,コメント確認が今頃になってしまいました.すみません.
原因は私のコードがRuby on Rails環境を前提に書かれていることですね.申し訳ないです.
Ruby on Railsでは,ActiveRecordというモジュールを利用して,MySQLやPostgreSQLといったRDBMSの違いを吸収したRubyコードを書くことができます.
そのため,データベースのテーブル定義はdocument.rbの中には無く,別のファイルで定義しています.また,Railsがテーブル名(documents)をクラス名(Document)から推測して自動的にテーブルへ接続してくれます.
このあたりで,document.rbをRubyのコードとして実行されたbluedemioさんの環境ではエラーが出てしまったのだと思います.
ご質問の項目に対しては,
1. 使い方は以下のとおりです.(ただし,Railsアプリケーション上での実行のみ)
$ ruby script/runner ’Document.import’
2. PostgreSQL上でのテーブル定義は以下のとおりです.
wikipedia_development=# ¥d documents
Table ”public.documents”
Column | Type | Modifiers
--------------+-----------------------------+--------------------------------------------------------
id | integer | not null default nextval(’documents_id_seq’::regclass)
url | character varying(1024) |
title | character varying(255) |
author | character varying(255) |
plain_text | text |
wiki_text | text |
pure_html | text |
created_time | timestamp without time zone |
updated_on | timestamp without time zone |
entity_id | integer |
Indexes:
”documents_pkey” PRIMARY KEY, btree (id)
”documents_entity_id_index” btree (entity_id)
3. XMLファイルの形式はbluedominoさんの利用されている jawiki-20070702-pages-articles.bz2 を解凍してできる jawiki-20070702-pages-articles.xml のものと同一です.
find_or_new_by_entity_idについては,ActiveRecordへ追加したメソッドでした.これについては私の別ブログになりますが,http://blog.fulltext-search.biz/articles/2007/06/27/acts-as-find-or-initialize-by をご覧ください.(やはりRailsが前提になってますが)
2007/04/03(火)
■[Rails][Ludia] acts_as_ludia を使ってLudiaのデモWebアプリケーションを作りました

第四十九回
先日のエントリ「Ludia 用の Rails プラグイン acts_as_ludia を作りました - のほほん徒然」で紹介したacts_as_ludiaプラグインを使って,簡単な検索アプリケーションをRailsで作ってみましたので紹介します.
はてなブックマーク全文検索
これはなに?
id:uchiuchiyamaこと,私がはてなブックマークしたWebページを対象にLudiaを使って全文検索を行うアプリケーションです.
どうやって使うの?
リンク先のページで検索ボックスに適当なキーワードを入れて検索ボタンを押してみてください.例えばid:uchiuchiyamaのはてなブックマークから「rails」を含むWebページを検索したりできます.
また,検索キーワードを空白で区切ればAND検索,「 OR 」で区切るとOR検索になります.例えば,id:uchiuchiyamaのはてなブックマークから「rails」を含み,「ludia」か「postgresql」か「senna」を含むWebページを検索することもできます.
なにがうれしいの?
はてなブックマークでは,タグによる検索と,タイトルからのキーワード検索はサポートしていますが,ブックマークしたWebページ本文を対象としたキーワード検索は(私の知る限り)できません.
そこで,「確かCSSのセレクタについて書いてあるページをブックマークしたはずなんだけど・・」というときや,「JavaScriptに詳しいb:id:amachangさんのはてなブックマークから,mochikitのことを書いてあるWebページがほしい」という場合に有用なのではないでしょうか.
つまり,その道の第一人者や一家言を持つ人の知識を検索できる,ということになります.これは,Google Coopのように,限定された(有用な)情報源から検索を可能にすると言えるのではないでしょうか.私は,はてなブックマークのようにソーシャルな情報蓄積サイトをうまく利用すれば,より簡単に精度の高い検索が行えるのではないかと思っています.あと,情報を知りたい時には「知ってる人」を探すというアプローチは面白そうだなーとか妄想してます.
制限事項
現在,b:id:uchiuchiyamaのはてなブックマークのうち,だいたい600件くらいが検索対象になっています.また,他のユーザのはてなブックマークを対象とした検索は,アイデアはあるものの時間がなくて作ってません.時間が出来れば作りたいなと思ってます.
2007/03/21(水)
■[Memo]アンケート詳細:Googleの大規模日本語データ公開に関する特別セッション

第四十八回
Googleは日本語の言語処理研究のためにWebインデックスから作成したコーパスデータの公開を予定しており,そのデータの形式や内容を含めた概要のたたき台を公開し,これらに対する研究者の意見を広く募りたい.
のほほん徒然 - 聞いてきました:Googleの大規模日本語データ公開に関する特別セッション
先日のエントリでお伝えしたように,Googleが日本語Webコーパスデータを学術用に公開するようです.そのときの公開データの形式や内容について,言語処理学会参加者からアンケートとして意見を募っていました.
このエントリでは,そのアンケートの内容を広く公開します.直接提出する先などはありませんが,Webのデータの話なので,Web上で広く議論してみるのも良いのではないでしょうか.
■[Memo]聞いてきました:Googleの大規模日本語データ公開に関する特別セッション

第四十七回
写真はGigazineのマネです(笑)
特別セッションをやるそうです。大規模日本語データについて。
[を
グーグル株式会社では、日本語の言語処理研究推進のため大規模日本語データの公開を検討しています。つきましては仕様を決定するにあたり、実際にデータを御利用頂く研究者 / 技術者の皆様の「生の声」を是非お伺いしたく存じます。今回、言語処理学会様の御好意により、下記のとおりデータ仕様に関する特別セッションを設けて頂ける事になりました。
Google: 大規模日本語データ公開に関する特別セッション
はてなブックマークでも話題になっているGoogleの大規模日本語データ公開に関する特別セッション@NLP2007に,家が近いこともあり参加してきましたので,その詳細を書きます.






ちょww 普通の口ーションをあんな風に使うなんてどういうテクしてんだよww
ちょっとウソコ漏れたのに5万振り込んでくる金持ちの勢いには参りましたwww
http://dopyun.quitblue.com/9wfLRvB/
前のは俺と相性合わなかったから最近コッチに替えたらバッチリ最強ww
クリ舐めパイ揉み同時進行上等すぎだしwwwww
てか一発で10万貰えたんだけど、ここのお姉さんみんな金使いひでぇなwww
まぁ俺の懐は潤うからいいけどなw 金ってある所にはあるんだな(^^;
http://2N0KmpR.meshiuma.tsukimisou.net/
働かざるものヤルべし!!!ほんと働いたら負けだわ(´Д`;)
オレ真面目に会社員やってたけど、今はその頃より月の稼ぎ3倍だよ?
初めてヤった時は4万だけだったけど、今じゃ平均一回7万だかんなwww
もうアフォらしくて会社員ヤメたしwwwww 毎日ネトゲ最高wwww
http://netoge.bolar.net/qLjU5F2/
しばらくお互いに愛撫し合ってたら、女が急にカバンから蜂蜜取り出してボクのティンポに塗りたくってきてパイズリ始めたからビックリしたよ(^^;
パイズリされつつ蜂蜜塗られてティンポしゃぶってもらっての繰り返しで、気持ちよすぎて気がついたら3回イったしwww 俺淡白なのにすげwwwwww
やっぱ巨乳で工口工口な女が一番だよねーヽ(゜∀゜)ノヒャッヒャッ!!
http://ene.creampie2.net/lQDy4dn/
もーさすがに3回は果てるってーー!!!(>_<)
連続じゃないだけマシだけど1 0 万の為とはいえ3回ヤるとティ ンコさんが火を噴きそうなくらい真っ赤っ赤だよ(^^;
まー何気に足 コ キしてもらったのって初めてだし、得っちゃ得だけどねーwww
http://kachi.strowcrue.net/L7hqFOE/
セ ク っ て 稼 げ るなら旅したついでにヤる事にしたんだけど、これウメェわwww
女の家に泊まるから宿代いらないし、旅先で稼げるから財布もイラねーwwww
とりあえず女の子と約束して、家に泊めてもらって、ハ メ て、諭 吉ゲットwww
楽勝すぎてすげぇ笑えるwwwww
儲 か る旅って最高ーーーwwwwwww
http://yuzo.plusnote.net/N9tshD8/
とりま一回3 万って事で約束してたんだけど、
色々オモチャ使ったりビデオ撮ってあげたりしたら
1 0 万 貰 え た 件 wwwwwwwwww
やりたい放題するほど報 酬増えるとかマジキチwwwwww
http://koro.chuebrarin.com/BNXDqTI/