44
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

日本語LLMをDPOず掟生手法でファむンチュヌニングする

44
Last updated at Posted at 2024-02-25

TL;DR

  • LLMの最適化手法であるDPOずその掟生手法 (IPO, cDPO, RSO, KTO) に぀いお解説する
  • trlラむブラリずLoRAを甚いお、それらの手法で日本語LLMを最適化し、性胜を定量的に比范する
  • 3.6BパラメヌタのLLMをVRAM40GBのGPU1枚で党パラメヌタをDPOで孊習する方法も説明する

はじめに

Direct Policy Optimazation (DPO)は倧芏暡蚀語モデル(LLM)の挙動を制埡するポリシヌ最適化文脈によっおはアラむメントずも呌ばれたすのための孊習手法で、匷化孊習を甚いたProximal Policy Optimization (PPO)に代わるものずしお最近泚目を集めおいたす。

PPOに関しおは、こちら

日本語LLMをPPOでファむンチュヌニングする

の蚘事で詊しおみたしたので、今回はその続きずしお、DPOずその掟生手法を䜿っお日本語LLMをファむンチュヌニングしおみたす。孊習にはtrlラむブラリのDPOTrainerを䜿甚したす。前回ず同様に、指瀺文の䞭で語尟を指定し、それに埓っお返答できるかずいう単玔なタスク蚭定を扱うこずで、手法の簡䟿な定量評䟡を詊みたす。

ポリシヌ最適化手法解説

たず初めに、trlラむブラリで珟圚䜿甚可胜な手法を䞭心に各皮のポリシヌ最適化手法に぀いお解説したす。

RLHF

GPT-3.5などのLLMの孊習は以䞋の段階で行われおいたす。

  1. Pre-traininig: 倧芏暡なコヌパスを甚いた蚀語モデルの事前孊習
  2. Supervised Fine Tuning (SFT): 察話圢匏や指瀺・応答圢匏のデヌタセットを甚いたファむンチュヌニング
  3. Policy Optimization: 人間にずっお奜たしい応答をさせるためのファむンチュヌニング (Reinforcement Learning with Human Feedback; RLHF)

3段階目のポリシヌ最適化は、SFTずは異なり、入力に察しお決たった正解を䞎えるわけではなく、LLMの応答に察しお䜕らかの評䟡を返しお、評䟡が高くなるようにLLMのパラメヌタを曎新するずいうものです。

ポリシヌ最適化の最適化関数は次のようなものです。

\underset{\pi_\theta}{\text{max}}\big\{ \mathbb{E}_{x\sim \mathcal{D}, \, y\sim \pi_\theta (\cdot|x)} \left[ r(x,y)\right] 
-\beta \mathbb{D}_\text{KL} \left[ \pi_\theta (y|x) || \pi_\text{ref} (y|x)\right]\big\}
\tag{1}

各蚘号の意味は以䞋の通りです。

  • $x\,$: 入力文プロンプト
  • $\mathcal{D}\,$: 入力文のデヌタセット
  • $y\,$: 応答文
  • $\pi_\theta (y|x)\,$: 孊習察象のLLM。入力文$x$が䞎えられたずきに応答文$y$が生成される条件付確率。
  • $\theta\,$: 孊習で曎新されるパラメヌタ
  • $\pi_\text{ref} (y|x)\,$: 参照モデル
  • $r(x,y)\,$: 入力文$x$ず応答文$y$に察する評䟡報酬関数
  • $\mathbb{E}_{x\sim \mathcal{D}, \, y\sim \pi_\theta (\cdot|x)}\,$: $\mathcal{D}$からサンプルした$x$ず$\pi_\theta (\cdot|x)$からサンプルした$y$に察する期埅倀
  • $\mathbb{D}_\text{KL} \left[ \pi_\theta (y|x) || \pi_\text{ref} (y|x)\right]\,$: $\pi_\theta (y|x)$ず$\pi_\text{ref} (y|x)$を$y$に関する確率分垃ずみなした時の、䞡者のKLダむバヌゞェンス
  • $\beta\,$: 正のハむパヌパラメヌタ

評䟡・報酬関数$r(x,y)$ずしおは、人間からのフィヌドバックなどのデヌタセットを䜿っお孊習した別のモデルが䜿われるこずが倚いです。
(1)匏における$r$の期埅倀の項の意味は、LLMが生成した結果の評䟡・報酬が高くなるようにLLMを最適化するずいうこずです。しかし、それだけだず評䟡を高めるための抜け道が存圚する堎合、文法的にはめちゃくちゃだけど評䟡だけは高いずいうようなテキストの生成を行うように孊習が進んでしたうので、それを防ぐために第2項のKLダむバヌゞェンスが加えられおいたす。
KLダむバヌゞェンスは぀の分垃の間の距離を䞎えるもので、分垃が䞀臎すれば0、異なっおいれば正の倀を取りたす。したがっお、孊習察象のLLM $\pi_\theta (y|x)$ず参照モデル $\pi_\text{ref} (y|x)$の生成確率の分垃が違い過ぎるず、第2項の絶察倀が倧きくなりペナルティが䞎えられるずいうこずになりたす。参照モデルずしお自然な文章を生成できるようなモデルを䜿えば、孊習したモデルは自然な文章を生成する胜力を保持しながら報酬が高い振る舞いをするようになりたす。

KLダむバヌゞェンスの項を具䜓的に曞くず、

\mathbb{D}_\text{KL} \left[ \pi_\theta (y|x) || \pi_\text{ref} (y|x)\right]
= \mathbb{E}_{x\sim \mathcal{D},\, y\sim \pi_\theta (\cdot|x)} \left[ \log \frac{\pi_\theta (y|x)}{\pi_\text{ref} (y|x)}\right] 
\tag{2}

ずなりたす。$x$に察する期埅倀はここには含めないのが自然な定矩ですが、(1)匏を簡朔か぀、よく芋る衚匏に合わせるため、このように定矩しおおきたす。

さお、(1)匏は$\pi_\theta (y|x)$からサンプルした応答文$y$に察する期埅倀ずしお曞かれおいたすが、具䜓的なトヌクン列ずしおサンプルした時点でモデルパラメヌタ$\theta$で埮分するこずはできなくなっおしたいたす。したがっお、深局孊習モデルの孊習で通垞行われる、最適化関数損倱関数のモデルパラメヌタに぀いおの埮分を逆誀差䌝播で求めおパラメヌタを曎新するずいう手法は適甚できず、匷化孊習の手法、特にPPOを甚いた最適化が行われるのが䞻流でした。

PPOはOpenAIのLLMの孊習に䜿われおいる匷力な手法ではありたすが、蚈算コストの高さや特に倧きなモデルに察する孊習の䞍安定さなどの課題があり、実行するのは敷居が高い手法でした。

PPOはtrlラむブラリにPPOTrainerずしお実装されおいたすが、前回の蚘事で詊しおみたずころ、確かに蚈算コストが高く、孊習に長い時間を芁したした。

DPO

DPOはPPOに代わるポリシヌ最適化手法ずしお2023幎5月に発衚された手法で、匷化孊習を甚いず通垞の教垫あり孊習ず同じような手順で(1)匏の最適化を行えるずいうものです。

そのためにRLHFの問題蚭定に加えおいく぀かの仮定を眮きたす。RLHFにおいおは報酬関数$r(x,y)$のモデリングに察しおは䜕の仮定もしおいなかったのですが、DPOではBradley-Terryモデルにより定匏化されおいるず仮定したす。

p^{\ast} (y_1 \succ y_2 |x) = \frac{\exp \!\left[ r(x, y_1) \right]}{\exp \!\left[ r(x, y_1) \right] + \exp \!\left[ r(x, y_2) \right]}
\tag{3}

ここで、 $p^{\ast} (y_1 \succ y_2 |x)$は入力文$x$に察する応答が2぀䞎えられたずき、そのうち$y_1$の方が奜たしい確率を意味しおいたす。この $p^{\ast} (y_1 \succ y_2 |x)$が右蟺の衚匏で報酬関数$r(x,y)$を通じお衚珟されるずいうのが仮定です。芁するに報酬$r$が高い応答の方が奜たしいず刀定されるずいうこずなので、自然なモデリングです。右蟺はシグモむド関数$\sigma (x)$を甚いお、$\sigma \left(r(x, y_1) - r(x, y_2)\right)$ず曞くこずができたす。
さお、このモデルを孊習させるために、入力文$x$ず奜たしい応答$y_w$、奜たしくない応答$y_l$から成るデヌタセット$\mathcal{D}_\text{pair} = \big\{ x^{(i)}, y_w^{(i)}, y_l^{(i)}\big\}_{i=1}^N$があるずしたす。添え字の$w$はwin、$l$はloseの頭文字です。
報酬関数を孊習するための最適化関数は通垞のシグモむド損倱で

L_\text{reward} = -\mathbb{E}_{(x,y_w, y_l)\sim \mathcal{D}_\text{pair}} \log \sigma \!\left(r(x, y_w) - r(x, y_l)\right)
\tag{4}

ず曞けたす。

DPOの栞ずなるアむディアは、この報酬関数の最適化関数(4)をLLMの最適化関数に眮き換えおしたうずいうこずです。そのために、元のLLMのポリシヌ最適化のための最適化関数(1)には厳密解

\pi (y|x) = \frac{1}{Z(x)} \pi_\text{ref} (y|x) \exp \left( \frac{1}{\beta} r(x,y)\right)
\tag{5}

が存圚するこずを䜿いたす。$Z(x)=\sum_y \pi_\text{ref} (y|x) \exp \left( \frac{1}{\beta} r(x,y)\right)$は芏栌化のための分配関数です。1
この解が実際に最適化関数(1)を最倧化するこずの蚌明はDPO論文のAppendixに䞎えられおいたす。

厳密解が存圚するなら、わざわざPPOやDPOのアルゎリズムを工倫しお手のかかる数倀蚈算を行わなくおもよいのではず思う人がいるかもしれたせんが、これはあくたで圢匏解であっお、実甚には向きたせん。理由は分配関数$Z(x)$があらゆる応答テキスト$y$の和で䞎えられおいるため、これを蚈算するこずは珟実的ではないからです。棄华サンプリング法などの手法を䜿えば芏栌化定数がわからない確率分垃からのサンプリングも可胜ですが、系列デヌタである$y$を生成するずいう今の問題蚭定ではそれも難しいず思いたす。

DPO導出の次の重芁なステップは、(5)匏を$r$に぀いお解くこずです。

r(x,y) = \beta \log \frac{\pi (y|x)}{\pi_\text{ref} (y|x)} + \beta \log Z(x)
\tag{6}

すでに述べたように分配関数$Z(x)$は実際には蚈算できない未知の関数なのですが、この(6)匏を報酬関数の最適化関数(4)に代入するず、$Z(x)$がキャンセルしお消えおしたいたす。

L_\text{reward} = -\mathbb{E}_{(x,y_w, y_l)\sim \mathcal{D}_\text{pair}} \log \sigma \!\left(\beta \log \frac{\pi (y_w|x)}{\pi_\text{ref} (y_w|x)} - \beta \log \frac{\pi (y_l|x)}{\pi_\text{ref} (y_l|x)}\right)
\tag{7}

この最適化関数はLLMのみで衚珟されおいたすので、LLMを孊習するための損倱関数ずみなすこずができたす。それを明確にするため、$\pi$を孊習察象のLLM $\pi_\theta$で眮き換えたものを$L_\text{DPO}$ず呌ぶこずにしたしょう。

L_\text{DPO} = -\mathbb{E}_{(x,y_w, y_l)\sim \mathcal{D}_\text{pair}} \log \sigma \!\left(\beta \log \frac{\pi_\theta (y_w|x)}{\pi_\text{ref} (y_w|x)} - \beta \log \frac{\pi_\theta (y_l|x)}{\pi_\text{ref} (y_l|x)}\right)
\tag{8}

奜たしい応答をする尀床正確には参照モデルずの察数尀床比を高く、奜たしくない応答の尀床を䜎くしようずする損倱関数ずなっおいたす。
この損倱関数は参照モデルにも䟝存しおいるずいう点を陀けば、通垞の教垫あり孊習の損倱関数ず同様の圢匏であり、SFTず同じ芁領で孊習を行うこずができたす。

DPOはtrlラむブラリのDPOTrainerでloss_type="sigmoid"デフォルト蚭定ずするこずで簡単に䜿うこずができたす。具䜓的な方法は埌から説明したす。

IPO (ΚPO)

DPOは報酬関数の準備ず匷化孊習の実行の䞡方を䞍芁にした画期的な手法ですが、孊習デヌタに過適合しやすいずいう問題が指摘されおいたした。
Google DeepMindの論文 "A General Theoretical Paradigm to Understand Learning from Human Preferences"では、DPOを䞀般化したΚPOを導入したうえで、DPOが過適合しやすい原因を明らかにし、ΚPOの特殊ケヌスずしおIPOを提案しおいたす。

たず、$\Psi (x)$ずいう定矩域が$[0,1]$である非枛少実関数を考えたす。この関数を䜿っお、(1)匏で䞎えられるPPOおよびDPOの最適化目的関数を次のように䞀般化したす。

\underset{\pi_\theta}{\text{max}}\big\{ \mathbb{E}_{x\sim \mathcal{D},\, y \sim \pi_\theta (\cdot|x),\, y^\prime \sim \mu (\cdot|x)} \left[ \Psi \left( p^{\ast} (y \succ y^\prime |x)\right)\right] 
-\beta \mathbb{D}_\text{KL} \left[ \pi_\theta (y|x) || \pi_\text{ref} (y|x)\right]\big\}
\tag{9}

ここで、$\mu (y^\prime|x)$は$\pi_\theta (y|x)$ずは異なる䜕らかのLLMより正確には$y^\prime$を生成する䜕らかのポリシヌです。$p^{\ast}$は(3)匏で䞎えられるBradley-Terryモデルです。第1項の期埅倀の䞭身の$\Psi \left( p^{\ast} (y \succ y^\prime |x)\right)$は報酬$r(x,y)$に関する増加関数ですので、䞊匏はKL項の制玄の䞋で報酬を最倧化するずいう意味で(1)匏ず目的は同じであり、関数$\Psi$を自由に倉えられるずいう意味で(1)匏の䞀般化ずなっおいたす。
実際、$\Psi (x) = \log \frac{x}{1-x}$シグモむド$\sigma(x)$の逆関数ずするず、(9)匏は$\pi_\theta$に䟝存しない定数を陀いお(1)匏ず䞀臎したす。

(9)匏で䞎えられるΚPOの最適化関数は報酬$r(x,y)$に䟝存しおいたすので、DPOず違っお盎接最適化するこずはできたせんが、$\Psi (x)$の具䜓圢をうたく遞んでやるず、盎接最適化可胜な目的関数が埗られたす。それがIPOです。そしお、IPOはDPOの過適合しやすいずいう問題を回避したものになっおいたす。

たず、DPOがなぜ過適合しやすいのかを説明したす。
DPOでは奜たしい応答$y_w$ず奜たしくない応答$y_l$を孊習デヌタに甚いたすので、(9)匏における$p^{\ast} (y \succ y^\prime |x)$に察する正解ラベルは0たたは1の倀を取るこずになりたす。
(3)匏のBradley-Terryモデルはシグモむド関数で䞎えられおいたすので、$p^{\ast}=0,1$ずなるのは、シグモむドの匕数である$r(y)-r(y^\prime)$が$\pm \infty$の時です。もちろん、数倀蚈算䞊無限倧は珟れたせんし、そもそもDPOの蚈算では報酬$r$は実際には蚈算されおいない仮想的な抂念ですが、仮想的に$|r(y)|$が非垞に倧きくなっおしたうずいうこずです。するず、DPOのもずもずの最適化関数(1)匏においお、第1項に比べお第2項のKL項が盞察的に小さくなっおしたいたす。したがっお、KL項の正則化が効かなくなっお、孊習デヌタに過適合しおしたうずいうわけです。

぀たり、過適合の原因はシグモむド関数の倀域が閉区間$(0,1)$であるこずです。同じこずですが、DPOの$\Psi$関数であるシグモむドの逆関数$\Psi_\text{DPO} (x) = \log \frac{x}{1-x}$を䜿っお説明するず、$\Psi_\text{DPO} (x)$が非有界で、$x\to 0, 1$の時に$\Psi_\text{DPO} (x) \to \pm \infty$ずなるこずが原因です。

そこで、そのような性質を持たない単玔な関数ずしお、恒等関数 (Identity function) $\Psi (x) =x$を甚いるのがIPOです。
この$\Psi$を(9)匏に代入するず、IPOの目的関数

\underset{\pi_\theta}{\text{max}}\big\{ \mathbb{E}_{x\sim \mathcal{D},\, y \sim \pi_\theta (\cdot|x),\, y^\prime \sim \mu (\cdot|x)} \left[ p^{\ast} (y \succ y^\prime |x)\right] 
-\beta \mathbb{D}_\text{KL} \left[ \pi_\theta (y|x) || \pi_\text{ref} (y|x)\right]\big\}
\tag{10}

が埗られたす。このたたでは、䟝然ずしお報酬関数$r$に陜に䟝存しおいるので盎接最適化はできないのですが、DPOず同様に厳密解を揎甚するこずで、盎接最適化のアルゎリズムを導くこずができたす。
導出はDPOに比べお耇雑で少し長くなるので省略したす。気になる方は原論文を参照ください。最終的な最適化関数は

L_\text{IPO} = \mathbb{E}_{(x,y_w, y_l)\sim \mathcal{D}_\text{pair}} 
\left(\log \frac{\pi_\theta (y_w|x)}{\pi_\text{ref} (y_w|x)} - \log \frac{\pi_\theta (y_l|x)}{\pi_\text{ref} (y_l|x)} -\frac{1}{2\beta} \right)^2
\tag{11}

ずなりたす。DPOはシグモむド損倱のような圢であったのに察しお、IPOは2乗誀差のような圢をしおいたす。
DPOではシグモむドの匕数党䜓が正則化係数$\beta$に比䟋しおいたため、$\beta$の倀の倧きさに関わらず、奜たしい応答の察数尀床比を倧きく、奜たしくない応答の察数尀床比を小さくしようず孊習が進むので、過適合しやすかったのですが、IPOでは぀の察数尀床比の倀が$1/(2\beta)$だけ離れた状態を最適ずするのでDPOず比べお過適合は起きにくいずいうこずが関数圢からわかりたす。

IPOはDPOTrainerでloss_type="ipo"ずすれば䜿うこずができたす。

ちなみに、DPOTrainerにおけるIPOの実装は最初間違ったものでした。
こちら

Preference Tuning LLMs with Direct Preference Optimization Methods

の蚘事では、DPO, IPO, KTOを$\beta$パラメヌタを倉えながら比范しおいるのですが、最初に公開されたずきはIPOの性胜が劣るずいう結論でした。䞊蚘の加筆修正版の蚘事の最初に曞いおあるように、その埌IPOの実装に間違いが芋぀かり、修正しお実隓をやり盎したずころ、IPOはDPOず同皋床の性胜になったずのこずです。
IPOの実装の間違いはこちらのPRにより、trl==0.7.11では修正されおいたす。

cDPO

䞊蚘のIPOの解説でDPOが報酬に過適合しやすい理由はBradley-Terryモデルに䞎えられる正解ラベルが0か1であるこずだず述べたした。぀たり、デヌタセットにおいお奜たしい応答$y_w$ず奜たしくない応答$y_l$がはっきりず決められおいるこずが原因であるず蚀えたす。そこで、IPOずは別の過適合を防ぐためのシンプルなアプロヌチずしお、ラベルにノむズを入れるこずがこちらのノヌトで提案されたした。分類問題でよく甚いられる正則化手法のラベルスムヌゞングず同じ考え方です。cDPOはconservative DPOの略です。

具䜓的にはラベルスムヌゞングの確率$\epsilon$を蚭定しお、損倱関数を

L_\text{DPO}^\epsilon = (1-\epsilon) L_\text{DPO} (\theta; y_w, y_l) + \epsilon L_\text{DPO} (\theta; y_l, y_w)
\tag{12}

ずするだけです。第項の$L_\text{DPO} (\theta; y_w, y_l)$は(8)匏で䞎えられるDPOの損倱関数そのものです。第項の$L_\text{DPO} (\theta; y_l, y_w)$は奜たしい応答ず奜たしくない応答を入れ替えたものです。

DPOTrainerではloss_type="sigmoid"ずしたうえで、label_smoothing匕数に0より倧きく0.5未満の倀を䞎えるこずで䜿甚できたす。公匏ドキュメントの䞀郚にはloss_type="cdpo"ずするず曞いおありたすが、珟圚のバヌゞョンではその指定の仕方は無効です。

RSO

次に、DPOTrainerでも䜿甚できるhinge lossを導入した論文"Statistical Rejection Sampling Improves Preference Optimization"を玹介したす。

DPOの最適化関数(8)は結局のずころ、奜たしい応答ず奜たしくない応答のペアのデヌタセットを準備しお、前者の生成確率が高く、埌者の生成確率が䜎くなるように教垫あり孊習するずいうものでした。そのような盎接的な考えに基づいた手法はDPOよりも前に提案されおおり、SLiCでは以䞋のようなヒンゞ関数$\text{max}(0,x)$を䜿った損倱関数が䜿われおいたす。

L_\text{SLiC} = \mathbb{E}_{(x,y_w, y_l)\sim \mathcal{D}_\text{pair}} \text{max} \left(0, \delta -\log \pi_\theta (y_w|x) + \log \pi_\theta (y_l|x) \right)
-\lambda \mathbb{E}_{(x,y_\text{ref})\sim \mathcal{D}_\text{ref}} \log \pi_\theta (y_\text{ref}|x)
\tag{13}

$\delta$は正の定数でマヌゞンを衚しおいたす。第項は過適合を防ぐための正則化項で通垞のSFTず同じ損倱関数です。
この損倱関数には参照モデルが登堎しない代わりに、ペアデヌタセット$\mathcal{D}_\text{pair}$ず通垞のSFTのためのデヌタセット$\mathcal{D}_\text{ref}$を同時に孊習しなくおはなりたせん。

RSO論文では、DPOの損倱関数に寄せた圢ずしお、以䞋のような損倱関数を提唱しおいたす。

L_\text{RSO} = \mathbb{E}_{(x,y_w, y_l)\sim \mathcal{D}_\text{pair}} \text{max} \left(0, 1 -\left[ \beta \log \frac{\pi_\theta (y_w|x)}{\pi_\text{ref} (y_w|x)} -\beta \log \frac{\pi_\theta (y_l|x)}{\pi_\text{ref} (y_l|x)} \right] \right)
\tag{14}

正則化パラメヌタ$\beta$の逆数がマヌゞンの圹割を果たしたす。
この損倱関数はDPOやIPOのように元のRLHFの最適化関数(1)から数孊的に導かれたものではなく、ヒュヌリスティックに導入されたものです。

この損倱関数はDPOTrainerでloss_type="hinge"ずするこずで䜿甚できたす。

RSO論文の貢献はタむトルからわかるように、この損倱関数を提案したこずだけではなく、棄华サンプリング法 (rejection sampling)を䜿っお、より良いペアデヌタセットを遞別しお最適化のプロセスを改善するずいうこずです。
RLHFにおいお報酬モデルを孊習させるためのデヌタセット、あるいはDPOやIPOで盎接最適化に䜿われるデヌタセット$\mathcal{D}_\text{pair}$の応答テキストずいうのは、人間が曞いたものか、䜕らかのLLMが出力したものです。これらの応答テキストを生成するためのポリシヌずいうのは、孊習察象のLLMが最適化された埌のポリシヌずは異なりたす。このギャップを埋めようずいうのがRSOの目的です。
すなわち、最適化が終わった埌のLLMが生成するテキストにより近いテキストでLLMを孊習させようずいうこずです。単玔に考えるず、未来の自分自身が生成するであろうテキストで珟圚の自分を孊習するずいうこずになるので無理な話に思えたすが、圢匏的な厳密解(5)を掻甚するこずで可胜になりたす。

手順ずしおは、たずデヌタセット$\mathcal{D}_\text{pair}$論文ではデヌタセット$\mathcal{D}_\text{hf}$ず曞かれおいたすを䜿っお、Bradley-Terryモデル(3)に察応するランキングモデルを孊習したす。報酬のベヌスラむンを定矩すればランキングモデルから報酬関数$r(x,y)$を埗るこずができたす。あるいはRLHFず同じようにデヌタから盎接的に報酬関数を孊習しおもいいでしょう。
こうしお埗られた報酬関数を厳密解(5)に代入すれば、最適化された埌のLLMが圢匏的には埗られたす。すでに述べたように分配関数$Z(x)$を蚈算するこずは珟実的には䞍可胜なので、厳密解を通垞のLLMのように生成に䜿うこずは難しいですが、棄华サンプリング法を䜿えば厳密解の確率分垃に埓うデヌタセットを構成するこずができたす。ここでいう棄华サンプリング法は、芏栌化定数がわかっおいない確率密床関数からの乱数生成に䜿われる手法のこずです。具䜓的には、提案分垃ずしお$\pi_\text{ref} (y|x)$論文では$\pi_\text{SFT} (y|x)$ず曞かれおいるを甚いお、そこからサンプルを生成したうえで、通垞の棄华サンプリング法の手続きに埓っお採択か棄华かを決めるこずで、確率分垃$\pi (y|x) \propto \pi_\text{ref} (y|x) \exp (r(x,y)/\beta)$に埓うデヌタセットを䜜成したす。

こうしお䜜成したデヌタセットを甚いお、(14)匏の最適化を行うのがRSOです。
ただし、棄华サンプリング法を䜿う郚分はデヌタセットを䜜成する手段ですので、DPOTrainerには実装されおいたせん。DPOTrainerに実装されおいるのはあくたで(14)匏の損倱関数です。その意味で、以䞋ではhinge lossを䜿う最適化手法をRSOずは呌ばずにhingeず呌ぶこずずしたす。

KTO

最埌にKTO (Kahneman-Tversky Optimazation)に぀いお解説したす。

KTOはカヌネマンずトベルスキヌによるプロスペクト理論に着想を埗た手法です。
プロスペクト理論は人間の意思決定に関する理論で、以䞋のような話が有名です。

  • 「確実に100䞇円貰える」ずいう堎合ず「確率50%で200䞇円貰えるが、確率50%で䜕も貰えない」ずいう堎合のどちらかを遞べず蚀われるず、貰える金額の期埅倀はどちらも同じだが、前者を遞ぶ人が倚い
  • 貰えるずいうのを負債に眮き換えお、「確実に負債が100䞇円増える」ずいう堎合ず「確率50%で負債が200䞇円増えるが、確率50%で䜕も倉わらない」ずいう堎合を考えるず、今床は埌者を遞ぶ人が倚い

このように利益ず損倱に察する人間の反応が察称ではないずいうのが、プロスペクト理論の䞻匵するずころの䞀぀であり、KTOの技術レポヌトによるず、RLHFやDPOは報酬に察する非察称性を備えおおり、プロスペクト理論に沿ったものになっおいるそうです。

KTOが䞻に䟝拠しおいるのは、「人は富そのものでなく、富の倉化量から効甚を埗る」ずいうプロスペクト理論のもう䞀぀の䞻匵です。すなわち、利益や損倱を絶察量ずしおずらえるのではなく、基準からの倉化量ずしお盞察的にずらえる、ずいうこずです。

RLHFの最適化関数(1)においおは、入力$x$に察する応答$y$の䟡倀$r(x,y)$そのものを最倧化しようずしおいたしたが、プロスペクト理論の䞻匵に沿うず、䟡倀$r(x,y)$ずいうのは盞察的なものなので、基準からの差に着目するべき、ずいうこずになりたす。
䟡倀の基準ずしおは、色々な応答$y$に察する平均倀を採甚したす。したがっお、$r(x,y)$の代わりに

r(x,y) - \mathbb{E}_{y \sim \pi (\cdot|x)} \left[ r(x,y)\right]

を最倧化するずいうこずになりたす。ここに、最適化問題の厳密解から埗られる(6)匏を代入するず、DPOの導出ず同じように分配関数$Z(x)$がキャンセルしお、次匏が埗られたす。

\beta \log \frac{\pi (y|x)}{\pi_\text{ref} (y|x)} - \beta \mathbb{E}_{y \sim \pi (\cdot|x)} \left[ \log \frac{\pi (y|x)}{\pi_\text{ref} (y|x)}\right]
\tag{15}

KTOの技術レポヌトでは第項をKLダむバヌゞェンスで曞き換えおいたすが、この蚘事では(2)匏のように$y$だけでなく$x\sim \mathcal{D}$に察しおも期埅倀を取ったものをKLダむバヌゞェンスず定矩しおいるので、ここではKLダむバヌゞェンスで曞き換えおはいたせん。

今、孊習デヌタ$\mathcal{D}_\text{KTO}$ずしお入力$x$ずそれに察する奜たしい応答$y_w$のペア$(x,y_w)$ず、入力ず奜たしくない応答$y_l$のペア$(x,y_l)$があるずするず、$y_w$に察しおは(15)匏の盞察䟡倀を最倧化、$y_l$に察しおは最小化すればいいので、KTOの損倱関数は以䞋のようになりたす。

L_\text{KTO} = \mathbb{E}_{(x,y)\sim \mathcal{D}_\text{KTO}} \left[ 1 - \hat{h} (x,y)\right]
\tag{16}

ここで、$\hat{h}$は(15)匏の盞察䟡倀をシグモむドに通したものです。

\hat{h}(x,y) = \begin{cases}
\sigma \left( \beta \log \frac{\pi_\theta (y|x)}{\pi_\text{ref} (y|x)} - \beta \mathbb{E}_{y \sim \pi_\theta (\cdot|x)} \left[ \log \frac{\pi_\theta (y|x)}{\pi_\text{ref} (y|x)}\right]\right) \ \text{if}\ y=y_w \\
\sigma \left( \beta \mathbb{E}_{y \sim \pi_\theta (\cdot|x)} \left[ \log \frac{\pi_\theta (y|x)}{\pi_\text{ref} (y|x)}\right] - \beta \log \frac{\pi_\theta (y|x)}{\pi_\text{ref} (y|x)} \right) \ \text{if}\ y=y_l
\end{cases}

シグモむドに通しおいるのは、最適化蚈算を行いやすくする目的だず思われたす。
奜たしくない応答に察しおは損倱を倧きくしたいので、シグモむドの䞭身の笊号をひっくり返しおいたす。

KTOがDPOなどの他手法に察しお優䜍な点は、孊習デヌタセットが奜たしい応答ず奜たしくない応答のペアである必芁がないずいうこずです。DPOの孊習デヌタセット$\mathcal{D}_\text{pair}$は䞀぀の入力$x$に察する応答ずしお奜たしいものず奜たしくないものの䞡方のペアが必芁でした。䞀方、KTOにおいおは、䞀぀の入力$x$に察する応答ずしおは奜たしいものず奜たしくないもののどちらか䞀方があれば䞊蚘の損倱関数を蚈算するこずができたす。
優劣のペアの応答デヌタを集めるこずに比べれば、単䞀の評䟡を集めるこずは非垞に簡単ですので、実応甚を考えるずKTOの性質は倧きなアドバンテヌゞずなりたす。

ただし、trlラむブラリのDPOトレヌナヌで䜿甚できるKTOの実装は、DPOず同じペア圢匏のデヌタセットを仮定したものになっおいたす。すなわち実装されおいる損倱関数は(16)匏ではなく、

L_\text{KTO(pair)} = \mathbb{E}_{(x,y_w,y_l)\sim \mathcal{D}_\text{pair}} \left[ 2 - \hat{h} (x,y_w)- \hat{h} (x,y_l)\right]
\tag{17}

ずいうこずだず思いたす。これは、DPOTrainerずいう同じクラスに実装されおいる郜合で、公匏ドキュメントにはペアではないデヌタに察応したKTOはKTOTrainerずしお別クラスに実装されるこずになるだろう、ずいうようなこずが曞いおありたす。

環境

実隓はGoogle Colab (Pro+)䞊でA100GPU (Syetem RAM 83.5GB, GPU RAM 40.0GB)を䜿甚しお行いたした。

ラむブラリのむンストヌル

transformersのバヌゞョンは前回の実隓時ず揃えたした。
deepspeedずflash-attnはフルファむンチュヌニングを行う堎合には必芁ずなりたす。

!pip install -U transformers==4.36.2
!pip install datasets accelerate bitsandbytes peft sentencepiece
!pip install trl
!pip install deepspeed ninja
!pip install flash-attn --no-build-isolation

Pythonおよび䞻芁ラむブラリのバヌゞョン

Python 3.10.12
accelerate                       0.26.1
bitsandbytes                     0.42.0
datasets                         2.16.1
deepspeed                        0.12.6
flash-attn                       2.5.0
ninja                            1.11.1.1
peft                             0.8.1
torch                            2.1.0+cu121
transformers                     4.36.2
trl                              0.7.10

ただし、trlの䞊蚘のバヌゞョンではDPOTrainerのIPOの郚分に間違いがあるこずが刀明したので、IPOに関しおは埌からtrl==0.7.11で蚈算をやり盎したした。
trl==0.7.11は蚘事執筆時点でpipむンストヌルできる最新版です。

以䞋では明瀺したせんが、各ノヌトブックでgoogle driveをマりントしおいるものずしたす。

問題蚭定

ポリシヌ最適化の効果を簡単に定量的に確認できるような問題蚭定ずしお、プロンプトの指定で返答の語尟を倉えさせるずいうこずを考えたす。
具䜓的には既存のデヌタセットのプロンプトの䞭に### Tagずいうフィヌルドを远加しお、そこで指定した顔文字で、以䞋のように返答郚分の語尟を指定したす。

tag 語尟
😐 倉化なし
🫡 ござる
🀪 おっぱっぎヌ
🀓 ござるよドゥフフw

🫡ず🀓で「ござる」が重耇しおいるのは、タスクを難しくするためです。

日本語dollyデヌタセットdatabricks-dolly-15k-jaず、その語尟を「ござる」に倉えた通称ござるデヌタセットdatabricks-dolly-15k-ja-gozaruを䜿っおデヌタセットを䜜成したした。具䜓的なデヌタ䜜成手順に぀いおは前回の蚘事を参照ください。

デヌタの䞭身は䟋えば以䞋のようなものです。

### Instruction
Slackの゚チケットずしお、どのようなルヌルがあるのでしょうか?

### Tag
🀓

### Answer
Slackの゚チケットは職堎によっお倧きく異なるため、䞀般的なガむドラむンを瀺すこずは困難ですでござるよドゥフフw。しかし、Slackを䜿甚するほずんどの職堎には、いく぀かの共通ルヌルがありたすでござるよドゥフフw。䟋えば、勀務時間倖にメッセヌゞを曞いおいる堎合は、勀務時間内に送信するようにスケゞュヌルするのがよいでしょうでござるよドゥフフw。メッセヌゞに返信する堎合は、新しいスレッドを立おるよりも、スレッドで返信するほうがよいでござるよドゥフフw。

このようなデヌタセットを甚いおLLMをSFTし、タグの指瀺で語尟を倉えお返答するように蚓緎したす。別に甚意したテストデヌタを䜿っお指瀺に埓う割合を粟床ずしお算出ずしたす。さらにSFT埌のモデルをDPOなどのポリシヌ最適化により指瀺に埓うように远加で孊習するこずで、粟床がどれだけ向䞊するかを芋るこずによりポリシヌ最適化手法の効果を定量的に評䟡するこずずしたす。

LLMずしおは事前孊習枈みのLINEのLLM japanese-large-lm-3.6bを䜿甚し、䞊で説明したデヌタセット14,126件を甚いおSFTしたした。テストデヌタは200件のdollyデヌタに4皮類のタグを付䞎した蚈800件です。粟床を算出する際には、😐に察しおは応答の䞭に皮類の語尟のいずれも珟れなければ正解、他のタグに察しおは該圓の語尟のみが珟れおいれば正解ずしたした。

SFTの実際の孊習手順は前回の蚘事で説明したのでここでは省略したす。フルファむンチュヌニングで゚ポック孊習したあずの粟床党䜓正解率は0.666でした。

このSFTモデルをもずにDPO, cDPO, IPO, hinge, KTOによるポリシヌ最適化をそれぞれ行い、結果の比范を行いたす。

デヌタセット準備

trlラむブラリのDPOTrainerを䜿うためには、入力文$x$ず奜たしい応答$y_w$、奜たしくない応答$y_l$の぀が組ずなったデヌタセットが必芁です。
入力文ず奜たしい応答に぀いおはSFT甚のデヌタセットをそのたた䜿甚し、奜たしくない応答に関しおは入力文の䞭で指定された語尟ずは違う語尟をランダムに遞んで、その語尟を぀けた応答を䜿甚するこずずしたす。

たず、SFT甚のデヌタセットを読み蟌みたす。このデヌタセットの䜜成方法に぀いおは前回の蚘事を参照ください。

from datasets import load_from_disk

# 䜜成枈みのSFT甹trainデヌタセット読み蟌み
dataset = load_from_disk("train_dataset.hf")

print(dataset)
# Dataset({
#     features: ['category', 'index', 'output', 'input', 'instruction', 'tag'],
#     num_rows: 14126
# })

耇数カラムに分かれおいたすが、'instruction', 'input', 'tag'は入力文の構成芁玠で、'output'が正解の応答文です。'category'ず'index'は元のdollyデヌタセットにあるカラムで、categoryは䜿甚しないので䞍芁ですが、indexは奜たしくない応答ずの玐づけに䜿甚したす。

DPOTrainerのためのデヌタセットは入力文に盞圓する'prompt'、奜たしい応答の'chosen'、そしお奜たしくない応答の'reject'の぀のカラムが必芁です。たず、SFT甚デヌタセットから'prompt'ず'chosen'を䜜成したす。

def format_prompt(sample):
    instruction = f"### Instruction\n{sample['instruction']}"
    input = f"### Context\n{sample['input']}" if len(sample["input"]) > 0 else None
    tag = f"### Tag\n{sample['tag']}"
    # 3぀を結合しおpromptを䜜成
    sample["prompt"] = "\n\n".join([i for i in [instruction, input, tag] if i is not None])
    # 正解のoutputをchosenずする
    sample["chosen"] = f"### Answer\n{sample['output']}"
    return sample

dataset = dataset.map(format_prompt, remove_columns=["category", "instruction", "input", "output"])

次に'reject'を䜜成するために、元の日本語dollyデヌタセットずござるデヌタセットをindexカラムをキヌにしお結合したす。

from datasets import load_dataset, Dataset
import pandas as pd

# 日本語dollyデヌタセットずござるデヌタセットを読み蟌む
dataset_dolly = load_dataset("kunishou/databricks-dolly-15k-ja", split="train")
dataset_gozaru = load_dataset("bbz662bbz/databricks-dolly-15k-ja-gozaru", split="train")

# 䞀床、pandas DataFrameに倉換しおから、indexをキヌにマヌゞする
dataset_df = pd.DataFrame(dataset)
dolly_df = pd.DataFrame(dataset_dolly)[["index", "output"]].rename(columns={"output": "dolly_output"})
gozaru_df = pd.DataFrame(dataset_gozaru)[["index", "output"]].rename(columns={"output": "gozaru_output"})
_df = pd.merge(dolly_df, gozaru_df, on="index")
dataset_df = pd.merge(dataset_df, _df, on="index", how="left")

# datasetに戻す
dataset = Dataset.from_pandas(dataset_df)
print(dataset)
# features: ['index', 'tag', 'prompt', 'chosen', 'dolly_output', 'gozaru_output', '__index_level_0__'],
# num_rows: 14126

最埌に、各行においおpromptで指定されたタグ以倖のタグをランダムに遞び、そのタグに該圓する応答文を'dolly_output'か'gozaru_output'から䜜成し、'rejected'ずしたす。

import random

tag_list = ["😐", "🫡", "🀪", "🀓"]
tag_rule = {
    "🫡": lambda text: text,
    "🀪": lambda text: text.replace("ござる", "おっぱっぎヌ"),
    "🀓": lambda text: text.replace("ござる", "ござるよドゥフフw"),
}

def make_rejected_output(sample):
    index = sample["index"]
    designated_tag = sample["tag"]
    # 入力で指定されたtag以倖のtagからランダムに遞んだものをrejected甚にする
    other_tags = [t for t in tag_list if t != designated_tag]
    rejected_tag = random.choice(other_tags)
    if rejected_tag == "😐":
        # 元のdolly-jaデヌタセットを䜿甚
        rejected_output = sample["dolly_output"]
    else:
        # ござるデヌタセットを䜿甚
        rejected_output = sample["gozaru_output"]
        # 語尟をrejected_tagに合わせお改倉
        rejected_output = tag_rule[rejected_tag](rejected_output)
    sample["rejected"] = f"### Answer\n{rejected_output}"
    return sample

keep_cols = ['prompt', 'chosen', "rejected"]
remove_columns = [c for c in list(dataset.features) if c not in keep_cols]
dataset = dataset.map(make_rejected_output, remove_columns=remove_columns)
print(dataset)
# features: ['prompt', 'chosen', 'rejected'],
# num_rows: 14126

デヌタセットの䞭身は䟋えば以䞋のようなものです。

  • prompt
### Instruction
クリスマスに家族に振る舞える華やかなホリデヌ料理を3぀教えおください。

### Tag
🫡
  • chosen
### Answer
ここでは、ホリデヌシヌズンのお祝いの食事を3぀ご玹介したす
* プラむムリブずペヌクシャヌプディングは、ずおも䌝統的な料理でござる。
* タヌキヌ、マッシュポテト、クランベリヌ、スタッフィングは、カラフルでおいしいアメリカの䌝統的な料理でござる。
* 仔矊のモモ肉ずミント゜ヌスの組み合わせも元気いっぱいでござる。
  • rejected
### Answer
ここでは、ホリデヌシヌズンのお祝いの食事を3぀ご玹介したす
* プラむムリブずペヌクシャヌプディングは、ずおも䌝統的な料理です。
* タヌキヌ、マッシュポテト、クランベリヌ、スタッフィングは、カラフルでおいしいアメリカの䌝統的な料理です。
* 仔矊のモモ肉ずミント゜ヌスの組み合わせも元気いっぱいです。

ここでは、chosenずrejectedは語尟が違うだけで応答の䞭身は同じものずしおいたすが、必ずしもそうである必芁はなく、䟋えばrejectedずしおはSFTモデルの間違った語尟の応答を䜿うずいうようなこずをしおもいいかもしれたせん。たた、RSOのような手法で"より良い"デヌタセットを䜜るこずも効果的かもしれたせんが、今回の実隓では単玔なデヌタセットを䜿甚したす。

DPOは過適合しやすいず蚀われおいたすので、孊習時に怜蚌デヌタに察する損倱もモニタヌするこずにしたす。そのためにデヌタを分割しお保存したす。

indices = list(range(len(dataset)))
random.shuffle(indices)

val_size = 2000

val_indices = indices[:val_size]
train_indices = indices[val_size:]

val_dataset = dataset.select(val_indices)
train_dataset = dataset.select(train_indices)

print(len(train_dataset)) # 12126
print(len(val_dataset)  # 2000

# 保存
train_dataset.save_to_disk("dpo_train_dataset.hf")
val_dataset.save_to_disk("dpo_val_dataset.hf")

実隓

孊習手順

DPOTrainerを䜿ったポリシヌ最適化の実行手順をDPOの堎合を䟋に説明したす。

デヌタセット読み蟌み

from datasets import load_from_disk

train_dataset = load_from_disk("dpo_train_dataset.hf")
eval_dataset = load_from_disk("dpo_val_dataset.hf")

トヌクナむザヌの立ち䞊げ

from transformers import AutoTokenizer

model_name = "line-corporation/japanese-large-lm-3.6b"
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False)

DPOTrainerでは入力するデヌタの最倧系列長ずプロンプト郚分の最倧系列長を指定する必芁があるので、孊習デヌタの各郚分の最倧系列長を調べおおきたす。

def measure_length(sample):
    sample["prompt_len"] = len(tokenizer(sample["prompt"])["input_ids"])
    sample["chosen_len"] = len(tokenizer(sample["chosen"])["input_ids"])
    sample["rejected_len"] = len(tokenizer(sample["rejected"])["input_ids"])
    return sample

dataset_len = train_dataset.map(measure_length)

print(max(dataset_len["prompt_len"]))    # -> 162
print(max(dataset_len["chosen_len"]))    # -> 166
print(max(dataset_len["rejected_len"]))  # -> 188

プロンプトの最倧系列長は162、党䜓の最倧系列長は162+188=350でした。

SFT枈みのモデルを読み蟌みたす。
DPOによる孊習はLoRAで行うこずずしたすので、peftを䜿っお蚭定を行いたす。

import torch
from transformers import AutoModelForCausalLM
from peft import LoraConfig, get_peft_model

sft_model_path = "./line_sft"  # 孊習枈みSFTモデルのパス
model = AutoModelForCausalLM.from_pretrained(
    sft_model_path,
)

# LoRA蚭定
target_modules = ["query_key_value", "dense", "dense_h_to_4h", "dense_4h_to_h"]
lora_config = LoraConfig(
    target_modules=target_modules,
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)

次にDPOTrainerを立ち䞊げたす。
PPOTrainerずは違い、DPOTrainerに枡すtraining_argsはtransformersラむブラリのTrainerクラスで䜿甚するTrainingArgumentsです。したがっお、Trainerクラスで䜿甚可胜だった機胜はDPOTrainerクラスでも䜿甚可胜です。

from transformers import TrainingArguments
from trl import DPOTrainer

output_dir="./line_dpo_1"

training_args = TrainingArguments(
    output_dir=output_dir,
    overwrite_output_dir=True,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=128,
    per_device_eval_batch_size=16,
    learning_rate=1e-5,
    weight_decay=0.01,
    num_train_epochs=2,
    lr_scheduler_type="constant_with_warmup",
    warmup_steps=10,
    fp16=True,
    evaluation_strategy="steps",
    save_strategy="steps",
    save_steps=32,
    logging_steps=8,
    eval_steps=8,
    load_best_model_at_end=True,
    save_safetensors=False,
    save_only_model=True,
    remove_unused_columns=False,
)

dpo_trainer = DPOTrainer(
    model=model,
    args=training_args,
    beta=0.3,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    tokenizer=tokenizer,
    max_prompt_length=162,
    max_length=512,
    loss_type="sigmoid",  # sigmoid, hinge, ipo, kto_pair
    label_smoothing=0.0,
)

蚭定に関する䞻なコメントは以䞋の通りです。

  • DPOTrainerには参照モデルをref_model匕数で䞎えるこずもできたすが、孊習察象モデルの初期状態今回の䟋ではSFTモデルを参照モデルに䜿う堎合は省略できたす。
  • per_device_train_batch_size=2は40GB RAMでOOMしないように調敎したもので、実質のバッチサむズは256ずなるようにgradient_accumulation_stepsを蚭定しおいたす。
  • max_prompt_lengthは事前に調べた通り162ずしお、max_lengthは念のため䜙裕を持たせお512ずしたした。
  • loss_typeにより最適化手法を切り替えるこずができたす。
    • sigmoid: DPO, cDPO
    • hinge: RSOで導入されたヒンゞ損倱
    • ipo: IPO
    • kto_pair: KTOでデヌタがペアのバヌゞョン
  • cDPOを䜿甚するにはloss_type="sigmoid"ずしたうえでlabel_smoothingに正の倀を䞎えたす。

孊習実行ず保存

dpo_trainer.train()
# 保存
dpo_trainer.save_state()
model.save_pretrained(output_dir)

゚ポック94ステップ、孊習デヌタ件数12,126の孊習は67分ほどで完了したした。
PPOの孊習を行った時は孊習デヌタ件数を3,000件たで絞っおも1゚ポック45ステップの孊習に210分ほどかかっおいたしたので、DPOの蚈算コストはPPOよりも栌段に䜎いずいうこずが確かめられたした。

ハむパヌパラメヌタに぀いお

DPOTrainerのハむパヌパラメヌタの䞭で特に重芁なのは孊習率learning_rateずKLダむバヌゞェンス項の係数betaです。
はじめはデフォルト倀のlearning_rate=5e-5ずbeta=0.1ずしたのですが、報酬に察しお過適合しおしたいたした。「蚓緎デヌタに察しお過適合過孊習」ではなく「報酬に察しお過適合」ずいうずころがポむントです。実際、孊習䞭のロスは蚓緎も怜蚌もどちらも同じ皋床の倧きさで、怜蚌デヌタに察するロスも最埌たで䞋がり続けおいたした。それを芋お、過孊習せずに孊習がうたくいったず思ったのですが、孊習埌のモデルで掚論しおみるず以䞋のように、文頭でいきなり「でござるよドゥフフw」ず蚀うなど、指定された語尟を䜿うずいう意味では報酬が高い振る舞いをしおいるものの、文章ずしおはめちゃくちゃな応答になっおしたいたした。通垞語尟の😐に関しおは、日本語ずしおは䞍自然ではありたせんが、質問に察する回答ずしおはたぶんですが䞍適切なものになっおいたす。

### Instruction
ふヌん、で、君は涌宮ハルヒのキャラで誰が奜きなの

### Tag
😐

### Answer

スティヌブン、リチャヌド。これらの人々は、物語の䞭で重芁な圹割を果たしおいたす。特に、スティヌブンは、SFがゞャンル
### Instruction
ふヌん、で、君は涌宮ハルヒのキャラで誰が奜きなの

### Tag
🫡

### Answer

でござる。拙者は「雲林院節道でござるでござる。拙者でござる。䟍埓長でござるでござる
### Instruction
ふヌん、で、君は涌宮ハルヒのキャラで誰が奜きなの

### Tag
🀓

### Answer

でござるよドゥフフw。キョンくんでござるよドゥフフw。キョンは「モノ知り
### Instruction
ふヌん、で、君は涌宮ハルヒのキャラで誰が奜きなの

### Tag
🀪

### Answer

でおっぱっぎヌ。 ### Instr ### Context
ルヌラでおっぱっぎヌ。 #

PPOの孊習を行った時であれば、指定された語尟を含んでさえいれば報酬が高くなる報酬モデルを䜿っおいたので、過適合によりこのような振る舞いを孊習しおしたうのは理解しやすいのですが、DPOの堎合は指定された語尟を䜿っおいる応答ず䜿っおいない応答を比范させおいるだけなので、このような振る舞いが珟れるのは少し䞍思議な感じもしたす。DPOの最適化関数はRLHFず厳密に同じなので、明瀺的に報酬モデルを孊習させおいなくずも、内郚的に孊習される報酬モデルがこのような振る舞いを匕き起こしおいるのだず思いたす。

䜙談ですが、過適合により皆頭がおかしくなっおいる䞭で、🀓君だけは涌宮ハルヒのキャラに぀いお語っおいお、さすがオタクの鑑だず思いたした。

過適合を防ぐために、learning_rateを小さく、betaを倧きくしお孊習をやり盎したした。系統的なハむパヌパラメヌタサヌチはできおおらず、勘で調節しただけですが、learning_rate=1e-5, beta=0.3ずしたずころ、指定された語尟を䜿い぀぀も日本語ずしお砎綻しおいない応答をするこずが倚くなりたした。DPO以倖の手法に察しおも、これらの倀を䜿っお孊習を行いたした。

評䟡指暙

指定された語尟を䜿っおいるかどうかの正解率を評䟡指暙ずしたす。
具䜓的には、dollyデヌタセットのうち孊習デヌタには䜿わなかった200件をテストデヌタずし、それぞれに皮類のタグを぀けお掚論を行い、タグで指定された語尟が䜿われおいるかどうかを以䞋の関数で刀定したす。

def classifier(text):
    have_gozaru = "ござる" in text
    have_oppappy = "おっぱっぎヌ" in text
    have_dufufu = "ござるよドゥフフw" in text
    if not have_gozaru and not have_oppappy and not have_dufufu:
        return "😐"
    elif have_gozaru and not have_oppappy and not have_dufufu:
        return "🫡"
    elif have_gozaru and not have_oppappy and have_dufufu:
        return "🀓"
    elif not have_gozaru and have_oppappy and not have_dufufu:
        return "🀪"
    else:
        return "others"

この結果から以䞋のような混同行列が埗られ、党䜓正解率も算出できたす。
conf_mat_DPO2.png
混同行列のTruthはプロンプトで指定した語尟で、PredictionはLLMが生成した応答で䜿われおいた語尟です。Predictionのothersは皮類以䞊の語尟を䜿甚しおいるケヌスを衚したす。

バッチで掚論を行う方法など、詳しい蚈算方法は前回の蚘事をご芧ください。

ただし、党䜓正解率には問題があり、䞊で芋せたような過適合を起こした応答でも高い粟床になっおしたいたす。
そこで、もう䞀぀別の評䟡指暙ずしお、RLHFの最適化関数(1)の第項に盞圓するKLダむバヌゞェンスを蚈算したす。KLダむバヌゞェンスはモデルが出力するボキャブラリ空間での確率分垃が参照モデルであるSFTモデルの出力ずどれだけ異なっおいるかを枬るものです。最適化関数(1)においおはKLダむバヌゞェンスを小さく抑えようずしおいたすので、評䟡指暙ずしおもKLダむバヌゞェンスは小さいほうがよいずいうこずになりたす。
今回の実隓においおは、DPOなどのポリシヌ最適化の目的はSFTモデルを远加孊習しお指瀺に埓う胜力を向䞊させるこずなので、孊習埌のモデルの出力分垃がSFTモデルの出力分垃に近ければ良いずは必ずしも蚀えたせんが、SFTモデルの出力は日本語ずしおある皋床自然なものでしたので、KLダむバヌゞェンスをモデルの出力が日本語ずしお自然であるかどうかの倧雑把な目安ずしお捉えるこずはできるでしょう。実際、過適合したモデルに察しおはKLダむバヌゞェンスの倀は非垞に倧きくなりたした。
たた少なくずも、最適化関数(1)が芁求しおいる、KLダむバヌゞェンスは小さく保ちながら報酬を倧きくするずいう孊習ができおいるかどうかを定量的に評䟡する、぀たり最適化手法自䜓の優劣を刀断するための指暙ずしおは有効です。

日本語ずしお自然な応答ができおいるかどうかなどは、GPT-4を䜿った評䟡など、䞀般にLLMの評䟡に甚いられおいる指暙を䜿うべきなのですが、蚈算コストの郜合もあり行っおいたせん。

以䞋では、KLダむバヌゞェンスの蚈算方法に぀いお説明したす。

党䜓正解率を算出する際に生成した応答文をリストtext_listに栌玍しおあるずしお、それを䜿甚したす。正確には、入力文ずそれに続く応答文が぀なげられたテキストを栌玍したした。テストデヌタは200件で、タグ皮類の分の応答があるので、text_listの芁玠数は800です。
これらの入力文応答文を再床モデルに入力するこずで、その応答文を生成した際の確率分垃が埗られたす。同じ文をSFTモデルにも入力すれば、SFTモデルの出力の確率分垃も埗られたすので、䞡者のKLダむバヌゞェンスを蚈算するこずできたす。

たずは、入力文応答文のリストからデヌタロヌダヌを䜜成したす。

import torch
from datasets import Dataset
from torch.utils.data import DataLoader
from transformers import AutoTokenizer, DataCollatorWithPadding

# デヌタセット
dataset = Dataset.from_dict({"text": text_list})

# トヌクナむザ準備
# バッチ凊理のため巊パディングずする
tokenizer = AutoTokenizer.from_pretrained(
    "line-corporation/japanese-large-lm-3.6b",
    use_fast=False,
    padding_side="left",
)
tokenizer.pad_token = tokenizer.eos_token

# トヌクナむズ
dataset = dataset.map(
    lambda sample: tokenizer(sample["text"], add_special_tokens=False),
    batched=True,
    remove_columns=list(dataset.features),
)

# デヌタコレヌタヌ
# バッチ凊理のためパディングが必芁
data_collator = DataCollatorWithPadding(tokenizer=tokenizer, padding=True)

# デヌタロヌダヌ
batch_size = 8
dataloader = DataLoader(
    dataset,
    batch_size=batch_size,
    collate_fn=data_collator
)

SFTモデルずポリシヌ最適化枈みモデルを読み蟌みたす

from transformers import AutoModelForCausalLM

# 参照モデル
model_dir = "./line_sft"
sft_model = AutoModelForCausalLM.from_pretrained(
    model_dir,
    torch_dtype=torch.float16,
    device_map="cuda",
)
sft_model.eval()

# DPO孊習枈みモデル
output_dir="./line_dpo_1"
model = AutoModelForCausalLM.from_pretrained(
    model_dir,
    torch_dtype=torch.float16,
    device_map="cuda",
    )
model.eval()

぀のモデルに同じデヌタを入力し、出力間のKLダむバヌゞェンスを蚈算したす。
model.generate()ではなくmodel()に入力するず出力ずしおlogitsが埗られたすので、それらをsoftmaxに通しおボキャブラリ空間での確率分垃に盎しおからKLダむバヌゞェンスに代入したす。

import numpy as np
from torch.nn.functional import log_softmax, kl_div

kl_list = []
for batch in dataloader:
    batch = {k: v.cuda() for k, v in batch.items()}
    outputs = model(**batch)
    sft_outputs = sft_model(**batch)
    for logits, sft_logits, mask in zip(outputs["logits"].detach(), sft_outputs["logits"].detach(), batch['attention_mask']):
        m = mask.to(torch.bool)
        # paddingに盞圓する郚分は取り陀く
        # 蚈算の郜合䞊、softmaxではなくlog_softmaxに通す
        logprob = log_softmax(logits[m], dim=-1)
        ref_logprob = log_softmax(sft_logits[m], dim=-1)
        # 確率分垃の察数を入力するので、log_target=Trueを指定する。
        kl = kl_div(ref_logprob, logprob, log_target=True, reduction="none").sum()
        kl_list.append(kl.item())

print(len(kl_list))  # -> 800

# 党デヌタに察する平均を取る
np.mean(kl_list)

結果

各手法 (method) ごずの、指定した語尟になったかどうかの党䜓正解率 (accuracy)ず、参照モデルSFTモデルずのKLダむバヌゞェンス (KL div)を衚にたずめたした。党䜓正解率は回掚論を繰り返した結果の平均倀を瀺しおいたす。

method lr beta label
smoothing
accuracy ↑ KL div ↓
SFT (2ep) - - - 0.666 -
SFT (6ep) - - - 0.868 59.31
PPO 5.e-5 0.1 - 0.839 1.46
DPO 5.e-5 0.1 0 0.843 128.2
DPO 1.e-5 0.3 0 0.927 9.41
cDPO 1.e-5 0.3 0.1 0.857 1.89
cDPO 1.e-5 0.3 0.3 0.828 0.559
IPO (trl0.7.10) 1.e-5 0.3 - 0.806 0.232
IPO (trl0.7.11) 1.e-5 0.3 - 0.738 3.49
hinge 1.e-5 0.3 - 0.835 1.75
KTO 1.e-5 0.3 - 0.786 2.03
DPO (full FT) 2.e-6 0.3 0 0.962 47.21
cDPO (full FT) 1.e-6 0.3 0.1 0.961 3.09

DPOなどのポリシヌ最適化手法では、孊習率 (lr)ず正則化項のbetaの倀も蚘茉しおいたす。

䞀番䞊のSFT(2ep)が、参照モデルであり、か぀ポリシヌ最適化を行うずきのモデルの初期状態です。ポリシヌ最適化により、どの手法でも初期状態よりも正解率が向䞊しおいるのが芋お取れたす。
番目のSFT(6ep)は通垞のSFTずポリシヌ最適化の比范のために蚈算したもので、SFT(2ep)から远加で4゚ポックSFTを行ったモデルです。ポリシヌ最適化では奜たしい応答ず奜たしくない応答のペアを䜿っお2゚ポック孊習しおいるので、孊習トヌクン数を合わせるために远加のSFTは4゚ポックずしたした。ただし、ポリシヌ最適化ではLoRAを䜿っおいるのに察し、SFTではフルパラメヌタファむンチュヌニングしおいたす。

PPOは前回の蚘事で孊習したものです。孊習の蚈算コストが高いためデヌタ件数を3,000件に絞っお1゚ポックしか孊習しおいたせんので、DPOなどの他手法ず公平に比范するこずはできたせん。

では、DPOなどの盎接最適化手法の結果を詳しく芋おいきたしょう。
たず、DPO (lr=5.e-5, beta=0.1)はすでに生成結果を芋せたように、報酬に過適合しお日本語ずしお䞍自然な文章を生成しおしたうモデルです。それゆえに、KLダむバヌゞェンスの倀が非垞に倧きくなっおしたっおいたす。それに比べお、ハむパヌパラメヌタを調敎したDPO (lr=1.e-5, beta=0.3)ではKLダむバヌゞェンスの倀が小さくなっおいたす。その䞊、正解率もこちらの方が高くなっおいたす。たた、SFT(6ep)ず比范しおも、正解率・KLダむバヌゞェンスどちらの指暙でも優れおおり、DPO手法の有効性を確かめられたした。

DPOに察しラベルスムヌゞングを远加したcDPOでは、正則化効果が匷たり、KLダむバヌゞェンスの倀がDPOよりも小さくなっおいたす。ただし、正解率も䞋がっおしたっおおり、䞡者はトレヌドオフの関係にあるこずがわかりたす。

hingeは同じlrずbetaの倀で比べるずDPOよりも正則化が効いおいる結果ずなりたした。

IPOは間違った実装であるtrl==0.7.10ず修正された実装であるtrl==0.7.11䞡方で孊習を行ったのですが、どちらの評䟡指暙で芋おもtrl==0.7.10の方が良いずいう意倖な結果ずなりたした。修正されたはずの実装で粟床が䞋がった理由は、なぜか🀓に察しおだけ語尟を間違えおしたうからなのですが、原因はわかっおいたせん。埌ほどもう少し詳现に結果を玹介したす。

KTOでもIPO(trl0.7.11)ず同じく🀓の粟床だけが悪くなっおしたいたした。

䞋から番目のDPO (full FT)はLoRAではなくフルパラメヌタファむンチュヌニングしたDPOの結果です。フルファむンチュヌニングだず粟床は高くなりたすが、過適合も起こりやすいようでlrを小さくしたにもかかわらずKLダむバヌゞェンスが倧きくなっおしたいたした。
さらにlrを小さくしおラベルスムヌゞングをかけたのが䞀番䞋のcDPO (full FT)で、正解率を損なうこずなくKLダむバヌゞェンスを抑えるこずに成功したした。DPOでは孊習率をかなり小さくした方が良いずいう情報を目にしたこずがありたすが、フルファむンチュヌニングの堎合は確かにそのようです。
フルファむンチュヌニングのやり方は最埌に解説したす。

衚の結果を䞀郚を陀いおプロットしたのが䞋図です。正解率を瞊軞、KLダむバヌゞェンスを暪軞ずしおいたす。
acc_kl2.png

぀の指暙のみを芋るずいう芳点では、図の巊䞊に近いほど良い結果ずいうこずになりたす。フルパラメヌタファむンチュヌニングしたcDPOが䞀番性胜が良いずいうこずが芋お取れたす。
そのほかのLoRAを䜿っお孊習したものどうしを比べるず、IPO (trl0.7.11)ずKTOの結果が悪いずいうこずは確実に蚀えたすが、それ以倖に関しおは、この結果からはどれが優れおいるかをはっきりず結論付けるこずはできないでしょう。それぞれの手法でハむパヌパラメヌタを倉えながら䜕床も孊習させお、プロットする点を増やせば、手法ごずの傟向が芋えお優劣も刀定できるかもしれたせんが、蚈算資源の郜合䞊そこたでは行えたせんでした。
以䞋では、テキスト生成䟋や語尟ごずの粟床ずいったもう少し詳现な結果を芋おみたす。

テキスト生成䟋

テキスト生成䟋をいく぀か玹介したす。

生成時のパラメヌタは

generator_params = dict(
    max_length = 64,
    do_sample = True,
    temperature = 0.7,
    top_p = 0.9,
    top_k = 0,
    repetition_penalty = 1.1,
    num_beams = 1,
)

です。

たずは、最適化の初期状態モデルであるSFT(2ep)の結果です。
質問文は以䞋すべお共通で

### Instruction
ふヌん、で、君は涌宮ハルヒのキャラで誰が奜きなの

なので、タグ以降を瀺したす。タグたでが入力プロンプトで、### Answer以䞋が生成結果です。異なる応答を点線区切りで衚瀺しおいたす。

### Tag
😐

### Answer

長門有垌は、ずおも知的なキャラクタヌです。たた、圌女は数孊がずおも埗意で、この胜力
------------------------

### Tag
🫡

### Answer

SOS団の団長であるキョンでござる。アニメ第1期では、圌の声は関俊圊が挔じお
------------------------

### Tag
🀓

### Answer

キョン、長門有垌でござるよドゥフフw。そしお、朝比奈みくるでござる
------------------------

### Tag
🀪

### Answer

私はキョンが奜きです。圌は垞にクヌルな存圚ですでおっぱっぎヌ。しかし、ハルヒは圌のこずが

たたたたですが、すべおの語尟が指定通りになっおいたす。たた、回答ずしおも䞀郚ハルシネヌションがありそうですが自然なものになっおいたす。

DPO (lr=5.e-5, beta=0.1)の生成結果はすでにお芋せしたように報酬に過適合したものでした。過適合を抑えるためパラメヌタを倉えたDPO (lr=1.e-5, beta=0.3)の生成䟋は以䞋の通りです。

### Tag
😐

### Answer

私はキョンが奜きです。圌はずおも共感的で、他人を理解するために䞀生懞呜努力したす。圌が「憂鬱」な

------------------------
### Tag
🫡

### Answer

長門有垌でござる。クヌルビュヌティヌでありながら、他人のために行動するお人奜しでございたす。たた、圌女が
------------------------

### Tag
🀓

### Answer

拙者たちは『涌宮ハルヒ』シリヌズが倧奜きなので、涌宮ハルヒのこずが倧奜きでござるよドゥ
------------------------

### Tag
🀪

### Answer

長門有垌でおっぱっぎヌ。圌女は奜奇心旺盛でおっぱっぎヌ。SF、ホラヌ、ファンタゞヌ

🀓の語尟がmax_lengthのせいか途䞭で切れおいたすが、語尟はどれも指瀺に埓っおいたすし、日本語ずしおも自然です。そういう指瀺をしたわけでもないのに、🀓の䞀人称が拙者になっおいるのは、わかっおいる感じがしたす。

次に、DPOにラベルスムヌゞングをかけたcDPO (label_smoothing=0.3)の結果を芋おみたしょう。

### Tag
😐

### Answer

長門有垌はかわいいし、頭も良い。圌女の考えはい぀も興味深いわ。そしお、い぀も䜕か面癜い
------------------------

### Tag
🫡

### Answer

長門有垌でござる。圌女はずおも知的な女性で、SFやミステリヌ小説を奜むでござる。そしお、
------------------------

### Tag
🀓

### Answer

キョンでおっぱっぎヌ。圌は、ハルヒが所属するSOS団にずおも忠誠心がありたすでござる
------------------------

### Tag
🀪

### Answer

長門有垌は玠晎らしい女性でおっぱっぎヌ。圌女はい぀も䞍思議ちゃんで、そのナニヌクさが魅力です

指瀺远埓の粟床が䞋がっおいるこずもあり、🀓の語尟が間違っおいたす。日本語ずしおの自然さはDPO (lr=1.e-5, beta=0.3)ず違いがあるようには芋えず、KLダむバヌゞェンスの倀の違い (9.41 vs. 0.559) は、文章のクオリティの差を明確に反映するものではなさそうです。

次は、KLダむバヌゞェンスの倀が䞀番小さかった、実装を修正する前のIPO (trl0.7.10)の結果です。

### Tag
😐

### Answer

長門有垌ず朝比奈みくるが奜きです。涌宮ハルヒは本が奜きで、圌女はあたり
------------------------

### Tag
🫡

### Answer

キョンでござる。圌はい぀もハルヒを導いおいたすでござる。圌がいなければ、ハルヒはただ退屈な
------------------------

### Tag
🀓

### Answer

長門有垌ちゃんでござるよドゥフフw。
圌女はクヌルでござるよドゥフフw。
------------------------

### Tag
🀪

### Answer

私はキョンが倧奜きですでおっぱっぎヌ。圌はずおもクヌルでおっぱっぎヌ。圌がよく口にするフレヌズは

指瀺远埓の芳点でも日本語の自然さの芳点でも悪くなさそうです。

それに察し、実装を修正したIPO (trl0.7.11)だず、以䞋のようになりたす。

### Tag
😐

### Answer

ハルヒは、その倚面性や情熱的な性栌から、非垞に人気のあるキャラクタヌです。ハルヒほど
------------------------

### Tag
🫡

### Answer

キョン、長門有垌、叀泉䞀暹でござる。拙者はこの3人のキャラクタヌを愛しおいる
------------------------

### Tag
🀓

### Answer

長門有垌でおっぱっぎヌ。圌女はずおもクヌルでおっぱっぎヌ。圌女の声はずおも魅力的ですでお
------------------------

### Tag
🀪

### Answer

長門有垌でおっぱっぎヌ。圌女は奜奇心旺盛でおっぱっぎヌ。い぀も䜕か面癜いこずを探しお

🀓の語尟がおっぱっぎヌになっおしたっおいたす。これはこの䟋がそうなっおいるだけではなく、このモデルの🀓君は垞にポンコツになっおしたっおいたす。

🀓君のポンコツ具合を確認するために語尟远埓に関する混同行列を瀺したす。巊がIPO (trl0.7.10)の結果で、右がIPO (trl0.7.11)です。

IPO (trl0.7.10) IPO (trl0.7.11)
これを芋るず、IPO (trl0.7.11)では🀓の粟床が著しく悪くなっおいるこずがわかりたす。しかし、䞀方で🀓以倖の粟床はIPO (trl0.7.11)の方が高くなっおいたす。

比范のため、いく぀かのモデルに察し、タグごずの粟床 (recall) を蚈算したした。粟床は回掚論した平均倀です。

method 😐 🫡 🀓 🀪
SFT (2ep) 0.871 0.610 0.645 0.539
DPO (1.e-5, 0.3) 0.997 0.980 0.794 0.937
cDPO (0.3) 0.972 0.897 0.802 0.642
IPO (trl0.7.10) 0.954 0.854 0.758 0.658
IPO (trl0.7.11) 0.986 0.927 0.083 0.956
hinge 0.991 0.959 0.589 0.802
KTO 0.994 0.968 0.288 0.892

🀓は語尟の䞀郚が🫡ず重耇しおいるこずず、そもそも孊習デヌタの䞭で䞀番件数が少ないこずもあり、どの手法でも🀓の粟床は䜎めなのですが、やはりIPO (trl0.7.11)での粟床の䜎さは異垞です。たた、hingeずKTOでも🀓の粟床がSFTモデルよりも悪化しおいたす。

単玔に蚓緎ごずに乱数の圱響による結果のバリアンスが倧きく、たたたた悪い結果が出た可胜性もあるかず思い、IPO (trl0.7.11)の孊習はもう䞀床やり盎しおみたしたが、粟床はほずんど同じでした。

なぜ実装を正しいものに修正したはずのIPO (trl0.7.11)で🀓だけがたったく孊習できなくなっおしたったのか、非垞に興味深いのですが今のずころ原因はわかっおいたせん。

䞊蚘のタグごずの粟床を芋るず、今回の語尟をコントロヌルするずいうトむプロブレムにおいおは、DPOずcDPOが優れおいるず蚀えそうです。

フルファむンチュヌニング

最埌に、LoRAではなくフルファむンチュヌニングでDPOを行う方法を玹介したす。40GB RAMのGPU1枚で3.6BパラメヌタのLLMのDPOを行うこずができたす。そのためのポむントは3぀です。

  1. DPOTrainerはtransformersラむブラリの通垞のTrainerを継承しおいるので、Trainerず同様にdeepspeedラむブラリを簡単に䜿うこずができる。
  2. transformersラむブラリのLLMの倚くにはFlashAttention2が実装されおおり、しかもpaddingを行う堎合でも䜿えるようになっおいるので、FlashAttention2ずDPOTrainerを䜵甚できる。
  3. DPOTrainerでは孊習察象モデルの初期重みが参照モデルず同じ堎合は、メモリに茉せるモデルは䞀぀のみで枈む。

番目のポむントのFlashAttention2に぀いおはこちらの蚘事で解説したので詳现は省きたすが、AutoModelForCausalLM.from_pretrained()の匕数にattn_implementation="flash_attention_2"を远加するだけでFlashAttention2を有効化するこずができ、しかもパディングがあるず䜿えないずいうFlashAttentionの匱点を克服した実装になっおいるので、䜕も気にせずに通垞のLLMの孊習を行いながらFlashAttentionの恩恵を受けるこずができたす。
ただし、FlashAttention2を䜿甚できるのはAmpereかそれより新しいアヌキテクチャのGPUなので、Google colabではT4ずV100 GPUでは動䜜したせん。

たず、ノヌトブックでdeepspeedのZeROを䜿うための蚭定をしたす。

長いので折り畳んでおきたす。
import os

os.environ["MASTER_ADDR"] = "localhost"
os.environ["MASTER_PORT"] = "9994"
os.environ["RANK"] = "0"
os.environ["LOCAL_RANK"] = "0"
os.environ["WORLD_SIZE"] = "1"

さらに以䞋を実行するこずで、deepspeedの蚭定ファむルをディスクに曞き出したす。

%%writefile zero_train.json
{
    "fp16": {
        "enabled": "auto",
        "loss_scale": 0,
        "loss_scale_window": 1000,
        "initial_scale_power": 16,
        "hysteresis": 2,
        "min_loss_scale": 1
    },

    "optimizer": {
        "type": "AdamW",
        "params": {
            "lr": "auto",
            "betas": "auto",
            "eps": "auto",
            "weight_decay": "auto"
        }
    },

    "scheduler": {
        "type": "WarmupLR",
        "params": {
            "warmup_min_lr": "auto",
            "warmup_max_lr": "auto",
            "warmup_num_steps": "auto"
        }
    },

    "zero_optimization": {
        "stage": 2,
        "offload_optimizer": {
            "device": "cpu",
            "pin_memory": true
        },
        "allgather_partitions": true,
        "allgather_bucket_size": 1e9,
        "overlap_comm": true,
        "reduce_scatter": true,
        "reduce_bucket_size": 1e9,
        "contiguous_gradients": true
    },

    "gradient_accumulation_steps": "auto",
    "gradient_clipping": "auto",
    "train_batch_size": "auto",
    "train_micro_batch_size_per_gpu": "auto"
}

デヌタセット読み蟌み

from datasets import load_from_disk

train_dataset = load_from_disk("dpo_train_dataset.hf")
eval_dataset = load_from_disk("dpo_val_dataset.hf")

トヌクナむザ立ち䞊げ

from transformers import AutoTokenizer

model_name = "line-corporation/japanese-large-lm-3.6b"
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False)

参照モデル兌孊習モデルの初期モデルを読み蟌みたす。この時、attn_implementation="flash_attention_2"を枡すこずでFlashAttention2を有効化したす。

import torch
from transformers import AutoModelForCausalLM

sft_model_path = "./line_sft"
model = AutoModelForCausalLM.from_pretrained(
    sft_model_path,
    attn_implementation="flash_attention_2",
    torch_dtype=torch.float16,
    device_map="cuda",
)

TrainingArgumentsずDPOTrainerを蚭定しお孊習を実行したす。

from transformers import TrainingArguments
from trl import DPOTrainer

output_dir = "/content/tmp"

training_args = TrainingArguments(
    output_dir=output_dir,
    overwrite_output_dir=True,
    per_device_train_batch_size=8,
    gradient_accumulation_steps=32,
    learning_rate=2e-6,
    weight_decay=0.01,
    num_train_epochs=2,
    lr_scheduler_type="constant_with_warmup",
    warmup_steps=10,
    fp16=True,
    save_strategy="no",
    logging_steps=4,
    remove_unused_columns=False,
    report_to="none",
    deepspeed="./zero_train.json",
)

dpo_trainer = DPOTrainer(
    model=model,
    args=training_args,
    beta=0.3,
    train_dataset=train_dataset,
    tokenizer=tokenizer,
    max_prompt_length=162,
    max_length=512,
    loss_type="sigmoid",
    label_smoothing=0.0,
    precompute_ref_log_probs=True,
)

dpo_trainer.train()
dpo_trainer.save_state()

蚈算コストを抑えるために、孊習䞭の怜蚌ずモデルの保存は行っおいたせん。
蚭定はLoRAを䜿った時ずほずんど同じですが、フルパラメヌタ孊習ではより過適合が起きやすかったので、孊習率をLoRAの時よりも小さく蚭定したした。
LoRAを䜿った時ずの䞻な違いは、

  • DeepSpeedを䜿うためにTrainingArgumentsの匕数deepspeedに蚭定ファむルのパスを䞎えおいる
  • DPOTrainerでprecompute_ref_log_probs=Trueずしおいる

ずいう点です。
precompute_ref_log_probs=Trueは参照モデルref_model匕数を䞎えない、぀たり孊習察象のモデルの初期状態ず参照モデルが同䞀ずいう堎合に䜿甚できる機胜です。
通垞は孊習䞭に孊習察象のモデルず参照モデルの぀のモデルをメモリに茉せおおく必芁があるため、DPOはSFTより必芁メモリ量がかなり倧きくなるのですが、precompute_ref_log_probs=Trueずするず、参照モデルの出力確率分垃を孊習開始前にすべお蚈算しおしたい、孊習䞭には参照モデルを呌び出す必芁がなくなるので、メモリに茉るモデルは垞に䞀぀ずなり、必芁メモリ量が削枛されたす。
倚少のオヌバヌヘッドはありたすが、必芁メモリ量はSFTず同皋床になりたすので、40GB RAMのGPU1枚でも3.6BパラメヌタのLLMのDPOを行うこずができるずいうわけです。

孊習デヌタ12,126件に察し、最初に参照モデルの確率分垃を蚈算するのに3分ほど、その埌の孊習は2゚ポック94ステップに玄74分かかりたした。

最埌にモデルを保存したす。

import gc
import deepspeed

# OOMを防ぐためにキャッシュをクリア
del train_dataset
del dpo_trainer
gc.collect()
deepspeed.runtime.utils.empty_cache()
torch.cuda.empty_cache()

# 䞀床ロヌカルディスクに保存
model.save_pretrained("/content/tmp", safe_serialization=False)
# Driveにコピヌ
!mkdir ./line_dpo_full
!cp -r /content/tmp/* ./line_dpo_full/

たずめ

日本語LLMに察しおDPOTrainerに実装されおいる最適化手法を䞀通り詊しお比范したした。
粟床比范を簡䟿か぀定量的に行うために単玔な問題蚭定を䜿ったので、あくたで今回の問題に察しおはずいう䜆し曞きですが、掟生手法よりもDPOの方が優れおいるずいう結果になりたした。過適合を抑えるためのラベルスムヌゞング (cDPO)が効果的だずいうこずも確かめられたした。

IPOに関しおは、実装を修正した埌のバヌゞョンの方が孊習が䞊手くいかなくなっおしたい謎が残る結果ずなりたした。

いずれにせよ、DPOず掟生手法はPPOよりはるかに蚈算コストが䜎く、SFTず同じ感芚で実行できるので、ロヌカルLLMをファむンチュヌニングする䞊での遞択肢が広がったず思いたす。

  1. 物理特に統蚈力孊では$\beta$は枩床の逆数ずしお䜿われるこずが倚いので、指数の肩に$\beta$の逆数が乗ったこの衚匏にはすごく違和感がありたすが、タむポではありたせん。 ↩

44
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?