Rを使って短歌の「詩的度」を測る

あきる(paithiov909)

2024年2月24日

誰?

どっちが「詩的」に見える?

水星をのぞむ明け方 コンビニのFAXに「故障中」の張り紙

(五島諭『緑の祠』)

2月5日夜のコンビニ 暴力を含めてバランスを取る世界

(永井祐『日本の中でたのしく暮らす』)

  • 短歌は、しばしば前後2つの部分から構成されている(「句切れ」がある)
  • そうした作品では、上句と下句とのあいだに意味的な飛躍がある場合がある
  • 「詩的」に感じられる「飛躍がある」をハックできれば、よい短歌がつくれるかも?

アイデア

ラスボスの手前でセーブするように無意味に入るファミリーマート

水槽にさかなを容れて飼うようにたぶんこのまま付かない既読

  • 「詩的」のひとつの捉え方
    =巧みな比喩的表現1が用いられている
  • 逸脱的な共起がなされている=前後の意味的な隔たりが大きい場合、その表現は「詩的」だと期待できる

モチベーション

  • 前後2つの文(単語列)のあいだの意味的な隔たりを定量的に評価できれば、その短歌が「詩的」かを判断する目安にできるかもしれない
    • 単語列間の非類似度(距離)を計算すればよさそう
    • そうした非類似度を計算するには、単語列をその意味が反映されたベクトル表現にすればよい

自然言語処理における埋め込み

単語や文などを、それらの意味を表現するベクトル空間にマッピングする手法、そうした手法で得られるベクトル表現のことを 埋め込み(embeddings) という

  • 似た意味の語彙はベクトル空間のなかでも「近い」位置に写像される、「意味の演算」が可能であるといった特徴がある
  • 今回はchiVeという単語埋め込みを使いつつ、単語列間の WRD を計算することによって「単語列間の非類似度」を得る

chiVeの埋め込みの例

chiVeの単語ベクトルはこんな感じ。300次元のベクトルが得られる

コード
require(apportita) # paithiov909/apportita
chive_path <- path.expand("~/Downloads/models/magnitude/chive-1.2-mc90.magnitude")
conn <- magnitude(chive_path)
query(conn, c("新しい", "朝", "が", "来た"))
# A tibble: 4 × 301
  key      dim_0   dim_1  dim_2    dim_3   dim_4  dim_5   dim_6   dim_7   dim_8
  <chr>    <dbl>   <dbl>  <dbl>    <dbl>   <dbl>  <dbl>   <dbl>   <dbl>   <dbl>
1 が     -0.0990  0.102  0.0315  0.0344  -0.0516 0.0372 0.00477 -0.0513 0.0875 
2 新しい -0.0948 -0.0996 0.0667  0.00933 -0.0159 0.0456 0.111    0.0369 0.0713 
3 朝     -0.0754 -0.0258 0.145   0.0443   0.0791 0.0387 0.0115   0.0184 0.0777 
4 来た   -0.0675 -0.0613 0.0530 -0.0104   0.116  0.0115 0.0545  -0.0185 0.00332
# ℹ 291 more variables: dim_9 <dbl>, dim_10 <dbl>, dim_11 <dbl>, dim_12 <dbl>,
#   dim_13 <dbl>, dim_14 <dbl>, dim_15 <dbl>, dim_16 <dbl>, dim_17 <dbl>,
#   dim_18 <dbl>, dim_19 <dbl>, dim_20 <dbl>, dim_21 <dbl>, dim_22 <dbl>,
#   dim_23 <dbl>, dim_24 <dbl>, dim_25 <dbl>, dim_26 <dbl>, dim_27 <dbl>,
#   dim_28 <dbl>, dim_29 <dbl>, dim_30 <dbl>, dim_31 <dbl>, dim_32 <dbl>,
#   dim_33 <dbl>, dim_34 <dbl>, dim_35 <dbl>, dim_36 <dbl>, dim_37 <dbl>,
#   dim_38 <dbl>, dim_39 <dbl>, dim_40 <dbl>, dim_41 <dbl>, dim_42 <dbl>, …

Word Rotator’s Distance(1)

  • 2つの文\(a, b\)のあいだの最適輸送を考えて、文の非類似度を計算する手法
    • 文の重み(確率変量)として、単語ベクトルのL2ノルムを正規化したものを使う
    • このとき、輸送コストとして、単語ベクトル間のコサイン距離1を使う
  • 詳しくは [2004.15003] Word Rotator’s Distance を読んでください

Word Rotator’s Distance(2)

\(a\)の重みについて、文\(b\)の重みへと移し替えるような対応づけ(超球面上での回転)を考えて、得られたコストの総和の最小値を2つの文の非類似度とする

図は[2004.15003] Word Rotator’s Distanceから抜粋

Word Rotator’s Distance(3)

輸送コスト\(C\)は距離行列なのでWasserstein距離1としてWRDを計算できる

コード
a <- runif(6 * 300) |> matrix(6, 300) |> scale(center = FALSE)
b <- runif(4 * 300) |> matrix(4, 300) |> scale(center = FALSE)
# コサイン距離
d <- 1 - proxyC::simil(a, b, method = "cosine", use_nan = FALSE)
w_a <- (\() { x <- sqrt(rowSums(a^2)); x / sum(x) })()
w_b <- (\() { x <- sqrt(rowSums(b^2)); x / sum(x) })()
transport::wasserstein(w_a, w_b, p = 1, costm = d, prob = TRUE)
[1] 0.2138897

\[ \begin{align}\begin{aligned}W_p(a,b; C):=(\min_{P \in U(a, b)} \sum_{i}\sum_{j}{c(x_{i},y_{j})^{p}}{P_{ij}})^\frac{1}{p}\end{aligned}\end{align} \]

WRDを計算してみる(1)

先ほどの短歌について、全角スペースで区切る場合、五島の短歌のほうが前後の非類似度が大きい

コード
sudachi <- sudachir::rebuild_tokenizer(mode = "C")
form <- \(x) {
  unlist(sudachir::form(x, type = "normalized", pos = FALSE, instance = sudachi))
}
wrd <- \(conn, s1, s2) {
  purrr::map2_dbl(s1, s2, \(chunk1, chunk2) {
    chunk1 <- form(chunk1)
    chunk2 <- form(chunk2)
    tryCatch(
      apportita::calc_wrd(conn, chunk1, chunk2),
      error = \(e) { NA }
    )
  }, .progress = FALSE)
}
# 五島の短歌
wrd(conn, "水星をのぞむ明け方", "コンビニのFAXに「故障中」の張り紙")
[1] 0.7187364
# 永井の短歌
wrd(conn, "2月5日夜のコンビニ", "暴力を含めてバランスを取る世界")
[1] 0.67444

WRDを計算してみる(2)

一方で、WRDは単語列の長さの影響を受けるため、区切る位置によって値が変わってしまう。どのように区切るべきだろうか?

wrd(conn, "ラスボスの手前でセーブするように", "無意味に入るファミリーマート")
[1] 0.5455914
wrd(conn, "ラスボスの手前でセーブするように無意味に入る", "ファミリーマート")
[1] 0.8785529

短歌を文節で区切る(1)

短歌を人手で2つの文に区切るのではなく、可能な区切りすべてについて文のペアと見なし、それらのWRDを全部計算してみることにする

コード
strj_split_boundaries <- \(x) {
  stringi::stri_split_boundaries(
    x, # ICU>=73.2でビルドしたstringiが必要
    opts_brkiter = stringi::stri_opts_brkiter(locale = "ja@lw=phrase;ld=auto")
  )
}
split_kugire <- \(x) {
  purrr::map(strj_split_boundaries(x), \(elem) {
    if ((len <- length(elem)) < 2) {
      return(NA_character_)
    } else {
      sapply(seq_len(len - 1), \(i) {
        s1 <- paste0(elem[1:i], collapse = "")
        s2 <- paste0(elem[(i + 1):len], collapse = "")
        paste(s1, s2, sep = "\t")
      })
    }
  })
}
[[1]]
[1] "水槽に\tさかなを容れて飼うようにたぶんこのまま付かない既読"
[2] "水槽にさかなを\t容れて飼うようにたぶんこのまま付かない既読"
[3] "水槽にさかなを容れて\t飼うようにたぶんこのまま付かない既読"
[4] "水槽にさかなを容れて飼うように\tたぶんこのまま付かない既読"
[5] "水槽にさかなを容れて飼うようにたぶん\tこのまま付かない既読"
[6] "水槽にさかなを容れて飼うようにたぶんこのまま\t付かない既読"
[7] "水槽にさかなを容れて飼うようにたぶんこのまま付かない\t既読"
[8] "水槽にさかなを容れて飼うようにたぶんこのまま付かない既\t読"

短歌を文節で区切る(2)

ふつうにWRDを集計した場合
# A tibble: 4 × 6
  doc_id                                            max median  mean   min     n
  <fct>                                           <dbl>  <dbl> <dbl> <dbl> <int>
1 水星をのぞむ明け方コンビニのFAXに「故障中」の…  0.816  0.631 0.645 0.525     8
2 2月5日夜のコンビニ暴力を含めてバランスを取る世… 0.752  0.665 0.652 0.596     7
3 ラスボスの手前でセーブするように無意味に入るフ… 0.879  0.543 0.611 0.488     6
4 水槽にさかなを容れて飼うようにたぶんこのまま付… 0.986  0.543 0.625 0.494     8
前後ともに5文字以上あるペアだけにしぼった場合
# A tibble: 4 × 6
  doc_id                                            max median  mean   min     n
  <fct>                                           <dbl>  <dbl> <dbl> <dbl> <int>
1 水星をのぞむ明け方コンビニのFAXに「故障中」の…  0.719  0.608 0.630 0.525     5
2 2月5日夜のコンビニ暴力を含めてバランスを取る世… 0.674  0.633 0.634 0.596     4
3 ラスボスの手前でセーブするように無意味に入るフ… 0.879  0.543 0.611 0.488     6
4 水槽にさかなを容れて飼うようにたぶんこのまま付… 0.553  0.543 0.535 0.494     5

前後のWRDが大きい区切り方

字数の多い名詞句があったりして前後の語数に偏りがあると、WRDが大きくなってしまう

wrd text author
0.852 1千万円あったらみんな友達にくばるその僕のぼろぼろの/カーディガン 永井祐
0.832 こないだは祠があったはずなのにないやと/座りこむ青葉闇 五島諭
0.831 カードキー/忘れて水を買いに出て僕は世界に閉じ込められる 木下龍也

前後のWRDが小さい区切り方

同じような語句が繰り返し用いられている短歌では、前後のWRDが小さくなりやすい

wrd text author
0.276 おもうからあるのだそこにわたくし/はいないいないばあこれが顔だよ 望月裕二郎
0.281 立てるかい君が背負っている/ものを君ごと背負うこともできるよ 木下龍也
0.282 逢えばくるうこころ逢わなければ/くるうこころ愛に友だちはいない 雪舟えま

WRDの分布(1)

25名の作者による現代短歌、計72首について、同様の方法でWRDを計算した。作者名の後ろの数字はデータセットに含まれるその作者による作品の数

WRDの分布(2)

短歌投稿サイトの短歌74,844首と、発表者のツイート100件について、同様の方法でWRDを計算した

まとめ

  • 短歌の「詩的度」の一側面を測れそうな尺度として、短歌を前後2つに区切った「文」のあいだのWRDを計算した
  • 評価用データセットのようなものはないので客観的に評価できないが、感覚的には悪くない尺度に思われた
  • WRDは計算に時間がかかるので、短歌がたくさんあると大変
  • この方法で計算したWRDは、値が大きく・小さくなりやすいつくりに癖がありそうなので、短歌の「詩的度」を測るには別の観点も必要かも

Enjoy✨