0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

読めないのに「言語らしい」と分かる文章を作る:ブラウザで動く人工言語ジェネレーター

0
Posted at

ボタンを押すたびに、まったく新しい人工言語を作り、その言語で書かれた文章を出すツールを作りました。ブラウザだけで動きます。

01-scripts.png

文章は、ぱっと見ではまったく読めません。それでも統計で調べると、でたらめな文字列ではなく、規則を持つ言語らしいことが分かります。

この記事では、実際に生成した言語を1つ例にして、音・文法・文章・文字をどう作っているか、そして「言語らしさ」をどう確かめているかを、数字とコードを交えて書きます。

できること

  • 新しい言語を作る:音・語彙・文法・文字が、毎回すべて変わります。
  • seed で再現する:同じ seed なら、いつも同じ言語と文章になります。seed はアドレス(#seed=...)に入るので、URL を送れば同じ言語を見せられます。
  • 細かく決める:文字の仕組み、書く向き、語順、接辞の位置、格、時制の表し方、声調、母音調和、数の底など20項目を指定できます。指定しない項目は seed から決まります。
  • 分析:文章の文字列だけを使い、8つの統計で「言語らしさ」を調べます。ほかの文章を貼り付けて調べることもできます。
  • 姉妹言語:作った言語を祖先にして、音の変化で2〜3の子孫の言語を作ります。
  • 答え:文法書、接辞の表、数の言い方、行ごとの対訳、辞書が見られます。

例:seed「pine-hill」の言語 Amyinsu

まず、できあがるものを見てください。seed に pine-hill を入れると、Amyinsu という言語ができます(下の URL で同じものが開きます)。

「答え」タブの文法書によると、この言語は次のような言語です。

項目 Amyinsu
子音(11) t k m n s r w y d š q
母音(5) a i u e o
音節の型 (C)(L)V(C)。t k d q の後に y w r が続いてよい
強勢 最後の音節
語順 SOV(主語・目的語・動詞)
格 目的語に対格 -mir。場所 -nu、行き先 -im、起点 -ti、同伴 -mu
時制 過去 -mi、未来 -tu(現在は印なし)
複数・否定 どちらも小詞(複数 ur、否定 qis)
数 12 進法。12 は wim、13 は「wim muk tu」(12 つなぎ 1)
文字 アブギダ(子音の字に母音の印を付ける)、角ばった線、左から右

そして、この言語で書かれた文章の1行目は、対訳を付けるとこうなります。

Ki   ur  si    durmi-nu  keme-mi.
嬉しい PL 子ども  家-LOC    住む-PST
「嬉しい子どもたちは家に住んだ。」

ほかにも、たとえば次のような文が出てきます。

Triršos rur  wim muk  tu  ur  nu-nu    woni-tu.
大きい   馬   12  CONN 1   PL  野原-LOC  待つ-FUT
「大きい馬は13個の野原で待つだろう。」

Di   rur-mir  ma-yi!
THAT 馬-ACC   運ぶ-IMP
「あの馬を運びなさい。」

最後の文では、命令の接辞 -i を ma(運ぶ)に付けると母音が続くので、この言語の決まりどおり、間に y が入って mayi になっています。

02-analysis.png

答えのタブの対訳は、独自の文字、ラテン文字の転写、発音記号、語を形の部分(形態素)に分けたものとその意味、日本語訳の順に並びます。

ここからは、これがどう作られているかを順に見ていきます。

全体の流れ

seed + 指定(細かく決める)
   ├─ 音を作る ……… 子音・母音・音の使われやすさ・音節の型・直し方
   ├─ 文法を作る …… 語順・格・接辞の位置など
   ├─ 語彙を作る …… 約110〜160の概念に語根を当てる
   ├─ 接辞を作る …… 意味の違う形が重ならないまで選び直す
   ├─ 文字を作る …… 仕組み・見た目・向き・字形
   ├─ 意味を作る …… 「誰が・何を・どうした」の並び
   ├─ その言語で表す/日本語で表す(対訳)
   ├─ 分析する …… 文章の文字列だけを使う
   └─ 答えを作る …… 文法書・字の一覧・対訳・辞書

1. seed から、用途ごとに別の乱数を作る

乱数は mulberry32 で、seed の文字列をハッシュして初期値にしています。工夫したのは、用途ごとに 別々の乱数 を作ることです。

// 用途ごとに独立した乱数を作る。文章の長さを変えても言語は変わらない。
fork(label) { return new RNG(this.seed + '/' + label); }

音は seed/phon、文法は seed/gram、文字は seed/script のように、名前を付けて別の乱数を使います。一つの乱数を順に使い回すと、途中で引く回数が1回変わるだけで、そのあとがすべてずれてしまうからです。

こうしておくと、文章の長さを変えても言語は変わりません。音を指定しても文法は変わらず、文法を指定しても音は変わりません。「細かく決める」で指定した場合も、乱数は指定がないときと同じ順に引いてから値を置き換えるので、指定しなかった項目はできるだけそのまま残ります。

2. 音を作る

子音の候補は25個あり、それぞれに「珍しさ」を付けています。珍しい音ほど入りにくくしています。

珍しさ 子音 入る確率
0 t k m n s 0.93
1 p l r w y b d g h 0.6
2 f v z š č ŋ 0.3
3 ž c j x q 0.14

目標の数(8〜17)に足りなければ珍しくない順に足し、多すぎれば珍しいものから外します。母音は a i u を必ず入れ、e o などをそれぞれの確率で足します。25% の言語には母音調和(語の中の母音が前の母音か後ろの母音にそろう決まり)が、約2割の言語には声調が入ります(両方は入れません)。

音の使われやすさは、順位に応じた重み 1/(順位)^s で決めます。どの言語でも、よく使う音と少ししか使わない音の差ができます。

音節の型も言語ごとに変わります。たとえば、25% の言語は音節の終わりに子音を許さず、すべての音節が母音で終わります。接辞を付けて母音や子音が続きすぎたときの直し方(子音を挟む、母音を落とす、母音を挟む)も、言語ごとに決めます。Amyinsu の mayi は、このうち「子音 y を挟む」の言語です。

3. 文法を作る

語順は6種類すべてが出ますが、重みは偏らせています。

語順 重み
SOV 45
SVO 40
VSO 9
VOS 3
OVS 1.5
OSV 1

ほかの項目も、語順と組み合わせて決めます。目的語が動詞の前に来る言語(OV)は 0.9 の確率で後置詞、動詞の後に来る言語(VO)は 0.15 の確率で後置詞です。接辞の位置も、OV なら 0.9、VO なら 0.7 の確率で接尾辞にしています。世界の言語でよく見られる組み合わせのほうが出やすいように、重みを付けています。

このほか、格の有無、複数・時制・否定を接辞で表すか小詞で表すか、人称の一致、コピュラ(「〜である」の語)の有無などを決めます。

4. 語彙を作る

概念は、名詞・動詞・形容詞・代名詞の約110語(増やした語彙を入れると約160語)です。品詞ごとに順位をランダムに決め、1/順位 の重みで使われやすさを決めます。これがジップの法則のもとになります。

語根の長さは、よく使う語ほど短くしています。使われやすさの上位 12% は1音節、60% までは2音節、残りは3音節です(20% の語は1音節長くしたり短くしたりします)。

5. 意味から文章を作る

文章は、文字を並べて作るのではありません。先に意味の構造を作り、それを生成した言語の文法で表します。

項目 値
登場人物 生き物の名詞から3つ。名詞を選ぶとき重みを6倍
文の種類 他動詞の文 50、自動詞の文 34、「〜は〜い」の文 16(重み)
時制 過去 62、現在 26、未来 12(重み)
否定 0.12

動詞ごとに主語・目的語の条件もあります(「食べる」の目的語は食べ物、など)。登場人物を何度も出すので、同じ語が繰り返し現れ、本物の文章に近い頻度の偏りが生まれます。

同じ意味を日本語でも表すので、対訳が付きます。

意味の違う形が同じ綴りにならないようにする

接辞をでたらめに作ると、たとえば「犬が」と「犬を」、「見た」と「見ない」が同じ綴りになることがあります。読み手には区別できないので、すべての語について意味の違う組み合わせ(動詞なら 否定×時制×人称、名詞なら 複数×格)の形を作って比べ、重なったら接辞を選び直します(最大60回)。

6. 文字を毎回、部品から作る

字形は、0〜1 の座標で表した線・曲線・なめらかな線・円・点・多角形の部品を組み合わせて作り、SVG で描きます。文字の仕組みは7種類です。

仕組み 内容
アルファベット 子音と母音に1字ずつ
アブジャド 子音だけを書く(母音記号を添える指定もできる)
アブギダ 子音の字に母音の印(上・下・横)を付ける
音節文字 1つの音節に1字
規則的な音節文字 子音で形が決まり、母音で向き(回転・反転)が変わる
組み合わせ文字 子音と母音の字を、音節ごとに四角へまとめる
表語文字 語根ごとに1字。意味の部分(12種)と音の部分を組み合わせる。文法の部分は音節文字で書く

見た目は5種類(線、角ばった線、丸い線、方形の画、絵文字風)、書く向きは3種類(左から右、右から左、縦書き)です。実在の文字の形は使わず、見た目の傾向だけを決めて、字形はすべて毎回新しく作ります。

似すぎた字を作らない

ランダムに作ると、見分けにくい字ができます。そこで字形を 6×6 のます目に塗り、すでにある字と塗ったます目がどれだけ重なるか(ジャカード係数)を測って、重なりすぎる字は作り直します。

function jaccard(a, b) {
  let inter = 0;
  for (const x of a) if (b.has(x)) inter++;
  return inter / (a.size + b.size - inter || 1);
}

// 互いに似すぎない字形を n 個作る。条件を満たせないときは少しずつゆるめる。
function makeSet(rng, gen, n, maxSim, minPrims = 1, avoid = []) {
  const out = [], rs = avoid.slice();
  let sim = maxSim;
  for (let t = 0; out.length < n && t < 20000; t++) {
    if (t > 0 && t % 500 === 0) sim += 0.04;
    const g = gen();
    if (g.prims.length < minPrims) continue;
    const r = raster(g);
    if (r.size < 3 && minPrims > 1) continue;
    if (rs.some((x) => jaccard(x, r) > sim)) continue;
    rs.push(r);
    out.push(g);
  }
  return out;
}

基準はアルファベットの字で 0.55、音節文字で 0.6 など、仕組みごとに変えています。音節文字や表語文字は字の数が多いので、基準を厳しくしすぎると作れなくなります。500回ごとに基準を 0.04 ずつゆるめて、必ず作り終わるようにしています。

表語文字は、「人・動物・水・植物……」などの意味の部分と、語根の最初の音節を表す音の部分を組み合わせて作ります。同じ音節で始まる語は同じ音の部分を持つので、字の中に音の手がかりが残ります。

7. 「言語らしさ」を統計で確かめる

03-family.png

いちばん作りたかったのはこの部分です。分析では生成器の中身を見ずに、文章の文字列だけを使います。

比べる相手:「文字を混ぜた文章」

統計の値は、それだけを見ても大きいのか小さいのか分かりません。そこで、同じ文字を、同じ語の長さのまま並べ替えた文章 を作って比べます。

function letterShuffle(words, rng) {
  const letters = rng.shuffle(Array.from(words.join('')));
  let i = 0;
  return words.map((w) => { const n = Array.from(w).length; const s = letters.slice(i, i + n).join(''); i += n; return s; });
}

文字の種類と数、語の数と長さはまったく同じなので、差が出れば、それは文字の並び方に規則があるからだと言えます。

8つの項目と、Amyinsu の結果

Amyinsu(60文・327語)の結果を並べます。

項目 測るもの Amyinsu 文字を混ぜた文章
語の頻度 順位と頻度の両対数の回帰(ジップの法則) 傾き −0.84、R² 0.94 傾き −0.24、R² 0.66
同じ語の繰り返し 2回以上出る語が占める割合 78.6% 22.6%
次の文字の予測しやすさ 前後の文字の相互情報量 0.99 ビット 0.19 ビット
現れる文字の組み合わせ 現れた2文字の組の割合 40.6% 68.4%
母音と子音の並び方 語を CV の型にしたときのエントロピー 3.74 ビット(33種類) 5.86 ビット(117種類)
語の並び順 よく出る15語の前後の相互情報量 0.86 ビット(z = 18.6) 0.44 ビット(語の順番を混ぜた文章)
語形変化のあと よく付く語尾3つが付く幹の割合 19.5% 9.9%
よく使う語ほど短いか 頻度と長さのスピアマン順位相関 −0.37 (混ぜても長さは変わらないので比べない)

合格の条件は項目ごとに決めています。たとえば語の頻度は「傾き −1.6〜−0.6 かつ R² > 0.8」、文字の予測しやすさは「混ぜた文章より 0.25 ビット以上大きい」です。8項目のうち6項目以上で合格すれば「規則を持つ言語らしい」と判定します。Amyinsu は8項目すべてで合格しました。

語の並び順だけは、文字ではなく 語の順番 を混ぜた文章と比べます。混ぜ方を20回変えて平均とばらつきを出し、本物の値がばらつきの何倍離れているか(z)を見ます。Amyinsu の z = 18.6 は、語順に強い決まりがあることを示しています。

相互情報量は、前の文字(語)を知ったときに、次の文字(語)の不確かさがどれだけ減るかです。

// 対の並び [x, y] から、x を知ったときに y がどれだけ予測しやすくなるか(相互情報量)
function mutualInfo(pairs) {
  const hy = entropy(counts(pairs.map((p) => p[1])));
  const byX = new Map();
  for (const [x, y] of pairs) {
    if (!byX.has(x)) byX.set(x, []);
    byX.get(x).push(y);
  }
  let hcond = 0;
  for (const ys of byX.values()) hcond += (ys.length / pairs.length) * entropy(counts(ys));
  return hy - hcond;
}

母音をスホーチンの方法で推定する

「母音と子音の並び方」を調べるには、どの文字が母音かを知る必要があります。でも分析は生成器の中身を見ないので、母音が何かは分かりません。

そこで、未知の文字の解読で使われてきたスホーチンの方法を使います。母音と子音は交互に並びやすい、という性質を利用して、ほかの文字と隣り合う回数が多い文字から順に母音と決めていきます。

// スホーチンの方法:隣り合いやすい文字の組から母音を推定する
function sukhotin(words) {
  const letters = Array.from(new Set(Array.from(words.join(''))));
  const m = new Map(letters.map((l) => [l, new Map(letters.map((k) => [k, 0]))]));
  for (const w of words) {
    const a = Array.from(w);
    for (let i = 1; i < a.length; i++) {
      if (a[i] === a[i - 1]) continue;
      m.get(a[i - 1]).set(a[i], m.get(a[i - 1]).get(a[i]) + 1);
      m.get(a[i]).set(a[i - 1], m.get(a[i]).get(a[i - 1]) + 1);
    }
  }
  const sums = new Map(letters.map((l) => [l, Array.from(m.get(l).values()).reduce((s, x) => s + x, 0)]));
  const vowels = new Set();
  for (;;) {
    let best = null;
    for (const l of letters) if (!vowels.has(l) && sums.get(l) > 0 && (!best || sums.get(l) > sums.get(best))) best = l;
    if (!best) break;
    vowels.add(best);
    for (const l of letters) if (!vowels.has(l)) sums.set(l, sums.get(l) - 2 * m.get(l).get(best));
  }
  return vowels;
}

Amyinsu では a e i o u と推定し、答えと完全に一致しました。

解読の手がかりと答え合わせ

分析のタブには、解読の手がかりも出ます。Amyinsu では次のようになりました。

  • よく出る語:si(28回)、di(22回)、ur(19回)、simir(16回)……
  • 語尾の候補:-mi(9つの幹に付く)、-tu(8)、-mir(6)、-nu(5)

答え合わせをすると、-mi(過去)、-tu(未来)、-mir(対格)、-nu(場所格)はどれも本物の接辞でした。よく出る si は「子ども」(登場人物)、ur は複数の小詞です。答えを先に見ずに、この手がかりから文法を推理する遊び方もできます。

独自の文字のほうも数える

上の分析はラテン文字の転写で行いますが、描かれた字の形だけを使う分析もあります。字の種類の数は、古代文字の解読でも最初に数えるものです。

値 Amyinsu
字の種類 61
字の数(のべ) 800
1語あたりの字の数 2.45
1回しか出ない字の割合 16%

字の種類が45以下なら「1字が1つの音」、とても多ければ「表語文字など」、その間なら「1字が1つの音節」と推定します。Amyinsu は「1字が1つの音節」と推定され、答えはアブギダでした。アブギダは子音の字と母音の印の組を1字と数えるので、音節文字に近い数になり、推定は当たりです。

8. 姉妹言語と、音の対応

04-gloss.png

作った言語を祖先(祖語)として、音の変化の規則で子孫の言語を作ります。規則の候補は19種類(語末の母音の脱落、口蓋化、母音間の有声化、ウムラウト、母音の合流など)で、子孫ごとに3〜5個を選び、決まった順に当てます。文法と語の意味は祖語と同じで、音だけが変わります。

Amyinsu からは3つの子孫ができました。

子孫 起きた規則(順に)
Eyisu ウムラウト(次の母音が i なら a→e)、i の前で s→š、語頭の s→h、a→o、子音連続の単純化
Emyinsu e→i(母音の合流)、i の前で t→c、ウムラウト、母音にはさまれた y の消失
Amyins 語末の母音の脱落、語末の m→n、i の前で s→š

規則は前の規則の結果に当たるので、順番が意味を持ちます。たとえば Emyinsu では、「この」を表す ter がまず e→i で tir になり、次に i の前の t が c になって cir になりました。順番が逆なら、t の後ろはまだ e なので、c にはなりません。

意味 祖語 Eyisu Emyinsu Amyins
子ども si ši si ši
運ぶ ma mo ma ma
籠 renu renu rinu ren
暗い rem rem rim ren
広い mintu mitu mintu mint
村 twika twiko twika twik

規則的な対応を見つける

姉妹言語のタブでは、子孫どうしの語を文字ごとにそろえ(編集距離による並べ方)、「どの音がどの音に対応するか」を数えます。たとえば Eyisu と Emyinsu では次のような対応が見つかりました。

対応 回数 その音のうち 条件つきでは
e : i 58 88% 語末で 100%
t : c 22 32% i・e の前で 92%
h : s 7 100% —

「t : c」は全体では 32% ですが、i や e の前に限ると 92% になります。特定の音の前でだけ起きた変化(口蓋化)は、こうして条件で分けると見えてきます。同じ対応が多くの語で繰り返されることから、偶然の似かよりではなく、音の変化の規則があったと分かる、という比較言語学の考え方をそのまま体験できます。

9. 自動の検査

node tests/run.js で、次のようなことを確かめています。

  • 120個の言語で、同じ seed から同じ文章ができる。語順が3種類以上出る。
  • その言語にない文字が出ない。母音が続かない言語で母音が続かない。
  • 動詞の時制・否定の形、名詞の単数と複数、主語と目的語の形が重ならない。
  • 7種の仕組み・5種の見た目・3つの向きがすべて現れ、どの文も描ける。
  • 分析で平均7項目以上、最低でも5項目以上で合格する。でたらめな文字列は2項目以下にとどまる。
  • 姉妹言語の組の 90% 以上で、5回以上・8割以上の規則的な対応が見つかる。
  • 独自の文字の仕組みの推定が 85% 以上当たる(組み合わせ文字を除く)。
  • 「細かく決める」の各項目が指定どおりになり、指定しない部分は変わらない。

生成器と分析を同じ人が作っているので、「分析に合格するように作っただけ」にならないよう、でたらめな文字列が合格しないことも必ず確かめています。

作るときに気をつけたこと

  • 実在の言語や作品を写さない:実在の言語や、特定の作品に出てくる架空の言語の語彙・文法・字形は使っていません。借りているのは、語順の型や文字の仕組みの型だけです。
  • 生成に AI を使わない:言語も文章も、乱数と規則だけで作っています。同じ seed なら、いつ誰が開いても同じ結果になります。
  • 通信しない:ページはブラウザの中だけで動き、外部のライブラリやフォントも使っていません。音声の読み上げは、端末によっては外部と通信するので入れていません。

まだできないこと

  • 関係節・従属節(「〜する人」「〜なので」)はありません。
  • 祖語そのものの語形変化は規則的です。不規則に見える形は、子孫の言語で接辞との境目に音の変化が当たったときにだけ現れます。
  • 姉妹言語の比較は2言語ずつまでで、祖語の形を推定する(再建する)機能はありません。
  • 独自の文字の分析は字の種類の数までで、字の並びの統計はまだありません。

おわりに

ソースは GitHub で公開しています(MIT ライセンス)。確率・手順・分析の基準などの詳しい仕様は docs/spec.md にあります。

開発には、AI のコーディング支援(Claude Code)を使いました。言語や文章の生成そのものには AI を使っていません。

面白い言語ができたら、seed つきの URL を X(@son_jo_sokora)で教えてもらえるとうれしいです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?