Schema.org の語彙で RDFS 推論してみる(3):OWL-RL で推論

本記事では、OWL-RL、SPARQL/RDFLib、Schema.org 語彙定義を利用して検索や推論を行います。

【目次】

[1]はじめに


前回の記事では、Schema.org が提供する RDFS ベースの語彙定義を概観して、Python ライブラリ RDFLib に読み込む方法を見ました。

本記事では、Schema.org の語彙定義を利用した検索や推論を簡単に見ていきます。

なお、本記事では Google Colaboratory + RDFLib で動作を見てきましたので、RDFS 推論には、同じ環境で利用できる OWL-RL を利用します。

[2]語彙定義の一部で試してみる

(1)Schema.org 語彙定義の一部を抜き出す

前回の記事で Schema.org の概念階層を中心に概観し、RDFS ベースの機械可読な定義ファイルもダウンロードできることを見ました。

以降では、これを用いた推論などを見ていきますが、Schema.org の語彙定義には多くの内容が含まれていますので、推論などによって情報がどのように変化するかを具体的に確認したいときは大変です。

そこで動きを確認しやすいように、Schema.org の語彙定義から抜粋した小さな語彙定義を作って見ていく事にします。

ここでは、第1回の記事で取得したデータに現れる概念(タイプ、クラス)について、階層関係(サブクラス関係)に絞って見てみます。

具体的には、以下の概念が出現していました。
  • Article
  • Organization
  • ImageObject
  • BlogPosting
  • WebPage
  • Person

以下のコードは、上記クラスの階層関係(サブクラス関係)に絞って Schema.org の語彙定義ファイルから抜き出したものです。

# Schema.org 語彙定義から抜粋
partial_test_ttl = """
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix schema: <http://schema.org/> .

schema:Thing a rdfs:Class .

schema:CreativeWork rdfs:subClassOf schema:Thing .

schema:Article rdfs:subClassOf schema:CreativeWork.

schema:SocialMediaPosting rdfs:subClassOf schema:Article .

schema:BlogPosting rdfs:subClassOf schema:SocialMediaPosting .

schema:WebPage rdfs:subClassOf schema:CreativeWork .

schema:MediaObject rdfs:subClassOf schema:CreativeWork .

schema:ImageObject rdfs:subClassOf schema:MediaObject .

schema:Person rdfs:subClassOf schema:Thing  .

schema:Organization rdfs:subClassOf schema:Thing .
"""

ここで「A rdfs:subClassOf B」は、「A は B のサブクラス(下位概念)である」という定義です。(B は A の上位概念にもなります。)

具体的には、
  • BlogPosting subClassOf SocialMediaPosting .
  • SocialMediaPosting subClassOf Article .
は、BlogPosting は SocialMediaPosting の下位概念であり、SocialMediaPosting は Article の下位概念である、と定義しています。(つまり Article ⇒ SocialMediaPosting ⇒ BlogPosting と概念階層が定義されます。)

そして上記 subClassOf 定義の最も上位にある Thing は「schema:Thing a rdfs:Class」となっています。Thing は RDFS の仕様で定義されているクラス(rdfs:Class)の要素(インスタンス)です、と言っています。

ここでは、後に http から始まる IRI に展開した JSON-LD のデータとマージしますので、Schema.org の語彙定義ファイルは https 版ではなく http 版を利用します。
このため、Turtle 形式で「schemaorg-current-http」をダウンロードして、該当するクラスから Thing に至る階層構造を定義している部分のみ抜き出しました。
最後に、抜き出した Turtle 形式の語彙定義を変数 partial_test_ttl に格納しています。(これらの詳細は第2回の記事を参照して下さい。)

(参考)

第1回の記事のデータにどのような概念(クラス、タイプ)が含まれているかを知りたいときは、Turtle ファイルで「a」の次に出てくる単語を調べることもできますが、プログラムで確認すると簡単です。

例えば、SPARQL だと以下の検索で抽出できます。

SELECT DISTINCT ?type WHERE { ?node a ?type . }

なお、この元データは JSON-LD ですが、JSON-LD から探す場合は「@type」の値を探すことになります。(記事『Web ページ内の JSON-LD を Python + PyLD で覗いてみる(Schema.org)/[2]Web ページに組み込まれた JSON-LD の例』を参考にしてください。)

今回は触れませんが、Schema.org の語彙定義から欲しい部分を抜き出す処理もプログラムで書くことができます。

(2)語彙定義を追加して可視化してみる

先に用意した語彙定義の一部を、第一回の記事と同じ方法で再取得した Graph に追加してみます。(一部の語彙定義を利用する Graph は変数 g_partial として進めていきます。)

g_partial = SampleGraph() \
    .import_jsonld_from_html( "https://en.wikipedia.org/wiki/JSON-LD" ) \
    .import_jsonld_from_html( "https://technodaifuku.blogspot.com/2022/04/protege.html" ) \
    .graph

print( f"追加前 len(g_partial)={ len(g_partial) }")

g_partial.parse(data=partial_test_ttl)

print( f"追加後 len(g_partial)={ len(g_partial) }")

以下のような結果が表示されると思います。

追加前 len(g_partial)=40
追加後 len(g_partial)=50

語彙定義を追加すると 10 個の知識が増えました。直感的には、これは追加した語彙定義(トリプル)が 10 行ありましたが、この数と一致しています。

実際のトリプルも確認してきます。

print( g_partial.serialize(format='turtle') )

結果は以下のようになります。(追加された情報が増えただけですので読み飛ばして頂いて結構です。可視化ツールへのコピペ用には利用できます。)

@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix schema: <http://schema.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

schema:Thing a rdfs:Class .

schema:BlogPosting rdfs:subClassOf schema:SocialMediaPosting .

schema:MediaObject rdfs:subClassOf schema:CreativeWork .

schema:Person rdfs:subClassOf schema:Thing .

schema:SocialMediaPosting rdfs:subClassOf schema:Article .

schema:WebPage rdfs:subClassOf schema:CreativeWork .

<https://technodaifuku.blogspot.com/2022/04/protege.html> a schema:WebPage .

schema:Article rdfs:subClassOf schema:CreativeWork .

schema:CreativeWork rdfs:subClassOf schema:Thing .

schema:ImageObject rdfs:subClassOf schema:MediaObject .

schema:Organization rdfs:subClassOf schema:Thing .

[] a schema:BlogPosting ;
    schema:author [ a schema:Person ;
            schema:name "テクノ大福" ] ;
    schema:dateModified "2022-07-31T18:20:30+09:00"^^schema:Date ;
    schema:datePublished "2022-04-27T20:55:00+09:00"^^schema:Date ;
    schema:description "本記事では&#12289;デスクトップ版の Protégé のセットアップと&#12289;オントロジーの可視化&#12289;推論などの使い方を簡単に見ていきます&#12290;   &#12304;目次&#12305;  &#65339;1&#65341;はじめに &#65339;2&#65341;Protégé Desktop のセットアップ &#65288;1&#65289;Ubuntu でのインストール手順 &#65288;2&#65289;起動確認 &#65288;3&#65289;起動..." ;
    schema:headline "オントロジーエディタ Protégé を使ってみる" ;
    schema:image [ a schema:ImageObject ;
            schema:height 630 ;
            schema:url <https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiM1XoAmXI7mKT_9hFYwfU1uzlMJYtzZ4Irw_ZJ6kf0wpTj1UQAeYx3nvpQ6LFXJJtMo6XZLxHDxnvoz-y2Yn_fyv8frt4klSRmnTGE0Iz39nxbDqn78NRS4iYyqLx5418EaPxfYyoNjs27hqnY819tjhT75udNmd27GhgZOqwk-6oWnxPX8bQyNwiR/w1200-h630-p-k-no-nu/protegeinit.png> ;
            schema:width 1200 ] ;
    schema:mainEntityOfPage <https://technodaifuku.blogspot.com/2022/04/protege.html> ;
    schema:publisher [ a schema:Organization ;
            schema:logo [ a schema:ImageObject ;
                    schema:height 60 ;
                    schema:url <https://blogger.googleusercontent.com/img/b/U2hvZWJveA/AVvXsEgfMvYAhAbdHksiBA24JKmb2Tav6K0GviwztID3Cq4VpV96HaJfy0viIu8z1SSw_G9n5FQHZWSRao61M3e58ImahqBtr7LiOUS6m_w59IvDYwjmMcbq3fKW4JSbacqkbxTo8B90dWp0Cese92xfLMPe_tg11g/h60/> ;
                    schema:width 206 ] ;
            schema:name "Blogger" ] .

[] a schema:Article ;
    schema:author [ a schema:Organization ;
            schema:name "Contributors to Wikimedia projects" ] ;
    schema:dateModified "2022-06-15T04:04:15Z"^^schema:Date ;
    schema:datePublished "2011-12-30T17:43:17Z"^^schema:Date ;
    schema:headline "a method of encoding Linked Data using JSON" ;
    schema:mainEntity "http://www.wikidata.org/entity/Q6108942" ;
    schema:name "JSON-LD" ;
    schema:publisher [ a schema:Organization ;
            schema:logo [ a schema:ImageObject ;
                    schema:url <https://www.wikimedia.org/static/images/wmf-hor-googpub.png> ] ;
            schema:name "Wikimedia Foundation, Inc." ] ;
    schema:sameAs <http://www.wikidata.org/entity/Q6108942> ;
    schema:url <https://en.wikipedia.org/wiki/JSON-LD> .

これだけ見ると、追加した語彙が加わっただけで、何が嬉しいのかよく分かりませんね(笑)。

そこで、第1回の記事と同様の方法で、この Turtle ファイルを可視化してみます。

上記の Turtle 形式の出力をサイトのテキストボックスに張り付けて「グラフ描画/Draw」をクリックすると、以下の画像が表示されました。


Schema.org 語彙の一部をマージして可視化


拡大表示しないと何が何だかわからないと思いますが、第1回目の画像と少し形が変わってきたと思います。

語彙定義が10個増えたので、線が10本増えただけですが、これによって、Wikipedia 記事の情報とブログ記事の情報に関連性が出てきた(線を辿ればお互いの情報に辿り着ける)感じになってきました。

例えば(拡大しないと見えないと思いますが)、BlogPosting から SocialMediaPosting を通じて Article に繋がっています。逆に Article から BlogPosting へも行けます。

その他、細かくみるといろいろあるのですが、今は繋がりが増えた気がする、という程度で先に進みます。(後の推論を行うとさらに変わります。)

(3)SPARQL で検索してみる


第1回の記事では、Schema.org の語彙定義を利用していなかったため、得られた情報を頼りにアドホックに情報抽出していました。(Article と BlogPosting の関連が不明だったため、個別に検索してマージする形でした。)

Schema.org の語彙定義を利用すると、Article と BlogPosting が関連付けられますので、これを利用した検索が可能になります。

加えて、Schema.org の語彙定義はクラス関係だけではなく、プロパティについても説明があり、これを鑑みた検索条件を考えることもできますが、今回は深入りしません。とはいっても、せっかくなので、少しだけメモ程度に書いておきます。

  • 語彙定義を見ると、name、url、mainEntityOfPage は Thing レベルで利用できるプロパティである(つまりSchema.orgの要素なら意図に合えば何に対しても使える)のに対して、headline は CreativeWork レベルでの利用が意図されているようです。
  • mainEntityOfPage と url については Schema.org らしい?考え方が以下に書かれています。
  • 語彙定義の schema:name の説明には記載が無いように思いますが、語彙定義ファイル内には「schema:name owl:equivalentProperty dcterms:title. 」とあります。
  • クローラ用の構造化データの場合は、Schema.org の語彙定義よりクローラ側のドキュメントを参照して作成されると思いますので、そちらの利用例などが参考になる気もします。

詳細はともかく、以下の SPARQL を考えてみます。意図としては、Article を指定しただけで、タイトルにあたる情報とURLを抽出したいということです。

def test_query_1(g):
    # 問合せ内容
    query_str = """
PREFIX schema: <http://schema.org/>

SELECT DISTINCT
   ?name
   ?headline
   (IF(BOUND(?m_url), ?m_url, ?url) AS ?site_url)
WHERE {
    ?node a schema:Article .
    OPTIONAL {?node schema:name ?name. }
    OPTIONAL {?node schema:headline ?headline. }
    OPTIONAL {?node schema:url ?url. }
    OPTIONAL {?node schema:mainEntityOfPage ?m_url .}
}
"""
    # 問合せを実行して結果を表示
    qres = g.query(query_str)
    for row in qres:
        if row.name is None:
            print(f"{row.headline}\n    url = {row.site_url}")
        else:
            print(f"{row.name}({row.headline})\n    url = {row.site_url}")

これを実行してみます。

test_query_1( g_partial )

結果は以下の通り Wikipedia 記事しかヒットしません。

JSON-LD(a method of encoding Linked Data using JSON)
    url = https://en.wikipedia.org/wiki/JSON-LD

理由は、語彙定義を追加しても(Article と BlogPosting のサブクラス関係は定義されても)、Article の要素であると宣言しているものは Wikipedia 記事しかないからです。(ブログ記事は BlogPosting の要素としか宣言していません。)

しかし、以下のように少し抽出条件を変えると結果が変わります。

具体的には「?node a schema:Article .」を「?node a ?cls . ?cls rdfs:subClassOf* schema:Article .」のように変更してみます。

def test_query_2(g):
    # 問合せ内容
    query_str = """
PREFIX schema: <http://schema.org/>

SELECT  DISTINCT
   ?name
   ?headline
   (IF(BOUND(?m_url), ?m_url, ?url) AS ?site_url)
WHERE {
    ?node a ?cls .
    ?cls rdfs:subClassOf* schema:Article .
    OPTIONAL {?node schema:name ?name. }
    OPTIONAL {?node schema:headline ?headline. }
    OPTIONAL {?node schema:url ?url. }
    OPTIONAL {?node schema:mainEntityOfPage ?m_url .}
}
"""
    # 問合せを実行して結果を表示
    qres = g.query(query_str)
    for row in qres:
        if row.name is None:
            print(f"{row.headline}\n    url = {row.site_url}")
        else:
            print(f"{row.name}({row.headline})\n    url = {row.site_url}")

test_query_2(g_partial)

これを実行するとブログ記事も含まれるようになりました。

JSON-LD(a method of encoding Linked Data using JSON)
    url = https://en.wikipedia.org/wiki/JSON-LD
オントロジーエディタ Protégé を使ってみる
    url = https://technodaifuku.blogspot.com/2022/04/protege.html

何が変わったかというと「?cls rdfs:subClassOf* schema:Article .」により、Article を含めて Article の下位クラスを探すようになったことです。
(ちなみに、rdfs:subClassOf+ とすると、Article を含まない Article の下位クラスを探すようになります。)

このように、語彙定義の内容を把握した上で、語彙定義ファイルをマージして利用すれば、検索の幅が広がることがわかります。

とはいっても、この例はシンプルなので良いのですが、直感的に「Article」を検索したいというレベルにはなっていない気がします。(仕様に沿った検索という感じで、新たな発見を生むような検索ではない、という感じでしょうか。)

この後に書く推論を実行すると、test_query_1 のコードでブログ記事も抽出できるようになります。

[3]OWL-RL で推論

(1)ここで扱う推論とは:RDF(S) entailment

推論にもいろいろありますが、ここでいう推論は、RDF(S)の仕様では「entailment」です。
  • 「entailment」の訳語には「含意」がよく出てきますが、Wikipedia に含意と論理的帰結(伴意)の説明があります。日本語訳は難しい…
    • 論理的帰結(伴意、英: logical consequence, entailment)
    • 論理包含(含意(がんい)、内含、英: implication、IMP)

これまで RDF(S) の説明も割愛しているので、同様に難しいセマンティクスの話は置いといて、雰囲気レベルで話をすすめます。

説明が雑で大変恐縮ですが、ざっくり言えば、RDF/RDFS の仕様では、
  • 「もし X ならば Y である」
といった形のルールがいくつかあり、この X に該当するものがあれば Y を適用する、のように考えることができます。

例えば、以下のルールがあります。
  • X subClassOf Y かつ Y subClassOf Z ならば X subClassOf Z
    • X が Y の下位概念で、かつ Y は Z の下位概念なら、X は Z の下位概念である。

具体的には、Schema.org の語彙定義には、
  • BlogPosting subClassOf SocialMediaPosting .
  • SocialMediaPosting subClassOf Article .
を含んでいましたので、上記ルールにより
  • BlogPosting subClassOf Article .
を導出します。
つまり、BlogPosting は Article の下位概念となります。

また、次のルールもあります。
  • X subClassOf Y かつ z type X なら z type Y
    • X が Y の下位概念で、かつ z が X の要素ならば、z は Y の要素である。

これにより、BlogPosting の要素は、Article の要素でもあることになります

ルールで書くと少々面倒なことを言っていますが、テクノ大福が人間ならば動物でもある、と言うのと似たようなものです。オブジェクト指向の継承と同様の考え方です。

これ以外にも興味深いルールがあるのですが、そこは割愛して次へ進みます。

(参考)

正式な仕様は以下です。

ただ、この仕様は数学に興味がある人以外は読むのがつらいと思います(涙)。

数学的な理解には興味はないけど、RDF、RDFS、OWL での知識表現や推論には興味がある方は、前の記事で取り上げた本が(個人的には)お勧めです。

(2)OWL-RL をインストール

ありがたいことに、先に書いたような推論ルールを実行してくれるツールがあります。

本記事は Google Colaboratory 上で RDFLib を利用してきましたので、今回は OWL-RL という Python のオープンソースライブラリを利用します。(RDFLib ファミリーのパッケージでもあります。)

名前が OWL-RL となっていることから分かる通り、RDF(S) entailment に限ったものではありませんが、RDFS 推論が可能なライブラリです。

使い方はとても簡単です。OWL-RL が用意しているメソッドに Graph を与えると、RDFS のルールを次々と適用して、適用できるルールがなくなると終了します。これにより、明示されていない(ルールによって導かれる)知識が増えることになります。

早速インストールして準備します。インストールは pip で行います。

!pip install owlrl

RDFS の推論実行用に以下の関数を用意しておきます。(作るほどではないか。。。)

from owlrl import DeductiveClosure, RDFS_Semantics

def expand(g:Graph, axiomatic_triples = False):
    # RDFS 推論を実行
    DeductiveClosure(RDFS_Semantics, axiomatic_triples=axiomatic_triples ).expand(g)
    return g

(3)推論を実行して可視化してみる

それでは抜粋版の語彙定義をマージしたグラフ g_partial に対して推論を実行してみます。
  • ここでは冗長な感じになりますが、RDF/RDFS entailment により何が導かれるかを詳しく見れるように、axiomatic_triples 引数に True を設定して実行してみます。(axiomatic_triples 引数は、実用上はデフォルトの通り False で利用したので問題無いと思います。)

print( f"推論前 len(g_partial)={ len(g_partial) }")

# 推論実行
expand(g_partial, axiomatic_triples = True)

print( f"推論後 len(g_partial)={ len(g_partial) }")

これを実行すると以下のように表示されます。

推論前 len(g_partial)=50
推論後 len(g_partial)=350

知識が 50 から 350 に増えました。

Graph がどのような内容になったかは以下のコマンドで確認できます。

print(g_partial.serialize(format='turtle'))

とはいっても、ブログにこの実行結果を掲載するには長すぎるので部分的に見ておきます。

まず、先に追加した Schema.org のクラス定義部分の変化を確認します。

schema:Thing a rdfs:Class,
        rdfs:Resource ;
    rdfs:subClassOf schema:Thing,
        rdfs:Resource .

schema:CreativeWork a rdfs:Class,
        rdfs:Resource ;
    rdfs:subClassOf schema:CreativeWork,
        schema:Thing,
        rdfs:Resource .

schema:Article a rdfs:Class,
        rdfs:Resource ;
    rdfs:subClassOf schema:Article,
        schema:CreativeWork,
        schema:Thing,
        rdfs:Resource .

schema:SocialMediaPosting a rdfs:Class,
        rdfs:Resource ;
    rdfs:subClassOf schema:Article,
        schema:CreativeWork,
        schema:SocialMediaPosting,
        schema:Thing,
        rdfs:Resource .

schema:BlogPosting a rdfs:Class,
        rdfs:Resource ;
    rdfs:subClassOf schema:Article,
        schema:BlogPosting,
        schema:CreativeWork,
        schema:SocialMediaPosting,
        schema:Thing,
        rdfs:Resource .

schema:WebPage a rdfs:Class,
        rdfs:Resource ;
    rdfs:subClassOf schema:CreativeWork,
        schema:Thing,
        schema:WebPage,
        rdfs:Resource .

schema:MediaObject a rdfs:Class,
        rdfs:Resource ;
    rdfs:subClassOf schema:CreativeWork,
        schema:MediaObject,
        schema:Thing,
        rdfs:Resource .

schema:ImageObject a rdfs:Class,
        rdfs:Resource ;
    rdfs:subClassOf schema:CreativeWork,
        schema:ImageObject,
        schema:MediaObject,
        schema:Thing,
        rdfs:Resource .

schema:Person a rdfs:Class,
        rdfs:Resource ;
    rdfs:subClassOf schema:Person,
        schema:Thing,
        rdfs:Resource .

schema:Organization a rdfs:Class,
        rdfs:Resource ;
    rdfs:subClassOf schema:Organization,
        schema:Thing,
        rdfs:Resource .

細かい説明は割愛しますが、少し補足していきます。

これまでのデータの中には出てこなかった rdfs:Resource というものが登場してます。

RDFS の世界は、全てが rdfs:Resource だ、というモデルになっていますが、それを表すように、各クラスは rdfs:Class のインスタンスであると同時に rdfs:Resource のインスタンスになっています。(「a」(rdf:type)の関係。)

さらに、クラスの継承関係(subClassOf)も、直接の上位クラスだけでなく、上位クラスの全てが列挙されています。

例えば、Article の subClassOf は、その全ての上位概念である Article、CreativeWork、Thing、Resource が列挙されています。ちなみに、RDFSのクラスは自身のサブクラスでもありますので、Article は Article のサブクラスでもあります。

次に、ブログと Wikipedia の記事が所属するクラスについても見ておきます。

[] a schema:Article,
        schema:BlogPosting,
        schema:CreativeWork,
        schema:SocialMediaPosting,
        schema:Thing,
        rdfs:Resource ;
    schema:headline "オントロジーエディタ Protégé を使ってみる" ;
    schema:mainEntityOfPage <https://technodaifuku.blogspot.com/2022/04/protege.html>;
    (その他省略)

[] a schema:Article,
        schema:CreativeWork,
        schema:Thing,
        rdfs:Resource ;
    schema:name "JSON-LD" ;
    schema:headline "a method of encoding Linked Data using JSON" ;
    schema:url <https://en.wikipedia.org/wiki/JSON-LD> ;
    (その他省略)

これを見ると、ブログ議事は、BlogPosting だけでなく、その上位全てのクラスの要素であるとなっています。つまり BlogPosting の要素だけでなく、Article の要素でもあります。

これらの情報を、前と同様の手順で可視化してみます。

OWL-RL で RDFS 推論して可視化

ここまでくると画像を拡大しても何が何だか分からなくなってしまいましたが、それでも個々の繋がり(線)が凄く増えたのがわかります。ちなみに、右端の線が集中しているところが rdfs:Resource です。

(補足)
  • ブログに掲載するには画像が大きくなりすぎたため、上記の図は出力形式を「小PNG(約1112 x 1890 px以内)」で作成しています。ちなみに PNG 形式だと文字がつぶれてしまいますが、SVG で出力すればちゃんと文字も読むことができます。

(4)SPARQL で検索してみる

推論を行ったので、この状態で先に試した SPARQL の例を実行してみます。

test_query_1( g_partial )

すると、以下のように Article を指定するだけで BlogPosting の要素も抽出できました。

JSON-LD(a method of encoding Linked Data using JSON)
    url = https://en.wikipedia.org/wiki/JSON-LD
オントロジーエディタ Protégé を使ってみる
    url = https://technodaifuku.blogspot.com/2022/04/protege.html

ちなみに、test_query_2 を実行しても同じ結果になると思います。

[4]Schema.org 語彙定義を利用した推論や検索

長々と書いてきましたが、まとめを兼ねて、もともとの目的である Schema.org の語彙定義ファイルを使った検索や推論のコードをメモしておきます。

ただ、Turtle の出力を掲載したり、可視化するにはデータが大きすぎるため、ここでは実行手順を書くだけとします。

なお、第1回第2回の記事にあるコードを再利用していますので、適宜参照して下さい。

# 2つのページから JSON-LD を取り込んだ Graph を得る
# =>第1回の記事を参照
g = SampleGraph() \
    .import_jsonld_from_html( "https://en.wikipedia.org/wiki/JSON-LD" ) \
    .import_jsonld_from_html( "https://technodaifuku.blogspot.com/2022/04/protege.html" ) \
    .graph

print( f"語彙マージ前 len(g)={ len(g) }")

# http版のRDFS語彙定義をGraphに追加
# =>第1回の記事を参照
load_schema_org_rdfs(g, "http")

print( f"語彙マージ後 len(g)={ len(g) }")

# 推論実行(axiomatic_triples = False で実行)
expand( g )

print( f"推論後 len(g)={ len(g) }")

実行結果は以下のように表示されると思います。

語彙マージ前 len(g)=40
語彙マージ後 len(g)=16244
推論後 len(g)=31949

SPARQL の例(test_query_1、test_query_2)も同様に動作すると思います。

(参考)

第2回の記事で「[6]参考:JSON-LDをhttps語彙でRDFLibに読み込む例」を書きました。

https 語彙を利用する場合は、上記コードの「SampleGraph」を「SchemaOrgHttpsGraph」へ変更し、「load_schema_org_rdfs」関数の引数を「https」に変更します。

# https から始まる語彙で利用する場合
g_https = SchemaOrgHttpsGraph() \
    .import_jsonld_from_html( "https://en.wikipedia.org/wiki/JSON-LD" ) \
    .import_jsonld_from_html( "https://technodaifuku.blogspot.com/2022/04/protege.html" ) \
    .graph

print( f"語彙マージ前 len(g_https)={ len(g_https) }")

# https版のRDFS語彙定義をGraphに追加
load_schema_org_rdfs(g_https, "https")

print( f"語彙マージ後 len(g_https)={ len(g_https) }")

# 推論実行(axiomatic_triples = False で実行)
expand( g_https )

print( f"推論後 len(g_https)={ len(g_https) }")

これでhttp版と同様に動くと思います。
なお、SPARQL などを試す場合はプリフィクス schema を「https」から始まるものを指定します。

コメント

このブログの人気の投稿

VirtualBoxのスナップショット機能

Google Document AIで画像から表形式データを抽出する(Vision API OCRとの違い)

オントロジーエディタ Protégé を使ってみる