文字列カーネルSVMによる
辞書なしツイート分類
∼文字列カーネル入門∼
第7回自然言語処理勉強会 #TokyoNLP
2011/09/10 @a_bicky
2013/06/02 改訂版
• 自己紹介
• カーネル法の概要
• SVMの概要
• 文字列カーネル入門
• 文字列カーネルによるツイート分類
アジェンダ
アジェンダ
• 自己紹介
• カーネル法の概要
• SVMの概要
• 文字列カーネル入門
• 文字列カーネルによるツイート分類
自己紹介
• Takeshi Arabiki(データマイニングエンジニア1年目)
‣ Twitter: @a_bicky
‣ はてな: id:a_bicky
• 興味
機械学習、自然言語処理、R
• ブログ
あらびき日記 http://d.hatena.ne.jp/a_bicky/
Rユーザです
Osaka.R #4 Tokyo.R #16
http://www.slideshare.net/abicky/twitterr http://www.slideshare.net/abicky/r-9034336
そろそろ ScyPy に浮気しようと思ってます
Webプログラミングを少々
http://favmemo.com/
作りました!
http://favmemo.com/
http://favolog.org/
影に隠れました!
Webプログラミングを少々
• 自己紹介
• カーネル法の概要
• SVMの概要
• 文字列カーネル入門
• 文字列カーネルによるツイート分類
アジェンダ
カーネル法の概要
非線形データの解析
=
=
=
高次元に写像することで非線形問題を線形問題にする
変換
0
0
0
0
0
線形分離不可能(非線形問題) 線形分離可能(線形問題)
例
非線形データの非線形解析
入力 特徴ベクトル 線形多変量解析
入力空間 特徴空間
高次元空間に写像すると計算量が増える
入力空間
x f(x) = wT
φ(x)
カーネル法による非線形解析
入力 特徴ベクトル カーネル多変量解析
入力空間 特徴空間入力空間
x f(x) =
n
i=1
αik

x(i)
, x)

= wT
φ(x)

Representer定理
(カーネル関数)
k

x(i)
, x

= φ(x(i)
)T
φ(x)
k
カーネル関数を導入することで計算量削減(カーネルトリック)
カーネル関数の種類
カーネル関数によって写像する特徴空間は異なる
線形カーネル
多項式カーネル
ガウスカーネル
• 自己紹介
• カーネル法の概要
• SVMの概要
• 文字列カーネル入門
• 文字列カーネルによるツイート分類
アジェンダ
SVMの概要
SVMの概要
どのように境界面を定めると ”最適な” 分類ができるか?
+
−
0
0
g(x) = wT
x + w0
w
SVMの概要
マージンを最大化するように境界面を決定する
+
−
0
0
マージン
この軸の取り方だと
マージンが小さい!
サポートベクトル
g(x) = wT
x + w0
w
SVMの概要
マージンを最大化するように境界面を決定する
SVMの概要
境界面を変えてみる
+
−
0
0
g(x) = wT
x + w0
w
SVMの概要
+
−
0
0
マージン
サポートベクトル
g(x) = wT
x + w0
w
SVMの概要
SVMの概要
完全に分類できない場合(ソフトマージンの最大化)
罰則
制約条件
g(x) = wT
x + w0
w
0
+
−
0
マージン
サポートベクトル
SVMの解法(線形問題)
目的関数
最適解
SV: サポートベクトル
(ラグランジュ乗数)
双対問題 + ラグランジュの未定乗数法
s.t.
マージン    を大きくし,罰則    を小さくしたい
SVMの解法(非線形問題)
s.t.
リプレゼンター定理
非線形変換
目的関数
s.t.
線形問題と同様に
SVMの解法(非線形問題)
s.t.
リプレゼンター定理
非線形変換
目的関数
s.t.
線形問題と同様に
計算に関係する部分 を計算する必要がない!
(カーネルトリック)
• 自己紹介
• カーネル法の概要
• SVMの概要
• 文字列カーネル入門
• 文字列カーネルによるツイート分類
アジェンダ
文字列カーネル入門
文字列カーネルとは
入力として文字列を扱い部分文字列の出現回数などで表現される
特徴空間での内積を返す
k(x, x
) = xT
x
k(x, x
) = (xT
x
+ c)p
線形カーネル
多項式カーネル
文字列カーネル
入力が数値ベクトル
入力が文字列(1次元)(例)Gap-weighted String Kernel
文字数で区切るので辞書いらず!
k(s, t) =

u∈Σn

i:u=s[i]

j:u=t[j]
λspan(i)+span(j)
文字列カーネルの種類
• Spectrum Kernel
長さ n の連続な部分文字列の出現回数を素性とする
特徴空間としては 文字 n-gram と同じ!
• Gap-weighted String Kernel
長さ n の部分文字列(gapがあってもOK)に対してマッチ始端からマッチ
終端の距離に応じた値を素性とする
• Mismatch String Kernel
長さ n の連続な部分文字列の出現回数を素性とするが、m 文字の不一致が
あってもカウントする
e. g. ) m = 1の場合 car という部分文字列に対して cat が現れても出現回数に含める
• String Alignment Kernel
というのもあるらしい・・・
文字列カーネルの種類
• Spectrum Kernel
長さ n の連続な部分文字列の出現回数を素性とする
特徴空間としては 文字 n-gram と同じ!
• Gap-weighted String Kernel
長さ n の部分文字列(gapがあってもOK)に対してマッチ始端からマッチ
終端の距離に応じた値を素性とする
• Mismatch String Kernel
長さ n の連続な部分文字列の出現回数を素性とするが、m 文字の不一致が
あってもカウントする
e. g. ) m = 1の場合 car という部分文字列に対して cat が現れても出現回数に含める
• String Alignment Kernel
というのもあるらしい・・・
Gap-weighted String Kernel
φu(s) =

i:u=s[i]
λspan(i)
tokyonlp
nokuno
tokyonlp
φon(tokyonlp) = λ5
+ λ2
φon(nokuno) = λ4
span(i1 = 2, i2 = 6) = 5
span(i1 = 5, i2 = 6) = 2
span(i1 = 2, i2 = 5) = 4
マッチしなければ空集合
部分文字列 u の素性の値(素性ベクトルの1要素)
(例)u = “on”, s = “nokuno”,“tokyonlp” の場合

si1
= u1, si2
= u2, · · · , sin
= un
span(i) = sin
− si1
+ 1

decay factor λ ∈ (0, 1)
Gap-weighted String Kernel
Kn(s, t) =

u∈Σn
φu(s)φu(t) =

u∈Σn

i:u=s[i]
λspan(i)

j:u=t[j]
λspan(j)
=

u∈Σn

i:u=s[i]

j:u=t[j]
λspan(i)+span(j)
1つの計算量が c とすると
全体の計算量は O(|Σ|n) !!
カーネル関数(パラメータは n と λ の2つ)
正規化(文字列の長さの違いを考慮)
ˆK(s, t) = ˆφ(s)T ˆφ(t) =
φ(s)T
φ(s)
φ(t)
φ(t)
=
1
φ(s)φ(t)
φ(s)T
φ(t) =
K(s, t)

K(s, s)K(t, t)
長さ1になるように正規化した素性ベクトル
長さ n の全ての
文字列の集合
Dynamic Programming による効率化
Kn(sx, t) = Kn(s, t) +(u の末尾が x のものの追加分)
o-k o-n k-n …
φ(nokun) λ2 λ4 λ3
φ(tokyonlp) λ2 λ2 +λ5 λ4
K2(nokun,tokyonlp) = λ4 +λ6 +λ7 +λ9K2(nokun,tokyonlp) = λ4 +λ6 +λ7 +λ9K2(nokun,tokyonlp) = λ4 +λ6 +λ7 +λ9K2(nokun,tokyonlp) = λ4 +λ6 +λ7 +λ9K2(nokun,tokyonlp) = λ4 +λ6 +λ7 +λ9
文字列 s の末尾に文字 x を追加した場合
o-k o-n k-n o-o k-o …
φ(nokuno) λ2 λ4 λ3 λ5 λ4
φ(tokyonlp) λ2 λ2 +λ5 λ4 λ4 λ3
K2(nokuno,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ9K2(nokuno,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ9K2(nokuno,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ9K2(nokuno,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ9K2(nokuno,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ9K2(nokuno,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ9K2(nokuno,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ9
例1(nokun に o を追加)
Dynamic Programming による効率化
Kn(sx, t) = Kn(s, t) +(u の末尾が x のものの追加分)
文字列 s の末尾に文字 x を追加した場合
例2(nokun に n を追加)
o-k o-n k-n …
φ(nokunn) λ2 λ4 +λ5 λ3 +λ4
φ(tokyonlp) λ2 λ2 +λ5 λ4
K2(nokunn,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ8 +λ10K2(nokunn,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ8 +λ10K2(nokunn,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ8 +λ10K2(nokunn,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ8 +λ10K2(nokunn,tokyonlp) = λ4 +λ6 +λ7 +λ9 +λ7 +λ8 +λ10
u の末尾が x の素性の値を修正する必要あり!
o-k o-n k-n …
φ(nokun) λ2 λ4 λ3
φ(tokyonlp) λ2 λ2 +λ5 λ4
K2(nokun,tokyonlp) = λ4 +λ6 +λ7 +λ9K2(nokun,tokyonlp) = λ4 +λ6 +λ7 +λ9K2(nokun,tokyonlp) = λ4 +λ6 +λ7 +λ9K2(nokun,tokyonlp) = λ4 +λ6 +λ7 +λ9K2(nokun,tokyonlp) = λ4 +λ6 +λ7 +λ9
Dynamic Programming による効率化
Kn(sx, t) = Kn(s, t) +(u の末尾が x で終わるものの追加分)
文字列 s の末尾に文字 x を追加した場合
s
t
u
u x
x
x
|s| + 1i1
j1
t
x
jn−1 + 1 k
|s| + 1 − i1 + 1
u が x より手前にある t の部分文字列の集合
Kn(sx, t) = Kn(s, t) +

u∈Σn−1

i:u=s[i]

k:tk=x

j:u=t[j],jn−1k
λ|s|+1−i1+1
λk−j1+1
= Kn(s, t) +

k:tk=x
λ2

u∈Σn−1

i:u=s[i]

j:u=t[j],jn−1k
λ|s|−i1+1
λk−1−j1+1
= Kn(s, t) +

k:tk=x
λ2
K
n−1(s, t[1 : k − 1])
K
n(s, t) =

u∈Σn

i:u=s[i]

j:u=t[j]
λ|s|−i1+1
λ|t|−j1+1
※ は文字列 の長さ|s| s
Dynamic Programming による効率化
s
x
x
x
K
n(s, t) =

u∈Σn

i:u=s[i]

j:u=t[j]
λ|s|−i1+1
λ|t|−j1+1
t[1 : k − 1]
t[1 : k − 1]
Kn(sx, t) = Kn(s, t) +

k:tk=x
λ2
K
n−1(s, t[1 : k − 1])
s
t
j1
i1
|s| + i1 + 1
|t| + j1 + 1
u
k
の意味K
n(s, t)
改めて     の意味Kn(s, t)
Dynamic Programming による効率化
K
n(sx, t) = λK
n(s, t) +

u∈Σn−1

i:u=s[i]

k:tk=x

j:u=t[j],jn−1k
λ|s|+1−i1+1
λ|t|−j1+1
= λK
n(s, t) +

k:tk=x
λ|t|−k+2

u∈Σn−1

i:u=s[i]

j:u=t[j],jn−1k
λ|s|−i1+1
λk−1−j1+1
= λK
n(s, t) +

k:tk=x
λ|t|−k+2
K
n−1(s, t[1 : k − 1])
= λK
n(s, t) + K
n(sx, t)
K
n(s, t) の更新式(    と同様)Kn(s, t)
K
n(sx, t) =

k:tk=x
λ|t|−k+2
K
n−1(s, t[1 : k − 1])
K
n(sx, ty) =

λK
n(sx, t) if x = y
λK
n(sx, t) + λ2
K
n−1(s, t) otherwise
Dynamic Programming による効率化
の更新式
単純に2つの項に分解
t
= ty とおくと
よって
K
n(s, t)
K
n(sx, t
) =

k:t
k=x
λ|t
|−k+2
K
n−1(s, t
[1 : k − 1])
= λ

k:t
k=x,k|t|
λ|t|−k+2
K
n−1(s, t[1 : k − 1]) + λ2
K
n−1(s, t)[[t
k = x, k = |t
|]]
= λK
n(sx, t) + λ2
K
n−1(s, t)[[t
k = x, k = |t
|]]
K
i (sx, ty) =

λK
i (sx, t) if x = y
λK
i (sx, t) + λ2
K
i−1(s, t) otherwise
Gap-weighted String Kernel の更新式
i = 1, · · · , n − 1
K
0(s, t) = 1 ∀
s, t
K
i (s, t) = 0 if min(|s|, |t|)  i
K
i(s, t) = 0 if min(|s|, |t|)  i
K
i(sx, t) = λK
i(s, t) + K
i (sx, t)
Ki(s, t) = 0 if min(|s|, |t|)  i
Kn(sx, t) = Kn(s, t) +

k:tk=x
λ2
K
n−1(s, t[1 : k − 1])
Gap-weighted String Kernel の”擬似”コード(1)
l = 0.7 # lambda
def indices(t, x):
ret = [];
pos = -1;
while 1:
pos = t.find(x, pos + 1)
if pos != -1:
ret.append(pos)
else:
break
return ret
def K(i, s, t):
if min(len(s), len(t))  i:
return 0
return K(i, s[0:-1], t) + l ** 2 * sum([K1(i - 1, s[0:-1], t[0:j]) for j in
indices(t, s[-1])])
Ki(s, t) = 0 if min(|s|, |t|)  i
Kn(sx, t) = Kn(s, t) +

k:tk=x
λ2
K
n−1(s, t[1 : k − 1])
K
i (sx, ty) =

λK
i (sx, t) if x = y
λK
i (sx, t) + λ2
K
i−1(s, t) otherwise
def K1(i, s, t):
if i == 0:
return 1
if min(len(s), len(t))  i:
return 0
return l * K1(i, s[0:-1], t) + K2(i, s, t)
Gap-weighted String Kernel の”擬似”コード(2)
K
0(s, t) = 1 ∀
s, t
K
i (s, t) = 0 if min(|s|, |t|)  i
K
i(s, t) = 0 if min(|s|, |t|)  i
K
i(sx, t) = λK
i(s, t) + K
i (sx, t)
def K2(i, s, t):
if min(len(s), len(t))  i:
return 0
if s[-1] == t[-1]:
return l * (K2(i, s, t[0:-1]) + l * K1(i -1, s[0:-1], t[0:-1]))
else:
return l * K2(i, s, t[0:-1])
• 自己紹介
• カーネル法の概要
• SVMの概要
• 文字列カーネル入門
• 文字列カーネルによるツイート分類
アジェンダ
文字列カーネルによる
ツイート分類
データの取得
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tweepy
import re
import codecs
def format_tweet(tweet):
tweet = re.sub(r'[rn]', ' ', tweet) # remove CR and LF
tweet = re.sub(r's*https?://[-w.#%@/?=]*s*', ' ', tweet) # remove URL
tweet = re.sub(r'(^|s+)#[^s]+s*', ' ', tweet) # remove hash tags
tweet = re.sub(r's*(?!w)@w+(?!@)s*', ' ', tweet) # remove user names
return tweet
api = tweepy.API()
f = open('tweets.dat', 'w')
f = codecs.lookup('utf_8')[-1](f)
for c, user in zip((-1, 1), ('a_bicky', 'midoisan')):
print user
for i in range(1, 4):
while True:
print i
try:
statuses = api.user_timeline(user, count = 200, page = i)
break
except:
print ’Try again...’
for tweet in map(lambda s:format_tweet(s.text), statuses):
f.write('%s #%sn' % (c, tweet))
f.close()
# 1人あたり約500ツイート取得
# SVMlightで使えるデータ形式で保存
データセット
user train test
337 tweets 163 tweets
305 tweets 186 tweets
5文字以上のツイートのみ抽出し、約2対1の割合で学習データとテストデータに分割
何故か midoisan の方が 2対1 にはほど遠い割合ということに今更気付く・・・
a_bicky: いろいろとやばい状況…
midoisan: 狂気∼ッ!!
(例)
← ネガティブツイートが多い
← 特徴的な言葉遣いと狂った発言が多い
文字 n-gram, Gap-weighted 文字列カーネル(   ), 単語 unigram で正解率を比較λ = 0.7
結果
文字 n-gram
文字 n-gram と Gap-weighted 文字列カーネルで約4.0%の差
Gap-weighted 文字列カーネル
nokuno さんでもやってみた
文字 n-gram Gap-weighted 文字列カーネル
文字 n-gram と Gap-weighted 文字列カーネルで約3.6%の差
まとめ
• カーネル法によって入力データを複雑な空間に射影した
上で線形多変量解析の手法が適用可能
• SVMはマージン最大化に基づく線形判別手法であり、
カーネル法が適用できる代表的な手法
• 文字列カーネルは入力として文字列を使用
• 文字列カーネルSVMによって文字 n-gramよりもツイート
分類の正解率が数%向上
• 計算量が格段に増える割にはパフォーマンスが上がらな
いとの会場の声
まとめ
• 赤穂昭太郎, カーネル多変量解析, 岩波書店, 2008
前半部分は全てこの内容
• H. Lodhi, C. Saunders, J. Shawe-Taylor, N. Cristianini, and C.Watkins.Text
Classification using String Kernels. Journal of Machine Learning Research,Vol. 2, pp.
419–444, 2002.
Gap-weighted String Kernel の元論文
• カーネル法の応用 http://www.ism.ac.jp/~fukumizu/ISM_lecture_2006/
Lecture2006_application.pdf
• 文書分類と Kernel あれこれ(仮称) http://www.ism.ac.jp/~fukumizu/
ISM_lecture_2006/Lecture2006_application.pdf
• Juho Rousu , John Shawe-Taylor. Efficient Computation of Gapped Substring
Kernels on Large Alphabets,The Journal of Machine Learning Research,Vol. 6, pp.
1323-1344, 2005.
余裕があればこっちの方を理解して紹介したかった・・・
参考文献
おまけ
本発表の関連コード
https://github.com/abicky/tokyonlp07_abicky
以下にアップしたのでご自由にお使い下さい!
SVMlight で文字列カーネル
double custom_kernel(KERNEL_PARM *kernel_parm, SVECTOR *a, SVECTOR *b)
{
char* s = a-userdefined;
char* t = b-userdefined;
char param[BUFSIZE];
strcpy(param, kernel_parm-custom);
...
}
SVMlight で文字列カーネルを実装してみたい!
-1 #おぉ、SVMlightのデータ形式のうち、コメントの情報が格納されるのか!
-1 #こんなこと書くぐらいならspectrum kernelぐらい組み込んどいてほしい。
kernel.h ( kernel.c として Makefile をいじってもいい)
# コマンド引数で -u “parameters” と指定した内容が格納されている
データサンプル ハッシュ以下の内容が SVECTOR-userdefined に格納される
変更履歴
• 2013/06/02
‣ pp. 33-34 の上の表の値が間違っていたので修正

文字列カーネルによる辞書なしツイート分類 〜文字列カーネル入門〜