1
3

More than 5 years have passed since last update.

オントロジーのグラフデータベース化

Posted at

はじめに

SUMO(Suggested Upper Merged Ontology) を neo4j に変換する。

Kobito.A79jKQ.png

SUMO

クラス・インスタンス階層や一階述語論理の機構を備えたオントロジー。kifという言語で記述されている。

(subclass Physical Entity)
(partition Physical Object Process)

以下のページの Download から zip ファイルでダウンロードする。

使用するgem

Rubyからneo4jを扱うライブラリとしてneographerやneo4jがあるが、neographerがいまいちよく分からなかったのでneo4jを使う。kifファイルをパースするためにsxpを使う。zipファイルを読むためにrubyzipを使う。

sudo gem install neo4j
sudo gem install sxp
sudo gem install rubyzip

kifファイルを読む

とりあえずkifファイルを読むモジュールを書く。
kifはS式の一種なので、S式の文字列を読んで配列にしてくれるsxpを使用する。

SXP.read '((subclass Cat Animal) (instance Tama Cat))'
=> [[:subclass, :Cat, :Animal], [:instance, :Tama, :Cat]]

コメント行の削除などの掃除をしてから、SXPに食わせる。

require 'sxp'

module KIF
    module_function
    def parse(file)
        doc = file.read.force_encoding('UTF-8')
        # BOM削除
        doc = doc[1..-1] if doc[0].ord == 65279
        # コメント削除
        _doc = ''
        comment = false
        quote = false
        doc.split(/(\\"|"|;|\n)/).each do |item|
            if comment
                comment = false if item == "\n"
            elsif quote
                quote = false if item == '"'
                _doc += item
            else
                comment = true if item == ';'
                quote = true if item == '"'
                _doc += ' ' if item == '"'  # ComputerInput.kif のバグ対策
                _doc += item unless comment
            end
        end
        # S式
        doc = '(' + _doc + ')'
        sxp = SXP.read(doc)
        return sxp
    end
end

zipファイルから読み込む

zipファイルを展開してから1ファイルずつ処理してもいいのだが、かさ張るのでzipファイルのまま読むことにする。

require 'zip'

class KIF::Archive
    def initialize(path)
        @path = path
    end
    def each
        Zip::File.open(@path) do |zip|
            zip.glob('sumo-master/*.kif').each do |entry|
                yield entry
            end
        end
    end
end

kifファイルのうち、WorldAirports.kif などは項目数は多いがほとんど使わないので除外する。

sumo = KIF::Archive.new('sumo-master.zip')
sumo.each do |entry|
    next if entry.name =~ /WorldAirports/
    sxp = entry.get_input_stream{|f| KIF.parse(f) }
end

neo4j に書き込む

neo4jに書き込むための writer クラスを定義。

require 'neo4j-core'

class Writer
    def create_index
        Neo4j::Label.create('class').create_index(:name)
        Neo4j::Label.create('instance').create_index(:name)
    end
    def write(sxp)
        @nodes ||= Hash.new
        sxp.each do |axiom|
            next if axiom.any?{|item| item.is_a? Array }
            predicate, *arguments = *axiom
            case predicate
            when :subclass
                target, source = *arguments
                source_label = 'class'
                target_label = 'class'
            when :instance
                target, source = *arguments
                source_label = 'class'
                target_label = 'instance'
            when :subrelation, :subAttribute
                target, source = *arguments
                source_label = 'instance'
                target_label = 'instance'
            when :domain, :domainSubclass
                source, order, target = *arguments
                source_label = 'instance'
                target_label = 'class'
            when :range, :rangeSubclass
                source, target = *arguments
                source_label = 'instance'
                target_label = 'class'
            end
            next unless source
            @nodes[source] ||= Neo4j::Node.create({name: source}, source_label)
            @nodes[target] ||= Neo4j::Node.create({name: target}, target_label)
            relation = @nodes[source].create_rel(predicate, @nodes[target])
            relation[:order] = order if order
        end
    end
end

create_indexはノードのname属性をインデックスにする設定のためのメソッド。writeがS式を受け取ってデータベースに書き込むメソッド。ここでは、kifから以下の式のみを取り出してデータベースに書き込んでいる。

(subclass target source)
(instance target source)
(subrelation target source)
(subAttribute target source)
(domain source order target)
(domainSubclass source order target)
(range source target)
(rangeSubclass source target)

domainというのは定義域のことで、要するに項の型を指定している。

(domain instance 1 Entity)
(domain instance 2 Class)

rangeというのは値域のことで、関数タイプの述語に対して返り値の型を指定する。

(range whereFn Region)

グラフデータベースは要素と要素を関係で結ぶので、基本的に(関係 要素 要素)という2項述語しか表現できない。domainとdomainSubclassは3項述語だが、order項をリレーションの属性とすることで表現している。

実行

WorldAirports.kif などは項目数ばかり多くてほとんど使わないので除外し、他のものをデータベースに書き込む。

sumo = KIF::Archive.new('sumo-master.zip')
session = Neo4j::Session.open(:server_db, 'http://localhost:7474', basic_auth: {username: 'myname', password: 'mypassword'})
writer = Writer.new
writer.create_index
sumo.each do |entry|
    STDERR.puts entry.name
    next unless entry.name !~ /WorldAirports/
    sxp = entry.get_input_stream{|f| KIF.parse(f) }
    writer.write sxp
end

ToDo

WordNetにはSUMOへのリンクが収録されており、日本語->WordNet->SUMOという経路で語義を引くことができるので、これもデータベースに登録する。とりあえずここまで。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3