AI&Julia #08|最適化理論 » 非線形方程式 » 二分法, ニュートン法 » 平方根
はじめに
非線形方程式を数値的に解くための代表的な手法として「二分法(bisection)」 と 「ニュートン・ラフソン法(Newton-Raphson)」があります。本稿ではこの 2つの手法を使って平方根を求める方程式を解きます。これ以降、ニュートン・ラフソン法についてはニュートン法と略します。
平方根を求める方程式
ある数
知りたいのは
計算例
中間値の定理
関数
計算例
この結果より、区間
二分法
二分法の基本的なアイデアは、中間値の定理に基づいて解が存在する区間を半分ずつ狭めていくというものです。

図 1 二分法で解(√2)を求める
初期区間の設定
初期区間
中点の計算
区間の中点を求める方法は直感的に分かる通りです。この中点
区間の更新
収束判定
収束を判定するには許容誤差
計算例
図 1では式
ニュートン法
ニュートン法の基本的なアイデアは、関数の接線と

図 2 ニュートン法で解(√2)を求める
ニュートン法の漸化式を導出
点
この接線が
これを
平方根の漸化式を導出
今回はニュートン法を使って平方根を求めたいのでした。まずは、平方根の関数とその導関数を用意します。
式
これをさらに変形すると平方根の漸化式が導出されます。
これは結果的にはバビロニア法(古代バビロニアにおける平方根の計算方法)として知られている式です。
平方根の漸化式に初期値を設定
平方根の漸化式の初期値
図 2では
収束判定
収束を判定するには許容誤差
計算例
図 2の
実装例
二分法とニュートン法の 2つの手法について、平方根の数値計算を実装します。両者とも反復計算を行いますが、その収束速度は大きく異なるので、これを視覚的に確認できるようにします。また、計算結果(近似解)については、高精度解と比較することにより精度を把握できるようにします。
using CairoMakie, Printf
# 平方根(二分法)
function sqrt_bisect(a, ε; maxiter=60)
f(x) = x ^ 2 - a # 平方根の関数(区間の設定で使用)
lo, hi = 0.0, max(1.0, a) # 区間の初期値
xs = Float64[] # 途中経過(中点)の記録用
for i in 1:maxiter
x = (lo + hi) / 2 # 区間の中点を求める
push!(xs, x) # 中点を記録
abs(hi - lo) < ε && break # 許容誤差内であれば早期打ち切りへ
f(x) > 0 ? hi = x : lo = x # 区間を狭めて再試行へ
end
xs
end
# 平方根(ニュートン法)
function sqrt_newton(a, ε; maxiter=20)
f(x) = 0.5 * (x + a / x) # 平方根の漸化式
x = a > 1 ? a/2 : 1.0 # 漸化式の初期値
xs = Float64[x] # 途中経過(反復値)の記録用
for i in 1:maxiter
x_new = f(x) # 平方根の反復計算
push!(xs, x_new) # 反復値を記録
abs(x_new - x) < ε && break # 許容誤差内であれば早期打ち切りへ
x = x_new # 次の反復計算へ
end
xs
end
# 二分法とニュートン法の収束を可視化
function vis_convergence(vals_bisect, vals_newton, val_true)
errs_bisect = abs.(vals_bisect .- val_true) # 誤差(二分法)
errs_newton = abs.(vals_newton .- val_true) # 誤差(ニュートン法)
fig = Figure()
ax = Axis(fig[1,1], yscale=log10, xlabel="Iteration", ylabel="Error")
scatterlines!(ax, errs_bisect, color=:blue, marker=:circle, label="Bisection")
scatterlines!(ax, errs_newton, color=:red, marker=:diamond, label="Newton")
axislegend(ax)
display(fig)
fig
end
# 平方根の近似解と高精度解をテキストで表示
function show_convergence(vals, val_true, a, title)
val = vals[end] # 近似解を取得
err = abs(val - val_true) # 誤差を計算
acc = (1 - err/val_true) * 100 # 精度を計算
println("$title\n", "-"^40)
@printf "被開平数 : %s\n" a
@printf "反復回数 : %d\n" length(vals)
@printf "平方根‐近似解 : %.17f\n" val
@printf "平方根‐高精度解 : %.77f\n" val_true
@printf "誤差 : %.17f\n" err
@printf "精度 : %.17f%%\n\n" acc
end
実行例
初期設定
2の平方根を求めたいので、被開平数には 2を設定します。
a = 2 # 被開平数
ε = 1e-16 # 許容誤差
val_true = sqrt(big(a)) # 平方根(高精度解)
二分法とニュートン法の収束分析
二分法とニュートン法を用いて近似解を計算しますが、先に収束の様子を観察します。
vals_bisect = sqrt_bisect(a, ε) # 平方根(二分法)
vals_newton = sqrt_newton(a, ε) # 平方根(ニュートン法)
fig = vis_convergence(vals_bisect, vals_newton, val_true) # 収束の比較
save("08-conver.png", fig)

図 3 収束分析(√2)
ニュートン法は二分法と比べて少ない反復回数で急速に収束しているのが分かります。
二分法とニュートン法の近似解
二分法とニュートン法の近似解を比較します。また、近似解と高精度解も比較してみます。
show_convergence(vals_bisect, val_true, a, "二分法")
show_convergence(vals_newton, val_true, a, "ニュートン法")
二分法
----------------------------------------
被開平数 : 2
反復回数 : 60
平方根‐近似解 : 1.41421356237309492
平方根‐高精度解 : 1.41421356237309504880168872420969807856967187537694807317667973799073247846210
誤差 : 0.00000000000000013
精度 : 99.99999999999999113%
ニュートン法
----------------------------------------
被開平数 : 2
反復回数 : 7
平方根‐近似解 : 1.41421356237309492
平方根‐高精度解 : 1.41421356237309504880168872420969807856967187537694807317667973799073247846210
誤差 : 0.00000000000000013
精度 : 99.99999999999999113%
二分法とニュートン法の近似解を比較すると同等の精度を確保していることが分かります(表示範囲の限りでは同じです)。近似解と高精度解を比較しても十分な精度を確保していることが分かります。
収束の速さだけを比べるとニュートン法の方が優れているように見えますが、こちらは条件によっては収束しないことがあります。一方の二分法は中間値の定理を満たせば必ず収束するという性質があります。それぞれの性質についての詳細は省きますが、実用面では両者の長所を組み合わせたハイブリッド戦略も有効とされています。
おわりに
2の平方根を求めるという最小限の題材を通じて、二分法とニュートン法について理解することができました。特にニュートン法については勾配降下法に通じる考え方が含まれている点が有益でした。
Discussion