RDF*/SPARQL*概論 ~セマンティックなWebへの新たな一歩~

メリークリスマス🎅🎂🎄🎉

<a href="https://qiita.com/advent-calendar/2020/ateam-finergy">Ateam Finergy Inc. Advent Calendar 2020</a>の最終日、12/25を担当します@Adacchi3です。

クリスマスツリー🎄のてっぺんを飾っているのはスター⭐ですよね！

そんな星⭐にちなんで、RDF*/SPARQL*について紹介していきます。RDF*をRDF-starと表記することがありますからね。

# はじめに

皆さんはセマンティックWeb（Semantic Web）をご存知でしょうか？

SEO対策をしていると、JSON-LDやmicrodata, RDFaといった「構造化データ」を目にする機会があるでしょう。

構造化データは、ページ概要、著者、公開日といったWebページ（URL）を説明するデータ、いわゆるメタデータをコンピュータが理解しやすい（機械可読性が高い）表現で構成されています。

これは意味あるデータのWebの実現、セマンティックWebの試みの1つだったりします。

セマンティックWebは、World Wide Webの考案者Tim Berners-Leeが提唱する、Web上にデータをただ置くのではなく、データ同士を結ぶことで、人やコンピュータがそのデータを探索できるようにしようというデータのWebの試みです[^1]。

[^1]: https://www.w3.org/DesignIssues/LinkedData.html

今回はセマンティックWebを実現するための技術の1つであるデータ記述言語のRDFと、そのクエリ言語であるSPARQLを概観して、RDF*/SPARQL*について紹介していきます。

# RDF（Resource Description Framework）

まずは実際にRDFデータの一例を眺めてみましょう。

```turtle
<https://en.wikipedia.org/wiki/The_Idolmaster> a schema:Article ;
	schema:about wd:Q20739875 ;
	schema:inLanguage "en" ;
	schema:isPartOf <https://en.wikipedia.org/> ;
	schema:name "The Idolmaster"@en .
```

こちらはwikidataにあるアイドルマスターシリーズのページからRDF/Turtle形式で抜粋してきたものになります。https://www.wikidata.org/wiki/Special:EntityData/Q20739875.ttl

RDFは主語、述語、目的語の3つの要素で構成されており、それをトリプルと呼んでいます。それぞれの要素はURLで表現されており、目的語はしばしばリテラル（string, integer, dateTime等）で表現されます。

例えば`<https://en.wikipedia.org/wiki/The_Idolmaster> a schema:Article .`の読み方を説明します。

主語は`<https://en.wikipedia.org/wiki/The_Idolmaster>`で、述語は`a`、目的語は`schema:Article`になります。

`a`はRDFでの記述で`rdf:type`の省略語彙でis-aを表現してます。
`schema:Article`の`schema:`はschema.orgのnamespaceになっており、省略せずに記述すると`http://schema.org/Article`になります。

これを読み解くと、主語のURLはschama.orgでいうところの記事に属する、という意味になります。

末尾を`;`で記述すると、次のトリプルの主語を省略して記述することができるため、例として挙げているトリプルの主語はすべて`<https://en.wikipedia.org/wiki/The_Idolmaster>`になります。

RDFはこのようなトリプルがいくつも組み合わせてグラフを表現し、データ同士が結びつくことによって、データのWebができるわけです。

こうしたデータとデータがリンクして、公開されているデータ（オープンデータ）をLOD（Linked Open Data）と呼びます。
2020年11月時点では世界で1,269個のデータセットがLinked Open Dataとして公開されています[^2]。

[^2]: https://lod-cloud.net/

# SPARQL（SPARQL Protocol and RDF Query Language）

RDFで記述されたデータから特定の情報を取得したいとき、クエリ言語のSPARQLを使います。こちらも一例から確認していきましょう。

```sparql
PREFIX schema: <http://schema.org/>
PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/URIs/imas-schema.ttl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

SELECT DISTINCT ?name
WHERE {
  ?person  a imas:Idol ;
    schema:name ?name ;
    imas:Title  "283Pro"@en ;
    schema:birthDate  "--12-25"^^xsd:gMonthDay .
  FILTER ( lang(?name) = "ja" )
} limit 10
```

こちらは<a href="https://sparql.crssnky.xyz/imas/">imas@sparql</a>で実際に問い合わせすることができるSPARQLクエリになります。

SPARQLでは、トリプルを構成する3つの要素をそれぞれ変数を割り当てることができ、その3つ組をトリプルパターンと呼びます。
一例では、`?person`や`?name`が変数に当たります。
SQLライクにクエリを書くことができるので、実際に触って覚えるのが良いと思います。

今回のクエリの場合は、283Proというタイトルに登場し、誕生日が12月25日で、アイドルであるキャラの名前を日本語で10件取得するというものになります。クリスマスが誕生日のアイドルはいるのでしょうか。

実際にimas@sparqlのエンドポイントで問い合わせした結果が<a href="https://sparql.crssnky.xyz/spql/imas/query?output=json&force-accept=text%2Fplain&query=PREFIX%20schema%3A%20%3Chttp%3A%2F%2Fschema.org%2F%3E%0APREFIX%20imas%3A%20%3Chttps%3A%2F%2Fsparql.crssnky.xyz%2Fimasrdf%2FURIs%2Fimas-schema.ttl%23%3E%0APREFIX%20xsd%3A%20%3Chttp%3A%2F%2Fwww.w3.org%2F2001%2FXMLSchema%23%3E%0A%0ASELECT%20DISTINCT%20%3Fname%0AWHERE%20%7B%0A%20%20%3Fs%20%20a%20imas%3AIdol%20%3B%0A%20%20%20%20schema%3Aname%20%3Fname%20%3B%0A%20%20%20%20imas%3ATitle%20%20%22283Pro%22%40en%20%3B%0A%20%20%20%20schema%3AbirthDate%20%20%22--12-25%22%5E%5Exsd%3AgMonthDay%20.%0A%20%20FILTER%20(%20lang(%3Fname)%20%3D%20%22ja%22%20)%0A%7D%20limit%2010">こちら</a>になります。


```json
{
  "head": {
    "vars": [ "name" ]
  } ,
  "results": {
    "bindings": [
      {
        "name": { "type": "literal" , "xml:lang": "ja" , "value": "大崎甘奈" }
      } ,
      {
        "name": { "type": "literal" , "xml:lang": "ja" , "value": "大崎甜花" }
      }
    ]
  }
}
```

大崎甘奈と大崎甜花がヒットしました。誕生日おめでとうございます🎉🎂🎉

このようにRDFデータが公開され、誰もがSPARQLで必要な情報を取得できるようになると、同じ指標を見ながら議論できるようになり、データの透明性や根拠の信頼性の向上が期待できます。

# RDF*/SPARQL*

RDF*/SPARQL*（RDF-star/SPARQL-star）は、statement（状態）に対してメタデータの付与を省略して表現する方法を提案しています。
2017年6月にWorkshopで提案され[^3]、2020年12月18日時点でW3C Community Groupにて草案が作成されています[^4]。

[^3]: http://ceur-ws.org/Vol-1912/paper12.pdf
[^4]: https://w3c.github.io/rdf-star/rdf-star-cg-spec.html

では、実際にどのような状態を表現することが容易になったのかを見ていきましょう。

一例として下記のデータがあるとします。こちらはAliceとBobが人であり、AliceはBobを知っていることを表現しています。

```turtle
ex:Alice a schema:Person .
ex:Bob a schema:Person .
ex:Alice schema:knows ex:Bob .
```

では、「Aliceが人である根拠」や「AliceがBobを知っている根拠」をデータとして示したい場合はどのように表現すればよいでしょうか。
従来のやり方で「Aliceが人である根拠」を示しているURLを追加したデータは、以下のようになります。

```turtle
_:s rdf:type rdf:Statement ;
    rdf:subject ex:Alice ;
    rdf:predicate rdf:type ;
    rdf:Object  schema:Person ;
    ex:source ex:Alice_Profile .
```

従来のStatementの表現は`_:s`は空白ノードであり、その空白ノードに対して、主語や述語、目的語、それ以外のメタ情報を付与していきます。

ここで、RDF*の表現を用いると、以下のようになります。

```text
<<ex:Alice a schema:Person>> ex:source <http://example.com/Alice_Profile> .
```

すっきりしましたね。

RDF*では、1つのトリプルを主語または目的語の要素として表現できます。
トリプルをネストして表現することにより、より直感的にトリプルに対してメタデータを付与することができそうです。トリプルの数も少なくなるのも利点です。

また、SPARQL\*はRDF\*を検索できるようにしたクエリ言語になります。RDF\*と同様にトリプルパターンの主語（または目的語）にトリプルパターンを用いることができます。

例えば、人物を示しているURLとその根拠を調べるクエリが以下のようになります。

```sparql
PREFIX schema: <http://schema.org/>
PREFIX ex: <http://example.com/>

SELECT DISTINCT ?person ?source
WHERE {
  <<?person a schema:Person>> ex:source ?source .
} LIMIT 10
```
こちらも従来の表現を用いたRDFデータからクエリを作成するより、より直感的に記述できるように見受けられます。

まだW3C Community Groupにて草案段階ではありますが、ゆくゆくはW3C勧告されるのではないでしょうか。

# 実際にRDF*/SPARQL*を触ってみる

今回はProvisional supportとしてRDF\*/SPARQL\*を実装している<a href="https://github.com/ruby-rdf/rdf">RDF.rb</a>と<a href="https://github.com/ruby-rdf/sparql">SPARQL.rb</a>を触っていきます。

サンプルプログラムを実行するために使用したRubyのバージョンは以下のとおりです。

```sh
$ ruby -v
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin18]
```

はじめに、RDF.rbのライブラリと語彙辞書にあたる<a href="https://github.com/ruby-rdf/rdf-vocab">rdf-vocab</a>をインストールします。

```sh
$ gem install rdf rdf-vocab
$ gem list | grep rdf
rdf (3.1.7)
rdf-aggregate-repo (3.1.0)
rdf-vocab (3.1.9)
rdf-xsd (3.1.0)
```

続いて、RDF.rbに記載されているRDF*のサンプルコードを実行していきます。

```sh
$ irb
irb(main):001:0> require 'rdf'
=> true
irb(main):002:0> require 'rdf/vocab'
=> true
irb(main):003:0> statement = RDF::Statement(RDF::URI('bob'), RDF::Vocab::FOAF.age, RDF::Literal(23))
=> #<RDF::Statement:0x6cc(<bob> <http://xmlns.com/foaf/0.1/age> "23"^^<http://www.w3.org/2001/XMLSchema#integer> .)>
irb(main):004:0> graph = RDF::Graph.new << [statement, RDF::URI("ex:certainty"), RDF::Literal(0.9)]
=> #<RDF::Graph:0x974(default)>
irb(main):005:0> graph.dump(:ntriples, validate: false)
=> "<<<bob> <http://xmlns.com/foaf/0.1/age> \"23\"^^<http://www.w3.org/2001/XMLSchema#integer>>> <ex:certainty> \"0.9\"^^<http://www.w3.org/2001/XMLSchema#double> .\n"
```

実際にRDF*データをturtle形式で出力されることを確認することができました。

続いて、SPARQL*を実行してみます。SPARQL.rbをインストールしていきます。

```sh
$ gem install sparql
$ gem list | grep sparql
sparql (3.1.3)
sparql-client (3.1.0)
```

RDF\*データを作成した後、SPARQL\*クエリを作成、実行可能な形式（SPARQL Algebra）にパースし、クエリを実行してみます。

```sh
$ irb
irb(main):001:0> require 'sparql'
=> true
irb(main):002:1* data = RDF::Graph.new do |g|
irb(main):003:2"   g << RDF::NTriples::Reader.new(%(
irb(main):004:2"     <http://bigdata.com/bob> <http://xmlns.com/foaf/0.1/name> "Bob" .
irb(main):005:2"       <<<http://bigdata.com/bob> <http://xmlns.com/foaf/0.1/age> "23"^^<http://www.w3.org/2001/XMLSchema#integer>>> <http://example.org/certainty> "0.9"
^^<http://www.w3.org/2001/XMLSchema#decimal> .
irb(main):006:1*         ), rdfstar: :PG)
irb(main):007:0> end
=> #<RDF::Graph:0x104(default)>
irb(main):008:0> query_str = "PREFIX : <http://bigdata.com/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX ex:  <http://example.org/> SELECT ?age ?c WHERE {\n        
  ?bob foaf:name \"Bob\" .  <<?bob foaf:age ?age>> ex:certainty ?c . }"
=> "PREFIX : <http://bigdata.com/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX ex:  <http://example.org/> SELECT ?age ?c WHERE {\n          ?bob foaf:name \"Bo...
irb(main):009:0> query = SPARQL.parse(query_str)
=> #<SPARQL::Algebra::Operator::Prefix:0x80c(( ( (: <http://bigdata.com/>) (foaf: <http://xmlns.com/foaf/0.1/>) (ex: <http://example.org/>)) (project (?age ?c) (bgp...
irb(main):010:0> data.query(query).to_json
=> "{\"head\":{\"vars\":[\"age\",\"c\"]},\"results\":{\"bindings\":[{\"age\":{\"type\":\"typed-literal\",\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"value\":\"23\"},\"c\":{\"type\":\"typed-literal\",\"datatype\":\"http://www.w3.org/2001/XMLSchema#decimal\",\"value\":\"0.9\"}}]}}"
```

RDF\*データに対してSPARQL\*クエリを実行し、結果を取得できました。

今回はサンプルプログラムを動かしたのみとはなりますが、取り扱うデータの内容やデータ活用を検討することで、いろいろと楽しいことができそうな気がしますね。

# おわりに

データ記述言語のRDFとそのクエリ言語のSPARQL、新たに提案されているRDF\*/SPARQL\*を紹介し、先行実装されているライブラリにてサンプルコードを実行してみました。

今回はRDF\*/SPARQL\*のほんの触りだけを紹介しています。RDF\*/SPARQL\*の数学的定義や処理プロセスが気にある方は、ぜひ論文や実装を確認してみてください。

今後もセマンティックWeb技術の発展を追っていけたらなと思います。

これにて、<a href="https://qiita.com/advent-calendar/2020/ateam-finergy">Ateam Finergy Inc. Advent Calendar 2020</a>、無事完走です！
ご愛読していただき、ありがとうございました！
良い年末年始をお過ごしください。

# 参考
* https://www.w3.org/RDF/
* https://www.w3.org/TR/sparql11-protocol/
* https://schema.org/
* https://w3c.github.io/rdf-star/
* https://youtu.be/xWgWMxx_qkA