Ruby
neo4j
WordNet
sxp
SUMO

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

More than 1 year has passed since last update.

はじめに

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

Kobito.A79jKQ.png

SUMO

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

(subclass Physical Entity)
(partition Physical Object Process)

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

http://www.adampease.org/OP/

使用する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という経路で語義を引くことができるので、これもデータベースに登録する。とりあえずここまで。