SMO徹底入門
SVMをちゃんと実装する
2013-05-05
Last update: 2013-06-16
Yoshihiko Suhara
@sleepy_yoshi
1
v. 1.1
Thank you @a_bicky さん!
なんでこの資料つくったの?
• SMOの解説記事や疑似コード等あるけれど,自分の
頭ではその情報だけで理解できなかったから
• SMOの実装は過去に2回ほど行ったことがある
– 疑似コードをもとに打ち込んだだけ
– 写経しても結局理解できなかった
– なぜそうしているのかを理解していなかったことが原因
• 一念発起して更新式の導出等を行ったらようやくわ
かった気になったのでGW中にSMOを実装してみた
– 忘れないうちに資料化
2
はじめに
3
本資料の想定する読者
• 知っておいてほしいこと
– SVMが何かは知っている
– SVMはどうやら二次計画問題として定式化できるらし
いということを知っている
• 知っているとよりよいこと
– 主問題をいくら高速に解けても双対問題を解かない
ことにはカーネルが使えないので,やっぱり二次計画
を解かなければいけないこと
– 二次計画ソルバーの中でもSMOという方法があるら
しい
4
本資料のゴール
• 1-norm SVMのSequential Minimum Optimizer
(SMO) を自分で実装できるようにする
• SMO実装に必要な知識を解説することでSVM
に対する理解を深める
5
準備するもの
• PRML下巻
– 式番号や記法はPRMLに合せます
6
本資料の流れ
• 事前知識のおさらい
• SMOの概要
• SMOを理解する3つのポイント
– (1) KKT条件違反のチェック
– (2) 変数の更新方法
– (3) 変数の選択方法
• 疑似コード解説
7
事前知識のおさらい
8
カーネル法ひとこと要約
• 𝑓 𝒙 = 𝒘 𝑇
𝜙 𝒙 = 𝛼𝑖 𝑘(𝒙𝑖, 𝒙)𝑖
非線形変換した
入力データ
訓練データ𝒙𝑖
との類似度
• 予測値=訓練データとの類似度の重みづけ和
– 予測に用いるデータをサポートベクタと呼ぶ
カーネル法の学習=
サポートベクタの「重み」を学習
カーネル関数
9
カーネル関数の例
• 基本的なカーネル関数
– 線形カーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = 𝒙𝑖
𝑇
𝒙𝑗
– 多項式カーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = 𝛾𝒙𝑖
𝑇
𝒙𝑗 + 𝑟
𝑑
, 𝛾 > 0
– RBF (ガウス) カーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = exp −𝛾 𝒙𝑖 − 𝒙𝑗
2
, 𝛾 > 0
– シグモイドカーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = tanh(𝛾𝒙𝑖
𝑇
𝒙𝑗 + 𝑟)
10
SVMの予測値
• 𝑦 𝒙 = 𝑎 𝑛 𝑡 𝑛 𝑘(𝒙, 𝒙 𝑛)𝑁
𝑛=1 + 𝑏
– サポートベクタ (𝑎 𝑛 > 0) とのカーネル関数の値
の重み和
– 𝑏はバイアス項
11
二次計画問題 (QP) としてのSVM
• 1-norm SVMの目的関数の双対形式は以下
のQPで定式化できる (PRML下巻 p.38)
max. 𝑛=1
𝑁
𝑎 𝑛 −
1
2
𝑎 𝑛 𝑎 𝑚 𝑡 𝑛 𝑡 𝑚 𝑘(𝑥 𝑛, 𝑥 𝑚)
𝑁
𝑚=1
𝑁
𝑛=1
s. t. 0 ≤ 𝑎 𝑛 ≤ 𝐶, 𝑎 𝑛 𝑡 𝑛
𝑁
𝑛=1
= 0
12
SMOの概要
13
SMO開発の背景
• QPは求解に𝑂(𝑁3
)のコストがかかるため,全変数を対象
にしたQPは実行時間がかかる 
• 一部の選択された変数以外を固定し,選択された変数に
よるQPを解く方法が提案
– チャンキング
– Osuna’s method
• SMOはその考え方を最小単位である2変数に絞ったもの
–  2変数の場合,QPの解を閉じた解で求めることができる
14
過去の手法との比較
• 横軸は選択された変数
• 各行が各更新の試行に対応
各試行で
2変数のみ選択15
SMOの疑似コード
INPUT: (𝒙 𝑛, 𝑦𝑛) ∈ 𝐷,
OUTPUT: 𝒂 = 𝑎1, 𝑎2, … , 𝑎 𝑁
𝑇
1: Initialize 𝒂 = 𝟎
2: WHILE KKT条件を違反する変数が存在
3: KKT条件を違反する変数𝛼1を選択
4: 2つ目の変数𝛼2を選択
5: 𝛼1, 𝛼2を更新
6: END WHILE
7: RETURN 𝒂
16
SMOを理解するポイント
• 以下の3つの疑問を解消すればよい
– (1) KKT条件違反ってどうやって調べるのよ?!
– (2) 更新ってどうやるのよ?!
– (3) 更新に利用する変数ってどうやって選択する
のよ?!
• 順番に解説
ポイント
17
(1) KKT条件違反のチェック
18
SMOにおけるKKT条件
• 以下の3つの場合に分けてチェックする
– (a) 𝑎𝑖 = 0 ⇔ 𝑡𝑖 𝑦 𝒙𝑖 ≥ 1
– (b) 0 < 𝑎𝑖 < 𝐶 ⇔ 𝑡𝑖 𝑦 𝒙𝑖 = 1
– (c) 𝑎𝑖 = 𝐶 ⇔ 𝑡𝑖 𝑦 𝒙𝑖 ≤ 1
なんでこれが出てくるのか?
19
KKT条件の導出 (1/2)
• PRML (7.28)式と(7.31)式
𝜇 𝑛 𝜉 𝑛 = 0
𝑎 𝑛 = 𝐶 − 𝜇 𝑛
• これより
𝜉 𝑛 𝐶 − 𝑎 𝑛 = 0
• よって 𝜉 𝑛 > 0 となるのは 𝑎 𝑛 = 𝐶 の点のみ
– それ以外は𝜉 𝑛 = 0
– すなわちマージンの内側かマージン上に存在
20
KKT条件の導出 (2/2)
• PRML (7.16)式より 𝑎 𝑛 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 = 0
• 𝑎 𝑛 = 0 のとき
– 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 ≥ 0
– 𝑡 𝑛 𝑦 𝒙 𝑛 ≥ 1
• 0 < 𝑎 𝑛 < 𝐶 のとき (𝜉 𝑛 = 0)
– よって,ぴったり𝑡 𝑛 𝑦 𝒙 𝑛 − 1 = 0である必要
– 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 = 0
– 𝑡 𝑛 𝑦 𝒙 𝑛 = 1
• 𝑎 𝑛 = 𝐶 のとき (𝜉 𝑛 > 0)
– 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 ≤ 0
– 𝑡 𝑛 𝑦 𝒙 𝑛 ≤ 1
21
(2) 変数の更新方法
22
2変数の更新方法
• 𝑎1, … , 𝑎 𝑁から選択された2変数𝛼1, 𝛼2の値を
以下の手順で更新する
– (i) 更新値の値域を求める
– (ii) 更新式に基づいて値を更新する
23
更新値の値域の決定 (1/2)
• 選択された2変数を更新する際,線形制約
𝑎 𝑛 𝑡 𝑛
𝑁
𝑛=1
= 0
• があるため, 0 ≤ 𝛼 𝑛 ≤ 𝐶 という制約の中で
𝛼1
𝑛𝑒𝑤
𝑡1 + 𝛼2
𝑛𝑒𝑤
𝑡2 = 𝛼1
𝑜𝑙𝑑
𝑡1 + 𝛼2
𝑜𝑙𝑑
𝑡2
• を満たす必要がある
• 𝛼1
𝑛𝑒𝑤
と𝛼2
𝑛𝑒𝑤
の値域をあらかじめ求めておく
– 𝛼2
𝑛𝑒𝑤
の最大値と最小値 𝐿 ≤ 𝛼2
𝑛𝑒𝑤
≤ 𝐻を求める
24※𝛼2
𝑛𝑒𝑤
に対して求めるのは元論文との一貫性のため
更新値の値域の決定 (2/2)
• 𝑡1 ≠ 𝑡2 の場合
– 𝐿 = max 0, 𝛼2
𝑜𝑙𝑑
− 𝛼1
𝑜𝑙𝑑
– 𝐻 = min 𝐶, 𝐶 − 𝛼2
𝑜𝑙𝑑
+ 𝛼1
𝑜𝑙𝑑
• 𝑡1 = 𝑡2 の場合
– 𝐿 = max 0, 𝛼1
𝑜𝑙𝑑
+ 𝛼2
𝑜𝑙𝑑
− 𝐶
– 𝐻 = max 𝐶, 𝛼1
𝑜𝑙𝑑
+ 𝛼2
𝑜𝑙𝑑
25
はて,どうやって求めているのか?
𝐿と𝐻の求め方 (1/2)
• 𝑡1 ≠ 𝑡2 の場合
𝛼1
𝑛𝑒𝑤
− 𝛼2
𝑛𝑒𝑤
= 𝛼1
𝑜𝑙𝑑
− 𝛼2
𝑜𝑙𝑑
= const.
𝛼2
𝑛𝑒𝑤
= 𝛼1
𝑛𝑒𝑤
+ (𝛼2
𝑜𝑙𝑑
− 𝛼1
𝑜𝑙𝑑
)
26
𝛼2
𝑛𝑒𝑤
𝛼1
𝑛𝑒𝑤0 𝐶
𝐶
𝛼1
𝑜𝑙𝑑
− 𝛼2
𝑜𝑙𝑑
< 0 の場合
𝛼2
𝑛𝑒𝑤
𝛼1
𝑛𝑒𝑤0 𝐶
𝐶
𝛼1
𝑜𝑙𝑑
− 𝛼2
𝑜𝑙𝑑
> 0 の場合
𝐶 − 𝛼2
𝑜𝑙𝑑
+ 𝛼1
𝑜𝑙𝑑
𝛼2
𝑜𝑙𝑑
− 𝛼1
𝑜𝑙𝑑
𝐿 = max 0, 𝛼2
𝑜𝑙𝑑
− 𝛼1
𝑜𝑙𝑑
𝐻 = min 𝐶, 𝐶 − 𝛼2
𝑜𝑙𝑑
+ 𝛼1
𝑜𝑙𝑑
よって
𝐿と𝐻の求め方 (2/2)
• 𝑡1 = 𝑡2 の場合
𝛼1
𝑛𝑒𝑤
+ 𝛼2
𝑛𝑒𝑤
= 𝛼1
𝑜𝑙𝑑
+ 𝛼2
𝑜𝑙𝑑
= const.
𝛼2
𝑛𝑒𝑤
= −𝛼1
𝑛𝑒𝑤
+ (𝛼1
𝑜𝑙𝑑
+ 𝛼2
𝑜𝑙𝑑
)
27
𝛼2
𝑛𝑒𝑤
𝛼1
𝑛𝑒𝑤0 𝐶
𝐶
𝛼1
𝑜𝑙𝑑
+ 𝛼2
𝑜𝑙𝑑
< C の場合
𝛼2
𝑛𝑒𝑤
𝛼1
𝑛𝑒𝑤0 𝐶
𝐶
𝛼1
𝑜𝑙𝑑
+ 𝛼2
𝑜𝑙𝑑
> C の場合
𝛼1
𝑜𝑙𝑑
+ 𝛼2
𝑜𝑙𝑑
𝐿 = max 0, 𝛼1
𝑜𝑙𝑑
+ 𝛼2
𝑜𝑙𝑑
− 𝐶
𝐻 = max 𝐶, 𝛼1
𝑜𝑙𝑑
+ 𝛼2
𝑜𝑙𝑑
𝛼1
𝑜𝑙𝑑
+ 𝛼2
𝑜𝑙𝑑
− 𝐶
よって
𝛼1
𝑛𝑒𝑤
, 𝛼2
𝑛𝑒𝑤
の解析解
• 𝛼2
𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑
= 𝛼2 +
𝑡2 𝐸1−𝐸2
𝑘 𝒙1,𝑥1 −2𝑘 𝒙1,𝒙2 +𝑘(𝒙2,𝒙2)
– ただし 𝐸𝑖 = 𝑦 𝒙𝑖 − 𝑡𝑖
• 𝛼2
𝑛𝑒𝑤
=
𝐿 𝛼2
𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑
< 𝐿
𝐻 𝛼2
𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑
> 𝐻
𝛼2
𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑
otherwise
• 𝛼1
𝑛𝑒𝑤
= 𝛼1
𝑜𝑙𝑑
+ 𝑡1 𝑡2 𝛼2
𝑜𝑙𝑑
− 𝛼2
𝑛𝑒𝑤
クリッピングと呼んだりするそうです
28
𝛼2
𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑
の更新式導出
• 𝛼1
𝑛𝑒𝑤
= 𝛼1
𝑜𝑙𝑑
+ 𝛿1
• 𝛼2
𝑛𝑒𝑤
= 𝛼2
𝑜𝑙𝑑
+ 𝛿2 とする
• 目的関数のうち変化量𝛿1, 𝛿2に関わる部分だけ抜き出す
目的関数: 𝑛=1
𝑁
𝑎 𝑛 −
1
2
𝑎 𝑛 𝑎 𝑚 𝑡 𝑛 𝑡 𝑚 𝑘(𝑥 𝑛, 𝑥 𝑚)𝑁
𝑚=1
𝑁
𝑛=1
𝛿1 + 𝛿2 − 𝛿1 𝑡1 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙1, 𝒙 𝑛
𝑁
𝑛=1
− 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙2, 𝒙 𝑛
𝑁
𝑛=1
−
1
2
𝛿1
2
𝑘 𝑥1, 𝑥1 + 2𝛿1 𝛿2 𝑡1 𝑡2 𝑘 𝑥1, 𝑥2 + 𝛿2
2
𝑘 𝑥2, 𝑥2
29
※簡単のため,unclippedを外している
制約より
𝛿1 𝑡1 + 𝛿2 𝑡2 = 0
𝛼1 + 𝛿1 ≥ 0
𝛼2 + 𝛿2 ≥ 0
方針: 𝛿2に関する停留点を求める
※ 凸性を保証するため,正定値カーネルの必要
(*)
𝑡1 = 𝑡2 の場合
• 𝛿1 = −𝛿2 より(*)式は
𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙1, 𝒙 𝑛
𝑁
𝑛=1
− 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙2, 𝒙 𝑛
𝑁
𝑛=1
−
1
2
𝛿2
2
𝑘 𝒙1, 𝒙1 − 2𝛿2
2
𝑘 𝒙1, 𝒙2 + 𝛿2
2
𝑘 𝒙2, 𝒙2
𝛿2 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 −
1
2
𝛿2
2
𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2
• これを𝛿2に関して偏微分して0とおく
𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 − 𝛿2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 = 0
𝛿2 =
𝑡2 𝑦 𝒙1 − 𝑦 𝒙2
𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2
𝛿2 =
𝑡2 𝑦 𝒙1 − 𝑡1 − 𝑦 𝒙2 − 𝑡2
𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2
=
𝑡2 𝐸1 − 𝐸2
𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘(𝒙2, 𝒙2)
30
𝑡1 = −𝑡2 の場合
• 𝛿1 = 𝛿2 より(*)式は
2𝛿2 + 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙1, 𝒙 𝑛
𝑁
𝑛=1
− 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙2, 𝒙 𝑛
𝑁
𝑛=1
−
1
2
𝛿2
2
𝑘 𝒙1, 𝒙1 − 2𝛿2
2
𝑘 𝒙1, 𝒙2 + 𝛿2
2
𝑘 𝒙2, 𝒙2
2𝛿2 + 𝛿2 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 −
1
2
𝛿2
2
𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2
• これを𝛿2に関して偏微分して0とおく
2 + 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 − 𝛿2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 = 0
𝛿2 =
2𝑡2 𝑡2 + 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2
𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2
𝛿2 =
𝑡2 𝑦 𝒙1 − 𝑡1 − 𝑦 𝒙2 − 𝑡2
𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 31
※ほぼ同じ.異なる部分に赤の下線
𝛼1
𝑛𝑒𝑤
の導出
• 制約より
• 𝛼1
𝑛𝑒𝑤
𝑡1 + 𝛼2
𝑛𝑒𝑤
𝑡2 = 𝛼1
𝑜𝑙𝑑
𝑡1 + 𝛼2
𝑜𝑙𝑑
𝑡2
• 𝛼1
𝑛𝑒𝑤
= 𝛼1
𝑜𝑙𝑑
+
𝑡2
𝑡1
𝛼2
𝑜𝑙𝑑
− 𝛼2
𝑛𝑒𝑤
•
𝑡2
𝑡1
= 𝑡1 𝑡2 より
• 𝛼1
𝑛𝑒𝑤
= 𝛼1
𝑜𝑙𝑑
+ 𝑡1 𝑡2 𝛼2
𝑜𝑙𝑑
− 𝛼2
𝑛𝑒𝑤
32
(3) 変数の選択方法
33
1点目の選択法
• KKT条件を満たさない変数集合から選択する
– 高速に発見するため,0 < 𝑎𝑖 < 𝐶 であるような変数
集合から先にチェックする
– それらがすべてKKT条件を満たしている場合,残り全
ての点についてチェック
• 選択された変数を𝛼2とする (𝑎2 ではない)
– これ以降 𝒙2, 𝑡2は選択された事例の特徴ベクトルとラ
ベルを表す
– ※1点目なのに𝛼2としたのは元論文の記法と一貫性
を保つため
※元論文の疑似コードにおけるmain routine
34
2点目の選択法
• (1) 変数の更新量が大きくなるように選択
– すなわち 𝐸1 − 𝐸2 を最大化する事例
– 𝐸2が正の場合→最小誤差𝐸1の事例を選択
– 𝐸2が負の場合→最大誤差𝐸1の事例を選択
• (2) 境界上にない事例をランダムに選択
– すなわち0 < 𝑎 𝑛 < 𝐶であるような事例
• (3) ランダムに選択
– 残りの事例を選択
• 選択された変数を𝛼1とする
※変数の変化量が小さい場合には2点目の選択からやり直す
※元論文の疑似コードにおけるexamineExample(i2)
35
補足: LIBSVMの変数選択方法
• SVM実装として有名なLIBSVMはSMOベース
の手法
– この変数選択方法に特長がある
– Working Set Selection 3 (WSS3)
36
疑似コード解説
37
[Platt 98] の疑似コードを眺める
38
1点目の選択
更新されたものがあれば0 < 𝑎 𝑛 < 𝐶である変数を優先的にチェックする
それでも更新されなければ全データをチェックする
更新された変数があるか
全データチェックフラグがONの場合
39
takeStep(i1,i2)が0を返す場合には
2点目の各種選択法が順番に適用される
takeStep(i1, i2)がtrueを返せばi2に対する処理は終了
いずれも更新されない場合,0を返す
40
1点目の更新処理と
クリッピング
41
カーネルが正定値でない場合の処理
(解説は割愛)
42
変化量が微小の場合,更新処理を行わない
2点目の更新
更新結果の保存
まとめ
43
まとめ
• SMOの概要を解説
• SMOを理解する3つのポイントを解説
– (1) KKT条件違反のチェック
– (2) 変数の更新方法
– (3) 変数の選択方法
• 疑似コード解説により,実装例を紹介
44
本資料で扱わなかったこと
• SMO実装に必要な情報
– 更新途中のバイアス計算
– 停止条件の解説
• SMOの改善
– KKT条件違反チェックの高速化 [Keerthi+ 01]
– LIBSVMのWSS3の解説 [Fan+ 05]
• その他
– 汎用QPソルバーの概説
※元気があればバージョンアップで対応予定
45
感想
46
References
• [Platt 98] J. C. Platt, "Sequential Minimal Optimization: A Fast Algorithm for Training
Support Vector Machines", In Advances in Kernel Methods Support Vector Learning,
MIT Press, 1998.
– 元論文.基本的にこれを読めばよい
• [Cristianini+ 05] N. Cristianini, J. Shawe-Taylor (大北剛訳), “サポートベクタ―マシン入
門”, 共立出版, 2005.
– 7章でSMOを紹介
– 巻末に[Platt 98]の疑似コードに対する解説あり
• [Keerthi+ 01] S. S. Keerthi, S. K. Shevade, C. Bhattacharyya, K. R. K.
Murthy,”Improvements to Platt's SMO Algorithm for SVM Classifier Design”,
Neural Computation, vol. 13(3), pp.637-649, 2001.
– KKT条件チェックの高速化
• [Fan+ 05] R.-E. Fan, P.-H. Chen, C.-J. Lin, "Working Set Selection Using Second
Order Information for Training Support Vector Machines", Journal of Machine
Learning Research, vol.6, pp.1889?1918, 2005.
– 変数選択のWWS 3論文
– 目的関数の2次勾配の情報を用いることで適切な選択が可能
47
参考URL
• 福水健次. 正定値カーネルによるデータ解析ーカーネル法の基礎と展
開ー 4. サポートベクターマシン. 統計数理研究所公開講座
– http://www.ism.ac.jp/~fukumizu/ISM_lecture_2010/Kernel_4_SVM.pdf
• SMOアルゴリズム – Intelligence Architecture けんきうノート
– http://convexbrain.sourceforge.jp/cgi-
bin/wifky.pl?p=SMO%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0
• 戸田健一. SVMの2次計画問題に関する解法の考察
– http://numataws1.ms.kagu.tus.ac.jp/THESIS/H15/presen_toda.pdf
• 山下浩, 田中茂. サポートベクターマシンとその応用.
– http://www.msi.co.jp/vmstudio/materials/svm.pdf
• SMO法でSVMの学習してみた – きちめも
– http://d.hatena.ne.jp/se-kichi/20100306/1267858745
• SVMの学習用アルゴリズムSMOを実装してみる – きしだのはてな
– http://d.hatena.ne.jp/nowokay/20080730/1217371769
48
Many thanks to authors!
おしまい
49

SMO徹底入門 - SVMをちゃんと実装する

  • 1.
  • 2.
    なんでこの資料つくったの? • SMOの解説記事や疑似コード等あるけれど,自分の 頭ではその情報だけで理解できなかったから • SMOの実装は過去に2回ほど行ったことがある –疑似コードをもとに打ち込んだだけ – 写経しても結局理解できなかった – なぜそうしているのかを理解していなかったことが原因 • 一念発起して更新式の導出等を行ったらようやくわ かった気になったのでGW中にSMOを実装してみた – 忘れないうちに資料化 2
  • 3.
  • 4.
    本資料の想定する読者 • 知っておいてほしいこと – SVMが何かは知っている –SVMはどうやら二次計画問題として定式化できるらし いということを知っている • 知っているとよりよいこと – 主問題をいくら高速に解けても双対問題を解かない ことにはカーネルが使えないので,やっぱり二次計画 を解かなければいけないこと – 二次計画ソルバーの中でもSMOという方法があるら しい 4
  • 5.
    本資料のゴール • 1-norm SVMのSequentialMinimum Optimizer (SMO) を自分で実装できるようにする • SMO実装に必要な知識を解説することでSVM に対する理解を深める 5
  • 6.
  • 7.
    本資料の流れ • 事前知識のおさらい • SMOの概要 •SMOを理解する3つのポイント – (1) KKT条件違反のチェック – (2) 変数の更新方法 – (3) 変数の選択方法 • 疑似コード解説 7
  • 8.
  • 9.
    カーネル法ひとこと要約 • 𝑓 𝒙= 𝒘 𝑇 𝜙 𝒙 = 𝛼𝑖 𝑘(𝒙𝑖, 𝒙)𝑖 非線形変換した 入力データ 訓練データ𝒙𝑖 との類似度 • 予測値=訓練データとの類似度の重みづけ和 – 予測に用いるデータをサポートベクタと呼ぶ カーネル法の学習= サポートベクタの「重み」を学習 カーネル関数 9
  • 10.
    カーネル関数の例 • 基本的なカーネル関数 – 線形カーネル:𝑘 𝒙𝑖, 𝒙𝑗 = 𝒙𝑖 𝑇 𝒙𝑗 – 多項式カーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = 𝛾𝒙𝑖 𝑇 𝒙𝑗 + 𝑟 𝑑 , 𝛾 > 0 – RBF (ガウス) カーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = exp −𝛾 𝒙𝑖 − 𝒙𝑗 2 , 𝛾 > 0 – シグモイドカーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = tanh(𝛾𝒙𝑖 𝑇 𝒙𝑗 + 𝑟) 10
  • 11.
    SVMの予測値 • 𝑦 𝒙= 𝑎 𝑛 𝑡 𝑛 𝑘(𝒙, 𝒙 𝑛)𝑁 𝑛=1 + 𝑏 – サポートベクタ (𝑎 𝑛 > 0) とのカーネル関数の値 の重み和 – 𝑏はバイアス項 11
  • 12.
    二次計画問題 (QP) としてのSVM •1-norm SVMの目的関数の双対形式は以下 のQPで定式化できる (PRML下巻 p.38) max. 𝑛=1 𝑁 𝑎 𝑛 − 1 2 𝑎 𝑛 𝑎 𝑚 𝑡 𝑛 𝑡 𝑚 𝑘(𝑥 𝑛, 𝑥 𝑚) 𝑁 𝑚=1 𝑁 𝑛=1 s. t. 0 ≤ 𝑎 𝑛 ≤ 𝐶, 𝑎 𝑛 𝑡 𝑛 𝑁 𝑛=1 = 0 12
  • 13.
  • 14.
    SMO開発の背景 • QPは求解に𝑂(𝑁3 )のコストがかかるため,全変数を対象 にしたQPは実行時間がかかる  •一部の選択された変数以外を固定し,選択された変数に よるQPを解く方法が提案 – チャンキング – Osuna’s method • SMOはその考え方を最小単位である2変数に絞ったもの –  2変数の場合,QPの解を閉じた解で求めることができる 14
  • 15.
  • 16.
    SMOの疑似コード INPUT: (𝒙 𝑛,𝑦𝑛) ∈ 𝐷, OUTPUT: 𝒂 = 𝑎1, 𝑎2, … , 𝑎 𝑁 𝑇 1: Initialize 𝒂 = 𝟎 2: WHILE KKT条件を違反する変数が存在 3: KKT条件を違反する変数𝛼1を選択 4: 2つ目の変数𝛼2を選択 5: 𝛼1, 𝛼2を更新 6: END WHILE 7: RETURN 𝒂 16
  • 17.
    SMOを理解するポイント • 以下の3つの疑問を解消すればよい – (1)KKT条件違反ってどうやって調べるのよ?! – (2) 更新ってどうやるのよ?! – (3) 更新に利用する変数ってどうやって選択する のよ?! • 順番に解説 ポイント 17
  • 18.
  • 19.
    SMOにおけるKKT条件 • 以下の3つの場合に分けてチェックする – (a)𝑎𝑖 = 0 ⇔ 𝑡𝑖 𝑦 𝒙𝑖 ≥ 1 – (b) 0 < 𝑎𝑖 < 𝐶 ⇔ 𝑡𝑖 𝑦 𝒙𝑖 = 1 – (c) 𝑎𝑖 = 𝐶 ⇔ 𝑡𝑖 𝑦 𝒙𝑖 ≤ 1 なんでこれが出てくるのか? 19
  • 20.
    KKT条件の導出 (1/2) • PRML(7.28)式と(7.31)式 𝜇 𝑛 𝜉 𝑛 = 0 𝑎 𝑛 = 𝐶 − 𝜇 𝑛 • これより 𝜉 𝑛 𝐶 − 𝑎 𝑛 = 0 • よって 𝜉 𝑛 > 0 となるのは 𝑎 𝑛 = 𝐶 の点のみ – それ以外は𝜉 𝑛 = 0 – すなわちマージンの内側かマージン上に存在 20
  • 21.
    KKT条件の導出 (2/2) • PRML(7.16)式より 𝑎 𝑛 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 = 0 • 𝑎 𝑛 = 0 のとき – 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 ≥ 0 – 𝑡 𝑛 𝑦 𝒙 𝑛 ≥ 1 • 0 < 𝑎 𝑛 < 𝐶 のとき (𝜉 𝑛 = 0) – よって,ぴったり𝑡 𝑛 𝑦 𝒙 𝑛 − 1 = 0である必要 – 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 = 0 – 𝑡 𝑛 𝑦 𝒙 𝑛 = 1 • 𝑎 𝑛 = 𝐶 のとき (𝜉 𝑛 > 0) – 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 ≤ 0 – 𝑡 𝑛 𝑦 𝒙 𝑛 ≤ 1 21
  • 22.
  • 23.
    2変数の更新方法 • 𝑎1, …, 𝑎 𝑁から選択された2変数𝛼1, 𝛼2の値を 以下の手順で更新する – (i) 更新値の値域を求める – (ii) 更新式に基づいて値を更新する 23
  • 24.
    更新値の値域の決定 (1/2) • 選択された2変数を更新する際,線形制約 𝑎𝑛 𝑡 𝑛 𝑁 𝑛=1 = 0 • があるため, 0 ≤ 𝛼 𝑛 ≤ 𝐶 という制約の中で 𝛼1 𝑛𝑒𝑤 𝑡1 + 𝛼2 𝑛𝑒𝑤 𝑡2 = 𝛼1 𝑜𝑙𝑑 𝑡1 + 𝛼2 𝑜𝑙𝑑 𝑡2 • を満たす必要がある • 𝛼1 𝑛𝑒𝑤 と𝛼2 𝑛𝑒𝑤 の値域をあらかじめ求めておく – 𝛼2 𝑛𝑒𝑤 の最大値と最小値 𝐿 ≤ 𝛼2 𝑛𝑒𝑤 ≤ 𝐻を求める 24※𝛼2 𝑛𝑒𝑤 に対して求めるのは元論文との一貫性のため
  • 25.
    更新値の値域の決定 (2/2) • 𝑡1≠ 𝑡2 の場合 – 𝐿 = max 0, 𝛼2 𝑜𝑙𝑑 − 𝛼1 𝑜𝑙𝑑 – 𝐻 = min 𝐶, 𝐶 − 𝛼2 𝑜𝑙𝑑 + 𝛼1 𝑜𝑙𝑑 • 𝑡1 = 𝑡2 の場合 – 𝐿 = max 0, 𝛼1 𝑜𝑙𝑑 + 𝛼2 𝑜𝑙𝑑 − 𝐶 – 𝐻 = max 𝐶, 𝛼1 𝑜𝑙𝑑 + 𝛼2 𝑜𝑙𝑑 25 はて,どうやって求めているのか?
  • 26.
    𝐿と𝐻の求め方 (1/2) • 𝑡1≠ 𝑡2 の場合 𝛼1 𝑛𝑒𝑤 − 𝛼2 𝑛𝑒𝑤 = 𝛼1 𝑜𝑙𝑑 − 𝛼2 𝑜𝑙𝑑 = const. 𝛼2 𝑛𝑒𝑤 = 𝛼1 𝑛𝑒𝑤 + (𝛼2 𝑜𝑙𝑑 − 𝛼1 𝑜𝑙𝑑 ) 26 𝛼2 𝑛𝑒𝑤 𝛼1 𝑛𝑒𝑤0 𝐶 𝐶 𝛼1 𝑜𝑙𝑑 − 𝛼2 𝑜𝑙𝑑 < 0 の場合 𝛼2 𝑛𝑒𝑤 𝛼1 𝑛𝑒𝑤0 𝐶 𝐶 𝛼1 𝑜𝑙𝑑 − 𝛼2 𝑜𝑙𝑑 > 0 の場合 𝐶 − 𝛼2 𝑜𝑙𝑑 + 𝛼1 𝑜𝑙𝑑 𝛼2 𝑜𝑙𝑑 − 𝛼1 𝑜𝑙𝑑 𝐿 = max 0, 𝛼2 𝑜𝑙𝑑 − 𝛼1 𝑜𝑙𝑑 𝐻 = min 𝐶, 𝐶 − 𝛼2 𝑜𝑙𝑑 + 𝛼1 𝑜𝑙𝑑 よって
  • 27.
    𝐿と𝐻の求め方 (2/2) • 𝑡1= 𝑡2 の場合 𝛼1 𝑛𝑒𝑤 + 𝛼2 𝑛𝑒𝑤 = 𝛼1 𝑜𝑙𝑑 + 𝛼2 𝑜𝑙𝑑 = const. 𝛼2 𝑛𝑒𝑤 = −𝛼1 𝑛𝑒𝑤 + (𝛼1 𝑜𝑙𝑑 + 𝛼2 𝑜𝑙𝑑 ) 27 𝛼2 𝑛𝑒𝑤 𝛼1 𝑛𝑒𝑤0 𝐶 𝐶 𝛼1 𝑜𝑙𝑑 + 𝛼2 𝑜𝑙𝑑 < C の場合 𝛼2 𝑛𝑒𝑤 𝛼1 𝑛𝑒𝑤0 𝐶 𝐶 𝛼1 𝑜𝑙𝑑 + 𝛼2 𝑜𝑙𝑑 > C の場合 𝛼1 𝑜𝑙𝑑 + 𝛼2 𝑜𝑙𝑑 𝐿 = max 0, 𝛼1 𝑜𝑙𝑑 + 𝛼2 𝑜𝑙𝑑 − 𝐶 𝐻 = max 𝐶, 𝛼1 𝑜𝑙𝑑 + 𝛼2 𝑜𝑙𝑑 𝛼1 𝑜𝑙𝑑 + 𝛼2 𝑜𝑙𝑑 − 𝐶 よって
  • 28.
    𝛼1 𝑛𝑒𝑤 , 𝛼2 𝑛𝑒𝑤 の解析解 • 𝛼2 𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑 =𝛼2 + 𝑡2 𝐸1−𝐸2 𝑘 𝒙1,𝑥1 −2𝑘 𝒙1,𝒙2 +𝑘(𝒙2,𝒙2) – ただし 𝐸𝑖 = 𝑦 𝒙𝑖 − 𝑡𝑖 • 𝛼2 𝑛𝑒𝑤 = 𝐿 𝛼2 𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑 < 𝐿 𝐻 𝛼2 𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑 > 𝐻 𝛼2 𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑 otherwise • 𝛼1 𝑛𝑒𝑤 = 𝛼1 𝑜𝑙𝑑 + 𝑡1 𝑡2 𝛼2 𝑜𝑙𝑑 − 𝛼2 𝑛𝑒𝑤 クリッピングと呼んだりするそうです 28
  • 29.
    𝛼2 𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑 の更新式導出 • 𝛼1 𝑛𝑒𝑤 = 𝛼1 𝑜𝑙𝑑 +𝛿1 • 𝛼2 𝑛𝑒𝑤 = 𝛼2 𝑜𝑙𝑑 + 𝛿2 とする • 目的関数のうち変化量𝛿1, 𝛿2に関わる部分だけ抜き出す 目的関数: 𝑛=1 𝑁 𝑎 𝑛 − 1 2 𝑎 𝑛 𝑎 𝑚 𝑡 𝑛 𝑡 𝑚 𝑘(𝑥 𝑛, 𝑥 𝑚)𝑁 𝑚=1 𝑁 𝑛=1 𝛿1 + 𝛿2 − 𝛿1 𝑡1 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙1, 𝒙 𝑛 𝑁 𝑛=1 − 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙2, 𝒙 𝑛 𝑁 𝑛=1 − 1 2 𝛿1 2 𝑘 𝑥1, 𝑥1 + 2𝛿1 𝛿2 𝑡1 𝑡2 𝑘 𝑥1, 𝑥2 + 𝛿2 2 𝑘 𝑥2, 𝑥2 29 ※簡単のため,unclippedを外している 制約より 𝛿1 𝑡1 + 𝛿2 𝑡2 = 0 𝛼1 + 𝛿1 ≥ 0 𝛼2 + 𝛿2 ≥ 0 方針: 𝛿2に関する停留点を求める ※ 凸性を保証するため,正定値カーネルの必要 (*)
  • 30.
    𝑡1 = 𝑡2の場合 • 𝛿1 = −𝛿2 より(*)式は 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙1, 𝒙 𝑛 𝑁 𝑛=1 − 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙2, 𝒙 𝑛 𝑁 𝑛=1 − 1 2 𝛿2 2 𝑘 𝒙1, 𝒙1 − 2𝛿2 2 𝑘 𝒙1, 𝒙2 + 𝛿2 2 𝑘 𝒙2, 𝒙2 𝛿2 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 − 1 2 𝛿2 2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 • これを𝛿2に関して偏微分して0とおく 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 − 𝛿2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 = 0 𝛿2 = 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 𝛿2 = 𝑡2 𝑦 𝒙1 − 𝑡1 − 𝑦 𝒙2 − 𝑡2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 = 𝑡2 𝐸1 − 𝐸2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘(𝒙2, 𝒙2) 30
  • 31.
    𝑡1 = −𝑡2の場合 • 𝛿1 = 𝛿2 より(*)式は 2𝛿2 + 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙1, 𝒙 𝑛 𝑁 𝑛=1 − 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙2, 𝒙 𝑛 𝑁 𝑛=1 − 1 2 𝛿2 2 𝑘 𝒙1, 𝒙1 − 2𝛿2 2 𝑘 𝒙1, 𝒙2 + 𝛿2 2 𝑘 𝒙2, 𝒙2 2𝛿2 + 𝛿2 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 − 1 2 𝛿2 2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 • これを𝛿2に関して偏微分して0とおく 2 + 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 − 𝛿2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 = 0 𝛿2 = 2𝑡2 𝑡2 + 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 𝛿2 = 𝑡2 𝑦 𝒙1 − 𝑡1 − 𝑦 𝒙2 − 𝑡2 𝑘 𝒙1, 𝒙1 − 2𝑘 𝒙1, 𝒙2 + 𝑘 𝒙2, 𝒙2 31 ※ほぼ同じ.異なる部分に赤の下線
  • 32.
    𝛼1 𝑛𝑒𝑤 の導出 • 制約より • 𝛼1 𝑛𝑒𝑤 𝑡1+ 𝛼2 𝑛𝑒𝑤 𝑡2 = 𝛼1 𝑜𝑙𝑑 𝑡1 + 𝛼2 𝑜𝑙𝑑 𝑡2 • 𝛼1 𝑛𝑒𝑤 = 𝛼1 𝑜𝑙𝑑 + 𝑡2 𝑡1 𝛼2 𝑜𝑙𝑑 − 𝛼2 𝑛𝑒𝑤 • 𝑡2 𝑡1 = 𝑡1 𝑡2 より • 𝛼1 𝑛𝑒𝑤 = 𝛼1 𝑜𝑙𝑑 + 𝑡1 𝑡2 𝛼2 𝑜𝑙𝑑 − 𝛼2 𝑛𝑒𝑤 32
  • 33.
  • 34.
    1点目の選択法 • KKT条件を満たさない変数集合から選択する – 高速に発見するため,0< 𝑎𝑖 < 𝐶 であるような変数 集合から先にチェックする – それらがすべてKKT条件を満たしている場合,残り全 ての点についてチェック • 選択された変数を𝛼2とする (𝑎2 ではない) – これ以降 𝒙2, 𝑡2は選択された事例の特徴ベクトルとラ ベルを表す – ※1点目なのに𝛼2としたのは元論文の記法と一貫性 を保つため ※元論文の疑似コードにおけるmain routine 34
  • 35.
    2点目の選択法 • (1) 変数の更新量が大きくなるように選択 –すなわち 𝐸1 − 𝐸2 を最大化する事例 – 𝐸2が正の場合→最小誤差𝐸1の事例を選択 – 𝐸2が負の場合→最大誤差𝐸1の事例を選択 • (2) 境界上にない事例をランダムに選択 – すなわち0 < 𝑎 𝑛 < 𝐶であるような事例 • (3) ランダムに選択 – 残りの事例を選択 • 選択された変数を𝛼1とする ※変数の変化量が小さい場合には2点目の選択からやり直す ※元論文の疑似コードにおけるexamineExample(i2) 35
  • 36.
    補足: LIBSVMの変数選択方法 • SVM実装として有名なLIBSVMはSMOベース の手法 –この変数選択方法に特長がある – Working Set Selection 3 (WSS3) 36
  • 37.
  • 38.
    38 1点目の選択 更新されたものがあれば0 < 𝑎𝑛 < 𝐶である変数を優先的にチェックする それでも更新されなければ全データをチェックする 更新された変数があるか 全データチェックフラグがONの場合
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
    まとめ • SMOの概要を解説 • SMOを理解する3つのポイントを解説 –(1) KKT条件違反のチェック – (2) 変数の更新方法 – (3) 変数の選択方法 • 疑似コード解説により,実装例を紹介 44
  • 45.
    本資料で扱わなかったこと • SMO実装に必要な情報 – 更新途中のバイアス計算 –停止条件の解説 • SMOの改善 – KKT条件違反チェックの高速化 [Keerthi+ 01] – LIBSVMのWSS3の解説 [Fan+ 05] • その他 – 汎用QPソルバーの概説 ※元気があればバージョンアップで対応予定 45
  • 46.
  • 47.
    References • [Platt 98]J. C. Platt, "Sequential Minimal Optimization: A Fast Algorithm for Training Support Vector Machines", In Advances in Kernel Methods Support Vector Learning, MIT Press, 1998. – 元論文.基本的にこれを読めばよい • [Cristianini+ 05] N. Cristianini, J. Shawe-Taylor (大北剛訳), “サポートベクタ―マシン入 門”, 共立出版, 2005. – 7章でSMOを紹介 – 巻末に[Platt 98]の疑似コードに対する解説あり • [Keerthi+ 01] S. S. Keerthi, S. K. Shevade, C. Bhattacharyya, K. R. K. Murthy,”Improvements to Platt's SMO Algorithm for SVM Classifier Design”, Neural Computation, vol. 13(3), pp.637-649, 2001. – KKT条件チェックの高速化 • [Fan+ 05] R.-E. Fan, P.-H. Chen, C.-J. Lin, "Working Set Selection Using Second Order Information for Training Support Vector Machines", Journal of Machine Learning Research, vol.6, pp.1889?1918, 2005. – 変数選択のWWS 3論文 – 目的関数の2次勾配の情報を用いることで適切な選択が可能 47
  • 48.
    参考URL • 福水健次. 正定値カーネルによるデータ解析ーカーネル法の基礎と展 開ー4. サポートベクターマシン. 統計数理研究所公開講座 – http://www.ism.ac.jp/~fukumizu/ISM_lecture_2010/Kernel_4_SVM.pdf • SMOアルゴリズム – Intelligence Architecture けんきうノート – http://convexbrain.sourceforge.jp/cgi- bin/wifky.pl?p=SMO%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0 • 戸田健一. SVMの2次計画問題に関する解法の考察 – http://numataws1.ms.kagu.tus.ac.jp/THESIS/H15/presen_toda.pdf • 山下浩, 田中茂. サポートベクターマシンとその応用. – http://www.msi.co.jp/vmstudio/materials/svm.pdf • SMO法でSVMの学習してみた – きちめも – http://d.hatena.ne.jp/se-kichi/20100306/1267858745 • SVMの学習用アルゴリズムSMOを実装してみる – きしだのはてな – http://d.hatena.ne.jp/nowokay/20080730/1217371769 48 Many thanks to authors!
  • 49.