[Julia]よもやま
1, 関数か値か
using Flux
. . .
function score_matching_loss(chain,points)
N = size(points)[1]
f_val = x -> chain([x])[1]
for i in 1:N
. . .
end
end
for epoch in 1:n_epochs
loss = score_matching_loss(generator,data)
grads = gradient(() -> loss, ps)[1]
end
このコードはエラーを吐く:
"Only reference types can be differentiated with `Params`."
ピザうま夫(以下ピ): コードのにどのような誤りがあると推測されますか.
ChatGPT: エラーメッセージは、Params に関連する問題があることを示しています。この問題は、gradient 関数の呼び出し方に関連しています。gradient(() -> loss, ps) ではなく、gradient(() -> score_matching_loss(generator, data), ps) を使用する必要があります。また、[1] を削除して、grads にすべての勾配を含めます。
using Flux
. . .
function score_matching_loss(chain,points)
N = size(points)[1]
f_val = x -> chain([x])[1]
for i in 1:N
. . .
end
end
for epoch in 1:n_epochs
grads = gradient(() -> score_matching_loss(generator,data), ps)[1]
end
このコードでは該当のエラーは消える。
Flux.gradientは関数型を第一引数として、第二引数の変数について、その値における微分係数を与える。最初の例ではloss=の右辺でscore_matching_loss関数を呼び出した時点でlossは値となるので、() -> lossは関数型ではない。後の例では() -> score_matching_lossは関数型であるので適切。ChatGPT様曰く、
ChatGPT:
最初のコードでは、gradient(() -> loss, ps)としていましたが、loss はすでに数値であり、これは gradient 関数の引数として適切ではありません。gradient 関数は、損失関数を計算する無名関数を最初の引数として受け取り、パラメータpsとともに勾配を計算します。
はーむずかし。
2. 自動微分gradientの仕様
上のコードからは以下のエラーが出た。
Need an adjoint for constructor Zygote.OneElement{Float32,
1, Tuple{Int64}, Tuple{Base.OneTo{Int64}}}. Gradient is of type Vector{Float32}
juliaのドキュメントを眺めててもよくわからん。いろいろ試行錯誤した末にChatGPTに以下のように聞いてみた:
ピ:
前のコードのことは考慮せず,JuliaのZygote.gradientが"Need an adjoint for constructor"というエラーを出す時に考えられるもっともらしい原因を挙げてください.
ChatGPT様:
. . .
3. 配列の要素に対するインデックス操作を行っている場合、Zygoteはその操作に対する逆伝播ルールが定義されていないことがあります。この場合、配列操作に対応する逆伝播ルールを定義するか、または配列操作を回避する方法でコードを書き換えることで、エラーを解決できます。
. . .
なんだっそら!
ピ:
詳しく解説してください.
ChatGPT:
3番目の原因について詳しく説明します。Zygoteは、自動微分の際に、配列の要素に対するインデックス操作に対応する逆伝播ルールが定義されていない場合にエラーを発生させることがあります。これは、Zygoteが配列の要素に対するインデックス操作を処理する際に、勾配の計算方法が不明であるためです。
. . .
つまり関数内で配列の特定要素をインデックスで参照する操作には自動微分が未定義と。これ絶対引っかかるやろ。で、対策は?と聞くと
ChatGPT:
1. 配列操作に対応する逆伝播ルールを定義する: この方法では、配列の要素に対するインデックス操作に対応する逆伝播ルール(adjoint)を定義します。これにより、Zygoteが勾配の計算方法を理解できるようになります。
ん~難しそう(まだjuliaビギナーなので)。もうちょっと簡単な方法はないかね。
ピ:
Flux.Chainの出力は配列で,その第一成分が数値です.この数値の関数にZygoteの自動微分を適用しようとした場合に,インデックス操作を露わに用いない実装は可能でしょうか?
ChatGPT:
はい、Flux.Chainの出力を数値に変換せずに、Zygoteの自動微分を適用する方法があります。その方法は、sum関数を使用して、配列の第一成分を取得せずに配列全体を扱います。これにより、インデックス操作を明示的に使用せずに、Zygoteが適切に自動微分を行えます。
なんだっそら!
つまり、Flux.Chainの出力は数値ではなくタプルであるが、その形状は(数値, Null)という2成分タプルなのでsum関数をかませば結局成分が得られるというそうな。
ん-配列のインデックス操作に自動微分を定義できるならそっちの方が結局助けになりそうだけどね(難しい)。