読者です 読者をやめる 読者になる 読者になる

「ぬくもり」と「抱きしめたい」が似ているのか、類似度判定してみた

モチベーション

平浩二の「ぬくもり」とミスチル「抱きしめたい」の歌詞は本当に似ているのか
ちょうどデータサイエンスを勉強している身、類似判定をするいい機会。
練習のために類似判定をやってみた。
※データサイエンスは初学者です。至らない点があったら温かい目で見守ってください

類似度判定のやりかた

  1. 歌詞を形態素解析して、単語レベルまで分解する
  2. コサイン類似度で類似度判定を実施する
  3. 他の曲も比較して、値が妥当か検証する

歌詞を形態素解析して、単語レベルまで分解する。

まずはMecabを使って、歌詞を形態素解析する。
ただし、助詞 助動詞は除く

「ぬくもり」と「抱きしめたい」の歌詞を形態素解析した結果がこちら
この時点でもうなんだか似ている気がする…

単語(ぬくもり) 出現回数(ぬくもり) 単語(抱きしめたい) 出現回数(抱きしめたい)
7 3
4 3
4 ぬくもり 3
4 3
4 2
想い 3 2
2 2
2 人波 2
抱きしめ 2 あふれ 2
歩い 2 2
2 ショー 2
受け止め 2 ウインドウ 2
2 みとれ 2
2 ふい 2
溢れる 2 つまずい 2
いる 2 その 2
あの 2 受け止め 2
1 両手 2
両手 1 抱きしめ 2
落とす 1 出会っ 1
約束 1 同じ 1
1 霧雨 1
霧雨 1 降る 1
1 かがやく 1
忘れ 1 1
閉じれ 1 1
浮かん 1 閉じれ 1
同じ 1 浮かん 1
灯す 1 くる 1
探し 1 あの 1
1 まま 1
素敵 1 1
全て 1 1
1 つらい 1
静か 1 1
1 越え 1
人波 1 ゆける 1
震え 1 1
心配 1 きり 1
1 1
笑顔 1 約束 1
消え 1 たち 1
傷跡 1 覚え 1
傷つき 1 いる 1
出会っ 1 1
終わっ 1 愛しい 1
指切り 1 横顔 1
込み 1 1
行こ 1 濡れ 1
行き 1 1
見とれ 1 つなぎ 1
1 語ろ 1
1 想い出 1
1 1
泣き 1 いつも 1
1 そば 1
溢れ 1 居る 1
育て 1 1
1
ゆこ 1
もう 1
まま 1
ふい 1
ひそめ 1
ぬくもり 1
ならべ 1
ない 1
どんな 1
つまずい 1
そば 1
その 1
そっと 1
ずっと 1
ショウ 1
しまう 1
さみしい 1
こぼれ 1
この 1
こと 1
けむる 1
くる 1
1
キャンドル 1
1
ウィンドウ 1
いつも 1
いける 1
あずけ 1
あげ 1
Woh 1
1

コサイン類似度を判定する

歌詞を形態素解析した結果を特徴ベクトルとしてコサイン類似度判定する、
判断材料として、他の曲も追加

使用した曲は下記の通り

  • ぬくもり
  • 抱きしめたい
  • 終わりなき旅(ミスチル)
  • tomorrow never know(ミスチル)
  • ETERNAL BLAZE(水樹奈々)
  • ファッションモンスター(きゃりーぱみゅぱみゅ)

コサイン類似度の結果

1に近づくほど類似していて、0に近づくほど類似していない。
大体、0.5~0.7くらいになると似てるって話になるらしい。

- ぬくもり 抱きしめたい 終わりなき旅 Tomorrow never knows ETERNAL BLAZE ファッションモンスター
ぬくもり 1.0 0.47209 0.12586 0.13892 0.13579 0.01855
抱きしめたい 0.47209 1.0 0.19017 0.16992 0.16121 0.05872
終わりなき旅 0.12586 0.19017 1.0 0.20592 0.14023 0.13306
tomorrow_never_knows 0.13892 0.16992 0.20592 1.0 0.09775 0.12016
ETERNAL BLAZE 0.13579 0.16121 0.14023 0.09775 1.0 0.07178
ファッションモンスター 0.01855 0.05872 0.13306 0.12016 0.07178 1.0

びっくりするくらい結果に反映されてる…
これは言い逃れできんやつやね

使用したソース

突貫で作成したのでうんこーどなのはご愛嬌。
ソースは下記のサイトを参考にして作成しました。

http://qiita.com/tabachain/items/77a9b0a049ed5b8e5650
http://technica-blog.jp/358

考え方は下記の書籍を参考にしました。

集合知プログラミング

集合知プログラミング

require 'natto'
require 'matrix'

def parseKeitaiso(text)
  except_features = %w{助詞 助動詞 記号} #除外種類
  result = [];
  natto = Natto::MeCab.new
  natto.parse(text) do |n|
    # puts "#{n.surface}\t#{n.feature}"

    is_except = false
    except_features.each do |except_feature|
      if n.feature.to_s.include? except_feature
        is_except = true
        break
      end
    end
    if is_except
      next
    end
    result << n.surface
  end
  result
end


# コサイン類似度を計算
# @param [String] str1 文字列
# @param [String] str2 文字列
# @return [Float] スコア

def calc_score(str1,str2)
  vector = []
  vector1 = parseKeitaiso str1
  vector2 = parseKeitaiso str2
  frag_vector1 = []
  frag_vector2 = []


  vector += vector1
  vector += vector2

  #重複と空文字列を削除
  vector.uniq!.delete("")
  vector1.delete("")
  vector.delete("")
  vector2.delete("")

  vector.each do |word|
    if vector1.include?(word) then
      frag_vector1.push(1)
    else
      frag_vector1.push(0)
    end

    if vector2.include?(word) then
      frag_vector2.push(1)
    else
      frag_vector2.push(0)
    end
  end

  vector1_final = Vector.elements(frag_vector1, copy = true)
  vector2_final = Vector.elements(frag_vector2, copy = true)

  return vector2_final.inner_product(vector1_final)/(vector1_final.norm() * vector2_final.norm())

end


nukumori_text = ""
File.open("nukumori.txt") do |file|
  file.read.split('\n').each do |labmen|
    nukumori_text = labmen;
  end
end

dakishimetai_text = ""
File.open("dakishimetai.txt") do |file|
  file.read.split('\n').each do |labmen|
    dakishimetai_text = labmen;
  end
end

owarinakitabi_text = ""
File.open("owarinakitabi.txt") do |file|
  file.read.split('\n').each do |labmen|
    owarinakitabi_text = labmen;
  end
end

tomorrow_never_knows_text = ""
File.open("tomorrow_never_knows.txt") do |file|
  file.read.split('\n').each do |labmen|
    tomorrow_never_knows_text = labmen;
  end
end

eternal_blaze_text = ""
File.open("eternal_blaze.txt") do |file|
  file.read.split('\n').each do |labmen|
    eternal_blaze_text = labmen;
  end
end

fashionmonster_text = ""
File.open("fashionmonster.txt") do |file|
  file.read.split('\n').each do |labmen|
    fashionmonster_text = labmen;
  end
end


puts "|----------------------|ぬくもり|抱きしめたい|終わりなき旅|Tomorrow never knows|ETERNAL BLAZE|ファッションモンスター|"
puts "|----------------------|--------|------------|------------|--------------------|-------------|----------------------|"
print "|ぬくもり              |"
print "#{calc_score(nukumori_text,nukumori_text).round(5).round(5)}|"
print "#{calc_score(nukumori_text,dakishimetai_text).round(5)}|"
print "#{calc_score(nukumori_text,owarinakitabi_text).round(5)}|"
print "#{calc_score(nukumori_text,tomorrow_never_knows_text).round(5)}|"
print "#{calc_score(nukumori_text,eternal_blaze_text).round(5)}|"
puts "#{calc_score(nukumori_text,fashionmonster_text).round(5)}|"
print "|抱きしめたい          |"
print "#{calc_score(dakishimetai_text,nukumori_text).round(5)}|"
print "#{calc_score(dakishimetai_text,dakishimetai_text).round(5)}|"
print "#{calc_score(dakishimetai_text,owarinakitabi_text).round(5)}|"
print "#{calc_score(dakishimetai_text,tomorrow_never_knows_text).round(5)}|"
print "#{calc_score(dakishimetai_text,eternal_blaze_text).round(5)}|"
puts "#{calc_score(dakishimetai_text,fashionmonster_text).round(5)}|"
print "|終わりなき旅          |"
print "#{calc_score(owarinakitabi_text,nukumori_text).round(5)}|"
print "#{calc_score(owarinakitabi_text,dakishimetai_text).round(5)}|"
print "#{calc_score(owarinakitabi_text,owarinakitabi_text).round(5)}|"
print "#{calc_score(owarinakitabi_text,tomorrow_never_knows_text).round(5)}|"
print  "#{calc_score(owarinakitabi_text,eternal_blaze_text).round(5)}|"
puts "#{calc_score(owarinakitabi_text,fashionmonster_text).round(5)}|"
print "|tomorrow_never_knows  |"
print "#{calc_score(tomorrow_never_knows_text,nukumori_text).round(5)}|"
print "#{calc_score(tomorrow_never_knows_text,dakishimetai_text).round(5)}|"
print "#{calc_score(tomorrow_never_knows_text,owarinakitabi_text).round(5)}|"
print "#{calc_score(tomorrow_never_knows_text,tomorrow_never_knows_text).round(5)}|"
print "#{calc_score(tomorrow_never_knows_text,eternal_blaze_text).round(5)}|"
puts "#{calc_score(tomorrow_never_knows_text,fashionmonster_text).round(5)}|"
print "|ETERNAL BLAZE         |"
print "#{calc_score(eternal_blaze_text,nukumori_text).round(5)}|"
print "#{calc_score(eternal_blaze_text,dakishimetai_text).round(5)}|"
print "#{calc_score(eternal_blaze_text,owarinakitabi_text).round(5)}|"
print "#{calc_score(eternal_blaze_text,tomorrow_never_knows_text).round(5)}|"
print "#{calc_score(eternal_blaze_text,eternal_blaze_text).round(5)}|"
puts "#{calc_score(eternal_blaze_text,fashionmonster_text).round(5)}|"
print "|ファッションモンスター|"
print "#{calc_score(fashionmonster_text,nukumori_text).round(5)}|"
print "#{calc_score(fashionmonster_text,dakishimetai_text).round(5)}|"
print "#{calc_score(fashionmonster_text,owarinakitabi_text).round(5)}|"
print "#{calc_score(fashionmonster_text,tomorrow_never_knows_text).round(5)}|"
print "#{calc_score(fashionmonster_text,eternal_blaze_text).round(5)}|"
puts "#{calc_score(fashionmonster_text,fashionmonster_text).round(5)}|"