Chienomi

余分なホワイトスペースと検索の話

技術::NLP

あまりに馬鹿馬鹿しい内容の羅列なので出展は明記しないが、 COVID-19の流行により提唱されているソーシャルディスタンスの表現として文字間にスペースを入れる遊びが「情報を破壊している」とブチ切れている人がいるようだ。

だが、これは間違いなく、自然言語解析や全文検索に関わったことがないか、あるいは関わっても最低限の技術を身に着けていないかのどちらかだろう。

それ自体は「そんなことはないし、そんな無意味なことを掲載するほうが情報にとっては有害である」が答えになるのでそこで終わりとして、自然言語解析並びに検索エンジンにおけるホワイトスペースの話をしたいと思う。

ホワイトスペースについて

ホワイトスペースというのは、要は不可視空白文字のことである。

いわゆるスペース(0x20)のほかにも改行、タブ、垂直タブなんかがある。

ホワイトスペースはワードセパレータであり、前後にワード分割が入る。 だから“An apple”は/\bappleが効くし、

perl -e 'print "yes" if $ARGV[0] =~ /\b \b/;' "An apple"

とかやってもちゃんと通る。 重要なのは、\b\Wと等価ではないということを理解することだ。

ただし、正規表現エンジンに関しては、\Wの前後に\bが設定されるようになっている。 文字個別で\bの属性が定義されているわけではなく、\wの定義に依存しているわけだ。

日本語

Unicodeにおける\wの定義は非常に難しい。例えば

perl -e 'print "yes" if $ARGV[0] =~ /\b\W\b/;' "An😁apple"

はどうなると思うだろう。

Unicode時代の正規表現は、英語を前提とした正規表現ではなく、もっとスマートな書き方をすることが求められている。 そのためには、Unicodeに対する知識を深めることが必要だ。

日本語として利用するひらがな, カタカナ, 漢字はいずれも前後にワードセパレータを持っているという扱いが基本である。 これはある意味当たり前で、ひらがな, カタカナ, 漢字のいずれにおいても連続するその前後で単語が切れている可能性があるのだ。

自動折返し(word wrap)は普通は直前のワードセパレータで切るのだが、日本語の場合どの文字もワードセパレータを持っているので文字で切る。この単語識別はあくまで外形上可能かどうかで判断される。だから、形態素解析によって分割するという話ではないし、そもそも単語分割のために形態素解析を使うなんていう発想がある言語は非常に少ない。

特に組版系においては日本語の構成文字はワードセパレータを含んでいると考えるのが普通で、ウェブブラウザ(Mozilla, Blink)は明確なワードセパレータ文字(例えばホワイトスペース)と同等に日本語を扱う。 なので、日本語文字列において途中に含まれたホワイトスペースは、他言語と比べてもかなり弱く、飲み込まれやすい。

ホワイトスペースの扱い方

ホワイトスペースがあくまでもワードセパレータであるということも重要なポイントだ。 例えば今この段落は、ソース的には

ホワイトスペースがあくまでもワードセパレータであるということも重要なポイントだ。
例えば今この段落は、ソース的には

のようになっている。 読点のあとで改行されており、ここにひとつホワイトスペースが入る。 HTMLでは1+個のホワイトスペースは1個のスペース(U+0020)に変換する、ということになっているため、 結果としてこの改行は単なるスペースになる。

この仕様はHTMLだけに限ったものではなく、TeXなんかでも同様だ。

ここでは「ホワイトスペース」と言っているが、HTML, TeX共に「ASCIIのホワイトスペース」に限定されている。 だから、U+2004 THREE-PER-EM SPACEみたいなホワイトスペースがまとめられることはない。 位置調節に入れたいがために、まとめられてしまわないよう を入れる、なんて駄テクニックが流行ったこともあったが、U+00A0であるnon-break space(Latin-1 文字集合にある)はASCIIにはないため、このまとめる対象になっていないというわけだ。

これが割と合理的なのは、テキストをコードのように考えたときに、比較的簡単にその区別と表現が可能であるためだ。 これは、「表現者が適切になるように表記するはずである」という前提に基づいており、記述する者のリテラシーに頼る考え方だ。

表記ゆれと検索

自然言語解析において立ちはだかるのが「表記ゆれ」である。

コーディングと違い、自然言語解析においては「表記ゆれ、誤り、誤字、誤記などはあるもの」として扱う必要がある。 つまり、リテラシーを信用すべきでないという前提において行われているわけだ。

比較的わかりやすいのが、「大文字小文字」「セパレータとコネクタ」である。

例えばhighlightingというような複合語において、high-lightingのように分割するケースがあり、これをhigh lightingのようにスペース分割する場合もある。特にreで始まる「再なんちゃら」に関してはreなのかre-なのかということが分かれる。これもソフトハイフンを入れるのが最も正しいのだけど、ソフトハイフンを使う人なんてごく稀だ。

そして稀だが、ソフトハイフンや幅ゼロスペースのような「通常不可視なワードセパレータ」が入るケースもある。 文字列の処理としては不可視である文字が入っているのといないのは等価ではない。

% ruby -e 'p "Pine\u{200b}apple" == "Pinaplle"'
false

さらに_とか/とか:とかが入る場合もある。これはワードブレークの程度によって使い分けられたりすることもあるし、_I_B_M_みたいに特定の意図を持って使う場合もある。

また、ドイツ語では略語を表すときに.のあとにスペースを入れる(正式には、U+202F NARROW NO-BREAK SPACEを使うことになっている)し、発音を分けることを表現するためにスペースを入れる。

日本語ではさらに、漢字の違い、発音上の揺れ(たとえば「ず」と「づ」)、送り方など非常に表記ゆれが多い。

さらにいえば、日本語には闕字というものがあり、余計なスペースを入れる文化があった。

検索においてはこの表記ゆれは「飲み込む」という方向になっている。 文字列処理とテキスト検索は少し事情が違うのだ。

文字列処理は、厳密なコントロールができないと使い物にならない。 だから、あくまで厳格な意味において適用される。 だが、検索の場合は「人間的な意図の範疇に収まるかどうか」が問題になるため、より曖昧に検索することが望まれる。

このとき、データ比較を「曖昧な状態の差異を失わせて行う」というのは初歩的な手法だ。実際、Chienomiで採用している検索では

  idc = i.downcase

のように、小文字に統一した比較を行っている。

Chienomiにおける検索は、対象数が少なく、用語などで厳密に検索したい場合があること、OR検索によって十分カバーできること、通常スペースを含まない検索であることから、これが唯一の曖昧さである(厳密には、前後のホワイトスペースを除外するということはしている)。

だが、より大規模な検索になるともっと曖昧に検索することになる。 この「曖昧さ」において「句読点とスペースを除外する」というのは、かなり早い段階で採用される基本的なことである。

このほか、「1文字違い」とか、「文字の前後入れ替え」とかも早期に修正される。

今のGoogle検索が使いにくいのもこのあたりにある。 つまり、ユーザーが入力したもの、及びコンテンツ側の意図を「無視して」一致させることと、「数が多いことが正義」という考え方が結びついた結果、「絞り込むことができない一般的な結果に吸われてしまい、望むものを検索できない」という状態に至っているわけだ。

例えば、“ReST”というワードは大文字小文字に意味があり、これを尊重して検索すればReStructured Textは上位に来るはずである。 ところが、現在Google検索の結果は “ReST” と “rest” で全く同じであるため、意図は反映されない。

現状では、「表記ゆれに含まれる意図をどこまで反映すべきか」ということを考えなければならないフェーズに来ているのだが、表記ゆれを徹底的に除去して同一視するというのがポピュラーな考え方だ。 Twitterの検索はその捉え方が非常に独特で使い勝手が悪いが、スペースを余分に入れることくらい普通に考えられることなので、それによって情報が損失したりすることはない。 それよりは、全角英数を使うほうがよっぽど大ダメージだ。

厄介な全角スペース

U+3000 IDEOGRAPHIC SPACEは厄介な存在である。 日本人の感覚的にはU+3000はワードセパレータであり、U+0020に置き換えて考えても差し支えない、というのが自然な考え方だ。 ところが、伝統的にこのスペースはホワイトスペースとして扱われてこなかった。そのため、これをホワイトスペースから除外するような振る舞いをするケースが多いようだ。

Unicode 5からZs(Whitespace)に含まれるようになった。 そのため、Zsプロパティでマッチングすれば正しく識別できるはずだ。それでも、Onigmoは\sに全角スペースを含まないが。

処理前に

str.gsub!(/\p{Zs}+/, " ")

しとくほうが良いだろう。

曖昧補正の基準

あいまい補正は、それが明瞭な意思であることがどれだけ普通であるか、を基準に決めるのが一般的な考え方だ。

例えばZshの補正は打ち間違いに関わるもの、つまり文字の前後、位置ずれといったものは補正するべきだと考えられる。 ところが、シェルにおいてスペースは非常に重要であり、スペースの位置が補正の対象であるとは考えない。 もちろん、そういうことは普通にある。文字の前後が発生するのと同じ問題だからだ。 しかし、シェルではスペースは非常に強い区切りになるので、その区切りを尊重しているわけである。

文字列検索においては精密な検索が必要であることから普通、補正は行わない。 しかし、文字列検索においても「文字列値としての一致」を探しているわけではなく、「当該文字列出現箇所の探索」であるというケースにおいては、明らかに等価であるものについては吸収したほうが良いと考えられる。 実際、最近のウェブブラウザは大文字小文字の違いを無視するようになっているし、全角英数字をASCII文字相当で探したりもする。

これらの考え方は根底として、「等価性の拡張」である。

例えば transformationtransform\u{00AD}a\u{00AD}tion は言語表現としては明らかに等価である。 だが、文字列値としては等価であってはならない。

ソフトハイフンはあくまで表示上の補助的な情報であるから、文字列としてはないものとして扱う必要がある。 ところがこのあたりの配慮は実際には難しく、なかなか十分にならなかった。 (少なくとも、現在のChromiumはソフトハイフンの入れ方如何によらず、ソフトハイフンはないものとして検索できる)。

このように文字列値として等価ではないものを、等価として扱うために、「等価であるもの」を探したときに含まれるように「等価性を拡張する」必要があるのだ。

Google検索のように「発見すべきものについて知らないものを探す」場合は意図に基づいた拡張を行う必要がある。 例えば “settings” と “configuration” は同じ意味合いで使われる(この場合、略語の展開や語形の探索などは事前に済ませている前提で話している)。 だから、例えばfont settingsで検索したが、欲しいものはfont configurationで探さないと見つからないものである、という可能性は大いにありえる。

Googleの場合、「語の近さ」を定義して、近似の語を同一視するという手法を取っている。 これ自体はなるほどという感じなのだが、現実にはあまり良い方法ではないように思われる。 理由は、特殊性が吸われてしまうからだ。

文化的側面

ではそろそろ私の別の研究の話をしよう。

言語に一律の正しさなどない、というのは、自然言語に携わる者にとって当然に持ち合わせている常識だ。 そして、言葉のゆらぎこそ文化である、と考えられる。

興味深いのは、これらは自然発生し、伝播するということである。 同時多発的に生じる場合もある。

それは例えば表現的なものであることもあるし、用語的なもの(スラング)であることもある。 また、データ形式的な表現の問題であることもある。

これが問題でなく容易に理解できると思うのは、「今自分の立ち位置がその発生からみて十分に未来であるから」である。

例えば今当たり前に理解可能な「w」をLOL表現として使用することについても、当初非常に限定的な文化圏のものであったし、それを持ち出すことがあると全く以て理解できないものであった。 これがLOL表現であるということが定着するまでには5年以上を要しているのであり、多くの人にとっては意味が分からず、不快なものであった。(これに関してはより長く不快なものであると認識されたし、今以て不快であると感じる人は普通にいるが、それは特別な理由である)

今や廃れたスラングだが、「ガイシュツ」もある。 既出の意図的な読み違えであり、通常漢字で書くところひらがなで書くだけでも検索を妨げるが、さらに読み違えて、半角カナにするという相当ひねりの効いたものになっている。

言葉はその時によって人と文化を映す鏡であり、様々な言葉が生まれるのはごく自然なことである。 そして、コンピュータ上の文字としては文字表現そのものにも関わる。へた字 (たとえば 「おլਕょכֿ」みたいな) なんてのもある。 これはコンピュータ処理上は猛烈に困る、というよりもへた字を最初から想定できるのでなければ諦めたほうが良い。 だが、へた字が流行る、というのもそれなりに背景と動機があってのものであり、へた字が使われる文化というのもまたひとつの文化であると共に、そのようなコミュニケーションがあったこともまた歴史的側面においても文化的側面においても、あるいは文化史としても非常に重要なことなのである。

なによりも、表現を妨げる権利はない。 私はへた字は、処理しにくいわ読みにくいわでだいっきらいであるし、へた字を使う人とはお近づきになりたくないとも思っていたが、私に「へた字を使うな」という権利はない。だから当然にそのようなことは言わない。

例え未来永劫残るようなものに、その時流行った表現が用いられたとしても、表現の中に時代背景が滲むのはごく当たり前のことである。 実際、90年代の小説やエッセイを読むだけでも、現代の視点からすれば「文表現としての違和感」があるくらいだ。 それどころか、ほんの10年前の2ちゃんねるの書き込みを見ても、当時を知る人にとっては「古臭い」あるいは「懐かしい」と感じるし、知らない人にとっては今や解読できないレベルである。

どれほど洗練された文を書いたつもりになったところで、そこに時代の匂いがするのは避けられない。 そう感じるか感じないかは、あくまで「自分の今の立ち位置」に依存するに過ぎない。

むしろ、「その時」を色濃く反映することは、時節的なもの(ブログとかニュースとか)においては未来の視点からみれば意味深いことであるとすら言える。

もちろん、私はソーシャルディスタンスを文字上で表現することがセンスが良いとは思っていないし、むしろ可読性が下がるからやめてほしいと思っているくらいだし、よって私自身がすることはないのだが。

finally

  • 検索の話をするなら、スペース程度は飲み込まれてしまうものだからあまり気にする必要もない
  • 間にスペースを入れるのは表記ゆれとしてはかなり弱いほう。それに怒っていたら表記ゆれに対して片っ端から激怒することになってしまう
  • 日本語の場合特にスペースのワードセパレータとしての意味は薄い
  • スペース文字とか日本語文字とか国際化とかUnicodeとかは、一般の人が思っているよりずっと複雑な事情がある(し、それは避けられない)
  • Twitterのアカウントにおける名前に関しては、名前を変えると過去のツイートの分も含めて現在の名前に更新されるので、名前を一時的に変更することによって将来的な情報に影響があるとは言えない
  • そもそも名前を一時的に変更することは珍しいものではなく(特に4/1)、意味的に一変してしまうようなものも珍しくない
  • 表記ゆれなどを含めて物事をいかに表現するかというのは表現者の(保証された)自由の範疇であり、それを束縛する権利は誰にもない。その自由を越えて情報を扱いたいなら、それは扱う側がすべきことで、表現者に求めることではない
  • 表記ゆれのようなランダムさをなくしてしまおうといいう試みは、少なくともここ千年は何度も試みて、その尽くが失敗に終わっているもの