mots quotidiens.
Daichi Mochihashi (持橋大地) daichi <at> ism.ac.jp by hns, version 2.10-pl1.

先月 2022年11月 来月
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30

2022年07月15日(金) [n年日記]

#1 参院選からみる東京の「市区ベクトル」

先週, 全国で参議院議員選挙が行われました。今回は特に, 投票を呼びかける動きが多く見られたように記憶しています。私も, 出張先の名古屋で街頭演説を耳にしつつ, 日曜日に東京に戻って投票所に足を運びました。

選挙の結果はともかく, 終了後に NHKの開票速報 を見て気付いたことは, リンク先のページで一番下の「開票所」をクリックした時に現れる, 区ごとの投票結果がかなり異なるということでした。
たとえば, 私の投票した港区では海老澤由紀氏が2位で11.3%の得票率があるのに対し, 新宿区では5位で9.2%にすぎません。一方で港区では山添拓氏は6位で8.2%なのに対し, 新宿区では2位で, 11.8%もの得票率があります。 中野区では, 共産党の山添拓氏が自民党の朝日健太郎氏を抜いて1位になっています。 一番わかりやすいのは, 公明党の竹谷とし子氏が大田区, 北区, 板橋区, 荒川区, 八王子市などでは1位になっているということでしょう。 区だけでなく市部や離島にも大きな違いがあり, 神津島や三宅島では生稲晃子氏が圧倒的な得票で1位, 小笠原村では山本太郎氏が1位になっています。

これは, ある程度は所得や基盤地域(創価学会が多摩地域に大きな基盤を持っているなど)で説明できますが, 2位以降も含めると, 市区の間にはかなり似ているもの, あるいは違うものがあり, 選挙結果から統計的に地域の特徴が見えてきそうです。
そこで, 上のページから各市町村の選挙結果をテキストにコピペし, Pythonでパーズしてデータを作り, 統計的に分析してみました。 *1

・ 投票率の違い

まず, そもそも投票率が利島村の77.74%から瑞穂町の48.44%まで, かなり違いがあることに気づきます。この投票率の違いは, 自然に説明できる程度の分散なのでしょうか。
こういった場合には, 投票率そのものではなく, 各市町村の投票率の, 全体の投票率(56.55%)に対する比率を見るのが有効です。 *2 比が1であれば全体と同じということですので, 対数をとれば, 比が1のときに値は0になります。 地域 a での投票率を p(v|a), 全体での投票率を p(v) (=0.5655) とおくと, これは log p(v|a)/p(v) を計算することになります。これをプロットしたものが以下です。

これからわかるように, 投票率の差はほぼ正規分布で分布しているといってよいでしょう。ただし, 上位の3市町村, つまり利島村, 御蔵島村, 青ヶ島村は正規分布からの外れ値といえます。また, 外れ値とはいえませんが, それに続いて投票率の高い文京区, および逆に投票率の低い瑞穂町, 武蔵村山市, 足立区は正規分布のかなり裾の方に位置しており, 特異度が高い地域であることがわかります。

・ 「市区ベクトル」と相互情報量

さて, それでは本論の, 各市区の特徴を分析してみましょう。 これも同様に, それぞれの候補の, 東京都全体の平均得票率に対する比を計算すればよさそうです。各候補を c, 地域を a としたとき, 地域aでのcの得票率を p(c|a) と 表し, 東京都全体でのcの得票率を p(c) と表すと,

log p(c|a)/p(c)

を計算します。上でも計算したこの量は, 自己相互情報量(PMI)とよばれている量で, 地域 a と候補 c との相関を表す量 PMI(a,c) になっています。 各地域 a (たとえばa=千代田区) についてこれを34人の候補者について並べたベクトル, つまり

( PMI(a,c1), PMI(a,c2), …, PMI(a,c34) )

が, その地域の特徴ベクトルになります。 これは, 実は自然言語処理で知られている word2vec が計算している量と数学的に等価であることが示されています (Levy et al. 2014)。

上のベクトルのままでも地域の特徴は表せますが, word2vec と同様に, これを 次元圧縮することで, 情報をよりコンパクトに凝縮したベクトルを得ることができます。具体的には, PMI(a,c) を縦横に並べた行列を X とおくと,

X = USVT = (U√S)(V√S)T

とXを特異値分解します。このとき, U√SおよびV√Sの各列が, 地域および候補者を表すベクトルになります。この内積が大きければ, 元のデータでPMIの値が大きい, つまりその地域とその候補者の相関が大きいことを意味しているわけです。
特に, 特異値分解で上位2個の固有値および固有ベクトルをとれば, 最も違いの大きな直交する2次元で市区ベクトルを可視化することができます。 これを行ったのが下の図です。(クリックで拡大)

この方法では候補者も, 市区ベクトルと同じ空間に埋め込むことができます (ベクトルが似ていれば, そこで大きな支持があることを意味します)ので, 青字で示したのが候補者ベクトルです。
上の図は中心部にベクトルが集中していますので, 拡大したものが下の図です。 (クリックで拡大)

これを見ると, 図の上部に文京区, 千代田区, 武蔵野市といった文教的な市区が集まっている一方で, 郊外の市区は下の方に配置されていることがわかります。 また, 港区や渋谷区が最も左に, 青梅町や武蔵村山市 (および檜原村や神津島)が右に来ているのは, 横軸は「都会度」を表しているのかもしれません。この分析には, 各市町村の人口や位置はまったく用いていないことに注意してください。

この可視化は, あくまで最初の2個の固有ベクトルを用いたもので, 3個目以降にはもっと有益な情報が隠れている可能性があります。3個の固有ベクトルを用いれば, ベクトルを3次元にプロットすることができます。 これを行ったものが下の図です。

こうして特異値分解(=主成分分析)を使わず, 単に市区ベクトルを t-SNE で2次元に可視化することも可能ですが, その場合は結果は次のようになります。

この場合は非線形な可視化になるため, ベクトルの要素を全部使える一方で, 軸に上下左右の意味はなくなってしまいます。似た市区町村は確かに近い位置に配置されているものの, 本当に文京区と利島村が近いのかには, 疑問の余地がありそうです。

なお, 上の分析は, すべての候補者34人の情報を等価に用いると, 泡沫候補の一致が不必要に高く評価されてしまうため, 得票数上位15人のデータを用いています( (PMI(a,c1),…,PMI(a,c15) )を実際の特徴ベクトルにしています)。この値を変えると結果が変わるため, PMI(a,c)をその候補者の得票確率p(c)で重みづけることが考えられますが, そうするとword2vecとの対応関係がなくなるため, 単語ベクトルに関して知られている様々な理論的な性質が保証されるかどうかについては, 検証が必要になりそうです。

なお, 分析に用いたデータおよびPythonスクリプトは, すべてGithubのレポジトリ https://github.com/daiti-m/tokyo2022 にて公開しています。


*1: NHKのページでは選挙結果はJavascriptで生成されていますが, それを解読して元データを得ることは, 少し見た限りでは容易ではないようでしたので, 地道に手作業でテキスト化することにしました。
*2: 投票率は確率ですが, 確率の対数は情報量としての意味を持ちますから, 情報量の引き算を行って差を見ることは, 確率の比を計算してから対数をとることと等価になります。

2021年04月14日(水) [n年日記]

#1 NPMIによる教師なしフレーズ認識

Mikolov+(2013)の有名な Word2Vecの論文 では, 単語ベクトルを作る際に, "New York" や "Toronto Maple Leafs" (アイスホッケーチーム)の意味は要素である "new" や "maple" "leafs" とは基本的に 関係ないので, 先にフレーズを認識して "new_york", "toronto_maple_leafs" と 単語をまとめてからWord2Vecを適用する方法が述べられています。 もちろん固有表現認識(NER)を動かせばできますが, NERは事前に人が作成した教師データに依存する ため, 教師データを使わない方法として, word2vecの論文では単語vと単語wがフレーズとなる スコアを
score(v,w) = (n(v,w) - δ)/(n(v)*n(w))
とする, という方法が述べられています((6)式)。 ここでn(v,w),n(v),n(w)はバイグラムおよびユニグラム頻度で, δは, 頻度の低いペアが上位に来るのを抑えるためのディスカウント係数です。
このスコアが閾値以上になる単語バイグラムを一単語としてまとめれば, フレーズを自動的に認識することができます。

上のスコアは, 確率として考えると, ユニグラムの総和をNとして p(v,w)=n(v,w)/N, p(v)=n(v)/N, p(w)=n(w)/N なので, δの部分を除くと,

score(v,w) = n(v,w)/(n(v)*n(w)) = (p(v,w)*N)/(p(v)*N * p(w)*N) = p(v,w)/(p(v)*p(w)*N)
となり, ほぼ, 自己相互情報量 PMI(v,w) = log p(v,w)/(p(v)*p(w)) と同じスコアを計算していることになります。

ただしよく見ると, 元の式には 1/N というファクターが入っており, これは コーパスに依存する ので, スコアが基本的にコーパスの長さに依存したものになってしまいます。 コーパスを固定して閾値を探索するのであれば問題ありませんが, 閾値の意味がわかりにくい上, 長さの違うテキストを使って結果を比べたい場合などでは, 同じ基準のフレーズにはならなくなってしまいます。

元のスコアにNを掛けてコーパス非依存にすることもできますが, そもそもこのスコアは本質的にPMIを計算しているので,

という欠点を解消した, Normalized PMI (NPMI)(Bouma 2009) を使えばよいような気がします。NPMIは, 次のようにして定義されます。
NPMI(v,w) = log p(v,w)/(p(v)*p(w)) / (-log p(v,w))
NPMIは -1≦NPMI(v,w)≦1 の範囲で定義され, 1のときにvとwは完全に相関, -1のときに完全に逆相関という, 非常にわかりやすい基準になっています。 しかもPMIにあった「p(v)やp(w)が非常に小さい時にスコアがインフレしてしまう」 という欠点も解消されているので, こうすると, word2vecの元論文にあったヒューリスティックなディスカウント係数 δも不要になります。

というわけで, 少しスクリプトを書いて, フレーズの自動認識を試してみました。 核心部だけ書くと, unigram, bigram にそれぞれユニグラムとバイグラムの頻度が 辞書として保存されているとき, 単語バイグラム(v,w)がフレーズとなるかどうかは次のようにして 判定できます。

def compute_phrase (unigram, bigram, threshold=0.5):
    N = sum (list(unigram.values()))
    phrases = {}
    for bi,freq in bigram.items():
        if freq > 1:
            v = bi[0]; w = bi[1]
            npmi = (log(N) + log(freq) - log(unigram[v]) - log(unigram[w])) \
                    / (log(N) - log(freq))
            if npmi > threshold:
                phrases[bi] = npmi
    return phrases
閾値は, 試したところでは0.5くらいにするのがよいようです。

これをテキストについて1パス動かすと2単語のフレーズが得られ, それを入力にして2パス目を動かすと2〜4単語のフレーズが得られます。 同様にしてnパス動かすと, 2〜2^n単語からなるフレーズが得られます。
日本語版text8 (100MB, 56万文, 1694万語)について4パスで動かしてみたところ, 次のようになりました。

甲_越_同盟 ( こう え つどう めい ) は 、 天正 7 年 ( 1579 年 ) に 甲斐 の
戦国_大名 武田_勝頼 と 越後 の 戦国_大名 上杉_景勝 と の 間 で 成立 し_た 同盟 。
定義 上 は 超_硬_合金 と 呼ば_れる 炭化_タングステン ( wc ) を 主成分 と し_た
もの も 含ま_れる が 、 これ を 別 の もの として 扱う こと が 多い 。
ここでは「甲越同盟」「戦国大名」「武田勝頼」「した」「超硬合金」「炭化タングステン」「呼ばれる」「含まれる」 などが, 統計的に自動的にフレーズとして認識されています。 通常のNERでは教師データに依存するため, 「甲越同盟」「超硬合金」などを認識するのは難しいと思われるため, これは非常に有用だと思います。 もちろんこれは word2vec の元論文のときからできていたわけですが, 上に書いたように基準がデータ依存で, 低頻度語に弱い(&それを避けるためのヒューリスティックなパラメータに依存する)という問題があったため, NPMI>0.5という基準は非常にわかりやすく, 有用なのではないかと思います。

なお, 4パスなので原理的には16単語までの単語がフレーズとして認識される可能性が あるわけですが, 調べてみると, 「福島_第_一_原子力_発電_所」などはもちろん フレーズとして認識されている他, 非常に長いものとしては以下のようなものがありました。(一部抜粋)

第_二_次_世界_大戦_末期
福島_第_一_原子力_発電_所_事故
連合_国軍_最高_司令_官_総_司令_部
全国_高等_学校_野球_選手権_大会
学研_奈良_登美_ヶ_丘_駅
国際_連合_安全_保障_理事_会
ユーゴスラビア_社会_主義_連邦_共和_国
全国_高等_学校_サッカー_選手権_大会
ニューヨーク_州立_大学_バッファ_ロー_校
全日本_実業_団_対抗_駅伝_競走_大会
芸術_選奨_文部_科学_大臣_賞_受賞
mf_文庫_j_ライト_ノベル_新人_賞
〜_っと_!_お_ジャ_魔女_どれ_み
ufc_世界_女子_ストロー_級_タイトルマッチ
pc_エンジン_オール_カタログ_'_93
独立_行政_法人_産業_技術_総合_研究所
国際_連合_難民_高等_弁務_官
ほっ_かい_どう_さ_っぽ_ろ
ほっ_か_ほっ_か_亭_総本部
『_機動_戦士_ガン_ダム_seed
学_研究_科_修士_課程_修了
御_樋_代_木_奉_曳式
絶叫_カオス_傑作_選_大声_クイズ_vs_谷_桃子_vs_ヒム_子
「福島_第_一_原子力_発電_所_事故」は7単語, 「連合_国軍_最高_司令_官_総_司令_部」は8単語がまとまって1つのフレーズと認識されています。「御樋代木奉曳式」(みひしろぎほうえいしき)は木曽で切り出された御料木が伊勢神宮に運ばれる儀式のことのようですが, こうした特殊な語彙のため形態素解析に失敗していても, フレーズ認識で一単語と認識することができています。
ただ今回, 先頭から最長一致でフレーズ化しているため, 「〜_っと_!_お_ジャ_魔女_どれ_み」「『_機動_戦士_ガン_ダム_seed」は本来は, 後から伸ばすことでもっと正確に認識できる名前ではないかと思います。 なお, 「絶叫_カオス_傑作_選_大声_クイズ_vs_谷_桃子_vs_ヒム_子」は何と12単語からなる フレーズ(!)で, これは こういうバラエティ番組のDVD の名前であるようです。
認識された長いフレーズのリストの例は, こちらに置いておきました。 バイグラムを結合するときの頻度を10以上などにすると, より綺麗なフレーズが 得られますが, 頻度の低い長いフレーズは認識されなくなります。 頻度を1以上にした場合の, 面白い長いフレーズの例を, 自分用メモも兼ねて こちらに置いておきます。

下に, フレーズを認識するPythonスクリプトを置いておきます。 使い方はそのまま実行してヘルプを読むか, 中身をご覧下さい。


2 days displayed.
タイトル一覧
カテゴリ分類
 なかのひと
Powered by hns-2.10-pl1, HyperNikkiSystem Project