« 旧知の間柄 | トップページ | ブログのコメントのデータベース化 »

Nグラム文字分割による単語頻度分析

あけましておめでとうございます。
年末は忙しいやら体調を崩すやらで何もできない状態でしたが、ようやく休みに入って少しまとまった時間ができたので、文字解析の続きをやってみました。

今回は、文の中に現れる「単語」の頻度を調べてみます。
文字分析の世界では、文章から切り出した1文字の文字列切片を1グラム、2文字の文字列切片を2グラム・・という風に呼ぶそうです。任意の自然数Nに対して、その長さの文字列切片が考えられるので、切片を切り出す方法そのものをNグラム方式などと称するようです。

このNグラム方式で「単語」らしきものを切り出してみて頻度を集計してみました。
対象とする文としては、ネットウォッチ大賞2008の「瀬戸ゼリ幸」さんへの投票の際の「投票理由」を選んでみました。これは、おそらく「うんこ」とか「ウンコ」の頻度が高く出てくるだろうという手堅い予測(笑)があったからです。まぁザッと見ただけで分かるのですが。なお、投票の際には投票者名も自由に記入できるのですが、今回は対象としていません。

http://www4.atpages.jp/watch2008/vote/vote.cgi?mode=comm&no=3
ネットウォッチ大賞2008 瀬戸ゼリ幸 の投票理由

処理の手順と手法は以下の通りです。
(1) 投票理由のテキストファイルを作る(一行に一理由とする)
  秀丸エディタで手作業
(2) 上記を切り刻んで1グラム~8グラムに分解したテキストファイルを作る
  Perlのプログラムで処理
(3) 上記を集計
  ShunAnalyzeコマンド(Data Effector体験版)利用
(4) 上記から頻度10以上の切片のみを抽出
  ShunSelectコマンド(Data Effector体験版)利用
(5) 上記を頻度順にソート
  ShunSortコマンド(Data Effector体験版)利用

この方法では、形態素解析のように単語の区切りや品詞を意識しないので、結果として得られるリストには単語ではない文字列も含まれます。例えば、「価学会のウンコ攻」(8グラム)とか「ャーナリ」(4グラム)などです。
したがって、どれを単語とみなすかは最終的に人間が目で見て判断する必要があります。

という訳で、結果のリストを見て意味のありそうな文字列を拾ってみました。各カラムは、それぞれグラム数,文字列,頻度の意味です。
"3","うんこ",86
"3","ウンコ",76
"3","だから",23
"2","ゼリ",30
"2","創価",20
"2","瀬戸",19
"1","!",92
"1","・",39
"1","w",37
"1","人",25
"1","糞",22

やはり今回はウンコ用語が圧倒的な存在感を示しています。これがなければ、創価学会関係とゼリー関係が上位に来たのかもしれません。残念ながら分かったのはそれだけです(笑)。

今回使った方法を以下に説明します。

(1) 投票理由のテキストファイルを作る(一行に一理由とする)
投票理由の一行は「・最強のブーメラン使い (2008/11/03(Mon) 00:54/nanomania)」のような形式になっているので、先頭の「・」と末尾の「 (2008/」以降を秀丸エディタで削除しました。この結果をcom.txtというテキストファイルに入れます。

■com.txt(354行)

ニコニコにうpされてたナノゼリーの動画が最高に面白かった
そろそろこの人がとってると面白そうなので
ゼリーだから
・・・

(2)から(5)までは以下のバッチファイルで一括実行します。この位のデータ量であればExcelでの処理も可能ですが、時間がかかるのと手操作の繰り返しが苦しいので、できるだけバッチ化するのが吉です。
私も文字分析をやり初めて分かったのですが、多量のデータを簡単に集められるので計算量も簡単に増大して手に負えなくなります。Excelに頼り過ぎない方が結果的には作業の効率が上がると思います。

■バッチファイル

rem テキストファイルから1~Nグラム文字列の頻度を求めるバッチ

rem 1~Nグラムの文字列を生成
rem 入力:com.txt
rem 出力:D:\temp\com2gram-result.txt
com2gram.pl com.txt

shunanalyze -s analyze.cfg -a anacond.txt < com2gram-result.txt > com2gram-ana.txt

shunselect -s select.cfg -q selectcond.txt < com2gram-ana.txt > com2gram-select.txt

shunsort -a sortcond.txt < com2gram-select.txt > com2gram-sort.txt

(2)で使ったPerlプログラムは以下です。処理の際の文字コードはUTF8に統一しておくのが吉です。Shift-JISやEUCでは表現できる文字の範囲が狭いので、もはや文字処理には使えないと思います。
PerlでのUTF8の使い方が分からずに苦労しましたが、以下のプログラムではOpenの際に明にUTF8である事を宣言しています。標準入出力でのUTF8の扱いが良く分からないので、ここでは入力も出力もファイルとしています。
また、このプログラムの中で1~8グラムの切片を切り出す事を決めてます。ここを変えれば、4グラムだけ、とか、3~5グラムを対象とするといった変更が可能(な筈)です。

■com2gram.pl

# com2gram.pl
# 引数にテキストファイル名(1個)を指定して実行
# 結果は $resultfilename ("D:\temp\com2gram-result.txt") に出力される

use strict;
use utf8;
use Encode;

# 結果出力ファイルをフルパスで指定(UTF8の想定)
my $resultfilename = "D:\\temp\\com2gram-result.txt";

# 第一パラメタは引数となるテキストファイル名(UTF8の想定)
# 内容を配列@comlistに読み込む
my $file = shift;
open(IN, "<:utf8","$file") || die("cannot open $file\n");
my @comlist = ;
close(IN);
chomp(@comlist);

# 出力ファイルは固定名、utf8指定
open(OUT,">:utf8",$resultfilename); 

print OUT "num\tstring\n";
foreach my $com ( @comlist ) {
  # $j はグラム数を表す
  for( my $j = 1; $j <= 8; $j++ ) {
    # 文字列の先頭から1文字ずつずらして区切って行く
    for( my $i = 0; $i < ( length($com) - ($j-1) ); $i++ ) {
      print OUT $j, "\t", substr($com,$i,$j), "\n";
    }
  }
}
close OUT;

このプログラムを通すと、例えば、最初の一行が以下のように1~8グラムに分割されます。こうやって処理過程でデータ量が何層倍にもなっていくのが醍醐味でしょうか(笑)。

■com2gram-result.txtの先頭(全体は29562行、約462KB)

num	string
1 ニ
1 コ
1 ニ
1 コ
1 に
1 う
1 p
1 さ
1 れ
1 て
1 た
1 ナ
1 ノ
1 ゼ
1 リ
1 ー
1 の
1 動
1 画
1 が
1 最
1 高
1 に
1 面
1 白
1 か
1 っ
1 た
2 ニコ
2 コニ
2 ニコ
2 コに
2 にう
2 うp
2 pさ
2 され
2 れて
2 てた
2 たナ
2 ナノ
2 ノゼ
2 ゼリ
2 リー
2 ーの
2 の動
2 動画
2 画が
2 が最
2 最高
2 高に
2 に面
2 面白
2 白か
2 かっ
2 った
3 ニコニ
3 コニコ
3 ニコに
3 コにう
3 にうp
3 うpさ
3 pされ
3 されて
3 れてた
3 てたナ
3 たナノ
3 ナノゼ
3 ノゼリ
3 ゼリー
3 リーの
3 ーの動
3 の動画
3 動画が
3 画が最
3 が最高
3 最高に
3 高に面
3 に面白
3 面白か
3 白かっ
3 かった
4 ニコニコ
4 コニコに
4 ニコにう
4 コにうp
4 にうpさ
4 うpされ
4 pされて
4 されてた
4 れてたナ
4 てたナノ
4 たナノゼ
4 ナノゼリ
4 ノゼリー
4 ゼリーの
4 リーの動
4 ーの動画
4 の動画が
4 動画が最
4 画が最高
4 が最高に
4 最高に面
4 高に面白
4 に面白か
4 面白かっ
4 白かった
5 ニコニコに
5 コニコにう
5 ニコにうp
5 コにうpさ
5 にうpされ
5 うpされて
5 pされてた
5 されてたナ
5 れてたナノ
5 てたナノゼ
5 たナノゼリ
5 ナノゼリー
5 ノゼリーの
5 ゼリーの動
5 リーの動画
5 ーの動画が
5 の動画が最
5 動画が最高
5 画が最高に
5 が最高に面
5 最高に面白
5 高に面白か
5 に面白かっ
5 面白かった
6 ニコニコにう
6 コニコにうp
6 ニコにうpさ
6 コにうpされ
6 にうpされて
6 うpされてた
6 pされてたナ
6 されてたナノ
6 れてたナノゼ
6 てたナノゼリ
6 たナノゼリー
6 ナノゼリーの
6 ノゼリーの動
6 ゼリーの動画
6 リーの動画が
6 ーの動画が最
6 の動画が最高
6 動画が最高に
6 画が最高に面
6 が最高に面白
6 最高に面白か
6 高に面白かっ
6 に面白かった
7 ニコニコにうp
7 コニコにうpさ
7 ニコにうpされ
7 コにうpされて
7 にうpされてた
7 うpされてたナ
7 pされてたナノ
7 されてたナノゼ
7 れてたナノゼリ
7 てたナノゼリー
7 たナノゼリーの
7 ナノゼリーの動
7 ノゼリーの動画
7 ゼリーの動画が
7 リーの動画が最
7 ーの動画が最高
7 の動画が最高に
7 動画が最高に面
7 画が最高に面白
7 が最高に面白か
7 最高に面白かっ
7 高に面白かった
8 ニコニコにうpさ
8 コニコにうpされ
8 ニコにうpされて
8 コにうpされてた
8 にうpされてたナ
8 うpされてたナノ
8 pされてたナノゼ
8 されてたナノゼリ
8 れてたナノゼリー
8 てたナノゼリーの
8 たナノゼリーの動
8 ナノゼリーの動画
8 ノゼリーの動画が
8 ゼリーの動画が最
8 リーの動画が最高
8 ーの動画が最高に
8 の動画が最高に面
8 動画が最高に面白
8 画が最高に面白か
8 が最高に面白かっ
8 最高に面白かった

次は(3)の集計ですが、これには「Interstage Data Effector Standard Edition 体験版」を使います。いつの間にかV9.0からV9.1になっていました。色々とエンハンスされているようですが、私にとってはタブ区切りテキストを読めるようになってくれたのが一番嬉しいですね。相変わらず正式サポートされてないWindowsXP上で動かしてます。
http://software.fujitsu.com/jp/middleware/softlook/interstage/is_dese.html

Analyze.cfgは以下の通り。LogFileやErrFileはデバッグ用なのでどうでも良いのですが、FieldSeparatorは重要です。このFieldSeparatorの指定でタブ区切りテキストを読めるようになります。

■Analyze.cfg

LogFile "D:\temp\analyze.log"
ErrFile "D:\temp\analyzeerr.csv" 10
FieldSeparator "\t"

Anacond.txtは以下の通り。GConditionで集計の切り口となる項目名を指定します。com2gram-result.txtの先頭には、numとstringを項目名とするための先頭行が加えてあります。RConditionで集計値として文字列の出現回数(count)を指定します。

■Anacond.txt

CharacterCode  UTF-8
InFileType     CSV
OutFileType    CSV
MemorySize     1500
GCondition  $num, substr($string,0,10)
RCondition  count($string) count

これで、以下のようなテキストファイルが出力されます。

■com2gram-ana.txt(20142行、約479KB)

"num","substr(string,0,10)","count"
"1"," ",5
"1","%",2
"1","(",6
"1",")",6
"1","0",7
"1","1",4
・・・

上記のcom2gram-ana.txtには、1回しか出現しないような文字列が大量に含まれています。そのため、(4)で頻度10未満のものを切り捨てます。これでテキストファイルの量は約100分の1くらいに縮小できます。以下の定義ファイルを使ってShunSelectを動かします。

■select.cfg

CharacterCode  UTF-8
InFileType     CSV
LogFile "D:\temp\Select.log"
ErrFile "D:\temp\selecterr.csv" 10

■selectcond.txt

1 $count >= 10

最後は(5)のソートです。以下の定義ファイルで、num(文字列長)とcount(頻度)の降順ソートを指定しています。

■sortcond.txt

CharacterCode  UTF-8
InFileType     CSV
OCondition     val($num) DESC, val($count) DESC

以上の結果が以下のテキストファイルとなります。同じ文章らしきものの8グラム文字列が繰り返し現れているのは、この同じ文章を理由として複数回の投票があったからです。逆に言うと、8グラムという長い切片になると、この程度の対象文章量では10回以上出現というハードルを越えられるものは少ないという事です。当たり前ではありますが面白いです。コピペされた文章をある程度自動的に検出できたと言う事もできるでしょう(汎用的に使えるかは別の問題として)。

■com2gram-sort.txt(256行、約5KB)

"num","substr(string,0,10)","count"
"8","会のウンコ攻撃に",12
"8","価学会のウンコ攻",12
"8","創価学会のウンコ",12
"8","学会のウンコ攻撃",12
"8","攻撃に負けないで",12
"8","に負けないで!!",11
"8","ウンコ攻撃に負け",11
"8","コ攻撃に負けない",11
"8","ンコ攻撃に負けな",11
"8","撃に負けないで!",11
"8","のウンコ攻撃に負",10
"7","のウンコ攻撃に",12
"7","会のウンコ攻撃",12
"7","価学会のウンコ",12
"7","創価学会のウン",12
"7","学会のウンコ攻",12
"7","撃に負けないで",12
"7","攻撃に負けない",12
"7","に負けないで!",11
"7","ウンコ攻撃に負",11
"7","コ攻撃に負けな",11
"7","ンコ攻撃に負け",11
"7","負けないで!!",11
"7","wwwwwww",11
"7","ジャーナリスト",10
"6","に負けないで",13
"6","ウンコ攻撃に",13
"6","wwwwww",13
"6","けないで!!",12
"6","のウンコ攻撃",12
"6","会のウンコ攻",12
"6","価学会のウン",12
"6","創価学会のウ",12
"6","学会のウンコ",12
"6","撃に負けない",12
"6","攻撃に負けな",12
"6","コ攻撃に負け",11
"6","ンコ攻撃に負",11
"6","負けないで!",11
"6","ジャーナリス",10
"6","ャーナリスト",10
"5","wwwww",15
"5","に負けない",13
"5","ウンコ攻撃",13
"5","ンコ攻撃に",13
"5","創価学会の",13
"5","攻撃に負け",13
"5","負けないで",13
"5","けないで!",12
"5","ないで!!",12
"5","のウンコ攻",12
"5","会のウンコ",12
"5","価学会のウ",12
"5","学会のウン",12
"5","撃に負けな",12
"5","コ攻撃に負",11
"5","ジャーナリ",10
"5","ャーナリス",10
"5","ーナリスト",10
"4","wwww",17
"4","のウンコ",16
"4","創価学会",15
"4","けないで",14
"4","に負けな",13
"4","ウンコ攻",13
"4","コ攻撃に",13
"4","ンコ攻撃",13
"4","価学会の",13
"4","撃に負け",13
"4","攻撃に負",13
"4","負けない",13
"4","いで!!",12
"4","ないで!",12
"4","会のウン",12
"4","学会のウ",12
"4","ジャーナ",10
"4","ナリスト",10
"4","ャーナリ",10
"4","ーナリス",10
"3","うんこ",86
"3","ウンコ",76
"3","だから",23
"3","www",19
"3","ないで",17
"3","のウン",16
"3","攻撃に",16
"3","に負け",15
"3","ゼリー",15
"3","価学会",15
"3","創価学",15
"3","けない",14
"3","学会の",14
"3","コ攻撃",13
"3","ンコ攻",13
"3","撃に負",13
"3","負けな",13
"3","いで!",12
"3","で!!",12
"3","会のウ",12
"3","nko",10
"3","です。",10
"3","ジャー",10
"3","ナリス",10
"3","ャーナ",10
"3","リスト",10
"3","ーナリ",10
"3","偽うん",10
"2","うん",96
"2","んこ",86
"2","ウン",78
"2","ンコ",77
"2","から",41
"2","ゼリ",30
"2","ない",27
"2","!!",27
"2","って",25
"2","だか",23
"2","です",23
"2","ww",23
"2","いで",20
"2","創価",20
"2","瀬戸",19
"2","リー",17
"2","・・",17
"2","攻撃",17
"2","して",16
"2","のウ",16
"2","学会",16
"2","撃に",16
"2","負け",16
"2","に負",15
"2","会の",15
"2","価学",15
"2","けな",14
"2","す。",14
"2","この",13
"2","コ攻",13
"2","スト",13
"2","リス",13
"2","で!",12
"2","ko",11
"2","った",11
"2","ャー",11
"2","nk",10
"2","un",10
"2","さい",10
"2","した",10
"2","んだ",10
"2","ジャ",10
"2","ナリ",10
"2","ーナ",10
"2","偽う",10
"1","ん",149
"1","の",138
"1","う",129
"1","い",120
"1","こ",120
"1","ン",102
"1","ー",92
"1","!",92
"1","ウ",87
"1","で",85
"1","に",84
"1","コ",84
"1","な",82
"1","か",81
"1","る",72
"1","て",68
"1","は",66
"1","だ",63
"1","ら",63
"1","を",55
"1","し",53
"1","す",52
"1","リ",51
"1","た",49
"1","っ",48
"1","、",44
"1","が",41
"1","も",40
"1","・",39
"1","と",38
"1","w",37
"1","ト",35
"1","。",34
"1","れ",33
"1","け",31
"1","ゼ",31
"1","ま",30
"1","さ",27
"1","よ",27
"1","ち",26
"1","お",25
"1","人",25
"1","り",24
"1","ス",24
"1","く",23
"1","糞",22
"1","き",20
"1","会",20
"1","価",20
"1","創",20
"1","大",20
"1","ナ",19
"1","戸",19
"1","撃",19
"1","瀬",19
"1","ぎ",18
"1","カ",18
"1","ロ",18
"1","あ",17
"1","イ",17
"1","タ",17
"1","偽",17
"1","学",17
"1","攻",17
"1","負",17
"1","k",16
"1","え",16
"1","そ",15
"1","ジ",15
"1","「",14
"1","」",14
"1","ね",14
"1","ャ",14
"1","国",14
"1","政",14
"1","せ",13
"1","党",13
"1","年",13
"1","思",13
"1","日",13
"1","n",12
"1","o",12
"1","ク",12
"1","ブ",12
"1","一",12
"1","右",12
"1","波",12
"1","票",12
"1","電",12
"1","(",12
"1",")",12
"1","u",11
"1","め",11
"1","グ",11
"1","ッ",11
"1","ノ",11
"1","ラ",11
"1","想",11
"1","笑",11
"1","者",11
"1","風",11
"1","ど",10
"1","わ",10

|

« 旧知の間柄 | トップページ | ブログのコメントのデータベース化 »

文字分析」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.f.cocolog-nifty.com/t/trackback/1109195/26706733

この記事へのトラックバック一覧です: Nグラム文字分割による単語頻度分析:

« 旧知の間柄 | トップページ | ブログのコメントのデータベース化 »