言語の記憶性

記事の内容


言語に関する本[2]が面白かったので, 自分でも実験してみました. 言語の記憶性を数学的に検証します. 実データを処理するのは面倒なので, 本来の言語に近いモデルを用います.

分布の記憶性

生存時間解析などで用いられる2つの分布, 指数分布とWeibull分布を導入します.

指数分布

【指数分布】

指数分布\(\mathrm{Exp}(\lambda)\)の確率密度関数は次式である.

\begin{equation} p(x) = \frac{1}{\lambda}\mathrm{exp}\left( -\frac{x}{\lambda}\right) \end{equation}

\(\mathrm{P}\)は確率測度とします. 指数分布に従う確率変数\(X\)について, 次の条件付き確率を調べます.

\begin{equation} \mathrm{P}(X\gt s+t\mid X\gt s) = \frac{\mathrm{P}(X\gt s+t, X\gt s)}{\mathrm{P}(X\gt s)} = \mathrm{exp}\left(-\frac{t}{\lambda}\right) = \mathrm{P}(X\gt t) \end{equation}

例えば, \(X\)が故障までにかかる時間だとすれば, \(s\)まで故障せず, さらに時間\(t\)だけ持ち堪える確率は, \(s\)に依存しません. この, 過去の履歴に依らない性質を無記憶性と呼びます. この性質から, 指数分布はランダムな現象のモデル化に適しているとされます. 逆に, 右辺の超過確率を計算して指数関数の形になれば, ランダムな現象だと判定できそうです.

Weibull分布

以下のWeibull分布は, 指数分布とは異なり, 無記憶性を示しません. もちろんパラメータの値に依りますが...

【Weibull分布】

Weibull分布\(\mathrm{Weibull}(\eta,\lambda)\)の確率密度関数は次式である.

\begin{equation} p(x) = \frac{\eta}{\lambda}\left(\frac{x}{\lambda}\right)^{\eta-1}\mathrm{exp}\left\{ -\left(\frac{x}{\lambda}\right)^{\eta}\right\} \end{equation}

擬似的な言語データの作成

ここから自然言語の話に移ります. 実データを処理するのは面倒なので, 本来の言語に近いデータを人工的に作ります. [2]によると, Barabási-Albertネットワーク上のランダムウォークから生成されるデータが自然言語に割と近いらしいです. まずは, Barabási-Albertネットワークの説明から入ります.

Barabási-Albertネットワーク

Barabási-Albertネットワーク(以下, BAネットワーク)は, 少数のノードが多くのリンクを持ち, 多くのノードが少数のリンクを持つ複雑ネットワークです. "rich gets richer"をモデル化するのに役立ちます. 自然言語の場合, 少数の単語のみが頻出することが分かっています(Zipfの法則). 言語のモデル化にBAネットワークは適しているでしょう.

BAネットワークの作り方を説明します. まず, 最初にノードがいくつか存在するとします. これらのノードに新規のノードを繋げます. 新規のノードからは, \(m\)本のリンクが出ており, 既存の, どのノードに繋ぐかは確率的に決めます. 既に持っているリンク数に比例する確率で繋げるノードを選択します. この操作を繰り返します.

ネットワーク作成の様子を, アニメーションで示します. \(m=1\)の場合をノード数が10に達するまで反復しました. 最初に出現したノードほど, 多くのリンクを持っています.

【コード3の実行結果】

BAネットワーク上のランダムウォーク

先ほど作成したBAネットワーク上でランダムウォークさせます. 言語の例で言うと, 各ノードは単語に対応します. 歩く主体がノードに達するごとに, そのノードに対応する単語が発生すると考えます. ノードに移動する確率は, 移動先のリンク数に比例する確率で選択します. ここでは, 50回ほど歩かせます. つまり, 50単語からなる文書が作成されるイメージです. アニメーションを以下に示します. 星マークが歩く主体です.

【コード4の実行結果】

言語の記憶性

以下, BAネットワーク上のランダムウォークから発生させた人工データを本物の単語データと思って話を進めます. [2]に従って, 10万ノードのBAネットワーク上で100万回ランダムウォークさせます. このデータを用いて, 文書中の単語の出現がランダムと言えるかどうかを検証します.

1番目のノードに対応する単語(以下, 単語1)は頻出と期待できます. この単語1に注目して解析します. まず, 単語1の出現具合を数量化します. 単語1の出現間隔は, 出現具合の指標として, 1つの自然な選択です. この出現間隔の分布を調べてみましょう.

データがランダムに発生しているかどうかは, 指数分布に従うかどうかで峻別できるのでした. 単語1の出現間隔に対して, 間隔の頻度から確率を計算し, 超過確率を計算してランダム性を調べます. 計算結果を下図にプロットしました. 横軸は出現間隔の長さ, 縦軸はその出現間隔よりも長い間隔で単語1が現れる確率を表します. 擬似的なデータが青線です. オレンジの線は, 単語列をシャッフルしたものです. シャッフルした列は単語1の出現間隔がランダムであると期待できます.

【コード7の実行結果】

オレンジのシャッフル列はほぼ直線です. 縦軸は対数スケールですから, おおよそ指数的に減衰する形です. 超過確率が指数関数となるので, シャッフル列の出現間隔の分布は指数分布と考えられます. 一方, 青の単語列は, 指数分布からの乖離を示します. 実はこの青のカーブはWeibull分布であることが知られています. ここまでの話をまとめれば, シャッフル列は無記憶性を示し, 単語列は記憶を保持します.

我々の用いる自然言語には, このように記憶が関わっており, 偶然性のみでは説明できません. 系列の過去の履歴にも依存し, 長い間隔を隔てて単語が再び出現する可能性が十分あります. 今回は擬似的なデータを用いましたが, 実際の言語データでも成り立つはずです(未確認).

コード

【Juliaコード1; 初期化】
#mathematics
using LinearAlgebra
using SparseArrays

#statistics
using Random
using StatsBase
using Statistics
using Distributions

#dataframe
using DataFrames

#visualize
using Plots
pyplot()

#macros
using ProgressMeter
using UnPack
【Juliaコード2; 関数定義】
#normalize
function normalize(vec)
    S = sum(vec)
    if S>0
        return vec/sum(vec)
    else
        vec[1] = 1
        return vec
    end
end
    
#initialize the graph
function initialize!(adj,m)
    for j in 1:m
       for k in j+1:m
            adj[k,j] = 1
            adj[j,k] = 1
        end
    end
end

#choose m nodes
function choose_nodes(adj,m)
    probs = normalize(sum(adj,dims=2)[:])
    return rand(Categorical(probs),m)
end

#update adjacency matrix
function update_adj!(adj,nodes,v)
    adj[nodes,v] = ones(Int64,m)
    adj[v,nodes] = ones(Int64,m)
end

#V nodes each of which has m edges
function BAmodel(m,V)
    adj = spzeros(Int64,V,V)
    nodes = zeros(Int64,m)
    initialize!(adj,m)
    @showprogress for v in m+1:V
        nodes = choose_nodes(adj,m)
        update_adj!(adj,nodes,v)
    end
    return adj
end

#choose the next node
function walk(current_node,adj,edges)
    nodes = adj[:,current_node].nzind
    probs = normalize(edges[nodes])
    idx = rand(Categorical(probs))
    return nodes[idx]
end

#random walk
function myRW(adj,m,V,N)
    edges = sum(adj,dims=2)[:]
    current_node = 1#rand(1:V)
    path = zeros(Int64,N)
    path[1] = current_node
    @showprogress for n in 2:N
        current_node = walk(current_node,adj,edges)
        path[n] = current_node
    end
    return path
end
【Juliaコード3; ネットワークの作成】
#set the random seed
Random.seed!(42)

#create the BA network
m = 1
V = Int(1e1)
adj = BAmodel(m,V)

#animation
θ = 2π/V
ts = 0:0.1:2π+0.1
coss = [cos(i*θ) for i in 0:V-1]
sins = [sin(i*θ) for i in 0:V-1]
plot(cos.(ts),sin.(ts),xlim=(-1.2,1.2),ylim=(-1.2,1.2),label=false,ls=:dot,aspect_ratio=:equal,title="BA network")
plot!(coss[1:1],sins[1:1],st=:scatter,color=:skyblue,markersize=10,label=false)
anim = @animate for v in 2:V
    plot!(coss[1:v],sins[1:v],st=:scatter,color=:skyblue,markersize=10,label=false)
    to_v = adj[v,1:v].nzind[1]
    plot!([coss[v],coss[to_v]],[sins[v],sins[to_v]],color=:blue,label=false)
end
gif(anim,"figs-NLPSD/anim1.gif",fps=1)
【Juliaコード4; ランダムウォーク】
#set the random seed
Random.seed!(42)

#create the BA network
m = 1
V = Int(1e1)
adj = BAmodel(m,V)

#BA network
θ = 2π/V
ts = 0:0.1:2π+0.1
coss = [cos(i*θ) for i in 0:V-1]
sins = [sin(i*θ) for i in 0:V-1]
ba_plot = plot(cos.(ts),sin.(ts),xlim=(-1.2,1.2),ylim=(-1.2,1.2),label=false,ls=:dot,aspect_ratio=:equal,title="RW on BA network")
plot!(coss,sins,st=:scatter,color=:skyblue,markersize=10,label=false)
for v in 2:V
    to_v = adj[v,1:v].nzind[1]
    plot!([coss[v],coss[to_v]],[sins[v],sins[to_v]],color=:blue,label=false)
end

#random walk on BA network
N = 50
history = myRW(adj,m,V,N)
anim = @animate for n in 1:N
    plot(ba_plot)
    plot!([coss[history[n]],],[sins[history[n]],],st=:scatter,color="red",markersize=20,markershape=:star,label=false)
end
gif(anim,"figs-NLPSD/anim2.gif",fps=1)
【Juliaコード5; データ作成】
#set the random seed
Random.seed!(42)

#create the BA network
m = 1
V = Int(1e5)
adj = BAmodel(m,V)

#create word sequence
N = Int(1e6)
W = myRW(adj,m,V,N)
n_unique = length(unique(W))

#data
data = (W=W,V=V,N=N,n_unique=n_unique)
println("The number of vocabrary = $(V). ")
println("The document has $(N) words in total. ")
println("The document has $(n_unique) unique words in total. ")
【Juliaコード6; 関数定義】
#word dataframe
word_df(W) = DataFrame(word_id=W)

#word frequency dataframe(sorted)
function word_freq_df(df)
    word_cnt_df = combine(nrow,groupby(df,:word_id))
    return sort(word_cnt_df,:nrow,rev=:true)
end

#unify rare wards lowest 1/Ψ
function unify_rare_words(data,my_word_freq_df,Ψ)
    @unpack W,N,V,n_unique = data
    Wrare = zeros(Int64,N)
    rare_word_id = maximum(W)+1
    sorted_freqs = sort(my_word_freq_df,:nrow)
    rare_words = sorted_freqs[cumsum(sorted_freqs.nrow) .< Int(floor(N/Ψ)),:].word_id
    for n in 1:N
        if W[n] in rare_words
            Wrare[n] = rare_word_id
        end
    end
    return Wrare, rare_word_id
end

#interval sequence for a specific word
function Qv_df(W,v)
    is_v_idx = SparseVector(W.==v).nzind
    Qv = is_v_idx[begin+1:end] - is_v_idx[begin:end-1]
    df = DataFrame(Qv=Qv)
    freq_df = combine(nrow,groupby(df,:Qv))
    return df, sort(freq_df,:nrow)
end

#cumulative distribution lower than q
cumv(q,Qv_freq_df) = sum(Qv_freq_df[Qv_freq_df.Qv.<=q,:].nrow)
function cumvvec(qs,W,v)
    freq_v = sum(W.==v)
    n_qs = length(qs)
    cums = zeros(n_qs)
    _, Qv_freq_df = Qv_df(W,v)
    @showprogress for j in 1:n_qs
        cums[j] = cumv(qs[j],Qv_freq_df)
    end
    return cums/(freq_v-1)
end
【Juliaコード7; 超過確率】
#frequency
my_word_df = word_df(W)
my_word_freq_df = word_freq_df(my_word_df)
qs = 1:60

#visualize (puseudo words data)
excess_probs = 1 .- cumvvec(qs,W,1)
fig = plot(qs,excess_probs,xlabel="interval",ylabel="prob",yscale=:log10,color=:blue,
    marker=:circle,markerstrokewidth=0,label="pseudo words")

#visualize (shuffle words)
shuffled_W = shuffle(W)
excess_probs = 1 .- cumvvec(qs,shuffled_W,1)
plot!(qs,excess_probs,xlabel="interval",ylabel="prob",yscale=:log10,
    marker=:circle,markerstrokewidth=0,label="shuffled")
savefig(fig,"figs-NLPSD/fig.png")
参考文献

      [1]久保川達也, 現代数理統計学の基礎, 共立出版, 2019
      [2]田中久美子, 言語とフラクタル 使用の集積の中にある偶然と必然, 東京大学出版会, 2021
      [3]A.L.Barabási, Network Science, Cambridge University Press, 2016