「オントロジー」という言葉を調べると、哲学の説明が出てきたり、セマンティック Web の専門用語が並んだりして、何をしている技術なのか分かりにくいと感じることが多いと思います。
しかし、IT エンジニアの視点で整理すると、オントロジーや知識グラフの世界は、実はそこまで捉えにくいものではありません。
難しく見える理由の多くは、異なるレイヤの話が一気に出てきてしまうことにあります。
この分野は、次のような積み上げ構造として理解するとかなり整理しやすくなります。
- まずものを一意に識別するための仕組みがある
- その上に知識を 3 つの組で表す仕組みがある
- その 3 つの組が集まると知識グラフになる
- さらに、その知識グラフの中で使う概念や関係を体系化したものがオントロジーである
- その定義やルールにもとづいて、新しい知識を導くのが推論である
- そして、その知識グラフを検索するのが SPARQL である
この記事では、この流れを順にたどりながら、説明とチュートリアルをセットで整理します。
全体像
最初に、全体のレイヤ構造を見ておきます。
この図は、オントロジーや知識グラフの技術が、次のような層で成り立っていることを表しています。
| 層 | 役割 |
|---|---|
| 識別子(URI / IRI) | ものや関係に一意な名前を付ける |
| RDF | 知識をトリプルで表現する |
| Turtle / JSON-LD / RDF/XML | RDF をファイルとして書く形式 |
| 知識グラフ(RDF Graph) | RDF トリプルの集合として表された知識ネットワーク |
| オントロジー(RDFS / OWL) | クラス、階層、性質、制約などを定義する |
| ルール(RIF / SWRL) | より明示的な「もし〜ならば〜」を書く |
| 推論 | 定義やルールにもとづいて新しい知識を導く |
| クエリ(SPARQL) | 知識グラフを検索する |
これを下から順番に理解していくと、「今どの話をしているのか」「何がどの上に乗っているのか」が見えやすくなります。
この記事で扱う題材
扱う概念は次の 5 つです。
-
Animal(動物) -
Mammal(哺乳類) -
Cat(猫) -
Dog(犬) -
hasLegCount(足の本数)
そして、基本となる知識は次の通りです。
-
CatはMammalである -
DogはMammalである -
MammalはAnimalに属する -
Catの足の本数は4 -
Dogの足の本数は4
1. 識別子(URI / IRI)
1.1. なぜ識別子が必要なのか
たとえば、システムの中で Cat という文字列を使ったとします。
このとき、次のような曖昧さが出ます。
- 英単語としての
"Cat" - 猫という一般概念
- ある特定の猫
- ほかのシステムが定義した
Cat - 開発者が勝手に決めた
Catというラベル
人間なら文脈で何となく分かりますが、機械が複数システムをまたいで知識を扱うときには、こうした曖昧さが大きな問題になります。
そこで RDF の世界では、概念や関係を単なる文字列ではなく、一意に識別できる名前で表します。
そのために使われるのが URI です。
たとえば、次のような形です。
http://example.org/Cathttp://example.org/Mammalhttp://example.org/Animalhttp://example.org/hasLegCount
ここで重要なのは、これらの URI が Web ページの場所を示しているわけではなく、概念や関係を一意に識別するための ID として使われているという点です。
1.2. URI とは何か
URI は Uniform Resource Identifier の略です。
厳密には URL より広い概念ですが、最初は「URL っぽい見た目の、一意な名前」と捉えて問題ありません。
例えば http://example.org/Cat であれば、「example.org という名前空間の中で定義された Cat」というように読み解くことができます。
この形式を使うと、単なる Cat よりもはるかに衝突しにくくなります。
たとえば別の組織が Cat を定義したとしても、
http://company-a.example/Cathttp://company-b.example/Cat
のように区別することができます。
1.3. なぜ URI 形式なのか
「なぜ、URL のような定義にするのか」と思われる方もいるかもしれませんが、理由は主に 3 つあります。
理由 1: グローバルでも衝突しにくいから
辞書に載っているような name、type、Person、id といった単語は皆が利用します。
URI の形にすると、どこの誰が定義した概念なのかを含めて識別できるため、大規模連携や異種システム統合で有利です。
理由 2: Web と親和性が高いから
RDF やセマンティック Web は、もともと Web 上で知識をつなぐ ことを強く意識して設計されています。
そのため、Web で自然に使われる識別形式を使うと都合がよいのです。
理由 3: 識別子としても、参照先としても使えるから
URI は単なる名前としても使えますし、場合によってはその URI にアクセスして、その概念の説明を取得することもできます。
ただし、「RDF で URI を使うとき、必ずしもアクセス可能な Web ページである必要はありません。」
実際のページがなくても、識別子として使えます。
1.4. IRI とは何か
IRI は Internationalized Resource Identifier の略で、URI を国際化したものです。
URI は ASCII 中心ですが、IRI ではより広い文字を扱えます。
実務では細かく意識しなくても進められることが多いですが、「国際化対応した識別子」と理解しておけば十分です。
1.5. チュートリアル:URI を作る
Python の rdflib では Namespace を使うと URI を簡単に組み立てられます。
from rdflib import Namespace
EX = Namespace("http://example.org/")
print(EX.Cat)
print(EX.Dog)
print(EX.Mammal)
print(EX.hasLegCount)
出力例:
http://example.org/Cat
http://example.org/Dog
http://example.org/Mammal
http://example.org/hasLegCount
ここで EX.Cat は、単なる "Cat" という文字列ではなく、http://example.org/Cat という識別子を表しています。
2. RDF
2.1. RDF とは何か
RDF は Resource Description Framework の略です。
名前は難しそうですが、本質はシンプルです。
RDF は、知識を 3 つの組で表現するためのデータモデルです。
形式は次の通りです。
subject predicate object
日本語で言えば
主語 述語 目的語
です。
たとえば Cat isA Mammal は、
-
主語:
Cat -
述語:
isA -
目的語:
Mammal
という 1 つのトリプルです。
同様に、
Dog isA MammalMammal isA AnimalCat hasLegCount 4Dog hasLegCount 4
も、それぞれ独立したトリプルです。
2.2. なぜ RDF が必要なのか
通常の表形式データでも、ある程度の事実は表現できます。
ただし、表形式は「列と行」に強く依存します。
たとえば次の表は作れます。
| animal | leg_count |
|---|---|
| Cat | 4 |
| Dog | 4 |
しかし、この表だけでは
-
CatはMammal -
MammalはAnimal -
hasLegCountは「足の本数」という関係
といった、関係そのものを表しにくいです。
RDF は、すべてをトリプルでそろえることで、関係中心にデータを扱えるようにしています。
2.3. RDF は「文」ではなく「辺」で考える
初学者が混乱しやすい点として、RDF は自然言語の文に似て見えますが、実体はグラフです。
Cat isA Mammal は文章というより、Cat --isA--> Mammal という有向辺です。
つまり、RDF は「文章を保存している」というより、ノード間の関係をエッジで張っていると考える方が理解しやすいです。
2.4. チュートリアル:RDF トリプルを作る
from rdflib import Graph, Namespace, Literal
g = Graph()
EX = Namespace("http://example.org/")
g.add((EX.Cat, EX.isA, EX.Mammal))
g.add((EX.Dog, EX.isA, EX.Mammal))
g.add((EX.Mammal, EX.isA, EX.Animal))
g.add((EX.Cat, EX.hasLegCount, Literal(4)))
g.add((EX.Dog, EX.hasLegCount, Literal(4)))
for s, p, o in g:
print(s, p, o)
http://example.org/Dog http://example.org/isA http://example.org/Mammal
http://example.org/Dog http://example.org/hasLegCount 4
http://example.org/Cat http://example.org/isA http://example.org/Mammal
http://example.org/Cat http://example.org/hasLegCount 4
http://example.org/Mammal http://example.org/isA http://example.org/Animal
順番が前後することがありますが、問題ありません。
RDF グラフは順序つき配列ではなく、集合的な構造として扱われるからです。
3. RDF の保存形式(Turtle / JSON-LD / RDF/XML)
3.1. RDF そのものと保存形式は別物
ここはかなり重要です。
RDF はデータモデルです。
一方で、Turtle や JSON-LD や RDF/XML は、その RDF をファイルにどう書くかの形式です。
つまり、
- RDF = 中身の構造
- Turtle / JSON-LD / RDF/XML = 書き方
です。
これは、たとえば
- オブジェクトモデル
- JSON 表現
- XML 表現
の違いに近いです。
3.2. 主な保存形式
| 形式 | 説明 |
|---|---|
| Turtle | 人間が最も読みやすい形式です。 教材や手書き例ではまずこれが使われます。 |
| JSON-LD | JSON ベースの RDF 表現です。 Web API やフロントエンド系との相性が良いです。 |
| RDF/XML | 古くからある XML ベースの表現です。 やや読みにくいですが、既存資産との互換で使われます。 |
3.3. チュートリアル:Turtle で出力する
print(g.serialize(format="turtle"))
@prefix ns1: <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ns1:Cat ns1:hasLegCount 4 ;
ns1:isA ns1:Mammal .
ns1:Dog ns1:hasLegCount 4 ;
ns1:isA ns1:Mammal .
ns1:Mammal ns1:isA ns1:Animal .
この形になると、「RDF は文章ではなく、関係のまとまりなんだな」という感覚がかなりつかみやすくなります。
3.4. チュートリアル:JSON-LD で出力する
print(g.serialize(format="json-ld", indent=2))
[
{
"@id": "http://example.org/Cat",
"http://example.org/hasLegCount": [
{
"@type": "http://www.w3.org/2001/XMLSchema#integer",
"@value": 4
}
],
"http://example.org/isA": [
{
"@id": "http://example.org/Mammal"
}
]
},
{
"@id": "http://example.org/Mammal",
"http://example.org/isA": [
{
"@id": "http://example.org/Animal"
}
]
},
{
"@id": "http://example.org/Dog",
"http://example.org/hasLegCount": [
{
"@type": "http://www.w3.org/2001/XMLSchema#integer",
"@value": 4
}
],
"http://example.org/isA": [
{
"@id": "http://example.org/Mammal"
}
]
}
]
JSON-LD は少し見慣れないかもしれませんが、JSON に近い形で RDF を表現できます。
実務では、
- Web API
- フロントエンド連携
- 構造化データ埋め込み
などで役立ちます。
4. 知識グラフ(RDF Graph)
4.1. 知識グラフとは何か
知識グラフとは、知識をノードと関係のネットワークとして表したものです。
今回の例では、すでに 5 つのトリプルを作りました。
Cat isA MammalDog isA MammalMammal isA AnimalCat hasLegCount 4Dog hasLegCount 4
これらをグラフとして見ると、次のようになります。
つまり、知識グラフは特別な別物というより、RDF トリプルをグラフとして見たものです。
4.2. なぜ「知識グラフ」という言葉を使うのか
RDF だけでも説明はできますが、「知識グラフ」という言葉は、単なるデータではなく、
- ノード同士がつながっている
- 関係をたどれる
- 推論や検索の土台になる
という性質を意識しやすくするために使われます。
とくに、表形式と比べたときの違いが見えやすいです。
4.3. チュートリアル:見やすい形で表示する
rdflib の生の表示は URI だらけで読みづらいので、人間向けに簡単な整形を入れると理解しやすくなります。
def short(node):
text = str(node)
if text.startswith("http://example.org/"):
return text.split("/")[-1]
return text
for s, p, o in g:
print(f"{short(s)} --{short(p)}--> {short(o)}")
Dog --hasLegCount--> 4
Cat --isA--> Mammal
Mammal --isA--> Animal
Cat --hasLegCount--> 4
Dog --isA--> Mammal
これでかなり読みやすくなります。
5. オントロジー(RDFS / OWL)
5.1. オントロジーとは何か
ここでようやく「オントロジー」の本体に入ります。
知識グラフは、個別の事実を並べるのに向いています。
たとえば、
Cat isA MammalDog hasLegCount 4
のような事実です。
一方、オントロジーは、その知識グラフの中で使う概念や関係を体系的に定義するものです。
たとえば、次のようなことを整理します。
-
Animalというクラスがある -
MammalはAnimalの下位クラスである -
CatはMammalの下位クラスである -
DogはMammalの下位クラスである -
hasLegCountは動物に対して使う属性である -
hasLegCountの値は整数である
つまりオントロジーは、知識グラフの設計図や意味づけと捉えると分かりやすいです。
5.2. RDF だけでは足りない理由
RDF だけでも、トリプルは書けます。
しかし、RDF だけでは
- これはクラスなのか
- これは個体なのか
- これは下位概念関係なのか
- これは属性なのか
- どんな値型を取るのか
といった意味づけが弱いです。
そのために RDFS や OWL が使われます。
6. RDFS
6.1. RDFS とは何か
RDFS は RDF Schema の略です。
RDF の上に、基本的な概念定義の語彙を追加したものです。
RDFS でよく使うものに、次があります。
rdfs:subClassOfrdf:typerdfs:domainrdfs:range
これらを使うことで、
- クラス階層
- インスタンスの型
- プロパティがどんな主語に使えるか
- プロパティがどんな値を取るか
を定義できます。
6.2. rdf:type と rdfs:subClassOf
ここは非常に大事です。
rdf:type
「ある個体が、あるクラスに属する」ことを表します。
tama rdf:type Cat
これは、「tama という個体は Cat クラスに属する」という意味です。
rdfs:subClassOf
「あるクラスが、別のクラスの下位クラスである」ことを表します。
Cat rdfs:subClassOf Mammal
Mammal rdfs:subClassOf Animal
これは、「Cat は Mammal の一種であり、Mammal は Animal の一種である」という意味です。
6.3. 導入例の isA と RDFS の違い
導入では分かりやすさを優先し、Cat isA Mammal のように書いてきました。
これは概念のつながりを直感的に見せるには便利ですが、厳密には、RDFS では次のように分けて考える方が自然です。
-
Animalはクラス -
Mammalはクラス -
Catはクラス -
Dogはクラス Cat rdfs:subClassOf MammalDog rdfs:subClassOf MammalMammal rdfs:subClassOf Animal
さらに個体を出したいなら、
tama rdf:type Catpochi rdf:type Dog
のように書きます。
これは、クラスとインスタンスを区別するという意味でとても重要です。
6.4. チュートリアル:RDFS でクラス階層を書く
from rdflib import Graph, Namespace
from rdflib.namespace import RDF, RDFS, XSD
g2 = Graph()
EX = Namespace("http://example.org/")
g2.bind("ns1", EX)
# g2.bind("ex", EX) も可
# クラス定義
g2.add((EX.Animal, RDF.type, RDFS.Class))
g2.add((EX.Mammal, RDF.type, RDFS.Class))
g2.add((EX.Cat, RDF.type, RDFS.Class))
g2.add((EX.Dog, RDF.type, RDFS.Class))
# クラス階層
g2.add((EX.Mammal, RDFS.subClassOf, EX.Animal))
g2.add((EX.Cat, RDFS.subClassOf, EX.Mammal))
g2.add((EX.Dog, RDFS.subClassOf, EX.Mammal))
# プロパティ定義
g2.add((EX.hasLegCount, RDF.type, RDF.Property))
g2.add((EX.hasLegCount, RDFS.domain, EX.Animal))
g2.add((EX.hasLegCount, RDFS.range, XSD.integer))
print(g2.serialize(format="turtle"))
@prefix ns1: <http://example.org/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ns1:Animal a rdfs:Class .
ns1:Cat a rdfs:Class ;
rdfs:subClassOf ns1:Mammal .
ns1:Dog a rdfs:Class ;
rdfs:subClassOf ns1:Mammal .
ns1:Mammal a rdfs:Class ;
rdfs:subClassOf ns1:Animal .
ns1:hasLegCount a rdf:Property ;
rdfs:domain ns1:Animal ;
rdfs:range xsd:integer .
この時点で、すでに「概念モデル」としてかなり整ってきています。
7. OWL
7.1. OWL とは何か
OWL は Web Ontology Language の略です。
RDFS よりも表現力の高いオントロジー言語です。
RDFS が「基本的なクラス階層や属性定義」だとすると、OWL はさらに次のようなことを表現できます。
- クラス同士が同値である
- クラス同士が排他的である
- あるプロパティが対称である
- あるプロパティが推移的である
- あるクラスを条件で定義する
- 同じ個体だとみなす
- 異なる個体だと明示する
つまり OWL は、もっと厳密に意味を表したいときの言語です。
7.2. RDFS と OWL の違い
一言で言えば、RDFS と OWL の違いは以下のようになります。
- RDFS:基本の語彙
- OWL:より豊かな意味づけと制約表現
RDFS だけでも「階層」は書けます。
ただし、
-
CatとDogは互いに別クラス -
hasAncestorは推移的 -
PersonとOrganizationは排他
といった細かい論理は OWL の方が得意です。
7.3. チュートリアル:OWL でプロパティ特性を書く
たとえば hasAncestor が推移的な関係だとします。
- A hasAncestor B
- B hasAncestor C
であるとき
- A hasAncestor C
となることを導きたいとき、OWL では、こうした性質をプロパティに与えられます。
from rdflib import Graph, Namespace
from rdflib.namespace import RDF, OWL
g3 = Graph()
EX = Namespace("http://example.org/")
g3.bind("ns1", EX)
g3.add((EX.hasAncestor, RDF.type, OWL.TransitiveProperty))
g3.add((EX.tama, EX.hasAncestor, EX.parentCat))
g3.add((EX.parentCat, EX.hasAncestor, EX.grandParentCat))
print(g3.serialize(format="turtle"))
@prefix ns1: <http://example.org/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
ns1:hasAncestor a owl:TransitiveProperty .
ns1:tama ns1:hasAncestor ns1:parentCat .
ns1:parentCat ns1:hasAncestor ns1:grandParentCat .
ここで重要なのは、OWL は「推移的プロパティである」という意味づけを追加していることです。
実際にそこから新しいトリプルを導くには、推論器が必要になります。
8. ルール(SWRL / RIF)
8.1. ルールは何のためにあるのか
RDFS や OWL は、クラスやプロパティの意味づけを行うのに強いです。
一方で、より直接的な
もし条件 A が成り立つなら、結論 B を追加する
という形のルールを書きたいことがあります。
そのために使われるのが、SWRL や RIF のようなルール系の仕組みです。
8.2. SWRL とは何か
SWRL は Semantic Web Rule Language の略です。
書き方のイメージは次のようなものです。
Mammal(?x) -> Animal(?x)
意味は、「もし ?x が Mammal ならば、?x は Animal である」です。
さらに、Animal(?x) ^ hasLegCount(?x, ?n) ^ swrlb:greaterThan(?n, 3) -> FourLeggedAnimal(?x) のように、より複雑な条件も書けます。
SWRLは、オントロジーにルールベースの推論を足したいときに使われます。
8.3. RIF とは何か
RIF は Rule Interchange Format の略です。
これは主に、ルールをシステム間で交換するための標準です。
実務や入門段階では、SWRL の方が話題に出やすいです。
RIF は「ルールの標準交換形式」という位置づけとして捉えるとよいです。
8.4. SWRL と RIF の違い
一言で言えば、SWRL と RIF の違いは以下のようになります。
- SWRL:オントロジーの上でルールを書くための言語
- RIF:ルール交換のための標準フォーマット群
です。
初心者がまず触るのであれば、SWRL の方が分かりやすいです。
8.5. チュートリアル:SWRL はどう触るのか
ここは正直に言うと、Python だけで手軽に始めるより、Protégé で触るのが一番分かりやすいです。
たとえば次のようなクラスを作ります。
AnimalMammalCatDog
そして
Cat SubClassOf MammalDog SubClassOf MammalMammal SubClassOf Animal
を定義します。
その上で、SWRL タブなどで次のようなルールを書きます。
animals:Mammal(?x) -> animals:Animal(?x)
これは、厳密には階層定義と重複する単純例ですが、「SWRL がどういう見た目なのか」を学ぶには十分です。
さらに、個体 tama を Cat として追加すると、推論後に tama が Animal でもあると分かります。
ここで大切なのは、
- オントロジーは概念定義
- SWRL は条件ルール
- 推論器がそれを使って知識を増やす
という役割分担です。
9. 推論
9.1. 推論とは何か
推論とは、書いてある知識から、書いていないが論理的に導ける知識を得ることです。
たとえば、次の知識があるとします。
Cat rdfs:subClassOf MammalMammal rdfs:subClassOf Animal
このとき、人間は自然に
Cat rdfs:subClassOf Animal
と考えます。
また、個体 tama について
tama rdf:type Cat
があるなら、
tama rdf:type Mammaltama rdf:type Animal
も推論できます。
つまり推論は、概念階層やルールにもとづいて、知識グラフを拡張する操作です。
9.2. 最初の RDF 例だけだと「これだけ?」になる理由
RDF トリプルを並べて、SPARQL で検索するだけだと、「ただのグラフデータでは?」という印象を持つかもしれません。しかし、本当の面白さは
- 概念を定義し
- ルールを与え
- 推論によって新しい知識を自動導出する
ところにあります。
つまり、「オントロジーは定義して終わりではなく、そこから知識を増やせるのが重要」ということです。
9.3. チュートリアル:推論前後を比べる
Pythonでは owlrl というライブラリを使うと、RDFS / OWL RL ベースの推論を試しやすいです。
まずは必要なライブラリを事前にインストールしておきます。
pip install rdflib owlrl
コード例:
from rdflib import Graph, Namespace
from rdflib.namespace import RDF, RDFS
from owlrl import DeductiveClosure, RDFS_Semantics
g4 = Graph()
EX = Namespace("http://example.org/")
# prefix登録
g4.bind("ex", EX)
g4.bind("rdf", RDF)
g4.bind("rdfs", RDFS)
# クラス階層
g4.add((EX.Animal, RDF.type, RDFS.Class))
g4.add((EX.Mammal, RDF.type, RDFS.Class))
g4.add((EX.Cat, RDF.type, RDFS.Class))
g4.add((EX.Mammal, RDFS.subClassOf, EX.Animal))
g4.add((EX.Cat, RDFS.subClassOf, EX.Mammal))
# 個体
g4.add((EX.tama, RDF.type, EX.Cat))
def short(node):
return g4.namespace_manager.normalizeUri(node)
print("=== 推論前 ===")
for s, p, o in g4:
print(f"{short(s)} --{short(p)}--> {short(o)}")
DeductiveClosure(RDFS_Semantics).expand(g4)
print("\n=== 推論後 ===")
for s, p, o in g4:
print(f"{short(s)} --{short(p)}--> {short(o)}")
=== 推論前 ===
ex:Cat --rdfs:subClassOf--> ex:Mammal
ex:Animal --rdf:type--> rdfs:Class
ex:Mammal --rdf:type--> rdfs:Class
ex:tama --rdf:type--> ex:Cat
ex:Cat --rdf:type--> rdfs:Class
ex:Mammal --rdfs:subClassOf--> ex:Animal
=== 推論後 ===
rdf:type --rdfs:subPropertyOf--> rdf:type
ex:tama --rdf:type--> ex:Cat
ex:Cat --rdfs:subClassOf--> ex:Cat
rdfs:subPropertyOf --rdf:type--> rdf:Property
rdfs:Class --rdf:type--> rdfs:Resource
ex:Mammal --rdfs:subClassOf--> ex:Mammal
rdfs:subClassOf --rdf:type--> rdf:Property
ex:tama --rdf:type--> ex:Mammal
ex:Cat --rdfs:subClassOf--> ex:Mammal
ex:Mammal --rdfs:subClassOf--> rdfs:Resource
ex:tama --rdf:type--> rdfs:Resource
ex:Cat --rdfs:subClassOf--> rdfs:Resource
ex:Animal --rdfs:subClassOf--> rdfs:Resource
ex:Animal --rdfs:subClassOf--> ex:Animal
rdfs:subClassOf --rdfs:subPropertyOf--> rdfs:subClassOf
ex:Animal --rdf:type--> rdfs:Class
ex:Mammal --rdf:type--> rdfs:Class
rdfs:subPropertyOf --rdfs:subPropertyOf--> rdfs:subPropertyOf
rdf:type --rdf:type--> rdf:Property
ex:Cat --rdf:type--> rdfs:Class
ex:Animal --rdf:type--> rdfs:Resource
ex:Mammal --rdf:type--> rdfs:Resource
ex:tama --rdf:type--> ex:Animal
ex:Cat --rdf:type--> rdfs:Resource
ex:Mammal --rdfs:subClassOf--> ex:Animal
ex:Cat --rdfs:subClassOf--> ex:Animal
推論後には、もともと明示していなかった
tama rdf:type Mammaltama rdf:type AnimalCat rdfs:subClassOf Animal
などが追加されるはずです。
ここで初めて、「オントロジー定義が実際に新しい知識を生んでいる」という感覚が得られます。
10. SPARQL
10.1. SPARQL とは何か
SPARQL は、RDF グラフを検索するための言語です。
SQL が表形式データを検索するのに対して、SPARQL はグラフのパターンを検索します。
たとえば、?x rdf:type Mammalという形に一致するノードを探したいとします。
これは、「Mammal 型の個体を探す」という意味です。
SPARQL ではこれを WHERE の中に書きます。
10.2. SPARQL の本質はパターンマッチ
ここを押さえると、一気に分かりやすくなります。
SPARQL は「文法が難しい検索言語」ではなく、本質的には「この形のトリプルに一致するものを探す」というグラフパターンマッチです。
たとえば、?x rdf:type ?class なら、すべての型付けを取ってきます。
?x rdf:type Animal なら、Animal 型のものだけを取ります。
10.3. チュートリアル:SPARQL で検索する
query = """
PREFIX ex: <http://example.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT ?x
WHERE {
?x rdf:type ex:Animal .
}
"""
results = g4.query(query)
for row in results:
print(str(row.x).split("/")[-1])
もし推論前なら、ex:Animal 型であると明示された個体だけ が出ます。今回のデータでは、そのような個体はまだ存在しないため、結果は空です。
推論後は、tama が ex:Animal 型として導かれるため、tama を取得できるようになります。
ex:tama --rdf:type--> ex:Animal
SPARQL 自体は検索言語であり、推論そのものではない。
しかし、推論済みグラフに対して SPARQL を実行すると、推論によって導かれた事実も検索できるようになる。
10.4. 例:哺乳類の個体を検索する
query2 = """
PREFIX ex: <http://example.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT ?x
WHERE {
?x rdf:type ex:Mammal .
}
"""
results2 = g4.query(query2)
for row in results2:
print(str(row.x).split("/")[-1])
推論後であれば、tama が ex:Mammal 型として導かれるため、tama がヒットします。
ex:tama --rdf:type--> ex:Mammal
11. 永続化(トリプルストア)
11.1. 永続化の必要性
ここまでの例は、すべて Python のメモリ上のグラフでした。
学習用としては十分ですが、実際のシステムではこれだけでは足りません。
本番では、次のような要件が出ます。
- RDF を保存したい
- 複数ユーザーで共有したい
- 大きな知識グラフを扱いたい
- SPARQL エンドポイントを公開したい
- 推論付きで検索したい
そこで使われるのが トリプルストア です。
11.2. トリプルストアとは何か
トリプルストアは、RDF トリプルを保存・検索・運用するためのデータベースです。
代表例としては、
などがあります。
役割は次の通りです。
- RDF の永続化
- SPARQL 検索
- オントロジー管理
- 推論機能の提供
- 複数システム連携
継続的に保存・検索・共有したい場合はトリプルストアを導入すべきですが、Python で少し試す程度であれば rdflib で充分です。
12. LLM との連携
12.1. なぜ LLM と相性がよいのか
LLM は自然言語を扱うのが得意です。
一方、知識グラフやオントロジーは、構造化知識を厳密に扱うのが得意です。
LLM の強み
- 自然文を理解・生成できる
- 曖昧な質問を扱える
- 要約や対話が得意
知識グラフの強み
- 事実関係を明示的に保持できる
- 関係構造を扱える
- 推論の根拠を追いやすい
- 更新しやすい
このため、両者は競合というより補完関係にあります。
12.2. 典型構成
典型的には、次のような流れになります。
たとえば、「tama は動物ですか?」という質問に対して、LLM が背後で tama rdf:type Animal を問い合わせる SPARQL を作り、その結果を自然な日本語で返すイメージです。
ここで、知識グラフ側に推論が入っていれば、tama rdf:type Cat しか明示していなくても tama rdf:type Animal を答えられるようになります。
13. まとめ
「オントロジー」という言葉は難しく見えますが、レイヤに分けて考えるとかなり整理しやすくなります。
全体の流れはこうです。
識別子を作る
→ RDFで知識を書く
→ その集合が知識グラフになる
→ RDFS / OWL で概念や関係を定義する
→ SWRLなどで追加ルールを書く
→ 推論で新しい知識を得る
→ SPARQLで検索する
→ トリプルストアで保存・運用する
この順番で見ると、オントロジーは哲学の話というより、「意味を持つデータを、機械が扱いやすい形で設計する技術」として理解できます。
そして、その本当の面白さは、単にトリプルを書くことではなく、
- 概念を定義し
- ルールを与え
- 推論で知識を増やし
- その結果を検索・活用できる
ところにあります。
補足:用語整理
この記事で使用した主な用語について簡単に整理したものになります。
| 用語 | 説明 |
|---|---|
| 識別子(URI / IRI) | 概念や関係や個体に、一意な名前を付けるための仕組み。 |
| RDF | 知識を 主語-述語-目的語 のトリプルで表すモデル。 |
| RDF 保存形式 | Turtle、JSON-LD、RDF/XML など、RDF の書き方。 |
| 知識グラフ | RDF トリプルが集まって構成される知識ネットワーク。 |
| RDFS | クラス階層や属性の基本定義を与える仕組み。 |
| OWL | より豊かな意味づけや制約を表せるオントロジー言語。 |
| SWRL / RIF | 条件に応じて新しい知識を導くルールを扱う仕組み。 |
| 推論 | 定義やルールにもとづいて、新しい知識を自動で導出すること。 |
| SPARQL | 知識グラフをパターンマッチで検索する言語。 |
| トリプルストア | RDF グラフを保存・検索・共有・推論するための基盤。 |
補足:ソースコード全文
RDF の基本実装(例)
from rdflib import Graph, Namespace, Literal
g = Graph()
EX = Namespace("http://example.org/")
g.add((EX.Cat, EX.isA, EX.Mammal))
g.add((EX.Dog, EX.isA, EX.Mammal))
g.add((EX.Mammal, EX.isA, EX.Animal))
g.add((EX.Cat, EX.hasLegCount, Literal(4)))
g.add((EX.Dog, EX.hasLegCount, Literal(4)))
for s, p, o in g:
print(s, p, o)
RDFS + 推論(例)
from rdflib import Graph, Namespace
from rdflib.namespace import RDF, RDFS
from owlrl import DeductiveClosure, RDFS_Semantics
g = Graph()
EX = Namespace("http://example.org/")
g.add((EX.Animal, RDF.type, RDFS.Class))
g.add((EX.Mammal, RDF.type, RDFS.Class))
g.add((EX.Cat, RDF.type, RDFS.Class))
g.add((EX.Mammal, RDFS.subClassOf, EX.Animal))
g.add((EX.Cat, RDFS.subClassOf, EX.Mammal))
g.add((EX.tama, RDF.type, EX.Cat))
print("=== before ===")
for s, p, o in g:
print(s, p, o)
DeductiveClosure(RDFS_Semantics).expand(g)
print("=== after ===")
for s, p, o in g:
print(s, p, o)
query = """
PREFIX ex: <http://example.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT ?x
WHERE {
?x rdf:type ex:Animal .
}
"""
print("=== query result ===")
for row in g.query(query):
print(row.x)

