【Python】対数計算
対数関数は指数関数 $y=a^x$ の逆関数として定義されます。
上図のように、ある正数 $R$ について、
\[R=a^r\tag{1}\]
を満たすような $r$ を
\[r=\log_{a}R\tag{2}\]
のように書き表し、$r$ を $R$ の対数とよびます。このとき、$a$ を底、$R$ を真数とよびます。
簡単なケースでは、(1) の定義に戻って値を計算できます。たとえば、$\log_{2}16$ は、$16=2^r$ を満たすような $r$ を考えて、$r=4$ を得ます。とはいえ、ほとんどの場合、$R$ から $r$ を得るのは手計算では極めて困難です。たとえば、$\log_3 100$ のように求める値が整数でない場合、昔は対数表を用いて値を探しました。現代では一般に計算機 (コンピュータ) を用いて値を得ます。Python 標準ライブラリの math モジュールには、対数を計算するための関数が揃っています。
たとえば、math.log(x [, base]) は base を底とする対数の値を返します。$2$ を底とする $5$ の対数を計算してみましょう。
# PYTHON_LOG # In[1] # 2を底とする5の対数 val = math.log(5, 2) print(val) # 2.321928094887362
ネイピア数とよばれる数学定数 $e=2.718…$ を底とする対数
\[\log_{e}x\tag{3}\]
を $x$ の自然対数とよび、数学や科学の広い分野で使用されています。math.log(x [, base]) の base を省略すると底として $e$ を指定したことになり、自然対数の値を返します。$\log_{e} e=1$ を確認してみましょう。ネイピア数 $e$ は math.e で呼び出せます。
# In[2] # eを底とするeの対数 val = math.log(math.e) print(val) # 1.0
底を $a=10$ とする
\[\log_{10}x\tag{4}\]
を常用対数 (common logarithm) とよび、科学技術計算で広く使われています。私たちは $10$ 進法に慣れているので、常用対数は感覚的にわかりやすいという利点があります。たとえば、$\log_{10} 1000$ は $3$ ですが、$\log_{10} 1050$ のような対数を見たときにも、$3$ より少し大きい数値であると推測できます。x の常用対数は math.log(x, 10) でも計算できますが、Python には常用対数専用の math.log10() が用意されているので、それを使って $\log_{10} 1050$ を計算してみましょう。
# In[3] # 1050の常用対数 val = math.log10(1050) print(val) # 3.0211892990699383
$2$ を底とする対数を二進対数とよび、情報理論や計算機科学の分野でよく用いられます。x の二進対数は math.log(x, 2) で計算できますが、二進対数専用の math.log2() も用意されています。コンピューターのバイト計算でおなじみの $2^{10}=1024$ から、$1024$ の二進対数 $\log_{2}1024$ が $10$ となることを確認してみましょう。
# In[4] # 1024の二進対数 val = math.log2(1024) print(val) # 10.0
対数関数 $y = \log_{a} x$ が $x \rightarrow \infty$ の極限で $+\infty$ となることを反映し、x に無限大 inf を渡すと、inf が返るようになっています。
# In[5] # 3を底とする+∞の対数 val = math.log(float("inf"), 3) print(val) # inf
実数の対数には真数を正の値に限定するという「真数条件」があります。math.log() の第1引数に 0 や負の数を渡すと ValueError が返ります。
# In[6] # -1の常用対数 val = math.log(-1) print(val) # ValueError: math domain error
戻り値を複素数に拡大すれば (複素解析学の分野では)、負数の対数を計算できますが、math モジュールでは扱えません。 負数の対数を計算するときは、複素数計算用モジュール cmath の対数関数を使います。
# In[7] # -1の常用対数 val = cmath.log(-1) print(val) # 3.141592653589793j
あるいは外部パッケージ NumPy を用いて負数の対数を計算することもできます。ただし、真数 x に負の浮動小数点数をそのまま入れるとエラーになるので、x には必ず complex 型の値を渡すようにしてください。たとえば、-1 の自然対数を計算したいときは numpy.log() に -1 + 0j を渡します。
# In[8] import numpy as np # -1の自然対数を計算 val = np.log(-1+0j) print(val) # 3.141592653589793j
cmath や NumPy の対数計算関数には complex 型数値(複素数)を引数に渡すことができます。複素解析学において、複素変数 $z$ の対数 $w = \log z$ は複素変数の指数関数 $z = \exp(w)$ の逆関数として定義されます。これは無限個の分枝
\[w_k=\log r+i(\theta+2k\pi)\]
をもつ無限多価関数ですが、$z$ 平面を切断して、その平面上で1つの分枝を選択することによって一価関数として扱えます。たとえば $w = \log i$ の値は $(2k + 1/2)\pi i$ であり、任意定数 $k$ によって無限の値を取り得ますが、Python では一意の値 $\pi i/2$ を返すようになっています。cmath.log() を使って虚数単位 $i$ の対数を求めてみます。
# In[9] # eを底とする虚数iの対数 val = cmath.log(1j) print(val) # 1.5707963267948966j
対数関数 y=logx のグラフ
NumPy の対数関数には配列を渡せるので、ループ処理なしで、複数変数に対してまとめて対数の値を計算できます。この機能はグラフをプロットするのに適しています。以下に Matplotlib を使って対数関数 $y=\log{x}$ のグラフを描画させるサンプルコードを載せておきます。
# LOG_PLOT # In[1] import matplotlib.pyplot as plt # フィギュアを設定 fig = plt.figure() # グリッド線を表示 plt.style.use("ggplot") # グラフ描画領域を追加 ax = fig.add_subplot(111) # グラフタイトルを設定 ax.set_title("y = log(x)", fontsize = 16) # x軸, y軸のラベルを設定 ax.set_xlabel("x", fontsize = 16) ax.set_ylabel("y", fontsize = 16) # xのデータを用意 x = np.arange(0.1, 4, 0.1) # yのデータを用意 y = np.log(x) # データをプロット ax.plot(x, y)
底の変換
NumPy には e, 2, 10 という 3 種類の底の対数関数しか用意されていません。任意の底 $a$ の対数を計算するときには底の変換公式
\[\log_a x=\frac{\log x}{\log a}\]
を利用します。
# CHANGE_BASE # In[1] import numpy as np # 3を底とする9の対数を計算 val = np.log(9) / np.log(3) print(val) # 2.0
以下は Python の各種ライブラリで扱える対数計算用関数の一覧です。
math.log()
math.log(x [, base]) は base を底とした真数 x の対数を返します。
# MATH_LOG # In[1] # eを底とするeの対数 val = math.log(math.e) print(val) # 1.0
オプション引数の base を省略した場合、ネイピア数 e を指定したことになります。
# In[2] import math # eを底とする5の対数 val = math.log(5) print(val) # 1.6094379124341003
math.log10()
math.log10(x) は x の常用対数を返します。
# MATH_LOG10 # In[1] import math # 10を底とする100の対数を計算 val = math.log10(100) print(val) # 2.0
math.log2()
math.log2(x) は 2 を底とした x の対数を返します。
# MATH_LOG2 # In[1] import math # 2を底とした8の対数 val = math.log2(8) print(val) # 3.0
math.log1p()
math.log1p(x) は、e を底とした 1 + x の対数を計算します。この関数は真数が 1 に近い値のときの対数を正確に計算するために用意されたものです。たとえば、math.log1p(0.01) は math.log(1.01) よりも精度の良い値を返します。
# MATH_LOG1P # In[1] import math # eを底とする1.01の対数 val = math.log1p(0.01) print(val) # 0.009950330853168083
cmath.log()
cmath.log(x[, base]) は base を底とする x の対数を計算します。math ではサポートしていない負数の対数を計算できます。引数の型に関わらず戻り値はすべて複素数型です。
# CMATH_LOG # In[1] # 3を底とする19の対数 val = cmath.log(19, 3) print(val) # (2.680143859246375+0j)
base を省略すると自然対数を計算します。
# In[2] # 100の自然対数 val = cmath.log(100) print(val) # (4.605170185988092+0j)
負数の対数も計算できます。
# In[3] # -10の常用対数 val = cmath.log(-10,10) print(val) # (1+1.3643763538418412j)
複素数の対数も計算できます。
# In[4] # eを底とする複素数1+iの対数 val = cmath.log(1+1j) print(val) # (0.34657359027997264+0.7853981633974483j)
cmath.log10()
cmath.log10(x) は x の常用対数を返します。戻り値はすべて複素数型です。
# CMATH_LOG10 # In[1] # 5の常用対数 val = cmath.log10(5) print(val) # (0.6989700043360187+0j)
x に負の数を渡すこともできます。
# In[2] # -100の常用対数 val = cmath.log10(-100) print(val) # (2+1.3643763538418412j)
複素数の対数も計算できます。
# In[3] # 2+3iの常用対数 val = cmath.log10(2+3j) print(val) # (0.5569716761534184+0.42682189085546657j)
numpy.log()
numpy.log(x) はネイピア数 e を底とした x の対数を返します。
# NUMPY_LOG # In[1] import numpy as np # 変数の入った配列を作成 arr = np.array([1, np.e, 10]) # 配列の各要素に対してeを底とする対数を計算 log_arr = np.log(arr) print(log_arr) # [0. 1. 2.30258509]
numpy.log10()
numpy.log(x) は x の常用対数を返します。
# NUMPY_LOG10 # In[1] import numpy as np # 変数の入った配列を作成 arr = np.array([1, 10, 0.1]) # 配列の各要素に対して常用対数を計算 log_arr = np.log10(arr) print(log_arr) # [0. 1. -1.]
numpy.log2()
numpy.log2(x) は 2 を底とした x の対数を返します。
# NUMPY_LOG2 # In[1] import numpy as np # 変数の入った配列を作成 arr = np.array([1, 2, 4, 0.5]) # 配列の各要素に対して2を底とした対数を計算 log_arr = np.log2(arr) print(log_arr) # [0. 1. 2. -1.]
numpy.log1p()
numpy.log1p(x) は e を底とした 1 + x の対数を計算します。この関数は真数が 1 に近い値のときの対数を正確に計算するために用意されています。たとえば、numpy.log1p(0.001) は numpy.log(1.001) よりも高い精度の値を返します。
# NUMPY_LOG1P # In[1] import numpy as np # eを底とする1.001の対数を計算 val = np.log1p(0.001) print(val) # 0.0009995003330835331
sympy.log()
sympy.log(x, b) は b を底とする対数関数を計算します。ただし、計算結果はネイピア数 e を底とする対数に変換して表示されます。引数 b を省略した場合は底としてネイピア数 e を指定したことになります。
# SYMPY_LOG # In[1] import sympy # 記号xを定義 sympy.var('x y') # 2を底とするx*yの対数 expr = sympy.log(x*y, 2) print(expr) # log(x*y)/log(2)
第 1 引数に複素数を渡すこともできます。
# In[2] # 虚数単位の記号を定義 i = sympy.I # f(x,y)=log(ix+y) f = sympy.log(i*x+y) # f(-i,1)を計算 val = f.subs([(x,-i),(y,1)]) print(val) # log(2)
sympy.logcombine()
sympy.logcombine() は対数関数の公式を使って数式をまとめます。
# SYMPY_LOG_COMBINE # In[1] from sympy import * var ("a b c x", positive=True) expr = log(a) + log(b) + c*log(x) # 対数関数の公式を使って数式をまとめる expr = logcombine(expr, force=True) print(expr) # log(a*b*x**c)
コメント
下記は誤植と思われますので、ご確認ください。
(4) 式の下の文で、3 より少し多い数値 → 3 より少し大きい数値
「多い」はおかしな表現ですね。
修正しておきました m(_ _)m
【AI連載小説】科学とコードの交差点(26)「最速の対数計算」
Pythonサークルのメンバーたちは、ある日対数を最速で計算する方法について討論していた。開誠、明信、あやか、たくや、まなみ、さとる、たけしが賑やかな雰囲気で情報交換を行っていた。
開誠: 対数を計算する方法って、いくつかあるよね。でも、最速の方法ってどれだろう?
明信: mathモジュールのlog()が一般的だけど、もっと高速な手法があるかもしれないね。
あやか: NumPyのnp.log()もあるけど、これはどのアルゴリズムを使っているんだろう?
たくや: わかりにくいけど、mathと同じくCのmath.hを使っているみたいだね。
まなみ: 他にも、二分法をベースにしたアルゴリズムとか、ニュートン法を使ったアルゴリズムもあるよね。
さとる: それぞれのアルゴリズムには計算の速さや精度の違いがあるはずだ。どのケースでどれが最適か知りたいな。
たけし: Pythonのバージョンによっても最適な方法が違うこともあるかもしれないね。
メンバーたちは手元のデバイスやPythonのバージョンに応じて最適な対数計算方法を見つけるために、実際にいくつかの手法を試してみることを提案した。各自が情報を共有しながら、最適な計算方法を見つけるために試行錯誤を重ねていた。
開誠: そうだね、実際にベンチマークを取ってみるといいかもしれない。同じ計算を異なるアルゴリズムで試して、どれが一番速いかを確認しよう。
明信: それにしても、ニュートン法って数学的には面白いアルゴリズムだよね。微分を使って収束する様子を見るとか。
あやか: でも、収束が速いからといって、すべてのケースで効果的とは限らないよね。
たくや: それぞれのアルゴリズムが得意とする領域があるから、データの特性によって選択するのがいいんだろうね。