はじめに
SUMO(Suggested Upper Merged Ontology) を neo4j に変換する。
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という経路で語義を引くことができるので、これもデータベースに登録する。とりあえずここまで。