Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
階層的クラスタリングをRubyで表現する / Implement Hierarchical...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Ayumi Tamai
December 14, 2019
Technology
2.3k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
階層的クラスタリングをRubyで表現する / Implement Hierarchical Clustering Analysis Using Ruby
Ayumi Tamai
December 14, 2019
Other Decks in Technology
See All in Technology
AIに「使われる」時代のSaaS戦略 〜既存WebAPIのMCPサーバー化における開発ノウハウ〜
ekispert_api
0
290
Kotlin 開発のツラミを爆破した話! / Explode the difficulty of Kotlin dev!
eller86
0
140
依頼文化をやめる日 EM視点で語るPlatform EngineeringとInclusive SRE / Discussing Platform Engineering and Inclusive SRE from an EM's Perspective
shin1988
3
1.8k
GuardrailからGovernanceへ~AIエージェント運用の次の課題~
sbspsy
1
120
デジタル・デザイン:次の50年を描く「進化する青写真」
y150saya
0
810
フルカイテン株式会社 エンジニア向け採用資料
fullkaiten
0
11k
飲食店もAIで。レジ締めやハンディシステムをつくってる話 / Using AI for restaurant management
vtryo
0
240
打造你的 AI 工作流:Agent Skill + MCP 實戰工作坊
appleboy
0
540
Text-to-SQLをAgentCoreで実現し、生成されるSQLの精度を定量的に評価する
yakumo
2
550
【FinOps】データドリブンな意思決定を目指して
z63d
3
610
End-to-Endで考える信頼性 —LINEアプリにおけるクライアント開発×SRE連携の実践
maruloop
2
390
事業価値を⽣み出すSREへ SREが担うべき意思決定の5層
kenta_hi
0
170
Featured
See All Featured
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
180
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
460
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
880
The Curse of the Amulet
leimatthew05
2
13k
Navigating Team Friction
lara
192
16k
Code Review Best Practice
trishagee
74
20k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
610
First, design no harm
axbom
PRO
2
1.2k
Transcript
平成 会議 階層的クラスタリングを で表現する
話すこと • 自己紹介 • 発表の経緯 • 階層的クラスタリングの説明 • 分析対象・方法 •
分析結果 • 実装の紹介 • まとめ
話すこと • 自己紹介 • 発表の経緯 • 階層的クラスタリングの説明 • 分析対象・方法 •
分析結果 • 実装の紹介 • まとめ
玉井あゆみ 平成 年生まれ カンファレンスでの発表は初 自己紹介
話すこと • 自己紹介 • 発表の経緯 • 階層的クラスタリングの説明 • 分析対象・方法 •
分析結果 • 実装の紹介 • まとめ
• せっかくの機会なので登壇してみたい ◦ しかし、 や に関する新しい知見は提供できそうにな い ◦ 加えて、語られるべきキャリアもない •
普段 を使わないことに敢えて を使って発表してみ ようか • そうだ、階層的クラスタ分析を実装してみよう ◦ のあるソフトウェアでこの分析を行ったことがある ◦ 階層的クラスタ分析ができる は調べた限り無さそう 発表の経緯
話すこと • 自己紹介 • 発表の経緯 • 階層的クラスタリングの説明 • 分析対象・方法 •
分析結果 • 実装の紹介 • まとめ
クラスタリングとは ※距離が近いほど似ているとする
クラスタリングとは ※距離が近いほど似ているとする
階層的クラスタリングとは 似ているサンプル群を順番にグループ化
話すこと • 自己紹介 • 発表の経緯 • 階層的クラスタリングの説明 • 分析対象・方法 •
分析結果 • 実装の紹介 • まとめ
平成の邦楽ヒット曲の歌詞 • 楽曲リスト: サイト「年代流行」の邦楽ヒット曲 ランキングを使用 ◦ 年のヒット曲上位 曲 ◦ アルバム名しかない場合は最初の曲を採用
• 歌詞: サイト「歌詞検索 」を使用 分析対象
形態素の出現回数により楽曲をベクトル化 • 形態素解析器: ( ) • 辞書: • 品詞大分類「記号」「助詞」「助動詞」と固有名詞は除く 恣意性
前処理
分析方法 階層的クラスタ分析 • クラスタの併合方法:ウォード法 ◦ 似ている つのサンプル 群 を順に繋げてクラスタにしていく •
クラスタ間の距離:ユークリッド距離 ◦ 日常会話で使う「距離」と同じ
分析対象の楽曲(一部) アーティスト名 楽曲名 AKB48 Teacher Teacher AKB48 センチメンタルトレイン 乃木坂46 シンクロニシティ
AKB48 願いごとの持ち腐れ AKB48 #好きなんだ AKB48 11月のアンクレット AKB48 翼はいらない AKB48 LOVE TRIP/しあわせを分けなさい AKB48 君はメロディー AKB48 僕たちは戦わない AKB48 ハロウィン・ナイト AKB48 Green Flash
前処理結果(一部) 楽曲名 学校 気づく いる 街 会う はっと する しまう
Teacher Teacher 1 1 1 1 1 1 3 1 センチメンタルトレイ ン 0 0 2 0 1 0 9 3 シンクロニシティ 0 3 8 1 0 0 5 0 願いごとの持ち腐れ 0 0 1 0 0 0 1 0 #好きなんだ 0 1 1 0 0 0 2 0 11月のアンクレット 0 0 1 0 1 0 2 0 翼はいらない 0 0 2 0 0 0 1 0 しあわせを分けなさ い 0 0 0 0 0 0 0 0 君はメロディー 0 1 3 1 0 0 1 1 僕たちは戦わない 0 0 2 0 0 0 2 0 ハロウィン・ナイト 0 0 2 0 0 0 2 0 Green Flash 0 0 1 1 0 0 2 1
話すこと • 自己紹介 • 発表の経緯 • 階層的クラスタリングの説明 • 分析対象・方法 •
分析結果 • 実装の紹介 • まとめ
• 併合水準により適切そうなクラスタ数を指定したもの 分析結果
• クラスタ数を多めに指定したもの 分析結果
おもしろい結果は出なかった... 20
分析結果 • 都道府県別人口・人口密度による都道府県のクラスタリ ングの分析結果 ◦ クラスタ数: ◦ データ出典:都道府県・市区町村別統計表(国勢調査)(男女別人 口,年齢3区分・割合,就業者,昼間人口など) 都道府県・市区町
村別統計表(一覧表)平成 年
話すこと • 自己紹介 • 発表の経緯 • 階層的クラスタリングの説明 • 分析対象・方法 •
分析結果 • 実装の紹介 • まとめ
ウォード法の実装 23 ※簡略化・説明のため、実際のコードとは異なる箇所があります
class WardMethod Cluster = Struct.new(:samples, :dissimilarity, keyword_init: true) def execute(clusters_count:)
# samples: 楽曲名と歌詞の形態素の属性を持つ構造体の配列 clusters = samples.map { |smpl| Cluster.new(samples: [smpl], dissimilarity: 0) } # 指定したクラスタの数になるまでサンプル群どうしを併合する while clusters.count > clusters_count combinations = gen_combinations_from(clusters: clusters) new_cluster = gen_new_cluster_from(combinations: combinations) clusters = reunite_clusters(old_clusters: clusters, new_cluster: new_cluster) end save_results(clusters: clusters) end end
class WardMethod Cluster = Struct.new(:samples, :dissimilarity, keyword_init: true) def execute(clusters_count:)
# samples: 楽曲名と歌詞の形態素の属性を持つ構造体の配列 clusters = samples.map { |smpl| Cluster.new(samples: [smpl], dissimilarity: 0) } # 指定したクラスタの数になるまでサンプル群どうしを併合する while clusters.count > clusters_count combinations = gen_combinations_from(clusters: clusters) new_cluster = gen_new_cluster_from(combinations: combinations) clusters = reunite_clusters(old_clusters: clusters, new_cluster: new_cluster) end save_results(clusters: clusters) end end
def gen_combinations_from(clusters:) provisional_clusters = [] # いくつかのサンプル群(仮置きクラスター)から2つとって併合 clusters.combination(2) do |c1,
c2| # 併合前のサンプル群の重心を求める cg_of_c1 = Calc.cg(array: c1.samples, name: 'C1の重心', member_variable_names: member_variable_names) cg_of_c2 = Calc.cg(array: c2.samples, name: 'C2の重心', member_variable_names: member_variable_names) # サンプル群を併合後してできた仮置きクラスターの重心を求める cu_samples = c1.samples | c2.samples cg_of_cu = Calc.cg(array: cu_samples, name: 'C1とC2を連結した仮クラスターの重心', member_variable_names: member_variable_names) # 次スライドへ続く
# 併合前後で、「仮置きクラスターの重心」と各「サンプル」との # ユークリッド距離の二乗和 ‘sum of squared differences’ を求める sum_of_sqd_between_c1_cg_and_sample
= Calc.sum_of_sqds(samples: c1.samples, cg: cg_of_c1, member_variable_names: member_variable_names) sum_of_sqd_between_c2_cg_and_sample = Calc.sum_of_sqds(samples: c2.samples, cg: cg_of_c2, member_variable_names: member_variable_names) sum_of_sqd_between_cu_cg_and_sample = Calc.sum_of_sqds(samples: cu_samples, cg: cg_of_cu, member_variable_names: member_variable_names) # 次スライドへ続く
# 併合してできた仮置きクラスターのサンプルとメタ情報をメモしておく # 併合後の 重心–各サンプル 間距離の二乗和から併合前の 重心–各サンプル 間距離の二乗和を引く # `diff_between_sqds`:
クラスタの重心と各サンプルとの距離の二乗和の差(後で使う) provisional_clusters << ProvisionalCluster.new( diff_between_sqds: sum_of_sqd_between_cu_cg_and_sample - sum_of_sqd_between_c1_cg_and_sample - sum_of_sqd_between_c2_cg_and_sample, c1: c1, c2: c2, ) end provisional_clusters end
class WardMethod Cluster = Struct.new(:samples, :dissimilarity, keyword_init: true) def execute(clusters_count:)
# samples: 楽曲名と歌詞の形態素の属性を持つ構造体の配列 clusters = samples.map { |smpl| Cluster.new(samples: [smpl], dissimilarity: 0) } # 指定したクラスタの数になるまでサンプル群どうしを併合する while clusters.count > clusters_count combinations = gen_combinations_from(clusters: clusters) new_cluster = gen_new_cluster_from(combinations: combinations) clusters = reunite_clusters(old_clusters: clusters, new_cluster: new_cluster) end save_results(clusters: clusters) end end
def gen_new_cluster_from(combinations:) # 含まれるサンプルがもっとも似ているクラスタを選ぶ # ※「含まれるサンプルがもっとも似ているクラスタ」:複数ある仮置きクラスタの中で、 # クラスタの重心と各サンプルとの距離の二乗和の差(`diff_between_sqds`)が最も小さいもの new_cluster_prov =
combinations .min { |a, b| a.diff_between_sqds <=> b.diff_between_sqds } #構造体クラス Cluster の構造体として返す new_cluster = Cluster.new( samples: new_cluster_prov.c1.samples | new_cluster_prov.c2.samples, dissimilarity: dissimilarity(new_cluster_provision: new_cluster_prov) ) end
class WardMethod Cluster = Struct.new(:samples, :dissimilarity, keyword_init: true) def execute(clusters_count:)
# samples: 楽曲名と歌詞の形態素の属性を持つ構造体の配列 clusters = samples.map { |smpl| Cluster.new(samples: [smpl], dissimilarity: 0) } # 指定したクラスタの数になるまでサンプル群どうしを併合する while clusters.count > clusters_count combinations = gen_combinations_from(clusters: clusters) new_cluster = gen_new_cluster_from(combinations: combinations) clusters = reunite_clusters(old_clusters: clusters, new_cluster: new_cluster) end save_results(clusters: clusters) end end
def reunite_clusters(old_clusters:, new_cluster:) # 新しく作ったクラスタとサンプルを共有する古いクラスタを削除 # 理論上、2つのクラスタ(Cluster構造体)を配列から除くことになっているはず old_clusters.delete_if { |cl|
(new_cluster.samples & cl.samples).count > 0 } # 除いた古いクラスタを併合して新しく作ったクラスタ(Cluster構造体)を配列に追加 old_clusters.push new_cluster end
class WardMethod Cluster = Struct.new(:samples, :dissimilarity, keyword_init: true) def execute(clusters_count:)
# samples: 楽曲名と歌詞の形態素の属性を持つ構造体の配列 clusters = samples.map { |smpl| Cluster.new(samples: [smpl], dissimilarity: 0) } # 指定したクラスタの数になるまでサンプル群どうしを併合する while clusters.count > clusters_count combinations = gen_combinations_from(clusters: clusters) new_cluster = gen_new_cluster_from(combinations: combinations) clusters = reunite_clusters(old_clusters: clusters, new_cluster: new_cluster) end save_results(clusters: clusters) end end
次元データでの例 34
併合 段階目 35
class WardMethod Cluster = Struct.new(:samples, :dissimilarity, keyword_init: true) def execute(clusters_count:)
# samples: 楽曲名と歌詞の形態素の属性を持つ構造体の配列 clusters = samples.map { |smpl| Cluster.new(samples: [smpl], dissimilarity: 0) } # 指定したクラスタの数になるまでサンプル群どうしを併合する while clusters.count > clusters_count combinations = gen_combinations_from(clusters: clusters) new_cluster = gen_new_cluster_from(combinations: combinations) clusters = reunite_clusters(old_clusters: clusters, new_cluster: new_cluster) end save_results(clusters: clusters) end end
None
……併合を繰り返す
……併合を繰り返す
当例ではサンプルが 個なので 回併合を繰り返す (1..10).to_a.combination(2).count # => 45
class WardMethod Cluster = Struct.new(:samples, :dissimilarity, keyword_init: true) def execute(clusters_count:)
# samples: 楽曲名と歌詞の形態素の属性を持つ構造体の配列 clusters = samples.map { |smpl| Cluster.new(samples: [smpl], dissimilarity: 0) } # 指定したクラスタの数になるまでサンプル群どうしを併合する while clusters.count > clusters_count combinations = gen_combinations_from(clusters: clusters) new_cluster = gen_new_cluster_from(combinations: combinations) clusters = reunite_clusters(old_clusters: clusters, new_cluster: new_cluster) end save_results(clusters: clusters) end end
……この 点が一番近そう
class WardMethod Cluster = Struct.new(:samples, :dissimilarity, keyword_init: true) def execute(clusters_count:)
# samples: 楽曲名と歌詞の形態素の属性を持つ構造体の配列 clusters = samples.map { |smpl| Cluster.new(samples: [smpl], dissimilarity: 0) } # 指定したクラスタの数になるまでサンプル群どうしを併合する while clusters.count > clusters_count combinations = gen_combinations_from(clusters: clusters) new_cluster = gen_new_cluster_from(combinations: combinations) clusters = reunite_clusters(old_clusters: clusters, new_cluster: new_cluster) end save_results(clusters: clusters) end end
None
併合 段階目 45
……併合を繰り返す
……併合を繰り返す
現在クラスター数が 個なので 回併合を繰り返す (1..9).to_a.combination(2).count # => 36
クラスタ数指定の基準 49
• 非類似度 併合水準 • クラスタどうしが似ていないほど数値が高い • 今回は、距離の二乗和の差(前述)の常用対数( を底と する対数)をとったものを非類似度とする 非類似度
非類似度 • 以下のようなとき適切なクラスタ数は ◦ 併合水準が急に大きくなる直前のクラスタ数を選ぶ 急に大きくなる
話すこと • 自己紹介 • 発表の経緯 • 階層的クラスタリングの説明 • 分析対象・方法 •
分析結果 • 実装の紹介 • まとめ
階層的クラスタリングをなんとか で表現できた
• 階層的クラスタリングをなんとか で表現できた • 非文章(歌詞や詩)をこの方法で分析するのは難しい ◦ を使用した前処理 ◦ 語のスコアによる重み付きユークリッド距離 •
併合水準のグラフ デンドログラムの描画には手をつけら れなかった まとめ
今回の分析に使用したコード •