勾配降下党青年局

万国のグラーディエントよ、降下せよ!

LyCORISのアルゴリズムまとめ

なんか色々増えてきたのでまとめるよ。

りこりこ

LoRA

LoRAは行列W\in \mathbb{R}^{(m,n)}に対して、差分\Delta W \in \mathbb{R}^{(m,n)}を学習します。このとき、A\in\mathbb{R}^{(m,r)}, \ B\in\mathbb{R}^{(r,n)}という二つの行列で\Delta W = ABとすることで、学習対象のパラメータを大幅に削減します。1層目をdown層、2層目をup層と呼びます。学習時は入力x\in\mathbb{(r,b)}に対して、出力をy=Wx+A(Bx)とします。これは計算量的には大きくなりますが、学習対象パラメータが大幅に削減されるメリットの方が大きいです。推論時はW'=W+ABとすることで、計算量を増やさずに済みます。実際はLoRAの結果を\frac{\alpha}{r}でスケーリングします。\alphaはハイパーパラメータで、rの増減でLoRAのスケールが変動することを防ぎ、rを変えた時に学習率を変更せずに済むようになっています。
アルゴリズムの総称として使われることもありますが、一番狭い意味では全結合層及び1×1畳み込み層に適用するものになっています。1×1畳み込み層にはピクセルごとに同じ全結合層を適用しているだけなので簡単に表現できます。
r \le \mathrm{rank}(\Delta W)となります。このことからrはrankと呼ばれています。上界だけどね。
r=1のLoRAを考えてみましょう。するとLoRAは列ベクトルと行ベクトルの積になります。Aの各要素をa_iとすると、
\Delta W = \begin{pmatrix}
a_1B \\ \vdots \\  a_mB
\end{pmatrix}
です。行基本変形を繰り返せば1行を残し全部0になることがすぐ分かり、\mathrm{rank}(\Delta W)=1になります。
一般のrのときは、
\Delta W = \begin{pmatrix}
a_{11}B_1 + a_{12}B_2 + \cdots + a_{1r}B_r \\ a_{21}B_1 + a_{22}B_2 + \cdots + a_{2r}B_r \\ \vdots \\  a_{m1}B_1 + a_{m2}B_2 + \cdots + a_{rm}B_r
\end{pmatrix}
みたいな感じになります。上のr行目までで行基本変形を行えば三角行列的な形(足し算に対してだけど)にできるので、r+1行目以降を0にできます。よって\mathrm{rank}(\Delta W)\le rです。

LoCon

LoConはフィルターサイズが1×1以外の畳み込み層にもLoRAを適用する方法です。たとえば3×3フィルターの場合、1ピクセルの計算にスポットを当てれば、9マス×入力チャンネル⇒出力チャンネルの全結合層です。3×3畳み込み層の重みは\mathbb{R}^{(m,n,3,3)}なので数は合いますよね。LoRAの場合はこの変換を9マス×入力チャンネル⇒rチャンネル⇒出力チャンネルと二つの変換に分解します。A\in\mathbb{R}^{(m,r)}, B\in\mathbb{R}^{(r,n,3,3)}として、3×3畳み込み層にもLoRAを拡張できます。入力を合わせるために、down層のstrideやpaddingは元のモジュールと同じものにします。up層は1×1畳み込みです。

LoHa

LoHaは二つのLoRAのアダマール積をとる手法です。アダマール積とは単純に行列の要素ごとの積をとる演算です。
\Delta W = AB \odot CDです。
LoRAの項目があった形になおすと、
\Delta W = \begin{pmatrix}
a_{11}B_1 + a_{12}B_2 + \cdots + a_{1r}B_r \\ a_{21}B_1 + a_{22}B_2 + \cdots + a_{2r}B_r \\ \vdots \\  a_{m1}B_1 + a_{m2}B_2 + \cdots + a_{rm}B_r
\end{pmatrix}
\odot
\begin{pmatrix}
c_{11}D_1 + c_{12}D_2 + \cdots + c_{1r}D_r \\ c_{21}D_1 + c_{22}D_2 + \cdots + c_{2r}D_r \\ \vdots \\  c_{m1}D_1 + c_{m2}D_2 + \cdots + c_{rm}D_r
\end{pmatrix}
r個の足し算同士の掛け算によって、項がr^2個になります。よって\Delta W \le r^2になります。パラメータ数が2倍になるのに対して、rankの上界が2乗になります。

LoKr

LoKRはクロネッカー積を使う手法です。
クロネッカー積とは行列A,Bに対して、以下のような行列を返す演算です。
A \otimes B = 
\begin{pmatrix}
a_{11}B & \cdots &  a_{1n}B \\
\vdots & \ddots & \vdots \\
a_{m1}B &\cdots & a_{mn}B \\
\end{pmatrix}

(m,n)行列と(p,q)行列に対してクロネッカー積を適用すると、(mp,nq)行列になります。

\Delta W = W_1 \otimes ABとなります。右だけLoRAです。左までLoRAにする設定もあるようです。
クロネッカー積に関して、\mathrm{rank}(A\otimes B) = \mathrm{rank}(A)\mathrm{rank}(B)となるので、rank的にはめちゃくちゃ効率よくなります。ただし重み共有を使っているので、rank的にいいからといって表現力が高いかというと微妙だと思いますけどね。

IA3

各モジュールの入力もしくは出力をチャンネルごとにスケーリングします。数式的には対角行列を重みの左側もしくは右側にかけます。パラメータ数は非常に小さいです。入出力両方に適用すれば素朴に表現力があがりそうですが、そういった設定はないようですね。

GLoRA

GLoRAは差分だけでなく、元の重みへの入力に対してもLoRAを適用します。
W' = W(x + ABx) + CDx = (W+WAB+CD)x
元論文ではもっと複雑な設計ですが、省略した実装のようですね。これをLoKR版にしたGLoKRもあります。
LoRAと比べて表現力は増えていないんですが、元の重みを利用することで学習が早くなったりするんですかね(よく分かりません)。

DyLoRA

複数のrankのLoRAを同時に学習する方法です。LoRAはAB=\displaystyle{\sum_{i=1}^{r}A_iB_i}という形にできます。ここでA_iAi列目、B_iBi行目です。DyLoRAではステップごとにランダムにkを選び、\Delta W = \mathrm{no\_grad}(\displaystyle{\sum_{i=1}^{k-1}A_iB_i}) + \mathrm{enable\_grad}(A_kB_k)とします。このように学習すると、任意のrank(k)で、\Delta W=\displaystyle{\sum_{i=1}^{k}A_iB_i}として生成できるようになります。kのrankを学習するときに、k-1以下のrankのLoRAに影響を与えないため勾配を無効にしています。rankは1ずつではなくブロック分けすることも可能です。

Norm

GroupNormやLayerNorm層も学習対象にします。重みやバイアスの差分を学習するというだけで特に難しいことはありません。

Full

\Delta Wを元の重みと同じサイズの行列にします。つまりやっていることは普通のファインチューニングと同じです。差分をとっておくのはVRAMの浪費でしかないんですが、LyCORIS上でフルファインチューニングを実装するにはそうするしかなかったんでしょう。一応差分に対してドロップアウトを実行できるというメリットはありますけどお。