N-gramモデル作ってみた

ちょっと必要になったので。

今までテキストデータを使った研究をしていた時には、一つのドキュメントにそれなりの文字数が使われていたので、

形態素解析chasenMeCab)→助詞等を削る→各ドキュメントを単語のfeatureベクトルに

という流れが常套手段だったわけですが、Twitterの分析をしていると、140文字という制約、新語、誤字脱字等の問題があり、この方法ではちょっと難しいかなぁと感じました。

そこで、今回はN-gramを使って各文章のfeautureベクトルを作って分析してみようと思いました。

N-gramモデルに関しては、大学院の授業で多少学んでいたので、そこまで時間はかかりませんでした。

自分用にカスタマイズしてある部分が多いので、無駄が多いです。また、所属に関係する部分は取り除いたりしたので、プログラムが多少崩れています。

使っているデータは以下のような形

データの中身
A_Koide0519 \t 2011/10/02 12:11:30 \t きゃりーなんちゃらってなんだ?
・
・
・

出力はtweetの各文字列の出現頻度(ラベルリスト)、WORDID、Tweet集合、Timestampとなっています。(ここまで分割した深い意味はありません)

# -*- coding:utf-8 -*-
require 'pp'

n = ARGV[0] #N
Dn = n.to_i
name = ARGV[1] #ファイル名
name3 = ARGV[2] #print time stamp
name4 = ARGV[3] #print wordlist
name5 = ARGV[4] #print wordid
name6 = ARGV[5] #print tweet

VecA = []
VecB = []
VecC = []

s = name.to_s
def readValue(name)
  i = 0
  File.open(name) do |f|
    f.each_line do |line|
      if i != 0
        col = line.chomp.split("\t")
        c = col[1].gsub("/"," ").gsub(":"," ").split(' ')
        day = Time.local(c[0],c[1],c[2],c[3],c[4],c[5])
        s = day.to_i
        VecA[i-1] = s
        VecB[i-1] = col[2].upcase #文字列を統一するため
      end
      i += 1
    end
  end
  return i-1
end

def calNgram()
  k = 0
  hash = Hash.new()
  mat = Array.new(VecB.size)
  mat2 = Array.new(VecB.size)
 for i in 0..VecB.size-1
   mat[i] = Array.new(1);mat2[i] = Array.new(1)
 end
  for i in 0..VecB.size-1
    list = VecB[i].split(//)
      if list.size < Dn
        next
      end
    0.upto(list.size - Dn) do |c|
      word = list[c..c+Dn-1].join('')
      if VecC.index(word) == nil 
        VecC[k] = word
        mat[i].push(k+1)
        hash.store(word,1)
        k += 1
      else
        n = VecC.index(word)
        if hash.key?(word) == true
          p = hash[word]
          hash.store(word,p+1)
        else
          mat[i].push(n+1)
          hash.store(word,1)
        end
      end
    end
    col = hash.values
    for j in 0..col.size-1
      mat2[i].push(col[j])
    end
    hash.clear()
  end
  return mat,mat2
end

def printValue(name3,name4,name5,name6)
  File.open(name3,"w") do |name3|
    for i in 0..VecA.size-1
      name3.puts VecA[i]
    end
  end
  File.open(name4,"w") do |name4|
    name4.printf("%d %d 1\n",Dm,VecC.size)
    for i in 0..MatA.size-1
      name4.printf("%d ",MatA[i].size-1)
      for j in 1..MatA[i].size-1
        name4.printf("%d:%d ",MatA[i][j],MatB[i][j])
      end
      name4.puts "1 1\n"
    end
  end
  File.open(name5,"w") do |name5|
    for i in 0..VecC.size-1
      name5.printf("%d %s\n",i+1,VecC[i])
    end
  end
  File.open(name6,"w") do |name6|
    for i in 0..VecB.size-1
      name6.printf("%s\n",VecB[i])
    end
  end
end

Dm = readValue(name)
MatA,MatB = calNgram()
printValue(name3,name4,name5,name6)

使い方は

 ruby ngram.rb N ファイル名 N.tmp N.lbl N.wid &
第一引数にNを指定すればそれに合わせて出力してくれます。

結果

ラベルリスト
tweet数 文字列数
各tweetの異なり文字列数 1:1 2:1 3:1 …
・
・
・


WORDID(tri-gramの場合)
1 きゃり
2 ゃりー
3 りーな
・
・
・


という感じ。核はcalNgramメソッドになります。

何か分析結果が形になれば、またお知らせしたいですね。