a/bで表す傾き、「微分」
安原祐二氏:Unityの安原です。今回は「微分」についてお話ししていきたいと思います。
微分、どうですか? なんか難しそうだなと思うかもしれません。でもちょっとかっこいいなとも思うかもしれません。今回は、まず微分の数学的なところを簡単に説明をしてから、プログラムでどう使うのかを話していきたいと思います。
ではまず、微分の説明からいってみましょう。こんなグラフがあったとしましょう。これはy=f(x)というグラフです。ある点に注目してみましょう。(スライドを示して)ここが例えば4だったとすると、ここはf(4)になりますよね。これは難しくないと思うんですが、この点の傾きに注目するのが微分です。傾きをちょっと考えてみましょう。
傾きと聞いて角度で表すのかなと思ったら、それは間違いです。傾きはこういう角度では表さないんですね。
ではどうやって表すか。a/bで表すのが傾きになります。今の場合、傾きの線が右に上がっていますよね。この場合、a/bはプラスになります。0より大きい。右に下がっている場合は、a/bはマイナスの値を取ることになります。水平だったら傾きは0です。この3パターンを理解するのはとても大事です。
こういうふうに垂直になっているのは数学では考えません。aが0になっているので、これだと0で割っていることになります。これは数学では考えないんですね。
a/bはどうやって出すのか? 微分の考え方
というわけでこの傾き、a/bをどうやって出そうかな。これはなかなかわからない。どうしたらいいのかわからない。こんな時にはやり方があります。ちょっと離れた点を考えます。4を止めてxにしましょう。この点をx+h。hだけズラすわけですね。するとここはf(x+h)になります。
まだこの黄色の傾きはわかりませんが、この2点を通る傾きならわかるじゃないですか。だって、これ分のこれですからね。
じゃあ「これ分のこれ」を計算してみましょう。ここはhなので、これ引くこれ(※f(x+h)−f(x)/h)。とりあえずはこれがこの白い線の傾きになります。
この時にこのhをグーッと小さくしていって、すごく小さければこの黄色い線と同じになるでしょうというのが微分の考え方になるわけです。
ここで新しい記号を数学的には導入しました。hが0に行くよ。limと言うんですが、この記号を使って表現された式が、この傾きということになるわけです。
まだピンと来ないかもしれません。ちょっと具体的な例で考えてみましょう。f(x)=x^2(※xの2乗)というシンプルな関数で考えた時にこれを計算してみましょう。
はい、こうなります。分母にhがある時に、0に上っていく計算はなかなかできませんが、計算していくと分母のほうのhが消えていくんですね。このhは0に持って行けるので、答えは2xになります。
例えばf(x)=x^2(※xの2乗)の関数のxが4の時の傾きは8。f(x)=x^2(※xの2乗)の関数のxが20の時の傾きは40という感じになるわけです。
さまざまな導関数
ここで書き方があります。'(ダッシュ)を付けるんです。プライムと言うのですがf'(x)、これはxを微分するとも言って、導関数という言い方をします。つまり、f(x)=x^2(※xの2乗)の導関数は2x。微分すると2xという言い方もできます。
どんな関数もこのやり方をすれば導関数は出ます。いろいろあるので見てみましょう。x^2(※xの2乗)は2xでした。微分すると2xになるんだよという話。これを導関数と呼びました。x^3(※xの3乗)は3x^2(※3xの2乗)になるんですよね。実はこれには法則性があって、x^n(※xのn乗)だったらそのnを前に持ってきて、次数を1つ下げるんですね。3乗だったら2乗、2乗だったら1乗。
では123みたいな定数だったらどうなるのか。どのxのポイントも123だから、これはグラフでいうと水平線になります。これは常に傾きが0だから、微分すると0になります。xは、45度の傾きの線ですね。どの点を取っても傾きは1になります。
これがおもしろいんですが、三角関数のsin(x)を微分するとcos(x)になるんですね。じゃあcos(x)を微分するとどうなるかというと、−sin(x)になるというおもしろい性質があります。三角関数はおもしろいんですが、特に微分する時のこの挙動が非常におもしろいと僕は思っています。ここまでは学校で習うような話でした。
プログラミングによる導関数の実装
プログラムで理解するとよりわかるので、ちょっとこんなデモを見てみましょう。これはSharpLabというもので、サイトを開くといきなりこうなっているんですが、C#の実行環境です。Unityでもよかったんですが、これがお手軽なのでやってみましょう。SharpLabは書いているそばからコンパイルされて、すぐに結果が出るというすごく便利なサイトです。
まずいらないところは削って、Console.WriteLineで適当な数を出してみましょうか。100とかにすると、いきなりコンパイルされて横に100が出ていますね。便利ですね。
func()で、xを引数とする関数を作ってみましょう。xの2乗を返す。それでこの関数を呼んでみましょう。float y = func(3f)で3にしてみましょうか。3を入れると、答えはどうなるかというと3×3が9、2乗が出ていますよね。9が出るということになります。いいですね。
ではここから導関数を実装してみましょう。導関数の実装はどうするか。導関数は英語でderivativeと言います。derivativeで引数xを取って、ここに何を書けばいいでしょう。上で定義をしたfunc()を呼びます。その時、func(x+h)で呼びます。hを上で定義したほうがいいですね。先に定義しておきましょう。小さな値の0.1にしておきましょう。
このとおりに実装します。func(x+h)からfunc(x)を引いてその全体をhで割る。これで完成です。この導関数を呼んでみましょう。どうなるでしょうか。ここにまた3を入れてみましょうか。いったいいくつが出るでしょうか。先ほどx^2(※xの2乗)の導関数で2xとやりましたね。ということは、3を入れたら何が出るかというと、6なんですね。6に近い値が出ました。プログラム中には、2xとは書いていないですね。だけどこのようにきちんと定義どおりに書くと、6に近い値が出ます。
ではこの0.1をより小さくしたらどうなるのかやってみましょう。0.01、すると答えはより6に近づいてきました。0.001にしたらさらに6に近づきました。
つまりこのhは、“精度に影響する”という言い方ができると思います。もちろん浮動小数の限界があるので、いくらでも精度を上げられるわけではありませんが、こういう挙動を示すということは理解しておいたほうが良いでしょう。
微分がゲーム制作で使われるタイミングとは?
プログラムでやってより理解が深まったと思いますが、実はゲームを作ったり、プログラムを組む中で、関数が与えられてその導関数を作ることはあまりないんですよね。
実は微分はそういう使われ方はあまりありません。じゃあどう使われるかというと、例えばこんな例です。ある地点の傾斜を知りたい。デコボコした地形があって、その地形は関数で与えられているわけではないので導関数を作れません。だけど、傾斜が知りたい時があると思うんですよ。プレイヤーの立っているところはどのくらい傾いているのか。
どの地点も高さがわかっているという条件があるなら、みなさんやり方が思いつくんじゃないでしょうか。ある地点の高さがわかっていて、ちょっと離れたところの高さを調べる。そこを引き算すれば傾きが出そうじゃないですか。これはみなさんも思いつくと思うんですよね。差を取るだけで傾きが出るし、ゲームもたぶん完成するでしょう。
だけど、hで割るという微分の知識があれば、精度だけに影響するということがわかるので、後で調整できるんですよね。こういうことを知っておくと便利だと思います。x軸の傾きが出たので、z方向にも傾きが取れますよね。結果的に2つの値が傾きのベクトルになるでしょう。
今、偏微分の記号が入っていて、これはgradientという名前が付いたりもします。難しそうに見えて、実はそんなに大したことじゃないんですね。
どんな時に使えるか。例えばパーリンノイズで作られたノイズが流れていく、落ちていく時、gradientを使って傾きの力をかけてあげればこんなふうに流れが作れます。
そうすると、水のエフェクトを作ったとしても、こういう窓ガラスをつたって流れる感じのエフェクトがよりリアルになっていくんじゃないでしょうか。
というわけで、プログラムで使う時は実はすごく難しいことを知らなくても普通にやっちゃうことなんですね。ですが、たまにこういう勉強をしておくことでわかることはあります。ちょっとそんな例を見てみましょう。
Unityシェーダーグラフのノイズ関数から見る微分
Unityのシェーダーグラフです。いろいろなノイズ関数をここに出していますが、ノイズ関数の中は実装を見ることができます。見てみるとこうなっています。そんなに長いプログラムではありません。これが全体です。上から1、2、3行目を見てみると、これはいったい何だろう。何をしているんだろう。初見でこれがわかる方は、なかなか少ないんじゃないかなと思うので、ちょっとこれを解明してみましょう。
こんなグラフがあったとしましょう。f(x)=xという単純な、常に傾きが1のグラフです。当然(1,1)のポイントを通ります。
ここが角ばっているのが嫌。(スライドを示して)ここが角ばっているのが嫌。何を言っているかというと、始まりを水平にしたい。終わりも水平にしたい。
こんな関数で置き換えたら滑らかになるんじゃないかなと思ったとしましょう。
この関数をg(x)にしましょう。このg(x)の正体、どんな関数かを考えてみましょう。3点を通るので、3つ条件が簡単に出ます。(0,0)を通るし(1,1)を通るし、(1/2,1/2)を通ると。
次に導関数。0のところの傾きを0にしたい。1のところの傾きも0にしたいというわけで、5つの条件があることになります。5つあるので変数は5つまで大丈夫で、仮にg(x)をこう過程してみましょう。
a、b、c、d、eの5つがわからない数字として、x^4(※xの4乗)の係数にaが入っているという関数を考えてみましょう。
そうするとこれで導関数がわかりますね。あとはこの2つの式にこれ(5つの変数)を全部突っ込んで、連立方程式を解けばa、b、c、d、eがわかります。そうすると結果はこうなります。bとc以外は0ですが、結果としてg(x)というのはこういうかたちをしています。これが答えということになります。
グラフで見るとこんな感じですね。これがf(x)=−2x^3+3x^2(※2xの2乗+3xの2乗)のグラフになります。
では先ほどのプログラムにもう1度戻ってみましょう。これはこの関数そのものだったんですね。このプログラムではこんなノイズが出ていました。
では仮にこの3行目をコメントアウトしたらどうなっちゃうのかな。やってみるとこんなふうになるんですね。ガクガクしてあまりきれいじゃないノイズが出ている気がしませんか?
あまりきれいじゃないものを滑らかにする効果がここにあったわけですね。つまり0と1の値で導関数を使って傾きが0になるように、調整した関数に置き換えていたということになります。こんなふうに微分を使って、こういう関数を導くことができますよという話でした。
いかがでしたか。微分の話はここまでです。そんなに難しい話じゃなかったと思います。なんか使い方がわかってきたような気がしませんか? ぜひみなさんUnityでプログラムを組む時に、微分の考え方を使ってすてきなアプリケーションを作ってください。以上です。