全文を圧縮した前文
長文なのではじめに結論を置いときますが、最高に頭のネジが飛んでいるポエム(当人比)ができました。
最初と最後だけでも流し読みしていただけると嬉しいです。
結論から言うと文章を非可逆圧縮、冗長展開するとこうなります。
どうしてこうなったのかは筆者も分かりません。
- 原文: ディレクトリAの内容を親ディレクトリに移動する
- 非可逆圧縮: 名鑑aの刃を親名鑑に動為す
- 冗長展開: メモリー(普通ハードディスク)に保存されているファイルのリストローマ字の最初の文字の感じ取ったこと、発見したこと、あるいは学習したことの全体あるいは範囲を子どもを生み出す、出産する、子供を養育して、あるいは育てる人メモリー(普通ハードディスク)に保存されているファイルのリストに新しい位置または場所に動かす、あるいは移動させる、具体的および抽象的な意味でも考慮、判断または使用のために提案する
画像や音声が非可逆圧縮できるのに文章ができない訳がない
画像をJPEG
で保存すると、無圧縮のBMP
よりサイズが小さくなるじゃないですか。
勘違いを恐れずに詳細を省いて説明を圧縮すると、JPEG
の原理は画像を(人間の目では分かりにくい程度に)多少粗くしてサイズを縮小します。
JPEG
に変換したファイルをBMP
やPNG
に再変換しても細かいノイズは消えません。
つまり元の画像には戻せない(非可逆)圧縮が起こるってことです。
ところでZIPファイルなんですけど、これもテキストとかのサイズが小さくなりますね。
あえて誤解を恐れずにざっくり説明を圧縮すると、ZIP
ってPCの中にいるハフマンさんが辞書をランレングすると小さくなるんでしたっけ?
そんな感じの仕組み(これをセルフマサカリと言います)で小さくなります!
圧縮の原理
難しい話は良く分かりませんが今回は辞書式圧縮に注目しました。
例えば下の文章があります。
フォルダ1のサブフォルダとしてフォルダ2とフォルダ3、そしてフォルダ4があります。
この文章では『フォルダ』という単語が頻出するのでフォルダ=F
という辞書を定義して書き換えてみましょう。
フォルダ=F;F1のサブFとしてF2とF3、そしてF4があります。
このように同一の単語を辞書化して短く置換することで、テキストを可逆圧縮することが可能です。
元の文章に戻す時は『F』を『フォルダ』に置換すれば完全に元通りです。
今回は13文字分短くなりました。
でもですね、次の文章はどうでしょうか。
フォルダ1のサブフォルダとしてディレクトリ2とフォルダー3、folder4があります。
これを先ほどのように辞書を使って圧縮するとこうなります。
フォルダ=F;F1のサブフォルダとしてディレクトリ2とFー3、folder4があります。
『フォルダ』と『フォルダー』が類似の表現だったのでかろうじて2文字分圧縮できましたが、辞書化による恩恵は微々たるものです。
文章の非可逆圧縮
ここで私に閃光が走りました!
『フォルダー』『ディレクトリ』のような表記揺らぎや類義語をすべてまとめてしまえば文意を損ねることなくテキストを非可逆圧縮できるのではないか!!
つまりこういうことです。
【圧縮前】わがはいはにゃんにゃんではありませんにゃ。名前はいまだに無いんですにょ。ネームはナッシングって言い換えることもできるにゃね。
【圧縮後】私は猫では無い。名は未だ無い。名は無いと換言可。
圧縮後の方が圧倒的に短くなり、表記揺らぎを抑えたので『無い』という単語が複数回出てきます。
しかも、文意も損ねてないよね。んー?どう見ても損ねてないっしょ。損ねてないって言ってよ。と棍棒を持ったおにいさんに肩をトントン叩かれながら言われたら、きっと文意も損ねてないと同意する人がいるはずです。
文章のフレーバーをすべて吹き飛ばしつつ、意図や個性を表すデジキャラ性も多少損失した気がしますが、棍棒外交に比べれば些細なことですにょ。
日本語Wordnet
類語や表記揺らぎをまとめれば、きっと長文もスッキリ短くなってハフマンさんが辞書をランレングする仕事(これを天丼といいます)が楽になること請け合いです!
でも類語データってお高いんでしょう?
いいえ、ご心配には及びません。あるんですよ…日本語Wordnet様がね!
リンク先を引用します。
国立研究開発法人情報通信研究機構(NICT)では、大規模かつどなたでもご入手いただける日本語の意味辞書を開発することを目的とし、 2006年から日本語ワードネットの開発を進めて参りました。(中略) ライセンスを保持していただければ、基本的に日本語ワードネットは無償で使用、複写、改変、頒布していただけます。詳細はライセンスをご参照下さい。
類語も分かる意味辞書が、無料で作成されているのです。ありがたやありがたや。
セットアップ
さっそくセットアップして使いましょう。
- 日本語Wordnetのダウンロードページから"Japanese Wordnet and English WordNet in an sqlite3 database"みたいなリンクをクリック
- ダウンロードした
wnjpn.db.gz
をWindowsユーザは妙に苦労しながら展開する - SQLiteの中身が見れるDBeaverとかDB Browserとか適当なDBクライアントを用意する
- あとはよしなによろしく
SQLで類語取得
Qiitaの素晴らしい記事をオマージュして実際にSQLで類語を取り出します。
with word_sense
as (select w.wordid, w.lemma, se.synset
from word w join sense se on w.wordid = se.wordid)
select luigi.*
from word_sense luigi
join word_sense src on luigi.synset = src.synset
where src.lemma = '猫'
order by LENGTH(luigi.lemma)
実行結果
wordid | lemma | synset |
---|---|---|
186873 | 猫 | 02121620-n |
208204 | ネコ | 02121620-n |
97864 | cat | 02121620-n |
181396 | ねんねこ | 02121620-n |
204434 | キャット | 02121620-n |
186221 | にゃんにゃん | 02121620-n |
57508 | true_cat | 02121620-n |
wordid
が単語IDで、lemma
が単語、synset
が単語の概念グループIDみたいな感じらしいです。
wordid
の属するsynset
から同じ概念グループの単語を丸ごと取れました。
「にゃんにゃん」とか「true_cat」とか美辞修辞に使えそうな単語を全部「猫」という一文字に圧縮できちゃいます!
韻律もレトリックも最後にはちゅーるもとい圧縮率にはかなわないんですよ。
pythonで短縮類語取得
これはおまけですが、さっきのSQLをpython 3.9から呼び出して一番短い類義語を取得するサンプルです。
import sqlite3
# SQL文
sql_format = """
with word_sense
as (select w.wordid, w.lemma, se.synset
from word w join sense se on w.wordid = se.wordid)
select luigi.*
from word_sense luigi
join word_sense src on luigi.synset = src.synset
where src.lemma='%s'
order by LENGTH(luigi.lemma)
"""
# 単語から短い類義語を取得する
def word_to_luigi(word):
with sqlite3.connect("wnjpn.db") as db:
cur = db.execute(sql_format % word)
row = cur.fetchone()
return row[1] if row else word
for s in ["ドラゴン ", "ヨッシー ", "ムーンライト", "玉兎 "]:
print(f"{s} is {word_to_luigi(s.strip(' '))}")
実行結果
ドラゴン is 龍
ヨッシー is ヨッシー
ムーンライト is 月
玉兎 is 月
ね?簡単でしょ?
ドラゴンは類義語の『龍』になります。
ヨッシーが『龍』にならないってことはつまり…スーパードラゴンだから上位種だってことなのです!(日本語Wordnetに載ってないだけとも言います)
ムーンライトも玉兎も、概念グループは違えど全部『月』です。
想定通り風情はありませんが、(文脈によって)意味は通らなくもないです。
実践!非可逆圧縮
あとはプログラムで組み合わせるだけです。
でも私はプログラム作るのが苦手なので、その前にきちんと設計をしておきます。
形態素解析する→類語を探す→一番短い単語に書き換える→ビクトリー
完璧な設計ができました!
ちなみに形態素解析は何でもいいですが、今回は使ったことのあるJanomeにしました。
この設計を元に作ったソースコードはこちらです。
ソースコードと出力結果
import sqlite3
from janome.tokenizer import Tokenizer
# SQL文
sql_format = """
with word_sense
as (select w.wordid, w.lemma, se.synset
from word w join sense se on w.wordid = se.wordid)
select luigi.*
from word_sense luigi
join word_sense src on luigi.synset = src.synset
where src.lemma='%s'
order by LENGTH(luigi.lemma)
"""
# 単語から短い類義語を取得する
def word_to_luigi(db, token):
word = token.surface
pos = token.part_of_speech.split(",")
#print(token)
if pos[0] in ["名詞", "形容詞", "副詞", "動詞"]:
cur = db.execute(sql_format % word)
row = cur.fetchone()
return row[1] if row else word
else:
return word
def compress_text(tokenizer, text):
with sqlite3.connect("wnjpn.db") as db:
return "".join([word_to_luigi(db, t) for t in tokenizer.tokenize(text)])
text_list = ["Beautiful moon is beautiful ですね。",
"あなたは非常に美しいです。",
"あなたは非常に美しかったです。",
"わたくしのドラゴンはサラマンダーよりも迅速に戦いの庭を駆け抜ける!",
"フォルダ1のサブフォルダとしてディレクトリ2とフォルダー3、folder4があります。",
"わがはいはにゃんにゃんではありませんにゃ。ネームはいまだに無いんですにょ。名前はナッシングって言い換えることもできるにゃね。",
]
tokenizer = Tokenizer()
for text in text_list:
compressed = compress_text(tokenizer, text)
print(f"変更前: {text}")
print(f"変更後: {compressed}")
print("")
出力結果
変更前: Beautiful moon is beautiful ですね。
変更後: Beautiful 月 is 佳 ですね。
変更前: あなたは非常に美しいです。
変更後: あなたは大に佳です。
変更前: あなたは非常に美しかったです。
変更後: あなたは大に美しかったです。
変更前: わたくしのドラゴンはサラマンダーよりも迅速に戦いの庭を駆け抜ける!
変更後: わたくしの龍はサラマンダーよりも急に戦の庭を駆け抜ける!
変更前: フォルダ1のサブフォルダとしてディレクトリ2とフォルダー3、folder4があります。
変更後: フォルダiのサブフォルダとして名鑑2とフォルダ3、小冊4があります。
変更前: わがはいはにゃんにゃんではありませんにゃ。ネームはいまだに無いんですにょ。名前はナッシングって言い換えることもできるにゃね。
変更後: わがはいはにゃんにゃんではありませんにゃ。名は猶無いんですにょ。名は零って言替える事も可能にゃね。
考察
たしかに短くはなります。
なるけど…なるほど分かりました。
これではまだ**GAFAMからのオファー**には足りません!
今から足りない部分の反省会をします。
大文字
変更前: Beautiful moon is beautiful ですね。
変更後: Beautiful 月 is 佳 ですね。
『moon』と『beautiful』は変換できていますが、意味辞典の単語は小文字なので『Beautiful』が変換できていません。
高みを目指すならもうちょっとがんばりましょう。
具体的に言うとmoon is beautiful ですね
→月がきれいですね
←I love you
からの連想でAIが「佳のままの君が好き。」とか訳すようにがんばりましょう。
活用形
変更前: あなたは非常に美しいです。
変更後: あなたは大に佳です。
変更前: あなたは非常に美しかったです。
変更後: あなたは大に美しかったです。
『大に佳』が日本語として大に佳かどうかはともあれ、日本語Wordnetで活用形をそのまま検索してもヒットしません。
『美しかっ』は形容詞・イ段,連用タ接続
なので、活用形を活かしたまま『佳かっ』に変換するとネクストステージへ行けるでしょう。
人称代名詞と熟語
変更前: わたくしのドラゴンはサラマンダーよりも迅速に戦いの庭を駆け抜ける!
変更後: わたくしの龍はサラマンダーよりも急に戦の庭を駆け抜ける!
意味辞典はどうも人称代名詞が得意でないのか、『わたくし』を変換してくれないようです。
それと意味辞典では『戦いの庭』と『戦場』が同じ概念なのですが、優秀なJanomeは『戦いの庭』を『戦い』『の』『庭』に分割します。熟語は特殊処理してあげましょう。
冒頭の例
変更前: フォルダ1のサブフォルダとしてディレクトリ2とフォルダー3、folder4があります。
変更後: フォルダiのサブフォルダとして名鑑2とフォルダ3、小冊4があります。
うーん、ダメダメ!
一応『フォルダー』は『フォルダ』になってますけど、半角カナは対象外とか1
がi
になっちゃうとかそれ以前の問題ですね。
ちなみに「ディレクトリAの中身を親ディレクトリに移動する」を変換してみたら**「名鑑aの刃を親名鑑に動為す」**ってなりました。キメツの中身に変換されちゃいましたね。ハハハ(刃だけに)。
にゃんにゃん(形態素解析)
変更前: わがはいはにゃんにゃんではありませんにゃ。ネームはいまだに無いんですにょ。名前はナッシングって言い換えることもできるにゃね。
変更後: わがはいはにゃんにゃんではありませんにゃ。名は猶無いんですにょ。名は零って言替える事も可能にゃね。
これは話し言葉をJanomeが形態素解析できてませんでした。
トークンを出力すると下のようになります。
わがはい 名詞,代名詞,一般,*,*,*,わがはい,ワガハイ,ワガハイ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
に 助詞,格助詞,一般,*,*,*,に,ニ,ニ
ゃんにゃんではありませんにゃ 名詞,一般,*,*,*,*,ゃんにゃんではありませんにゃ,*,*
。 記号,句点,*,*,*,*,。,。,。
『にゃんにゃん』が『に』『ゃんにゃん』になってるので猫じゃないです。名前がないどころではなく、生物かどうかも怪しいです。
考察まとめ
ということでこの記事を改善してビッグ・テックだかドッグタグだかの就職に使いたい人向けに改善点をまとめます。
- 人称および助詞や助動詞も変換できるよう辞書を改善すべし
- 大文字、漢字の処理や活用形は泥臭く対応する関数を作るべし
- 話し言葉に合わせて形態素解析を改修すべし
- 誤字脱字や表記揺らぎに対応するためレーベンシュタイン距離が1の表現の訂正機能を追加すべし
- 転変流転する固有名詞や流行語に対応すべくスクレイピングと機械学習を取り入れるべし
- さらに1つの文に限らず文章全体の構成を見直して模範的な構文と序論・本論・結論の流れに書きなおす機能を追加すべし
- 長文を3行以内に要約する機能を高い精度で実装すべし
これらの機能を全部実装して自然な言い回しへの変換や校正ができるツールを作れば一流企業もベンチャーも夢じゃないかもしれません。
責任はとれませんが。
今回のオチ
オチまでの冗長な言い訳
私にとっての夢物語で記事を終えるのはポエム無情断迅拳でしかありませんので、最後に逆転の発想で話をふくらませておきます。
令和となっては必ずしも圧縮にこだわる必要はないのです。
今や潤沢な記憶領域が約束されていて、動画ならいざしらずテキストをちまちまと圧縮しても効果が見込めない時代と言えます。
じゃあなぜこのポエムを書き始めたのかはおいといて、無理に圧縮するよりも日本語の初級者が読んでも分かりやすい表現に話を膨らませた方が有用ではないでしょうか!
そしてWordnetのsynset_def
テーブルには単語の概念の辞書的定義が載っているのです。
例えば次のSQLを実行してみましょう。
select sd.def
from word w
join sense se on w.wordid = se.wordid
join synset_def sd on se.synset = sd.synset
where sd.lang = 'jpn'
and w.lemma='猫'
order by LENGTH(sd.def) desc
実行結果
def |
---|
通常、厚く柔らかい毛皮を持ち、吠えることのできないネコ科の哺乳類:家ネコ |
ヤマネコ |
冗長なオチ
さきほどの実行結果の時点で大体のオチは見えた気がしてきましたが、下のソースコードを実行してみましょう。
import sqlite3
from janome.tokenizer import Tokenizer
# SQL文
sql_format = """
select sd.def
from word w
join sense se on w.wordid = se.wordid
join synset_def sd on se.synset = sd.synset
where sd.lang = 'jpn'
and w.lemma='%s'
order by LENGTH(sd.def) desc
"""
# 単語を説明に変換する
def word_to_luigi(db, token):
word = token.surface
pos = token.part_of_speech.split(",")
#print(token)
if pos[0] in ["名詞", "形容詞", "副詞", "動詞"]:
cur = db.execute(sql_format % word)
row = cur.fetchone()
return row[0] if row else word
else:
return word
def extract_text(tokenizer, text):
with sqlite3.connect("wnjpn.db") as db:
return "".join([word_to_luigi(db, t) for t in tokenizer.tokenize(text)])
text_list = ["あなたは非常に美しいです。",
"わたくしのドラゴンはサラマンダーよりも迅速に戦いの庭を駆け抜ける!",
"フォルダ1のサブフォルダとしてディレクトリ2とフォルダー3、folder4があります。",
"わがはいはにゃんにゃんではありませんにゃ。ネームはいまだに無いんですにょ。名前はナッシングって言い換えることもできるにゃね。",
"ディレクトリAの内容を親ディレクトリに移動する",
]
tokenizer = Tokenizer()
for text in text_list:
compressed = extract_text(tokenizer, text)
print(f"変更前: {text}")
print(f"変更後: {compressed}")
print("")
こうなります。
変更前: あなたは非常に美しいです。
変更後: あなたは突然の思いがけない危機(通例危険を伴う)で緊急な行動をとる必要があることに出演においてセンセーショナルであるか影響においてスリリングなです。
変更前: わたくしのドラゴンはサラマンダーよりも迅速に戦いの庭を駆け抜ける!
変更後: わたくしの通常、火を噴き、爬虫類のような体で、時として翼を持つとされるはサラマンダーよりも準備ができて、望んであるいはすぐに行動するさまに2人以上の人またはチームが競争する正式なコンテストの家や他の建物の回りの土地を駆け抜ける!
変更前: フォルダ1のサブフォルダとしてディレクトリ2とフォルダー3、folder4があります。
変更後: 中身を保護するため折り重ねられる覆いこの数を表わす最も小さな整数あるいは数字のサブフォルダとしてメモリー(普通ハードディスク)に保存されているファイルのリスト1と1の合計である基数、または、この数を表す数字と中身を保護するため折り重ねられる覆い1と1と1の合計である基数、中身を保護するため折り重ねられる覆い3と1の合計である基数があります。
変更前: わがはいはにゃんにゃんではありませんにゃ。ネームはいまだに無いんですにょ。名前はナッシングって言い換えることもできるにゃね。
変更後: わがはいはにゃんにゃんではありませんにゃ。ある人または物が認識される、言語の最小構成単位は変化、中断または停止なしで所有者を持たないさまんですにょ。誰かまたは何かが他から呼ばれる、分類されるあるいは区別されたにより、語を識別することは重要でない量って異なった言葉で同じメッセージを表現するそれ自身の存在を持つと考えられる特質または品質のいずれかも力量か能力を持つさまにゃね。
変更前: ディレクトリAの内容を親ディレクトリに移動する
変更後: メモリー(普通ハードディスク)に保存されているファイルのリストローマ字の最初の文字の感じ取ったこと、発見したこと、あるいは学習したことの全体あるいは範囲を子どもを生み出す、出産する、子供を養育して、あるいは育てる人メモリー(普通ハードディスク)に保存されているファイルのリストに新しい位置または場所に動かす、あるいは移動させる、具体的および抽象的な意味でも考慮、判断または使用のために提案する
なんて平易で完璧な説明を加えた、日本語に慣れていない初学者も上級者も理解に苦しむ文章になったのでしょう!
お酒に酔いながらこのプログラムを実行して結果を読んだら吹き出しました。
第二第三の犠牲者が出ませんようにお祈りしたところで、机の上を拭くために筆を擱かせていただきます。
やばい。主にキーボードがやばい。
おっとキーボードが打てないと文章を書けなくなって最強の圧縮になるとオチがつきました。
おあとがよろしいようで。