RDF、RDFSによるモデリング
RDFSは、集合の包含関係に基づくクラスやプロパティの定義をサポートしています(RDFSにおいてクラスとプロパティは直交することに注意)。本稿の例では、ただプロパティを用いてクラスのデータ属性を定義しているわけではありません。これはオブジェクトモデリングとは別のもので、Java、Ruby、Smalltalkなどのオブジェクト指向プログラミング言語で使われるプロシージャとも異なります。RDFS推論を使用すると、異種データソースの組み合わせが容易になるだけでなく、新規のRDFを手際よく生成できます。つまり、推論によって新規のRDFトリプルがアサートされます。
それでは、手始めにニュース記事から抽出したサンプルのRDFデータを使い、セマンティックWebアプリケーションを構築するための基本的なテクニックをサンプルプログラムで見てみましょう。
RDFに保存されている人、場所、業界用語
私は、ロイターのOpenCalaisシステムを使用し、ニュース記事を含むテキストファイルを読み込んでN-TriplesフォーマットのRDFデータを生成する簡単なRubyスクリプト「ruby_utilities/raw_data_to_rdf.rb」を作成しました。このフォーマットは主語、述語、目的語、およびピリオドで構成されます。
本稿のサンプルソースには、「rdf_files/news.nt」というサンプルのRDF N-Triples出力ファイルが含まれています。リスト1は、このファイルから数行を抜粋したものです。トリプルの要素が特定の名前空間で(または文字列リテラルとして)定義されていることが分かります。述語containsIndustryTermは、knowledgebooks.comドメインの名前空間である<http://knowledgebooks.com/ontology/#containsIndustryTerm>で定義されています。前置記法を使えば名前空間を短縮できます。この記法は、後ほどN3 RDFフォーマットに切り替えるとき使用します。
ところで、RDFデータは実際どこに保存されるのでしょう? Sesameはメモリ内のRDFストアをサポートしており、サンプルソース内のSesameラッパーはこれと、バックエンドの何種類かのデータストア機構を使用します。これらの代替ストレージバックエンドは数行のコードで選択できますが(Sesame Webサイトの解説書を参照)、それらの設定には時間がかかります。とりあえずRDF/RDFSモデリングの基本とSPARQLの効果的な使い方を習得し、実際の配備については、配備すべき具体的なアプリケーションを手にするまで、あまり気にしないことをお勧めします。
<http://news.yahoo.com/s/nm/20080616/ts_nm/usa_flooding_dc_16> <http://knowledgebooks.com/ontology/#containsCity> "Burlington" . <http://news.yahoo.com/s/nm/20080616/bs_nm/global_economy_dc_4> <http://knowledgebooks.com/ontology/#containsCountry> "Germany" . <http://news.yahoo.com/s/nm/20080616/ts_nm/worldleaders_trust_dc_1> <http://knowledgebooks.com/ontology/#containsOrganization> "University of Maryland" . <http://news.yahoo.com/s/nm/20080616/ts_nm/worldleaders_trust_dc_1> <http://knowledgebooks.com/ontology/#containsOrganization> "United Nations" . <http://news.yahoo.com/s/nm/20080616/ts_nm/worldleaders_trust_dc_1> <http://knowledgebooks.com/ontology/#containsPerson> "Gordon Brown" . <http://news.yahoo.com/s/nm/20080616/ts_nm/worldleaders_trust_dc_1> <http://knowledgebooks.com/ontology/#containsPerson> "George W. Bush" . <http://news.yahoo.com/s/nm/20080616/bs_nm/global_economy_dc_4> <http://knowledgebooks.com/ontology/#containsCompany> "Reuters" . <http://news.yahoo.com/s/nm/20080616/bs_nm/global_economy_dc_4> <http://knowledgebooks.com/ontology/#containsIndustryTerm> "food" . <http://news.yahoo.com/s/nm/20080616/bs_nm/global_economy_dc_4> <http://knowledgebooks.com/ontology/#containsIndustryTerm> "energy costs" . <http://news.yahoo.com/s/nm/20080616/bs_nm/global_economy_dc_4> <http://knowledgebooks.com/ontology/#containsIndustryTerm> "finance ministers" . <http://news.yahoo.com/s/nm/20080616/bs_nm/global_economy_dc_4> <http://knowledgebooks.com/ontology/#containsIndustryTerm> "crude oil prices" . <http://news.yahoo.com/s/nm/20080616/bs_nm/global_economy_dc_4> <http://knowledgebooks.com/ontology/#containsIndustryTerm> "oil prices" . <http://news.yahoo.com/s/nm/20080616/bs_nm/global_economy_dc_4> <http://knowledgebooks.com/ontology/#containsIndustryTerm> "oil shock" . <http://news.yahoo.com/s/nm/20080616/bs_nm/global_economy_dc_4> <http://knowledgebooks.com/ontology/#containsIndustryTerm> "food prices" .
SPARQLによるN-Triple RDFデータの問い合わせ
このセクションでは、SPARQLを用いてN-Triple RDFデータを問い合わせるJRubyおよびJavaの完全なサンプルを示します。この後に続くセクションでは、コードの一部しか示していません。Java/Rubyプログラマなら、サンプルのコードの意味は容易に分かると思うので、サンプルに倣って書いたコードを自分のプログラムで使うときもほとんど問題とはならないでしょう。いずれのサンプルでも私の作成したSesameラッパーライブラリを使用しており、これはSesameのAPIを直接呼び出すよりもずっと簡単です(最終的には、SesameのAPIをすべて使いたくなるかもしれませんが)。
リスト2は、JRubyサンプルファイル「jruby_sesame_example2.rb」の完全なリストです。リスト2のクラスTripleStoreSesameManager
は、ラッパーライブラリ内で定義されています。メソッドdoSparqlQuery
は2つの引数を取ります。1つは有効なSPARQLクエリを含む文字列で、もう1つはメソッドprocessResult
を定義するRubyの任意のクラスです。SPARQLクエリに構文エラーがある場合は、Sesameライブラリによって有用なエラーメッセージが出力されます。
SPARQLとSQLには類似点があります。SELECTステートメントでは、各クエリの結果として返されるトリプル項のうちの1つ、2つ、または3つを指定します。ここで、トリプルの述語項はWHERE句で<http:://knowledgebooks.com/ontology#containsCompany>
と完全にマッチするように定義されているので、主語と目的語しか見る必要はありません。
JRubyは、動的な型付けをする点と非常に簡潔に書ける点でSesameの操作に適した言語です。irbコンソールで対話的に使用できる点も見逃せません。総じて、コーディングの実験のときはJavaよりもJRubyを使用した方が簡単です。とはいえ、JRubyのような動的言語はコードの実験に使い、本番の作業には通常Javaを使うことにしています。リスト3は、リスト2と同様の例で、ここではJavaと私のSesameラッパーライブラリを使用しています。
Javaは強い型付けをする言語なので、リスト2のメソッドdoSparqlQuery
の第2引数はインターフェースISparqlResultHandler
(processResult
のメソッドシグニチャを定義する)を用いて定義されています。
本稿の以降の部分では、より優れたRDFフォーマットであるN3に話題を移し、スキーマの異なる異種データソースのデータを利用することを考えます。また、より高度なSPARQLの例も示します。
require "java" require "sesame_wrapper.jar" require 'pp' include_class "TripleStoreSesameManager" tsm = TripleStoreSesameManager.new # Load NTriples file generated by Ruby utility raw_data_to_rdf.rb that # uses Reuter's OpenCalais to process some sample Reuter's news articles: tsm.loadRDF("rdf_files/news.nt") # Use duck typing: any class that has a method "processResult" can be a # SPARQL Results Handler: class MySparqlResultHandler def processResult result_list # method is called once for each SPARQL query result pp result_list end end dh = MySparqlResultHandler.new # query to return article URL and company name results for all news articles containing a company name: sparql_query = "SELECT ?subject ?object WHERE { ?subject <http:://knowledgebooks.com/ontology#containsCompany> ?object . } ORDER BY ?object" tsm.doSparqlQuery(sparql_query, dh)
import java.io.IOException; import java.util.List; import org.openrdf.query.MalformedQueryException; import org.openrdf.query.QueryEvaluationException; import org.openrdf.repository.RepositoryException; import org.openrdf.rio.RDFParseException; public class SparqlQueries2 implements ISparqlResultHandler { public static void main (String [] args) throws RepositoryException, IOException, RDFParseException, MalformedQueryException, QueryEvaluationException { new SparqlQueries2(); } public SparqlQueries2() throws RepositoryException, IOException, RDFParseException, MalformedQueryException, QueryEvaluationException { TripleStoreSesameManager ts = new TripleStoreSesameManager(); ts.loadRDF("rdf_files/news.nt"); String sparql_query = "SELECT ?subject ?object WHERE { ?subject <http:://knowledgebooks.com/ontology#containsCountry> ?object . }"; ts.doSparqlQuery(sparql_query, this); } public void processResult(List data) { System.out.print("next result: "); for (String s : data) System.out.print("|"+s+"|" + "\t "); System.out.println(" . "); } }