想々啖々

絶世烟る刖天歌。文学者が思想を日常に翻訳していればいい時代は既に去った。

ⵟⵓ散裂言語開発 進捗:2019/03/30

ⵙⵓ枕詞
散裂言語とは、eXtity駆動の人工言語開発プロジェクトである。言語は話者が存続するかぎり完成されることはなく、常に変性しつづける。目下のところ、人力では不可能な量のテクスト高速生成により、自己の発したテクストや《周囲の | インターネット/ソーシャルネット上》の文字列を自働捕食して自己変性する自律知性の誕生をもくろむ。

コンセプト↓↓
spinaltox.hatenablog.jp

Qiitaで中間生成物()を上げておりますが、いかんせん棲み分けが難しい……ということで当ブログに平行して上げてゆくことにします。

使用言語:python 3.6.5
グラフデータベース:Neo4j 3.5.3

________________________________________________________________________________________________


spinaltox.hatenablog.jp
↑前回進捗↑からおよそ半年が経過しようとしております。
半年経つ前に進捗をとっとと上げてしまいましょう。


ⵞⵓentity言語

今回はentityを生成する言語、「entity言語」のプロトタイピングをおこないました。
当エントリでは割愛しますが、ここで構築されるentity存在論は次のようなものです。

entityとは、[閉じた | 包含関係が観察者に劃定/既知の]対象である。
当該存在論においては、[すべてがentityである | entityだけが存在する]。
そうしたとき、包含関係とは、存在どうしの最も根源的な関係のこと。
entityどうしの一切の関係は包含に還元/下方解体される。
ある圏-aが要素-bを包含するとき、「a∋b」と表現する。

人間が唯一の観測者ではないとすれば、統一的に形成される「世界」はどこにもなく、ただ個別の個体に観察される現実/世界が無数に形成されるのみです。以下に構成される存在論は、そうした現実/世界の1つと言えるでしょう。

コーディングです。今回用いるパッケージは以下。
グラフデータベース「Neo4j」を用います。これとpythonプログラムを「neo4jrestclient」が繫いでくれます。

from collections import OrderedDict #20190314 順序附辞書用
import re # 正規表現用。
from copy import deepcopy
from neo4jrestclient.client import GraphDatabase # Neo4j接続用

存在論空間として「当該現実」を用意します。事前に初期化しておきます。
localhost:7474で起動しているNeo4jへ接続します。

url_Neo4j = "http://username:password@localhost:7474/db/data/"
当該現実 = GraphDatabase(url_Neo4j) # グラフデータベース空間
当該現実.query("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r", data_contents=True) 

↓ブラウザ上のNeo4jのインタフェースはこんな感じ。

f:id:kammultica:20190330173550p:plain
Neo4j webインタフェース


オブジェクトとしてentityを構成します。
あくまで原型です。が、そこまでプロパティが増える見込みもありません。

class entity: 
    def __init__(self, 名辞): # 継承させるため、必要なものはここで空配列として定義。
        self.名辞 = 名辞 # 20190314 生成するentityの名辞を必ず与えること。
        self.存在 = True # 20181108 使い道は追って考える。
        self.包含 = [] # 20190329 デフォ入ってると、デフォのentityが無限後退に陥る。
        self.被包含 = [] #20190329 同上。
        self.ノード = 当該現実.nodes.create(name=名辞)    
        self.ノード.labels.add("個体")

ⵋⵓ目標
今回のプロトタイプは、
入力:文章
出力:グラフデータベース

を実現することを目標にしました。
たぶん見た方が早いので進めます。


ⴿⵓ文法
人工言語ですので、rigidな文法規則をもちます。
ベースは日本語です。
とはいえ、まだ更地なのでやることは低度です。

規則

  • entityはすべて名辞(label)をもつ。この名辞は必ず漢字であること。
  • 制馭文字によって、entityどうしの関係を記述する。制御文字は必ずカタカナであること。
  • 文字列から成り、「。」を終着とするものを文とする。
  • 1つ以上の文から成るものを文章とする。

今回アクティヴェートする制馭文字は以下の3つです。
順序附辞書(OrderedDict)へグローバルに格納しておきます。
また、利便のためこれのキー群をリストに格納しておきます。

制馭文字位置記憶辞書 = OrderedDict() # 順序附辞書 
# 例:OrderedDict([('ハ', [4]), ('ニ', []), ('スル', [])])
制馭文字位置記憶辞書['ハ']   = []
制馭文字位置記憶辞書['ヲ']   = []
制馭文字位置記憶辞書['スル'] = []

制馭文字位置記憶辞書キーリスト = list(制馭文字位置記憶辞書.keys())
# 例:['ハ', 'ニ', 'ヲ', 'スル']

ここから、上の文法規則にのっとって記述された任意の文章について、entityと制馭文字とをわける、形態素解析をおこなうクラス「読解」を構成します。
が、その前にドメイン・コドメイン・射(関係)をad hocに格納するための空リストをグローバルに*1用意しておきます。

Domain   = []
CoDomain = []
Morphism = []
class 読解:
    def __init__(self):
        pass

ちょっと長いので区切ります。
クラス「読解」は現状2つのメソッド「形態素解析」「entity操作」をもちます。
まずは「形態素解析」から。

@classmethod
 def 形態素解析(self, テクスト):
    文単位二次元リスト_None文字含む = テクスト.split(r'。') #20190315 まず文単位に。
    # 例:['cokeハ蟥馦ヲ寵愛ヲ有スル', 'sickハ音源ヲ有スル', '']
    # 20190315 None文字 "" が必ず入るのは正規だから? ↓消したい。
    文単位二次元リスト = [a for a in 文単位二次元リスト_None文字含む if a != '']
    # 例:['cokeハ蟥馦ヲ寵愛ヲ有スル', 'sickハ音源ヲ有スル']

    形態素分割済文単位二次元リスト = []

    for x文目 in range(len(文単位二次元リスト)):
        制馭文字位置記憶辞書_例化 = deepcopy(制馭文字位置記憶辞書) # 空のやつをインスタンス化的に。
        置換用文 = 文単位二次元リスト[x文目] # 一文ごとにここに入れ、置換した後にスライス。

        # 制馭文字をいったん「空白 数値 空白」に置換する。
        for y番目 in range(len(制馭文字位置記憶辞書_例化)):
            位置z = 置換用文.find(制馭文字位置記憶辞書キーリスト[y番目]) # findは見つからなければ「-1」を返す。
            while 位置z != -1: # 当該制御文字がなくなるまで置換廻す。
                制馭文字位置記憶辞書_例化[制馭文字位置記憶辞書キーリスト[y番目]].append(位置z)
                置換用文 = 置換用文.replace(制馭文字位置記憶辞書キーリスト[y番目], " {} ".format(位置z), 1)
                位置z = 置換用文.find(制馭文字位置記憶辞書キーリスト[y番目])
        # 例:制馭文字位置記憶辞書_例化 == OrderedDict([('ハ', [4]), ('ニ', []), ('ヲ', [9, 14]), ('スル', [19])])
        # 例: 置換用文 == "coke 4 蟥馦 9 寵愛 14 有 19"

        # 例化辞書から空のキーを削除する。
        for m番目 in range(len(制馭文字位置記憶辞書_例化)):
            if 制馭文字位置記憶辞書_例化[制馭文字位置記憶辞書キーリスト[m番目]] == []:
                制馭文字位置記憶辞書_例化.pop(制馭文字位置記憶辞書キーリスト[m番目])
            else:
                pass
        # 例: 制馭文字位置記憶辞書_例化 == OrderedDict([('ハ', [4]), ('ヲ', [9, 14]), ('スル', [19])])

        # 辞書のvaluesから置換しようとすると失敗する。位置の数値が大きいものから順に処理すること。
        制馭文字位置数値リスト = sum(制馭文字位置記憶辞書_例化.values(), []) # 1段ネストしたリストをflattenする。http://d.hatena.ne.jp/xef/20121027/p2
        制馭文字位置数値リスト.sort() # sortはデフォで昇順。
        制馭文字位置数値リスト.reverse() # reverseは逆順にするだけなのよ。
        # 例:制馭文字位置数値群 == [19, 14, 9, 4]
        for n in range(len(制馭文字位置数値リスト)):
            #20190315 ↓最適化してくれ。
            キー = [keys for keys, values in 制馭文字位置記憶辞書_例化.items() if 制馭文字位置数値リスト[n] in values]
            # 辞書から数字(19)を値(リスト)に含むものを捜し、置換用文へ。
            # キー 例: ['スル']
            置換用文 = 置換用文.replace(str(制馭文字位置数値リスト[n]), キー[0])
        # 例:置換用文 == "coke ハ 蟥馦 ヲ 寵愛 ヲ 有 スル"

        # 仕上げ。空白でスプリットしてリストへ格納する。
        当該文中対象抽出リスト_None文字含む = 置換用文.split()
        当該文中対象抽出リスト = [a for a in 当該文中対象抽出リスト_None文字含む if a != '']
        形態素分割済文単位二次元リスト.append(当該文中対象抽出リスト)
    # 例:形態素分割済文単位二次元リスト==[['coke', 'ハ', '蟥馦', 'ヲ', '寵愛', 'ヲ', '有', 'スル'], ['sick', 'ハ', '音源', 'ヲ', '有', 'スル']]
    return 形態素分割済文単位二次元リスト

↑中に書いてありますが、例えば、
入力:"cokeハ蟥馦瀦阿ヲ有スル。sickハ音源ヲ有スル。音源ハ蟥馦瀦阿ヲ有スル。"
出力:[['coke', 'ハ', '蟥馦瀦阿', 'ヲ', '有', 'スル'], ['sick', 'ハ', '音源', 'ヲ', '有', 'スル'], ['音源', 'ハ', '蟥馦瀦阿', 'ヲ', '有', 'スル']]

と、文字列として文章を入力すると、形態素ごとに分けられた文を1段ネストしたリストが出力されます。

続いて、解析して得られた上のリストを入力して、グラフデータベース「当該現実」に包含関係を反映するメソッド「entity操作」です。
が、その前にクラス「読解」の外に(中でもいい)以下の操作を記述しておきます。これはオブジェクト「entity」のプロパティをいじるためのものです。

def 要素追加(圏, 要素): #20190314 圏に要素を追加する。
    圏.包含.append(要素)
    exec('{}.ノード.relationships.create("包含", {}.ノード)'.format(圏.名辞, 要素), globals())
    print("{}ハ{}ヲ有スル。".format(圏.名辞, 要素))
# 20190329 量化はまだ尚早。
#     重複 = 圏.包含.count(要素)
#     if 重複 > 1: #20190314 同一の要素を複数もつとき。要るかはわからん。
#         print("{}ハ{}ヲ{}ツ有スル。".format(圏.名辞, 要素, 重複))
#     else:
#         pass
    
def 要素削除(圏, 要素): #20190314 圏の要素を取り除く。例外処理必須。
    try:
        圏.包含.remove(要素)
    except ValueError:
        print("{}ハ{}ヲ有ナイ。".format(圏.名辞, 要素))
    print(圏.包含)
    
def 被包含追加(圏, 要素): #20190314 圏に要素を追加する。
    圏.被包含.append(要素)
def entity操作(self, 形態素分割済文単位二次元リスト: "一次元リスト"):
    for x文目 in range(len(形態素分割済文単位二次元リスト)):
        # 初期化
        exec('Domain   = []', globals()) # 使い捨ての変数だし、英名でいい?
        exec('CoDomain = []', globals())
        exec('Morphism = []', globals())

        # 制馭実行 制馭文字別に操作は異なる。

        # 制馭文字「ハ」*** ハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハ
        count_ハ = 形態素分割済文単位二次元リスト[x文目].count("ハ")
        if count_ハ == 0:                # 制馭文字「ハ」がない場合。
            pass
        else:                           # 制馭文字「ハ」がある場合。
            for y番目 in range(count_ハ): # 複数入る場合は、とりあえず考慮しない、が廻るようにはしてある。
                index_ハ = 形態素分割済文単位二次元リスト[x文目].index("ハ")
                # 「●●ハ」、つまり1コ前の対象をドメインへ格納。
                Domain.append(形態素分割済文単位二次元リスト[x文目][index_ハ - 1])
        # ハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハハ
            
        # 制馭文字「ヲ」***ヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲ
        count_ヲ = 形態素分割済文単位二次元リスト[x文目].count("ヲ")
        if count_ヲ == 0:                # 制馭文字「ヲ」がない場合。
            pass
        else:                           # 制馭文字「ヲ」がある場合。
            for y番目 in range(count_ヲ): # 複数入る場合は、とりあえず考慮しない、が廻るようにはしてある。
                index_ヲ = 形態素分割済文単位二次元リスト[x文目].index("ヲ")
                # 「●●ヲ」、つまり1コ前の対象をコドメインへ格納。
                CoDomain.append(形態素分割済文単位二次元リスト[x文目][index_ヲ - 1])
        # ヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲヲ
            
        # 制馭文字「スル」***スルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスル
        count_スル = 形態素分割済文単位二次元リスト[x文目].count("スル")
        if count_スル == 0:                # 制馭文字「スル」がない場合。
            pass
        else:                           # 制馭文字「スル」がある場合。
            for y番目 in range(count_スル): # 複数入る場合は、とりあえず考慮しない、が廻るようにはしてある。
                index_スル = 形態素分割済文単位二次元リスト[x文目].index("スル")
                # 「●●スル」、つまり1コ前の対象を射へ格納。
                Morphism.append(形態素分割済文単位二次元リスト[x文目][index_スル - 1])
        # スルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスルスル
            
        # 以降、射ごとに異なる操作。 メタ関数:eval, exec 頻出。
        if Morphism[0] == "有": # ドメイン∋コドメイン
            # ドメインがentityとして定義されているか否かの判定。
            try:
                if eval('type({}) is entity'.format(Domain[0])): # ドメインがentityとして既に定義されている場合。
                    pass
                else: # ドメインがentityではない別の型で定義されている場合。
                    # 20190316 ↓みたいにしようかと思ったが、あまりメリット/使用機会がないため、except中の処理に一本化する。
                    # exec('{}_alt = entity(Domain[0])'.format(Domain[0])) # try中、例外が出る以前の処理は有効。
                    # ひとまずは「オルタナ」。ゆくゆくは「現実_a」とかにする。
                    type(むりやり例外処理に転移するためのダミー変数)
            except NameError: # ドメインをentityとして新たに定義する。
                exec('{} = entity(Domain[0])'.format(Domain[0]), globals())
                    
            # コドメインがentityとして定義されているか否かの判定。
            try:
                if eval('type({}) is entity'.format(CoDomain[0])): # コドメインがentityとして既に定義されている場合。
                    pass
                else: # コドメインがentityではない別の型で定義されている場合。
                    type(むりやり例外処理に転移するためのダミー変数)
            except NameError: # コドメインをentityとして新たに定義する。
                exec('{} = entity(CoDomain[0])'.format(CoDomain[0]), globals()) # これでグローバル変数を定義できる。
                # format()中に globals(), locals() と書いた場合は locals()中で操作することになる模様。
                
            exec( '要素追加({}, CoDomain[0])'.format(Domain[0]),  globals())  #  包含操作
            exec('被包含追加({},  Domain[0])'.format(CoDomain[0]), globals()) # 被包含操作
                
        else: # NetworkX(?)へ。
            pass

上の操作によって、例えば「cokeハ蟥馦瀦阿ヲ有スル。」という文は[、メソッド「形態素解析」適用後に]、『圏であるentity「coke」は要素としてentity「蟥馦瀦阿」を包含する』ものとして読解され、「当該現実」に反映されます。

ⵉⵓ描画
それでは試してみましょう。
前回進捗にて得られた熟語生成を用いて、500文を生成し、これを読解してみます。
クラス「散裂言語」に次のメソッドを追記します。
当初はentityをrandintで1~6文字で生成しようと思いましたが、同一entityの出現確率がほぼゼロなので諦めました。

def 文章生成(self, 文数):
    文章 = "" # これをreturnする。
    for x文を生成します in range(文数):
        ランダム整数1 = 1 # randint(1, 6) 
        ランダム整数2 = 1 # randint(1, 6)
        ランダムドメイン = 散裂言語.緊聖値制馭文字生成(聖値定義域=1, 文字数=ランダム整数1)
        ランダムコドメイン = 散裂言語.緊聖値制馭文字生成(聖値定義域=1, 文字数=ランダム整数2)
        当該文 = ランダムドメイン + "ハ" + ランダムコドメイン + "ヲ" + "有スル。"
        文章 += 当該文
    return 文章

↑の出力結果は、例えば次の通りです。

焻ハ馲ヲ有スル。
忏ハ煇ヲ有スル。
凮ハ鏒ヲ有スル。
玖ハ簦ヲ有スル。
馜ハ譚ヲ有スル。
僅ハ錰ヲ有スル。
駏ハ懎ヲ有スル。
雯ハ摖ヲ有スル。
瀒ハ狀ヲ有スル。
......

これを500文生成し、読解します。

ソース = 散裂言語.文章生成(500)
aaa = 読解.形態素解析(ソース)
読解.entity操作(aaa)

Neo4j上での出力結果は次のようになります。

f:id:kammultica:20190330182921p:plain
Neo4j 出力結果例


________________________________________________________________________________________________



ⵒⵓ結語
ここまでの開発内容をipynb形式で弊サイトにバックアップしておきます。
ご興味のある方はどうぞ。(閲覧・改変ご自由に)

entity存在論に関しては、このまま既存言語で十分に構成できそうです。ので、このまま進めてゆきます。
現在、rubyメタプログラミングに入門中です。が、当分はpythonで続けるはずです。
次回の進捗は3ヶ月以内に上げられたらいいな......(遠い目)

*1:execのスコープが面倒なため。参考:Qiita記事