Hatena::ブログ(Diary)

ぬいぐるみライフ(仮) RSSフィード

2011-02-12

映画「The Social Network」の脚本をNLTKで解析して遊んでみた

※この記事には映画「The Social Network」のネタバレがそれなりに含まれています.これから映画を観る予定の方は逃げた方が賢明です.


最近ブログで宣言した通り,入門 自然言語処理を読みつつPythonのNLTK(Natural Language ToolKit)を使った自然言語処理について勉強中.入門 自然言語処理はPythonをロクに触ったことがない私でもちゃんと理解しながら読み進められるようになっているのが嬉しい.

ところで,少し前に映画「The Social Network (ソーシャル・ネットワーク)」を観て,登場人物の台詞や行動がなかなか面白くて気に入ったのだけど,この脚本が映画の公式サイトで公開されていることを最近知った.映画の脚本となると,特徴的な表現が多く文章数もそれなりにあるので,興味深いコーパスになり得るのではないかと思う.

というわけで,NLTK習い立ての丁度いいタイミングなので,早速NLTKを使ってこの脚本を分析してみた.

脚本の入手

The Social Networkの公式サイトの右上の"DOWNLOAD THE SCREENPLAY"というリンクから脚本のPDFがダウンロードできる.

PDFからプレーンテキストへの変換

私はAdobe Readerを使ってプレーンテキストに変換した.ただ,生成されたテキストはそのままだと色々問題があったので,以下のように手作業でテキストを修正した.

  • ^Mを改行文字(\n)に置き換える (^Mは制御文字.ターミナルやVimだとCtrl-V Ctrl-Mで入力できる)
  • ^Lを取り除く (^Lも制御文字.Ctrl-V Ctrl-Lで入力できる)
  • シングルクォートなどの記号が変な制御文字に化けているのでPDFと照らし合わせながら置換する
  • ページ番号だけの行を取り除く
  • などなど

なかなか手間がかかったので,The Social Networkの脚本を使って同じことをやりたいと思っている人向けにテキスト版の脚本をアップロードしておいた.プレーンテキストにしたせいで台詞文と状況説明文の区別が付かなくなってるけど悪しからず.

NLTKを使って遊ぶ

では早速NLTKで脚本のテキストを解析して遊んでみよう.

$ python
>>> import nltk

以降,脚本の文書について以下の処理を行っていく.

  • 文書の単語数の確認
  • 特定の単語の検索
  • 特定の単語が出現する文の検索
  • 単語の頻度分布の可視化
  • 場面ごとの登場人物の推移の可視化

データを読み込む

テキストファイルを読み込んでトークン分割し,トークンリストからnltk.Textオブジェクトを作る.

>>> raw = open('the_social_network.txt').read()
>>> tokens = nltk.word_tokenize(raw)
>>> text = nltk.Text(tokens)

各変数にどんな値が入っているか見てみる.rawには脚本の文字列が入っている.

>>> type(raw)
<type 'str'>
>>> raw
'FROM THE BLACK WE HEAR--\nMARK (V.O.)\nDid you know there are more pe
ople with\ngenius IQ\'s living in China than there\nare people of any
kind living in the\nUnited States?\nERICA (V.O.)\nThat can\'t possibly
be true.\nMARK (V.O.)\nIt is.\nERICA (V.O.)\nWhat would account for th
at?\nMARK (V.O.)\nWell, first, an awful lot of people live\nin China.
(略)

tokensにはトークン分割された結果のリストが入っている.

>>> type(tokens)
<type 'list'>
>>> tokens
['FROM', 'THE', 'BLACK', 'WE', 'HEAR--', 'MARK', '(', 'V.O.', ')', 'Di
d', 'you', 'know', 'there', 'are', 'more', 'people', 'with', 'genius',
'IQ', "'s", 'living', 'in', 'China', 'than', 'there', 'are', 'people',
'of', 'any', 'kind', 'living', 'in', 'the', 'United', 'States', '?', '
ERICA', '(', 'V.O.', ')', 'That', 'ca', "n't", 'possibly', 'be', 'true
(略)

textにはNLTKのTextオブジェクトが入っている.

>>> type(text)
<class 'nltk.text.Text'>
>>> text
<Text: FROM THE BLACK WE HEAR-- MARK ( V.O....>

以降ではこれらのオブジェクトを使って解析していく.

文書の単語数を調べる

まず,手始めに脚本中のトークン数(単語数)を調べる.

>>> len(tokens)
34821

この脚本には全部で34,821個のトークン(単語)が存在する.

次に,脚本中の異なり語の個数を調べる.単語の大文字・小文字の表記揺れの影響をなくすために,単語を全て小文字化したトークンリストを用意し,そのsetを作って異なり語数を調べる.

>>> tokens_l = [w.lower() for w in tokens]
>>> len(set(tokens_l))
4275

異なり語の数は4,275個だった.

複数形や過去形を別の単語としてカウントしてしまっているため,実際の異なり語数はさらに少ない.また,nltk.word_tokenize(raw)が記号とアルファベットをうまくトークン分割できていないケースがあり,例えば'fuck.'を['fuck', '.']と分割したり['fuck.']と分割したりする場合があるため,正しい異なり語数を算出することは難しい.それでも脚本の大雑把な語彙数は分かるだろう.

Emacs vs Vim (or Vi)!!!

The Social Networkの一番気になる点といったら,何と言ってもMark Zuckerbergが劇中で使っていたエディタは何だったのかということ!映画の日本語字幕にはEmacsもVimも出てこなかったが,もしかすると英語の脚本には載っているかもしれない!というわけで早速調べてみよう!

リストのcountメソッドを使えば,指定した値がリスト中にいくつ含まれているかを調べることができる.今回はトークンリストのcountメソッドの引数に'emacs','vim','vi'を指定してやれば良さそうだ.どれどれ….

>>> tokens_l.count('emacs')
3
>>> tokens_l.count('vim')
0
>>> tokens_l.count('vi')
0

SON OF A BITCH!!!

特定の単語が出現する文を検索する

気を取り直して,次は特定の単語が出現する文について見ていこう.NLTKのTextオブジェクトを使えば,単語が出現する文を検索したり,文書中である単語と共起する他の単語を調べることが簡単にできる.

例えば'Facebook'という単語はどのような文中で登場しているのか調べよう.この場合はTextオブジェクトのconcordanceメソッドを使えばよい.

>>> text.concordance('Facebook', lines=5)
Displaying 5 of 47 matches:
 on his desktop labeled " Kirkland Facebook " . He clicks and opens it. A menu
 's a Tuesday night ? The Kirkland Facebook is open on my desktop and some of 
hese people have pretty horrendous Facebook pics . ( MORE ) Billy Olson 's sit
does n't keep a public centralized Facebook so I 'm going to have to get all t
ry to download the entire Kirkland Facebook . Kids ' stuff . On the computer s

'Facebook'が出てくる箇所と前後の文が表示される.concordanceでは大文字・小文字の違いは区別されない.

次は'bitch'が出てくる文を調べてみよう.

>>> text.concordance('bitch')
Displaying 2 of 2 matches:
 TheFacebook ? ERICA You called me a bitch on the internet , Mark . MARK That '
 published that Erica Albright was a bitch right before you made some ignorant 

2つしか出てこなかった.しかし,実は脚本中に'bitch'が出てくる箇所はまだまだある.うちひとつは'bitch.'で検索すると出てくる.

>>> text.concordance('bitch.')
Displaying 1 of 1 matches:
. MARK ( V.O. ) Erica Albright 's a bitch. Do you think that 's because her fa

これは,最初のnltk.word_tokenize(raw)の処理で"Erica Albright's a bitch."という文をトークン分割する時に,トークナイザが誤って'bitch'と'.'をひとつのトークンと見なしてしまったことが原因である.

また,残りのbitchは'CEO...Bitch'で検索すると見つけられる.

>>> text.concordance('CEO...Bitch')
Displaying 2 of 2 matches:
 a business card that says " I 'm CEO...Bitch " , that 's what I want for you ,
 cards out and looks at it . I 'm CEO...Bitch And over this we HEAR a woman 's 

このように,nltk.word_tokenize()によるトークン分割は失敗するケースもあるということを頭に入れておいた方が良さそうだ.

ちなみに'Vim'と'Vi'についてはトークン分割のミスはなく,そもそも単語が文書中に存在しないことを確認済みである.Fuck.

単語の頻度分布を可視化する

次は脚本における単語の頻度分布を調べて頻出単語の特徴を調べよう.

文書中の単語の頻度分布を調べるにはnltk.FreqDistを使う.FreqDistのkeysメソッドを使うと,最も頻出する単語から順に並んだ異なり語のリストを得ることができる.

>>> fdist = nltk.FreqDist(w.lower() for w in text)
>>> fdist.keys()[:50]
['.', 'the', 'to', ',', 'a', 'and', 'mark', 'you', 'i', "'s", '?', 'ed
uardo', 'of', ')', '(', 'it', 'that', 'in', 'is', 'we', "n't", 'he', '
on', 'sean', 'do', 'with', '-', ':', 'was', 'for', 'at', 'what', 'this
', '"', 'int.', 'cut', 'his', "'m", "'re", 'are', 'cameron', 'they', '
have', 'room', 'up', 'be', 'tyler', 'as', "'d", 'not']

また,plotメソッドを使うと頻度分布を可視化できる.

>>> fdist.plot(50, cumulative=True)

f:id:mickey24:20110212021523p:image

可視化結果を見てみると,'the','to'といった語彙内容に乏しい単語や記号ばかりが頻度分布の上位に出現してしまっている.これではあまり意味のある頻度分布を得られているとは言えないだろう.あらかじめこれらの単語や記号を除いた上で頻度分布を作るべきだ.

NLTKにはストップワード('the'や'to'などのようにどの文書でも高頻度で出現する単語)のコーパスが用意されている.これをうまく使えば脚本の特徴をよく表す頻度分布が作れそうだ.

>>> stopwords = nltk.corpus.stopwords.words('english')
>>> stopwords
['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', '
your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself
', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'th
em', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom',
(略)

このストップワードと一部の記号を取り除いた頻度分布を作ってみる.

>>> symbols = ["'", '"', '`', '.', ',', '-', '!', '?', ':', ';', '(', ')']
>>> fdist = nltk.FreqDist(w.lower() for w in text if w.lower() not in stopwords + symbols)
>>> fdist.plot(50, cumulative=True)

f:id:mickey24:20110212021524p:image

大分マシになったように見える.

登場人物の名前である'mark','eduardo','sean'が最も頻出しており, 'room'や'night'といった単語の出現頻度も高いことが分かる. 一部"'s"や"n't"といったアポストロフィー付きの短いトークンも混ざっているが,これらは"Mark's"や"didn't"のような単語が分割された結果出てきたトークンである.本来はこれらも取り除いてから頻度分布を調べた方がいいだろう.

場面ごとの登場人物の推移を可視化する

今度は映画の各場面で現れる登場人物の推移を可視化してみよう.

映画の脚本には各登場人物の台詞の前に,発言する人物の名前が大文字で書かれている(例:MARK,EDUARDO).よって,脚本でこれらの文字列が出現する箇所を調べればよさそうだ.ここでは主人公のMark Zukerberg,ガールフレンドのErica Albright,Markの親友のEduardo Saverin,ボート部の兄弟Cameron WinklevossとTyler Winklevoss,そして映画後半のキーパーソンであるSean Parkerについて調べてみる.

分散プロットを使うと,各単語が文書中に登場する位置を可視化することができる.分散プロットはTextオブジェクトのdispersion_plotメソッドで表示させることができる.

>>> persons = ['MARK', 'ERICA', 'EDUARDO', 'CAMERON', 'TYLER', 'SEAN']
>>> text.dispersion_plot(persons)

f:id:mickey24:20110212021525p:image

図の横軸が映画の時間軸にほぼ対応する.

この分散プロットから以下のことが把握できる.

  • Mark Zuckerbergは映画の主人公だけあって登場シーンが多い.ボート部の兄弟がメインで登場するシーン以外ではMarkはほぼ確実に登場している.
  • Erica Albrightは最初の方と映画の半ばあたりのみ登場している.半ばのシーンについてはMarkが久し振りにEricaに話しかけたあのシーンのことだろうなーと類推できる.
  • Eduardo SaverinはMarkに次いで登場している場面が多い.
  • ボート部の兄弟のCameron WinklevossとTyler Winklevossは要所要所に台詞があるが,Mark以外の他の人物とはあまり一緒に登場していない.
  • Sean Parkerは映画の後半で登場するので,当然ながら脚本の台詞も後半から出てきている.

いろいろ思い出していたらもう一度映画を観たくなってきた.

以上

NLTKを使うと文書のインスペクトや可視化が簡単にできることが確認できた.この記事ではThe Social Networkの脚本について,以下の処理を行った.

  • 文書の単語数の確認
  • Emacs...
  • 特定の単語が出現する文の検索
  • 単語の頻度分布の可視化
  • 場面ごとの登場人物の推移の可視化

もう疲れたので今日のところはこれくらいにしておいてやろう.

もっと面白いネタを思い付いたらまたブログ記事にまとめてみたい.

参考資料

入門 自然言語処理

入門 自然言語処理

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。