オントロジーは、ある領域にある「モノの種類」と「モノ同士の関係」を、機械が読める形で定義した語彙の体系です。W3C標準のRDF・RDFS・OWLで書けて、小さな語彙ならテキストファイル1枚に収まります。この記事では、RDF・RDFS・OWLがそれぞれ何を担うのかを先に整理し、Turtleで語彙を書き、SPARQLで引き、推論器owlrlで知識を増やし、SHACLで誤データを止めるところまでをPythonで通します。実行結果は2026年10月16日に Python 3.14.6・rdflib 7.6.0・pySHACL 0.40.1・owlrl 7.6.2 で取ったものです。
まとめ:RDFは器、RDFSとOWLは型、SHACLは門番
- 役割の分担:RDFはトリプル(主語・述語・目的語)でデータを書く枠組み、RDFSはクラス階層と定義域・値域を足す語彙、OWLはクラスの排他や性質の特性まで書ける言語です。SPARQLは問い合わせ、SHACLは検証を担います
- 型を直接聞くと0件:下位クラスで宣言した個体は、推論しないと上位クラスでは引けません。SPARQLのパスでたどるか、推論器でトリプルを増やします(実測で21件→59件)
- rdfs:domainは制約ではない:誤った主語にプロパティを付けると、エラーにならず「取引先」の型が足されます。OWL 2 入門も「定義域や値域の宣言は知識への制約ではない」と明記しています
- 誤データはSHACLで止める:必須項目と書式はSHACLに書きます。ただし pyshacl に
-i rdfsを付けると、推論が先に型を足して違反が消えます - 採否は組織側で決まる:語彙の最終決定者が決まっていない組織では、技術より先に調整で止まります
オントロジーの土台になるRDFのトリプルとIRI
RDF(Resource Description Framework)では、データの最小単位を主語・述語・目的語の三つ組(トリプル)で表します。現行の勧告はRDF 1.1で、次版のRDF 1.2 Concepts and Abstract Data Modelは2026年4月7日付の勧告候補(Candidate Recommendation Snapshot)です。問い合わせ言語のSPARQL 1.2も2026年10月11日付の作業草案の段階で、実装で使うのは引き続き1.1系です。
リレーショナルデータベースが「顧客テーブルの1行」でデータを持つのに対し、RDFは「株式会社サンプルは法人取引先である」「株式会社サンプルは契約K2026-001を持つ」と事実を1文ずつ並べます。列を足すたびにテーブル定義を変える必要はなく、述語を1つ足せば表現が増えます。
述語には必ずIRI(国際化リソース識別子)を、主語にはIRIか空白ノードを使います。https://example.com/ontology#Customer のような一意の識別子を割り当てると、部署ごとに「顧客」「取引先」「クライアント」と揺れていた語を、識別子のレベルで1つにまとめられます。
RDF・RDFS・OWLの違いを担当範囲で比較する
「RDFとOWLのどちらを使うか」という問いは成り立ちません。OWLの語彙はRDFのトリプルとして書け(OWL 2で全ツールが必ず扱える構文はRDF/XML)、RDFSの語彙(rdfs:subClassOf など)もOWLの中でそのまま使います。違いは、どこまでの意味を書けるかです。
| 規格 | 担当 | 代表的な語彙 | W3Cの段階 |
|---|---|---|---|
| RDF | データの形 | rdf:type | 1.1勧告・1.2は勧告候補 |
| RDFS | 階層と定義域・値域 | rdfs:subClassOf / rdfs:domain | 1.1勧告(2014年2月25日) |
| OWL 2 | 排他・同一性・性質の特性 | owl:disjointWith / owl:FunctionalProperty | 勧告(第2版 2012年12月11日) |
| SPARQL | 問い合わせ | SELECT / プロパティパス | 1.1勧告・1.2は作業草案 |
| SHACL | 入力の検証 | sh:NodeShape / sh:minCount | 勧告(2017年7月20日)・1.2は作業草案(2026年10月14日) |
RDFSで書けるのはクラス階層と定義域・値域まで
RDF Schema 1.1(2014年2月25日勧告)は、クラスの上下関係(rdfs:subClassOf)、プロパティの上下関係(rdfs:subPropertyOf)、プロパティの主語側のクラス(rdfs:domain)と目的語側のクラス(rdfs:range)を書く語彙です。分類の木を作り、プロパティの向きを宣言するところまでならRDFSで足ります。
RDFSでは「法人取引先と個人取引先は重ならない」「主担当者は1人だけ」といった排他や個数を書けません。誤データを推論で見つける手段が、RDFSにはほとんどありません。
OWL 2で増えるのは排他・同一性・性質の特性とプロファイル
OWL 2は、クラスの排他(owl:disjointWith)、個体の同一性(owl:sameAs)、値を1つしか持たない性質(owl:FunctionalProperty)、推移や逆関係などを書けます。OWL 2 Profilesは、推論の計算量を抑えた3つの部分言語を定めています。
| プロファイル | 想定する規模 | 向く使い方 |
|---|---|---|
| OWL 2 EL | クラス・プロパティが非常に多い | 医療用語のような大語彙の分類 |
| OWL 2 QL | インスタンスが大量 | 既存RDBの上で問い合わせに答える |
| OWL 2 RL | ルールで回せる範囲 | ルールエンジンでのスケールする推論 |
後で使う推論器owlrlは、このうちOWL 2 RLと基本的なRDFS推論を実装したライブラリです。Pythonで推論を試すなら、まずRLの範囲で考えると手元の結果と仕様が対応します。
Turtleで最小のオントロジーを書きrdflibで読み込む
題材は受託開発でよく出る「取引先と契約」です。Turtleは人が読み書きしやすいRDFの記法で、拡張子は .ttl です。次の内容を ontology.ttl として保存します。前半が語彙(クラスとプロパティ)、後半が個体のデータです。
# ontology.ttl
@prefix ex: <https://example.com/ontology#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:Ontology a owl:Ontology .
ex:Customer a owl:Class ;
rdfs:label "取引先" .
ex:Corporate a owl:Class ;
rdfs:subClassOf ex:Customer ;
rdfs:label "法人取引先" .
ex:Contract a owl:Class ;
rdfs:label "契約" .
ex:hasContract a owl:ObjectProperty ;
rdfs:domain ex:Customer ;
rdfs:range ex:Contract ;
rdfs:label "契約を持つ" .
ex:contractNumber a owl:DatatypeProperty ;
rdfs:domain ex:Contract ;
rdfs:range xsd:string ;
rdfs:label "契約番号" .
ex:c001 a ex:Corporate ;
rdfs:label "株式会社サンプル" ;
ex:hasContract ex:k2026001 .
ex:k2026001 a ex:Contract ;
ex:contractNumber "K2026-001" .
a は rdf:type の略記で「〜である」を表します。セミコロンは同じ主語の続き、ピリオドは文の終わりです。rdfs:subClassOf の1行で、法人取引先を取引先の下位に置きました。
読み込みにはPythonのRDFLibを使います。PyPIの最新は7.6.0(2026年2月13日公開・Python 3.8.1以上)です。
pip install rdflib
python -c "from rdflib import Graph; g=Graph(); g.parse('ontology.ttl'); print(len(g))"
21
21と出れば読み込みは成功です。構文エラーのメッセージには行番号が出るので、その行の終端のピリオドとセミコロン、prefixの綴りを確認します。
Turtleは保存形式の1つにすぎず、同じグラフはJSON-LDやRDF/XML、1行1トリプルのN-Triplesでも書けます。rdflibなら g.serialize(format="json-ld") のように形式名を変えるだけで出力でき、このファイルではN-Triplesが21行、RDF/XMLが44行、JSON-LDが115行になりました。Webページへの埋め込みはJSON-LD、古い推論ツールとの受け渡しはRDF/XML、差分管理と手書きはTurtleが扱いやすい形式です。
SPARQLで引くと型を直接聞いた問い合わせは0件になる
語彙を書いただけでは、意味が引けるかどうか分かりません。SPARQL 1.1で、取引先の名前を3通りの書き方で問い合わせます。SPARQLの文法とSQLとの違いはSPARQLとは?RDFを検索するクエリ言語の書き方とSQLとの違いを実行例で解説【2026年版】で実行例つきで整理しています。
# query.py
from rdflib import Graph
g = Graph()
g.parse("ontology.ttl", format="turtle")
PREFIX = """
PREFIX ex: <https://example.com/ontology#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
"""
patterns = {
"型を直接聞く": "?s a ex:Customer .",
"下位クラスを1段だけたどる": "?s a ?cls . ?cls rdfs:subClassOf ex:Customer .",
"プロパティパスで0段以上たどる": "?s a/rdfs:subClassOf* ex:Customer .",
}
for label, where in patterns.items():
q = PREFIX + "SELECT ?name WHERE { " + where + " ?s rdfs:label ?name . }"
print(label, [str(r.name) for r in g.query(q)])
型を直接聞く []
下位クラスを1段だけたどる ['株式会社サンプル']
プロパティパスで0段以上たどる ['株式会社サンプル']
個体 ex:c001 は「法人取引先」としか書いていないので、「取引先」を直接聞くと0件です。1段だけたどる書き方は今の階層では引けますが、階層が2段になると漏れます。a/rdfs:subClassOf* のパスなら何段でもたどれるので、下位クラスを足しても問い合わせを書き直さずに済みます。
推論器owlrlで知識を増やすと21件のトリプルが59件になる
問い合わせ側でパスを書く代わりに、推論器で「取引先である」というトリプルを実際に足す方法があります。owlrl(最新7.6.2・2026年7月8日公開)は、RDFLibのグラフに対して前向き連鎖でRDFSとOWL 2 RLの推論を実行します。
# infer.py
from rdflib import Graph
import owlrl
Q = """
PREFIX ex: <https://example.com/ontology#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name WHERE { ?s a ex:Customer ; rdfs:label ?name . }
"""
g = Graph()
g.parse("ontology.ttl", format="turtle")
print("推論前:", len(g), "件 / Customerとして引ける個体:", [str(r.name) for r in g.query(Q)])
owlrl.DeductiveClosure(owlrl.RDFS_Semantics).expand(g)
print("RDFS推論後:", len(g), "件 / Customerとして引ける個体:", [str(r.name) for r in g.query(Q)])
pip install owlrl
python infer.py
推論前: 21 件 / Customerとして引ける個体: []
RDFS推論後: 59 件 / Customerとして引ける個体: ['株式会社サンプル']
推論後は、型を直接聞く素朴な問い合わせでも株式会社サンプルが返ります。推論前の ontology.ttl(21件)に、RDFSの代わりに OWLRL_Semantics を当てると172件まで増えました。増えた151件のうち76件は「各個体は自分自身と owl:sameAs」、36件はデータ型の宣言といった自明なトリプルです。推論結果を書き戻すと、データ量と問い合わせ結果の読みにくさが一緒に増えます。問い合わせごとにパスで書くか、推論済みのグラフを別名で持つかは、更新頻度で決めます。
開世界仮説では書かれていない事実は偽ではなく未知になる
OWLの推論は開世界仮説(Open World Assumption)に立ちます。契約番号を書いていない契約があっても、「番号が無い」とは判定されず「番号は分からない」として扱われます。もう1つの前提が、名前が違えば別物とは限らない、という扱いです。値を1つしか持てない owl:FunctionalProperty に2人を入れると、OWLは矛盾とは見なさず、2人が同一人物だと推論します。
# bad.ttl
@prefix ex: <https://example.com/ontology#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
# 誤データ1:商品にhasContractを付け、契約番号も書いていない
ex:p100 a ex:Product ;
rdfs:label "保守パック" ;
ex:hasContract ex:k2026002 .
ex:k2026002 a ex:Contract .
# 誤データ2:1つしか持てない主担当者に2人を入れた
ex:primaryContact a owl:ObjectProperty , owl:FunctionalProperty .
ex:c001 ex:primaryContact ex:tanaka , ex:suzuki .
このファイルを足してOWL RLで推論すると、ex:tanaka owl:sameAs ex:suzuki が導かれます。データ入力の誤りが、エラーではなく「新しい知識」として静かに増える点が、RDBの一意制約に慣れた開発者がつまずく所です。
rdfs:domainは制約ではなく推論で型を足す
上の bad.ttl では、商品 ex:p100 に ex:hasContract を付けました。ex:hasContract の定義域は取引先なので、RDBの外部キーのように「商品には付けられない」と弾かれることを期待しがちです。実際は逆の結果になります。
誤った主語に「取引先」の型が足される実測
# pitfall.py
from rdflib import Graph, URIRef, RDF, OWL
import owlrl
EX = "https://example.com/ontology#"
g = Graph()
g.parse("ontology.ttl")
g.parse("bad.ttl")
owlrl.DeductiveClosure(owlrl.OWLRL_Semantics).expand(g)
p100 = URIRef(EX + "p100")
print("p100の型:", sorted(o.split("#")[1] for o in g.objects(p100, RDF.type) if o.startswith(EX)))
print("主担当者は同一人物と推論:", (URIRef(EX + "tanaka"), OWL.sameAs, URIRef(EX + "suzuki")) in g)
p100の型: ['Customer', 'Product']
主担当者は同一人物と推論: True
保守パックは「商品であり、取引先でもある」と推論されました。RDF 1.1 Semanticsの推論規則rdfs2(OWL 2 RLでは同じ働きの規則prp-dom)が、定義域から主語の型を導くためです。OWL 2 Primer(入門)も「a domain (or range) statement is not a constraint on the knowledge, but allows a reasoner to infer further knowledge」と書いています。
OWLで誤りとして検出させるには、商品と取引先が重ならないことを ex:Product owl:disjointWith ex:Customer と明示します。この1行を足して推論すると、owlrlは矛盾をエラーとしてグラフに書き込む仕組みです。エラーの文面は述語 http://www.daml.org/2002/03/agents/agent-ont#error の目的語に入るので、次のように取り出します。
# disjoint.py
from rdflib import Graph, URIRef
import owlrl
g = Graph()
g.parse("ontology.ttl")
g.parse("bad.ttl")
g.parse(data="""
@prefix ex: <https://example.com/ontology#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
ex:Product owl:disjointWith ex:Customer .
""", format="turtle")
owlrl.DeductiveClosure(owlrl.OWLRL_Semantics).expand(g)
ERROR = URIRef("http://www.daml.org/2002/03/agents/agent-ont#error")
for msg in g.objects(None, ERROR):
print(msg)
Disjoint classes https://example.com/ontology#Product and https://example.com/ontology#Customer have a common individual https://example.com/ontology#p100
つまりOWLで誤データを捕まえるには、起こりうる誤りを排他の公理として先回りして書く必要があります。契約番号の書き忘れのように「無いこと」を捕まえる手段は、開世界のOWLにはありません。
SHACLで止めるときはpyshaclの推論オプションに注意する
入力の検証はSHACL(Shapes Constraint Language)に書きます。検証条件のshapes graphと検査対象のdata graphを突き合わせ、条件を満たさないノードを報告する仕組みです。次の内容を shapes.ttl として保存します。
# shapes.ttl
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <https://example.com/ontology#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:ContractShape a sh:NodeShape ;
sh:targetClass ex:Contract ;
sh:property [
sh:path ex:contractNumber ;
sh:datatype xsd:string ;
sh:minCount 1 ;
sh:pattern "^K[0-9]{4}-[0-9]{3}$" ;
sh:message "契約番号が無いか、K+年4桁+連番3桁の形式になっていません" ;
] .
ex:HasContractSubjectShape a sh:NodeShape ;
sh:targetSubjectsOf ex:hasContract ;
sh:class ex:Customer ;
sh:message "hasContractを持てるのは取引先だけです" .
pySHACL(最新0.40.1・2026年7月28日公開・Python 3.9以上)で、正常データと誤データを足したファイルの両方を検証します。次の出力は各違反のMessage行だけを残した抜粋で、実際には先頭にValidation Report、違反ごとにSource ShapeやFocus Nodeなども出ます。
pip install pyshacl
cat ontology.ttl bad.ttl > merged.ttl
pyshacl -s shapes.ttl -f human ontology.ttl
Conforms: True
pyshacl -s shapes.ttl -f human merged.ttl
Conforms: False
Results (2):
Message: hasContractを持てるのは取引先だけです
Message: 契約番号が無いか、K+年4桁+連番3桁の形式になっていません
pyshacl -s shapes.ttl -i rdfs -f human merged.ttl
Conforms: False
Results (1):
Message: 契約番号が無いか、K+年4桁+連番3桁の形式になっていません
推論なしでは、商品に付いたhasContractと契約番号の欠落の2件が報告されます。-i rdfs を付けると、検証の前にRDFS推論が走り、定義域から保守パックに「取引先」の型が足されるので、1件目の違反が消えるわけです。語彙の定義(rdfs:domain)をデータと同じファイルに置いても、-e ontology.ttl で別に渡しても、pyshaclは語彙をデータに混ぜてから推論するので結果は同じで、-i owlrl でも1件に減ります。門番として使うSHACLに推論オプションを付けないことが判断の基準です。推論が必要な問い合わせ用のグラフと、検証用のグラフは分けて持ちます。
| 観点 | OWL推論(owlrl) | SHACL検証(pySHACL) |
|---|---|---|
| 働く向き | 事実を導いて足す | 違反を見つけて報告する |
| 前提 | 開世界・名前の一意性なし | 書かれたデータだけで判定 |
| 契約番号の欠落 | 検出しない | minCountで検出 |
| 商品にhasContract | 取引先の型を足す | sh:classで検出 |
| 主担当者が2人 | 同一人物と推論 | sh:maxCount 1を書けば検出 |
ナレッジグラフ・セマンティックレイヤー・SKOSとの違い
ナレッジグラフは、実体と関係をグラフで保持したデータそのものです。オントロジーはその中で使う語彙とルールの定義で、データベースとスキーマの関係に近い対応です。オントロジーを持たないグラフDBも運用できます。プロパティグラフ型の代表であるNeo4jとは?グラフデータベースの構造とCypher・エディション選定を実装視点で解説【2026年版】はRDFではなくプロパティグラフモデルとCypherを使います。
セマンティックレイヤーは、BIやデータ基盤の上で売上や粗利の計算式を共有する層で、クラス階層の推論や制約検査は守備範囲に入りません。集計の定義を揃えるだけならセマンティックレイヤーとは?意味・仕組みから会話型AI時代の導入判断まで解説の範囲で十分。分類語彙を上位・下位・関連で緩く整理したいだけなら、OWLではなくSKOSのほうが合意の負荷が下がります。ベクトル検索が意味の近さを距離で近似するのに対し、オントロジーは関係を明示的に書き下す点が違い、検索方式の比較はベクトル検索とセマンティック検索の違い:包含関係・仕組み・pgvectorでの実装と使い分けで扱っています。
製品に組み込んだ例は、Microsoft FabricのFabricオントロジーとは?Fabric IQの作成手順と新エクスペリエンスの変更点【2026年9月】です。手元のPythonではなくHTTPで叩けるSPARQLエンドポイントが要る段階では、Apache Jena Fusekiとは?Jena 6の変更点とインストール・Docker運用を実装目線で解説のような専用サーバーへ移します。GUIで語彙を編集するならスタンフォード大学のProtégéが使えます。
受託開発でオントロジーを採る条件と見送る場面
採用する条件は名寄せの争点が3部署以上にまたがるかで見る
最初に見るのは名寄せの争点の規模です。同じ実体を指す語が部署ごとに割れ、その争点が3部署以上にまたがっているなら採用を検討します。2部署なら対訳表とビューで片づく規模で、オントロジーを持ち出すと管理対象が増えるだけです。
この条件を満たしたうえで、参照する側にAIエージェントなどの機械が入るか、語彙の変更が今後も続くかを確かめます。人間だけが読み、定義が固定でよいなら、RDBのスキーマ設計で足ります。
着手は1つの業務プロセスに絞ります。まず上の ontology.ttl のような数クラスの規模で、SPARQLで引けることとSHACLで通ることを確かめ、そのプロセスに登場する20〜30語まで広げ、引けない語は定義から外します。費用は語彙の定義より、既存システムからRDFへ変換する経路と名寄せの精度確認に寄ります。データ分析基盤構築・MLOps構築支援では、こうした変換経路を含むデータ基盤の設計から相談を受けています。
見送る場面は語彙の決定者が社内に決まっていないとき
語彙の最終決定者が決まっていない組織では、定義作業が部署間の調整で止まります。この段階でOWLのクラス設計に入るより、SKOSの skos:prefLabel(正式名)と skos:altLabel(別名)で「顧客・取引先・クライアント」を1つの概念に束ねる用語集を作り、責任者を決めるほうが先です。用語集の段階なら、排他や個数の公理で合意を取る必要がありません。
RAGの検索精度だけが目的の場合も見送ります。誤答の原因が語彙の不一致ではなく、文書の分割や埋め込みの選び方にあるなら、グラフを足しても改善しません。テーブル数が少なく名寄せの争点も無い環境では、正規化されたRDBのままで支障は出ません。
よくある質問
SHACLとOWLの読み方は?
SHACLは英単語のshackle(手かせ。日本語表記ではシャックル)と同じ音で呼ばれる名前です。仕様に読みの規定はありません。2015年5月にはW3C作業部会のメーリングリストで、印象が良くないとして「DASH(Data Shapes Language)」への改名が提案されましたが、名前はそのまま残りました。OWLは英単語のowl(フクロウ)と同じ読みで、カタカナ表記はアウルとオウルで揺れます。W3CのFAQによれば、Web Ontology Languageの頭文字「WOL」を作業部会が好まず、OWLと名付けました。
新しく語彙を作るならRDFSとOWLのどちらで書くべきですか?
クラス階層と定義域・値域だけで足りるならRDFSで書き始め、排他や個数の公理が必要になった時点でOWLの語彙を足します。OWLの中でRDFSの語彙はそのまま使えるので、書き直しにはなりません。入力の検証が目的なら、どちらを選んでもSHACLを別に用意します。
OWLのプロファイルはEL・QL・RLのどれを選べばよいですか?
クラス数が非常に多い語彙ならEL、大量のインスタンスを既存RDBの上で問い合わせたいならQL、ルールエンジンで推論を回したいならRLです。Pythonのowlrlで試すならRLの範囲になります。迷う段階ではプロファイルを宣言せず、SPARQLのパスで足りるかを先に確かめます。
既存のリレーショナルデータベースから移行する必要はありますか?
移行は必要ありません。W3C勧告のR2RML(2012年9月27日)がRDBのテーブルからRDFへの対応づけを定めており、RDBを正としたまま参照用のRDFグラフを別に生成できます。更新系はRDBに残し、意味づけと横断参照だけをグラフで担うと、既存システムへの影響を抑えられます。
オントロジーを作るとAIエージェントの精度は上がりますか?
効くのは、社内固有の語や関係を前提にした問い合わせで、モデルが一般知識だけでは答えられない領域です。文章要約や翻訳の精度には寄与しません。導入前に、誤答している質問のうち語彙の不一致が原因のものが何割あるかを数えると、投資判断の根拠になります。