行列制約付きの線形計画法


10

次のような最適化の問題があります

minJ,Bij|Jij|s.t.MJ+BY=X

ここでは、変数は行列 JおよびBですが、問題全体は依然として線形プログラムです。残りの変数は固定されています。

このプログラムをお気に入りの線形プログラミングツールに入力しようとすると、いくつかの問題が発生します。つまり、これを「標準」線形プログラム形式で記述した場合、パラメーター行列MYは(Xの各列に対して1回)何回も繰り返されることになりますX

上記のフォームの最適化を処理できるアルゴリズムやパッケージはありますか?MYは何度もコピーする必要があるため、現在メモリが不足しています!


であるBパラメータ行列、またはあなたが意味するかY?さまざまな行列の形は何ですか?
ジェフリーアービング

[こんにちは、ジェフリー!] JとBは変数で、残りはパラメータです。Bには比較的少ない列がありますが、残りのすべての次元は非常に大きいです(正方形はありません)。
Justin Solomon

[こんにちは!]投稿を編集して、Bがパラメータであることを2度言わないようにしてください。
ジェフリーアービング

1
興味深いがおそらく役に立たない、この問題のバージョンは、代わりに使用していいくつかのSVDで解決できます。Jij2|Jij|
Geoffrey Irving 2014年

1
@ジェフリー、それは偶然ではない:-)
ジャスティン・ソロモン

回答:


12

概観

投げ縄型の問題で驚くほど速く収束することが判明している、乗数の交互方向法(ADMM)のバリアントを試してみるとよいでしょう。戦略は、拡張ラグランジアンで問題を定式化し、二重問題で勾配上昇を行うことです。この特定の正則化問題に特に適しています。メソッドの各反復の滑らかでない部分には、要素ごとに単純に評価できる正確な解があり、滑らかな部分には線形システムの解決が含まれるためです。l1l1

この投稿では

  • 問題を一般化するための全体的なADMM公式を導き出し、
  • 各ADMM反復のサブ問題を導出し、それらを状況に特化し、次に
  • 各反復を解く必要がある結果の線形システムを調査し、およびの固有値分解(またはその低ランク近似)の事前計算に基づいて高速ソルバー(または前提条件子)を開発します。MTMYYT
  • いくつかの結論をまとめて要約する

ここでの大きなアイデアのほとんどは、次のすばらしいレビューペーパーで説明されています。

ボイド、スティーブン等。「乗数の交互方向法による分散最適化と統計学習。」Foundations andTrends®in Machine Learning 3.1(2011):1-122。http://www.stanford.edu/~boyd/papers/pdf/admm_distr_stats.pdf

詳細に入る前に、これはメソッド/アルゴリズムの答えであり、実際の既存のコードの答えではないことに注意してください。このメソッドを使用する場合は、独自の実装をロールする必要があります。

ADMMの公式

一般に、を解決したいとします

minxi|xi|s.t.Ax=b.

元の投稿の問題は、適切なベクトル化を行うと、このカテゴリに分類されます。(これは原則としてのみです-実際にはベクトル化を実行する必要がないことがわかります)

代わりに、同等の問題である解くことができます これにはラグランジアン

minx,zi|xi|+α2||xz||2+β2||Azb||2s.t.Az=b&x=z,
L(x,z,λ,γ)=i|xi|+α2||xz||2+β2||Azb||2+λT(Azb)+γT(xz)=i|xi|+α2||xz+1αγ||2+β2||Azb+1βλ||2+α2||1αγ||2+β2||1βλ||2.

乗数の交互方向法は、双対問題の 勾配上昇を介して、二重問題解き ます二重副問題の不正確な交互投影。つまり、反復

maxλ,γminx,zL(x,z,λ,γ),
xk+1=argminxL(x,zk,λk,γk)zk+1=argminzL(xk+1,z,λk,γk)γk+1=γk+α(xk+1zk+1)λk+1=λk+β(Azk+1b).

パラメータおよび(上記のBoyd&Parikhの論文で説明されています)の特定の穏やかな条件下では、ADMMメソッドは真の解に収束します。収束率は、勾配上昇法の中心にあるため、線形です。多くの場合、1)ヒューリスティックに基づいてパラメータおよびを変更するか、2)Nesterov加速を使用することにより、超線形になるように加速できます。ペナルティパラメータの変更に関する注意事項については、ボイドサーベイペーパーを参照してください。ADMMでNesterovアクセラレーションを使用する場合は、次のペーパーを参照してください。αβαβ

ゴールドスタイン、トム、ブレンダンオドノヒュー、サイモンセッツァー。「高速交互方向最適化手法。」CAMレポート(2012):12-35。ftp://ftp.math.ucla.edu/pub/camreport/cam12-35.pdf

ただし、全体の収束率が線形にすぎない場合でも、問題の場合、このメソッドはスパースパターンを非常にすばやく見つけ、正確な値にゆっくりと収束することが確認されています。スパースパターンを見つけるのが最も難しいため、これは非常に偶然です。現在の研究分野のように思われる正確な理由。誰もがスパースパターンが速く収束するのを目にしますが、なぜそれが起こるのか正確に知っている人はいません。しばらく前に私はメールでボイドとパリクにこれについて尋ねましたが、パリクはそれが制御システムのコンテキストでメソッドを解釈することで説明されるのではないかと考えました。現象のもう1つのヒューリスティックな説明は、次の論文の付録にあります。l1

ゴールドスタイン、トム、スタンリーオシャー。「L1正則化問題の分割ブレグマン法。」SIAM Journal on Imaging Sciences 2.2(2009):323-343。ftp://ftp.math.ucla.edu/pub/camreport/cam08-29.pdf

もちろん、今の難しさは、特定の状況でおよび更新サブ問題を解決することです。ラグランジアンはで2次なので、更新サブ問題は、線形システムを解くだけで済みます。それは微分不可能であるため、サブ問題は難しいようだが、それは要素ごとに適用することができソリューションのための正確な式があることが判明します!ここで、これらのサブ問題についてさらに詳しく説明し、元の投稿の問題に特定します。xzzzx

更新サブ問題のセットアップ(線形システム)z

以下のために更新、我々は z

argminzL(xk,z,λk,γk)=argminzα2||xz+1αγ||2+β2||Azb+1βλ||2.

これはあなたの問題に特化して、

argminZJ,ZBα2||Jk+1ZJ+1αΓJ||Fro2+α2||Bk+1ZB+1αΓB||Fro2+β2||MZJ+ZBYX+1αΛ||Fro2,

ここで、はフロベニウス(要素ごとの)ノルムを示します。これは2次の最小化問題です次の最適性条件は、とに関する目的関数の偏微分を取り、それらをゼロに設定することで見つけることができます。これは、 ||||Frol2ZJZB

0=α2(Jk+1ZJ+1αΓJ)+β2MT(MZJ+ZBYX+1βΛ),0=α2(Bk+1ZB+1αΓB)+β2(MZJ+ZBYX+1βΛ)YT.

元のポスターJustin Solomonのコメントで述べたように、このシステムは対称的であるため、共役勾配法は理想的な行列のない方法です。後のセクションでは、このシステムと、それを解決/事前調整する方法について詳しく説明します。ZJ,ZB

更新サブ問題の解決(分析的しきい値ソリューション)x

ここで、副問題 x

argminxL(x,zk,λk,γk)=argminxi|xi|+α2||xzk+1αγk||2

最初に確認することは、合計が要素分割できることです。

i|xi|+α2||xzk+1αγk||2=i|xi|+α2i(xizik+1αγik)2,

したがって、要素に並列に最適化問題を解くことができ、

xik+1=argminxi|xi|+α2(xizik+1αγik)2.

この方程式の一般的な形式は、

mins|s|+α2(st)2.

絶対値関数は最適点をに引き寄せようとしていますが、2次項は最適点を引き寄せようとしています。したがって、真の解は2つの間のセグメントどこかにあり、が増加すると最適点がに引き寄せられ、減少すると最適点が引き寄せられ。s=0s=t[0,t)αtα0

これは凸関数ですが、ゼロでは微分できません。最小化ポイントの条件は、そのポイントでの目的のサブ導関数にゼロが含まれていることです。次の用語は、誘導体有する、絶対値関数は、HAS誘導体のための、セット値劣微分を間隔としてとき、および誘導体のための。したがって、目的関数全体のサブデリバティブ、 α(st)1s<0[1,1]s=01s>0

s(|s|+α2(st)2)={1+α(st)s>0[1,1]+αt,s=0,1+α(st),s<0.

このことから、我々はでの目的の劣微分いることがわかり含まれている場合にのみ、。この場合、が最小化です。一方、が最小化子でない場合は、単一値の導関数をゼロに設定して、最小化子を解くことができます。これを行うと、 s=00|t|1αs=0s=0

argmins|s|+α2(st)2={t1α,t>1α,0,|t|1α,t+1α,t<1α

この結果を、生成 する元の質問解決しようとしている実際の問題に再び特化 の更新は、単純に t=Zijk1αΓijk

Jijk+1={Zijk1αΓijk1α,Zijk1αΓijk>1α,0,|Zijk1αΓijk|1α,Zijk1αΓijk+1α,Zijk1αΓijk<1α.
B
Bk+1=ZB1αΓB,

元のポスター、ジャスティンソロモンのコメントに記載されています。全体として、更新を行うには、行列のエントリをループして、各エントリについて上記の式を評価する必要があります。J,B

システムのSchur補数ZJ,ZB

反復の最もコストのかかるステップは、システムを解くことです。

0=α2(Jk+1ZJ+1αΓJ)+β2MT(MZJ+ZBYX+1βΛ),0=α2(Bk+1ZB+1αΓB)+β2(MZJ+ZBYX+1βΛ)YT.

そのためには、このシステムに適したソルバー/プレコンディショナーを構築することは、いくらか努力する価値があります。このセクションでは、ベクトル化Schur補体の形成、Krnoecker積の操作の一部、およびその後のベクトル化解除によってこれを行います。結果として得られるSchur補体系は、わずかに変更されたSylvester方程式です。

以下では、ベクトル化とクロネッカー製品に関する次のアイデンティティが絶対に重要です。

  • vec(ABC)=(CTA)vec(B),
  • (AB)(CD)=ACBD
  • (AB)1=A1B1、および
  • (AB)T=ATBT

これらの恒等式は、行列のサイズと可逆性が方程式の各辺が有効な式になるような場合に常に保持されます。

システムのベクトル化された形式は、

(αI+β[IMTM(YM)TYMYYTI])[vec(ZJ)vec(ZB)]=[vec(αJ+βMTX+ΓJMTΛ)vec(αB+βXYT+ΓBΛYT)],

または、

[I(αI+βMTM)β(YM)TβYM(αI+βYYT)I][vec(ZJ)vec(ZB)]=[vec(F)vec(G)],

ここで、とは右側の簡略表記です。ここで、ブロックガウスの消去/シューア補数を実行して、クロネッカー積を凝縮するプロセスで、行列の左下のブロックを消去します。これは、 FG

[I(αI+βMTM)β(YM)T0(αI+βYYT)Iβ2YYTM(αI+βMTM)1MT][vec(ZJ)vec(ZB)]=[vec(F)vec(G)βYM(αI+βMTM)1vec(F)].

ベクトル化を解除すると、順番に解かなければならない2つの方程式は、

  1. ZB(αI+βYYT)(βM(αI+βMTM)1MT)ZB(βYYT)=GβM(αI+βMTM)1FYT
  2. (αI+βMTM)ZJ=FβMTZBY.

が正方形で上位の場合のSchur補体系の解Y,M

このセクションでは、事前に計算された行列フル SVD を使用し、SylvesterにBartels-Stewartアルゴリズムの修正バージョンを適用することにより、(上記の式1.)のSchur補体系をます。方程式。アルゴリズムは、第2項の余分なを考慮に入れるために標準バージョンからわずかに変更されています。これにより、シルベスターの方程式は完全ではなくなります。一旦最初の式を介して発見され、容易に第2の式から求めることができます。2番目の方程式は、任意の方法で簡単に解くことができます。ZBYYT,MMT,MTMβYYTZBZJ

この方法では、ADMMプロセスが開始する前に2つの完全なSVDを事前計算するための先行費用が必要ですが、実際のADMM反復に適用するには高速です。この方法は制約行列の完全なSVDを扱うので、それらが平方に近く、上位にある場合に適しています。低ランクSVDを使用するより複雑な方法も可能ですが、後のセクションで説明します。

この方法は次のように発展します。ましょう 示す事前計算フル特異値分解となるように右側を凝縮。次いで、第一方程式となり、 乗算左と右を、新しい一時的な未知のを設定する直交係数によって、これはさらに

QDQT=YYT,WΣWT=MMT,VTVT=MTM
H
ZBQ(αI+D)QTWβΣ(αI+Σ)1ΣWTZBQDQT=H.
A=WTZBQ
A(αI+D)βΣ(αI+Σ)1ΣAD=WHQT.

これで、対角システム 解くことでを見つけることができますA

((αI+D)I+DβΣ(αI+Σ)1Σ)vec(A)=vec(WHQT).

見つかっ、を計算し、を知ってについて上記の2番目の方程式を。これは、すでに固有値分解があるため、簡単です。AZB=WAQTZBZJMTM

初期費用は、と 2つの対称正定固有値分解を計算することであり、完全な解法の反復あたりのコストは、次数と同じ数の行列行列乗算によって支配されます。 1 CGサブイテレーションを行うときの大きさ。事前固有値分解のコストが高すぎる場合は、たとえば、ランチョス反復を早期に終了し、最大の固有ベクトルを維持することにより、不正確に計算できます。この方法は、直接ソルバーではなく、CGの優れた前提条件として使用できます。MTMYYT

が非常に長方形であるか、ランク近似が低い場合の解法M,Y

ここで、a)入力行列が非常に長方形である、つまり列よりも行数が多い、またはその逆の場合、またはb)ランクの近似が低い場合に、解決または事前調整に注意を向けます。以下の導出には、Woodbury式、Schur補数、およびその他の同様の操作の広範な使用が含まれます。ZJ,ZBM,Y

Schur補体系から始めます

(αI+βYYT)Iβ2YYTM(αI+βMTM)1MT.

いくつかの操作により、このシステムはより対称的な形式に変換されます

(αI+βIMMT+βYYTI)vec(ZB)=(I(I+βαMMT))vec(H).

ここで、低ランクの近似を取り入れます。LET の低下SVDのまたは低ランク近似のいずれかであり及び(プレースホルダでありません中古)。これらをシステムに代入すると、適用したい次の逆行列が得られます

QD1/2Q2T=YWΣ1/2VT=M
YMQ2
(αI+βIWΣWT+βYYTI)1.

反転するための行列は、アイデンティティの低ランク更新であるため、論理的な戦略は、ウッドベリーの公式

(A+UCUT)1=A1A1U(C1+UTA1U)1UTA1.

ただし、低ランクのピースとは直交していないため、注意が必要です。したがって、Woodburyの公式を適用するために、両方の低ランクの更新を1つの大きな更新に収集します。そうして、ウッドベリーの公式を適用すると、 IWYI

(1αI+β[IWQI][IΣDY][IΣTQTI])1=αIβα2[IWQI][I(Σ1+βαI)βαQWTβαQTW(D1+βαI)Y]1[IΣTQTI].

コアの逆は、ブロックごとの2x2逆公式

[ABBTC]1=[(ABC1BT)1A1B(CBTA1B)1C1BT(ABC1BT)1(CBTA1B)1].

この投稿はすでに十分に長いので、計算の長い詳細は省きますが、最終的な結果として、必要な部分行列をブロックごとの逆行列に差し込み、すべてを乗算すると、全体の逆行列に対して次の明示的な形式が得られます

(αI+βIMMT+βYYTI)1=1αIβα2(t11+s11+t12+s12+t21+s21+t22+s22),

ここで、

t11=αβIWl1WTs11=(QWl1)D11(QTl1WT)t12=αβQh1QTWl1WTs12=(Qh1Wl1)D22(h1QTWT)t21=t12s21=(Qh1W)D22(h1QTl1WT)t22=αβQh1QTIs22=(Qh1W)D22(h1QTWT)D11=αβ(hIIl1)1D22=αβ(Ilh1I)1l=αβΣ1+Ih=αβD1+I.

この形式では、逆行列を適用し、8つの左行列乗算サンドイッチと右行列乗算サンドイッチを介して項項を見つけることができます。クロネッカー積の合計を適用するための一般的な式は、 ZB

((A1B1)+(A2B2)+)vec(C)=vec(B1TCA1+B2TCA2+).

最後に行った明示的な逆はすべて対角線なので、「解決」するものは何もないことに注意してください。

線形ソルバーコード

上記の2つのソルバーをMatlab に実装しました。うまくいくようです。ソルバーコードはこちらです。zJ,ZB

https://github.com/NickAlger/MeshADMM/blob/master/zkronsolve.m

ソルバーが機能することを確認するためのテストスクリプトはこちらです。また、ソルバーコードの呼び出し例も示します。

https://github.com/NickAlger/MeshADMM/blob/master/test_zkronsolve.m

おわりに

ADMMタイプのメソッドはこのような問題に適していますが、独自の実装をロールする必要があります。メソッドの全体的な構造はかなり単純なので、MATLABのようなものでの実装はそれほど難しくありません。

問題の方法を完全に定義するために指定する必要がある、この投稿に欠けている部分は、ペナルティパラメータです。幸いなことに、パラメーターの値がおかしくない限り、メソッドは一般にかなり堅牢です。Boyd and Parikhの論文には、その参照と同様にペナルティパラメータに関するセクションがありますが、妥当な収束率が得られるまで、パラメータを試してみます。α,β

制約行列が良好な低ランク近似を持っている))、緻密、squareish、および高ランク、またはbのいずれかである場合に提示ソルバーの戦略は非常に有効です。将来の作業のトピックになる可能性のあるもう1つの有用なソルバーは、次の場合に最適化されたソルバーです。制約行列はスパースで正方形で高ランクですが、は適切な前提条件が存在します。これは、たとえば、が離散化されたラプラシアンの場合です。ZJ,ZBMαI+MMTM


これを今すぐ実装してください!確認するには、と行列は、最小二乗から来るため、対称/必要がありますよね?これは経験的に正しいようです:-)。では、CGRESはGMRESよりも優れたオプションですか?ZBZJ
Justin Solomon

また、Bのアップデートは間違っていると思いますか?私はこれについてさらに詳しく調べていますが、Bはエネルギー関数に表示されない(項はありません)ので、 私はこれが間違っていると考えていますか?ありがとう!|B|±(11/α).
Justin Solomon

1
[どちらかと言えば、 ]B=ZBΓB/α
Justin Solomon

3
すごい!と独自の式を入力した後(おそらく投稿したものに近い/同等ですが、何かが機能していません)、これはIRLSメソッドよりもはるかに優れています。ありがとう!JB
Justin Solomon

1
素晴らしいニュース。ここでの貢献が実際の結果につながるときを見るのはとても良いです。
マイケル・グラント

5

おそらく、線形計画法に行列のない方法を使用したいでしょう。特に線形計画法に特化した方法は知りませんが、二次計画法と一般的な非線形計画法のために、行列のない内点法が存在します。二次プログラムの場合は、二次形式係数がすべてゼロである問題に正確に対応します。(問題の構造に正確な線形解法を使用するメソッドを調整することもできますが、そのような特注の実装は価値がない場合があり、行列のないメソッドを使用するよりも実用的ではありません。)

内点法の行列のないバリアントを実装する商用最適化パッケージについては知りません。IPOPTは、非線形プログラミングのための行列なしの内点法を実装することになっていますが、私はそれを使用できるようにするAPI呼び出しを追跡できませんでした。

CVXに加えて、おそらくGAMSまたはAMPLを使用してマトリックスを1回入力し、そのマトリックスを使用するようにモデリング言語で制約を設定できます。ただし、CVX、GAMS、AMPLのソルバーバックエンドで使用されるメソッドは、行列のないソルバーを使用しません。すべてが標準形式の線形プログラムの完全な係数行列を必要としますが、これは巨大になります(行列のクロネッカー積になります)。おそらく、モデリング言語を使用して上記の形式で線形プログラムを入力すると、モデリング言語がデータをバックエンドソルバーで使用できる形式に変換します。このフォームには巨大な行列が必要であり、同じ種類のエラーが発生するのではないかと思います(十分なメモリのあるマシンで実行しない限り)。


私はすべての正しいことを試みたように見えます!最初にCVXを試しましたが、失敗したため、IPOPTに切り替えました。しかし、IPOPTにも同じ問題がありました。私はそれがマトリックスフリーのオプションを持っていることを知らなかったので、私がそれを理解できるかどうか見てみましょう。
ジャスティンソロモン

GAMS / AMPLが問題を解決するかどうかわかりません。ソルバーが正しいことを行うのに役立つ任意の形式で問題をコード化できてうれしいですが、裏で言っているように、クロネッカー製品を使用しても機能しません。
ジャスティンソロモン

4

ジェフリー・アーヴィングが言及したこれらのSVDを購入できますか?可能であれば、繰り返し再重み付けされた最小二乗(IRLS)アプローチを検討します。このアプローチは、の形式の問題を解決しますここで、は重み行列です。

minimizeijWijJij2subject toMJ+BY=X
W

反復は、すべて1の行列として始まります。これにより、最適なます。反復はで進みますここで、ゼロによる除算を防ぐ小さな定数です。私は収束基準について完全に確信はありませんが、おそらく私が上記で提供したウィキペディアのリンクがあなたに参照を与えることができます。W(0)J(0)

Wij(k+1)=|max{Jij(k),ϵ}|1
ϵ

また、平滑化された1次法を検討することもできます。私が共同執筆したTFOCSは、「スムーズコニックデュアル」(SCD)ソルバーを使用してこれを処理できますが、使いやすさは劣ります。

マトリックスフリーの内点法を試したい場合は、Jacek Gondzioの著作を読んでください。

編集:うーん、IRLSがSVDを使用してソリューションを計算できない場合があるかもしれません。もしそうなら、私は他の選択肢の1つにフォールバックします。


1
ここでSVDを使用できるかどうかはわかりませんが、IRLSは素晴らしいアイデアです。速度はメモリほど問題ではありません。恥ずかしいことに、私は関連する研究に数か月前にIRLSを使用しましたが、それはうまくいきました(これまで試したことがないので自分を蹴りました!)。IRLSのSVDがなくても、システム全体を必要としないCGのような線形ソルバーを使用してそれを実行できるはずです。実際、CGはおそらく、を調整する前に、かなり緩い制約で停止することができます。ADMMアプローチについても検討していますが、私はその経験があまりありません。Wij
Justin Solomon

はい、ADMMも素晴らしいです。私は実際にYを完全に削除することを提案するセクションを書きましたが、後でが正方ではないことを知りました。M
マイケル・グラント

1
IRLS戦略を実装しました-収束しますが、解く必要がある線形システムは広範囲ののおかげで悪条件であるため、数値的にはあまりうまくいきません。GMRESを使用してシステムを解く。次にADMMを試します!w
Justin Solomon

2

CVXを使用してみてください。CVXを使用すると、記述した形式で正確にコーディングできます(つまり、をベクトルではなく行列として)。おそらく、LPソルバーの代わりに、より一般的な凸型ソルバーを使用して解決されますが、凸型ソルバーが成功した場合は、気にしないと思います。X

別の可能性:制約行列をスパース行列として格納できるソルバーを使用します。これは、必要なメモリよりもはるかに多くのメモリを必要としますが、それらを密行列として保存した場合よりははるかに少なくなります。CVXではkron、スパースマトリックスを使用すると、これを試すのは簡単です。


Pythonは何らかの理由でMATLABよりも便利になる場合は、もありますcvxpyそれはかなりとしてCVXように研磨されていないが。
k20 2014

私の疑いは、このアプローチは表面的には機能し、CVXモデリング言語が入力データをバックエンドソルバー(線形プログラム、2次プログラム、2次コーンプログラム、半定型プログラム、および幾何学プログラム)。私の答えでその理由を説明します。Gurobiバックエンドは(他のタイプの中でも)最高のLPソルバーであるため、そのアルゴリズムでCVXを使用することは、コンパイルされた言語APIからCVXを呼び出すことを除いて、おそらく実装の面で実行できる最高の方法です。
Geoff Oxberry、2014

1
Geoffが言うように、ここではモデリングレイヤーは役に立ちません。結果のLPには、標準的な汎用ソルバーのデータが繰り返されます。これを回避するには、使用する必要があります、つまり、基本的に、残留帰国に基づいており、ソルバーOracleベースのソルバーを(開発)与えられた値のために、および/またはいくつかを適切なカットし、代わりにその説明で作業します。MJ+BYXJ,Y
JohanLöfberg、2014

はい、ジェフが言及している問題を正確に経験しています。実際、私は最初の推測にCVXを使用しました。私はGurobiを直接呼び出すことも試みましたが、それを行うと考えることができる唯一の方法は、同じ展開問題を実行することです。
ジャスティンソロモン

1
自分でロールする必要があると思います
JohanLöfberg
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.