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

18,955 views

Published on

2013-05-05 PRML復々習レーン#10 で発表

Published in: Technology
0 Comments
71 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
18,955
On SlideShare
0
From Embeds
0
Number of Embeds
2,283
Actions
Shares
0
Downloads
219
Comments
0
Likes
71
Embeds 0
No embeds

No notes for slide

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

  1. 1. SMO徹底入門SVMをちゃんと実装する2013-05-05Last update: 2013-06-16Yoshihiko Suhara@sleepy_yoshi1v. 1.1Thank you @a_bicky さん!
  2. 2. なんでこの資料つくったの?• SMOの解説記事や疑似コード等あるけれど,自分の頭ではその情報だけで理解できなかったから• SMOの実装は過去に2回ほど行ったことがある– 疑似コードをもとに打ち込んだだけ– 写経しても結局理解できなかった– なぜそうしているのかを理解していなかったことが原因• 一念発起して更新式の導出等を行ったらようやくわかった気になったのでGW中にSMOを実装してみた– 忘れないうちに資料化2
  3. 3. はじめに3
  4. 4. 本資料の想定する読者• 知っておいてほしいこと– SVMが何かは知っている– SVMはどうやら二次計画問題として定式化できるらしいということを知っている• 知っているとよりよいこと– 主問題をいくら高速に解けても双対問題を解かないことにはカーネルが使えないので,やっぱり二次計画を解かなければいけないこと– 二次計画ソルバーの中でもSMOという方法があるらしい4
  5. 5. 本資料のゴール• 1-norm SVMのSequential Minimum Optimizer(SMO) を自分で実装できるようにする• SMO実装に必要な知識を解説することでSVMに対する理解を深める5
  6. 6. 準備するもの• PRML下巻– 式番号や記法はPRMLに合せます6
  7. 7. 本資料の流れ• 事前知識のおさらい• SMOの概要• SMOを理解する3つのポイント– (1) KKT条件違反のチェック– (2) 変数の更新方法– (3) 変数の選択方法• 疑似コード解説7
  8. 8. 事前知識のおさらい8
  9. 9. カーネル法ひとこと要約• 𝑓 𝒙 = 𝒘 𝑇𝜙 𝒙 = 𝛼𝑖 𝑘(𝒙𝑖, 𝒙)𝑖非線形変換した入力データ訓練データ𝒙𝑖との類似度• 予測値=訓練データとの類似度の重みづけ和– 予測に用いるデータをサポートベクタと呼ぶカーネル法の学習=サポートベクタの「重み」を学習カーネル関数9
  10. 10. カーネル関数の例• 基本的なカーネル関数– 線形カーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = 𝒙𝑖𝑇𝒙𝑗– 多項式カーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = 𝛾𝒙𝑖𝑇𝒙𝑗 + 𝑟𝑑, 𝛾 > 0– RBF (ガウス) カーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = exp −𝛾 𝒙𝑖 − 𝒙𝑗2, 𝛾 > 0– シグモイドカーネル: 𝑘 𝒙𝑖, 𝒙𝑗 = tanh(𝛾𝒙𝑖𝑇𝒙𝑗 + 𝑟)10
  11. 11. SVMの予測値• 𝑦 𝒙 = 𝑎 𝑛 𝑡 𝑛 𝑘(𝒙, 𝒙 𝑛)𝑁𝑛=1 + 𝑏– サポートベクタ (𝑎 𝑛 > 0) とのカーネル関数の値の重み和– 𝑏はバイアス項11
  12. 12. 二次計画問題 (QP) としてのSVM• 1-norm SVMの目的関数の双対形式は以下のQPで定式化できる (PRML下巻 p.38)max. 𝑛=1𝑁𝑎 𝑛 −12𝑎 𝑛 𝑎 𝑚 𝑡 𝑛 𝑡 𝑚 𝑘(𝑥 𝑛, 𝑥 𝑚)𝑁𝑚=1𝑁𝑛=1s. t. 0 ≤ 𝑎 𝑛 ≤ 𝐶, 𝑎 𝑛 𝑡 𝑛𝑁𝑛=1= 012
  13. 13. SMOの概要13
  14. 14. SMO開発の背景• QPは求解に𝑂(𝑁3)のコストがかかるため,全変数を対象にしたQPは実行時間がかかる • 一部の選択された変数以外を固定し,選択された変数によるQPを解く方法が提案– チャンキング– Osuna’s method• SMOはその考え方を最小単位である2変数に絞ったもの–  2変数の場合,QPの解を閉じた解で求めることができる14
  15. 15. 過去の手法との比較• 横軸は選択された変数• 各行が各更新の試行に対応各試行で2変数のみ選択15
  16. 16. SMOの疑似コードINPUT: (𝒙 𝑛, 𝑦𝑛) ∈ 𝐷,OUTPUT: 𝒂 = 𝑎1, 𝑎2, … , 𝑎 𝑁𝑇1: Initialize 𝒂 = 𝟎2: WHILE KKT条件を違反する変数が存在3: KKT条件を違反する変数𝛼1を選択4: 2つ目の変数𝛼2を選択5: 𝛼1, 𝛼2を更新6: END WHILE7: RETURN 𝒂16
  17. 17. SMOを理解するポイント• 以下の3つの疑問を解消すればよい– (1) KKT条件違反ってどうやって調べるのよ?!– (2) 更新ってどうやるのよ?!– (3) 更新に利用する変数ってどうやって選択するのよ?!• 順番に解説ポイント17
  18. 18. (1) KKT条件違反のチェック18
  19. 19. SMOにおけるKKT条件• 以下の3つの場合に分けてチェックする– (a) 𝑎𝑖 = 0 ⇔ 𝑡𝑖 𝑦 𝒙𝑖 ≥ 1– (b) 0 < 𝑎𝑖 < 𝐶 ⇔ 𝑡𝑖 𝑦 𝒙𝑖 = 1– (c) 𝑎𝑖 = 𝐶 ⇔ 𝑡𝑖 𝑦 𝒙𝑖 ≤ 1なんでこれが出てくるのか?19
  20. 20. KKT条件の導出 (1/2)• PRML (7.28)式と(7.31)式𝜇 𝑛 𝜉 𝑛 = 0𝑎 𝑛 = 𝐶 − 𝜇 𝑛• これより𝜉 𝑛 𝐶 − 𝑎 𝑛 = 0• よって 𝜉 𝑛 > 0 となるのは 𝑎 𝑛 = 𝐶 の点のみ– それ以外は𝜉 𝑛 = 0– すなわちマージンの内側かマージン上に存在20
  21. 21. KKT条件の導出 (2/2)• PRML (7.16)式より 𝑎 𝑛 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 = 0• 𝑎 𝑛 = 0 のとき– 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 ≥ 0– 𝑡 𝑛 𝑦 𝒙 𝑛 ≥ 1• 0 < 𝑎 𝑛 < 𝐶 のとき (𝜉 𝑛 = 0)– よって,ぴったり𝑡 𝑛 𝑦 𝒙 𝑛 − 1 = 0である必要– 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 = 0– 𝑡 𝑛 𝑦 𝒙 𝑛 = 1• 𝑎 𝑛 = 𝐶 のとき (𝜉 𝑛 > 0)– 𝑡 𝑛 𝑦 𝒙 𝑛 − 1 ≤ 0– 𝑡 𝑛 𝑦 𝒙 𝑛 ≤ 121
  22. 22. (2) 変数の更新方法22
  23. 23. 2変数の更新方法• 𝑎1, … , 𝑎 𝑁から選択された2変数𝛼1, 𝛼2の値を以下の手順で更新する– (i) 更新値の値域を求める– (ii) 更新式に基づいて値を更新する23
  24. 24. 更新値の値域の決定 (1/2)• 選択された2変数を更新する際,線形制約𝑎 𝑛 𝑡 𝑛𝑁𝑛=1= 0• があるため, 0 ≤ 𝛼 𝑛 ≤ 𝐶 という制約の中で𝛼1𝑛𝑒𝑤𝑡1 + 𝛼2𝑛𝑒𝑤𝑡2 = 𝛼1𝑜𝑙𝑑𝑡1 + 𝛼2𝑜𝑙𝑑𝑡2• を満たす必要がある• 𝛼1𝑛𝑒𝑤と𝛼2𝑛𝑒𝑤の値域をあらかじめ求めておく– 𝛼2𝑛𝑒𝑤の最大値と最小値 𝐿 ≤ 𝛼2𝑛𝑒𝑤≤ 𝐻を求める24※𝛼2𝑛𝑒𝑤に対して求めるのは元論文との一貫性のため
  25. 25. 更新値の値域の決定 (2/2)• 𝑡1 ≠ 𝑡2 の場合– 𝐿 = max 0, 𝛼2𝑜𝑙𝑑− 𝛼1𝑜𝑙𝑑– 𝐻 = min 𝐶, 𝐶 − 𝛼2𝑜𝑙𝑑+ 𝛼1𝑜𝑙𝑑• 𝑡1 = 𝑡2 の場合– 𝐿 = max 0, 𝛼1𝑜𝑙𝑑+ 𝛼2𝑜𝑙𝑑− 𝐶– 𝐻 = max 𝐶, 𝛼1𝑜𝑙𝑑+ 𝛼2𝑜𝑙𝑑25はて,どうやって求めているのか?
  26. 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. 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. 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. 29. 𝛼2𝑛𝑒𝑤,𝑢𝑛𝑐𝑙𝑖𝑝𝑝𝑒𝑑の更新式導出• 𝛼1𝑛𝑒𝑤= 𝛼1𝑜𝑙𝑑+ 𝛿1• 𝛼2𝑛𝑒𝑤= 𝛼2𝑜𝑙𝑑+ 𝛿2 とする• 目的関数のうち変化量𝛿1, 𝛿2に関わる部分だけ抜き出す目的関数: 𝑛=1𝑁𝑎 𝑛 −12𝑎 𝑛 𝑎 𝑚 𝑡 𝑛 𝑡 𝑚 𝑘(𝑥 𝑛, 𝑥 𝑚)𝑁𝑚=1𝑁𝑛=1𝛿1 + 𝛿2 − 𝛿1 𝑡1 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙1, 𝒙 𝑛𝑁𝑛=1− 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙2, 𝒙 𝑛𝑁𝑛=1−12𝛿12𝑘 𝑥1, 𝑥1 + 2𝛿1 𝛿2 𝑡1 𝑡2 𝑘 𝑥1, 𝑥2 + 𝛿22𝑘 𝑥2, 𝑥229※簡単のため,unclippedを外している制約より𝛿1 𝑡1 + 𝛿2 𝑡2 = 0𝛼1 + 𝛿1 ≥ 0𝛼2 + 𝛿2 ≥ 0方針: 𝛿2に関する停留点を求める※ 凸性を保証するため,正定値カーネルの必要(*)
  30. 30. 𝑡1 = 𝑡2 の場合• 𝛿1 = −𝛿2 より(*)式は𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙1, 𝒙 𝑛𝑁𝑛=1− 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙2, 𝒙 𝑛𝑁𝑛=1−12𝛿22𝑘 𝒙1, 𝒙1 − 2𝛿22𝑘 𝒙1, 𝒙2 + 𝛿22𝑘 𝒙2, 𝒙2𝛿2 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 −12𝛿22𝑘 𝒙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. 31. 𝑡1 = −𝑡2 の場合• 𝛿1 = 𝛿2 より(*)式は2𝛿2 + 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙1, 𝒙 𝑛𝑁𝑛=1− 𝛿2 𝑡2 𝑎 𝑛 𝑡 𝑛 𝑘 𝒙2, 𝒙 𝑛𝑁𝑛=1−12𝛿22𝑘 𝒙1, 𝒙1 − 2𝛿22𝑘 𝒙1, 𝒙2 + 𝛿22𝑘 𝒙2, 𝒙22𝛿2 + 𝛿2 𝑡2 𝑦 𝒙1 − 𝑦 𝒙2 −12𝛿22𝑘 𝒙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. 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. 33. (3) 変数の選択方法33
  34. 34. 1点目の選択法• KKT条件を満たさない変数集合から選択する– 高速に発見するため,0 < 𝑎𝑖 < 𝐶 であるような変数集合から先にチェックする– それらがすべてKKT条件を満たしている場合,残り全ての点についてチェック• 選択された変数を𝛼2とする (𝑎2 ではない)– これ以降 𝒙2, 𝑡2は選択された事例の特徴ベクトルとラベルを表す– ※1点目なのに𝛼2としたのは元論文の記法と一貫性を保つため※元論文の疑似コードにおけるmain routine34
  35. 35. 2点目の選択法• (1) 変数の更新量が大きくなるように選択– すなわち 𝐸1 − 𝐸2 を最大化する事例– 𝐸2が正の場合→最小誤差𝐸1の事例を選択– 𝐸2が負の場合→最大誤差𝐸1の事例を選択• (2) 境界上にない事例をランダムに選択– すなわち0 < 𝑎 𝑛 < 𝐶であるような事例• (3) ランダムに選択– 残りの事例を選択• 選択された変数を𝛼1とする※変数の変化量が小さい場合には2点目の選択からやり直す※元論文の疑似コードにおけるexamineExample(i2)35
  36. 36. 補足: LIBSVMの変数選択方法• SVM実装として有名なLIBSVMはSMOベースの手法– この変数選択方法に特長がある– Working Set Selection 3 (WSS3)36
  37. 37. 疑似コード解説37[Platt 98] の疑似コードを眺める
  38. 38. 381点目の選択更新されたものがあれば0 < 𝑎 𝑛 < 𝐶である変数を優先的にチェックするそれでも更新されなければ全データをチェックする更新された変数があるか全データチェックフラグがONの場合
  39. 39. 39takeStep(i1,i2)が0を返す場合には2点目の各種選択法が順番に適用されるtakeStep(i1, i2)がtrueを返せばi2に対する処理は終了いずれも更新されない場合,0を返す
  40. 40. 401点目の更新処理とクリッピング
  41. 41. 41カーネルが正定値でない場合の処理(解説は割愛)
  42. 42. 42変化量が微小の場合,更新処理を行わない2点目の更新更新結果の保存
  43. 43. まとめ43
  44. 44. まとめ• SMOの概要を解説• SMOを理解する3つのポイントを解説– (1) KKT条件違反のチェック– (2) 変数の更新方法– (3) 変数の選択方法• 疑似コード解説により,実装例を紹介44
  45. 45. 本資料で扱わなかったこと• SMO実装に必要な情報– 更新途中のバイアス計算– 停止条件の解説• SMOの改善– KKT条件違反チェックの高速化 [Keerthi+ 01]– LIBSVMのWSS3の解説 [Fan+ 05]• その他– 汎用QPソルバーの概説※元気があればバージョンアップで対応予定45
  46. 46. 感想46
  47. 47. References• [Platt 98] J. C. Platt, "Sequential Minimal Optimization: A Fast Algorithm for TrainingSupport 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 Platts 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 SecondOrder Information for Training Support Vector Machines", Journal of MachineLearning Research, vol.6, pp.1889?1918, 2005.– 変数選択のWWS 3論文– 目的関数の2次勾配の情報を用いることで適切な選択が可能47
  48. 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/121737176948Many thanks to authors!
  49. 49. おしまい49

×