2018/2/28にPydata Osakaで'SciPyの概要と各モジュールの紹介'というタイトルで発表しました
2018/2/28にPydata Osakaで発表しました¶
非常に今更ですが2018/2/28に"SciPyの概要と各モジュールの紹介"というタイトルで発表しました。当日の資料は下記途中のセルにもあるようにAzure Notebooksでも公開していましたが、nikolaではipynbファイルを記事としても公開可能であるため今更にはなりますが公開いたしました。 以下当日の資料になります。
In [1]:
from IPython.display import IFrame
IFrame("https://www.hiromasa.info/slide/4.slides.html", "100%", "450px")
Out[1]:
SciPyの概要と各モジュールの紹介¶
自己紹介¶
- 大橋宏正
- 某メーカで音響エンジニア
- scipy.signalとscipy.fftpackを仕事でよく使う
- twitter: @wrist
SciPyとは¶
- NumPyの上に作られた数学的なアルゴリズムや便利な関数の集合
- 機能ごとに分けられたサブモジュール群から成る
本発表で紹介するサブモジュール(1)¶
- io
- 様々なファイル入出力
- special
- 特殊関数
- fftpack
- FFTルーチン
- interpolate
- 補間と平滑化
- linalg
- 線形代数
サブモジュール一覧(2)¶
- optimize
- 最適化および求根ルーチン
- signal
- 信号処理
- spatial
- 空間データ構造およびアルゴリズム
- stats
- 確率分布および密度関数
- misc
- 雑多な諸々
本日説明しないモジュール¶
- cluster
- クラスタリングアルゴリズム
- constants
- 科学定数、数学定数
- odr
- 直交距離回帰を行うためのFortranライブラリODRPACKのラッパー
- ndimage
- N次元画像処理
- sparse
- 疎行列および関連ルーチン
- integrate
- 積分および常微分方程式のソルバ
SciPy 1.0¶
- 2017/10/25にリリース
- Release Note
History¶
- 2001: 最初のリリース
- 2005: NumPyへの移行
- 2007: scikitsの誕生
- 2008: scipy.spatialモジュールと最初のCythonコードが追加
- 2010: 6ヶ月単位のリリースサイクルを採用
- 2011: GitHubに移行
- 2011: Python3サポート
- 2012: sparse graphモジュールと統合的な最適化インタフェースの追加
- 2012: scipy.maxentropyの削除
- 2013: TravisCIでの継続的インテグレーションを開始
- 2015: BLAS/LAPACKに対するCythonインタフェースとベンチマークスイートの追加
- 2017: scipy.LowLevelCallableを用いた統一的なC APIの追加; およびscipy.weaveの削除
- 2017: SciPy 1.0のリリース
- version 0.1から実に16年
何故Version 1.0か?(意訳)¶
- 大枠の背景
- Scipyはプロダクション環境で使われるほどに成熟し安定したことを反映
- 技術的な目標(WindowsでのWheel配布、CI)と組織的な目標(組織構造、行動規範、ロードマップ)の両方を近年達成
- 1.0の意味
- オープンソースプロジェクトにとって"1.0"と言うと100%完成したという印象があるが、実際は完璧ではなくいくつもの修正が必要な点があることを自分たちとしても認めている
- そうだとしてもSciPyは究極的にユーザーにとって便利なものであり、平均して高いクオリティのコードとドキュメントがあり、1.0のラベルが示すであろう安定性と後方互換性の保証がある。
SciPyを学ぶ際のリソース¶
scipy lecture note: 1.5 Scipy: high-level scientific computing¶
- http://www.scipy-lectures.org/intro/scipy.html
- scipyはさまざまな科学技術計算で共通する問題に対するtoolbox
- 補間、積分、最適化、画像処理、統計、特殊関数、など
- GSLやmatlabと比較されうるもの
本発表¶
- tutorialとscipy lecture noteからコード等を抜粋して紹介
- scipyを使った計算の簡単なデモ
In [1]:
# azure notebooksデフォルトのSciPyをpipで更新
!pip install -U scipy
In [2]:
# 本発表では以下を仮定
import numpy as np
import scipy as sp
import matplotlib as mpl
import matplotlib.pyplot as plt
In [3]:
# version
print("numpy version", np.version.full_version)
print("scipy version", sp.version.full_version)
print("matplotlib version", mpl.__version__)
注意¶
本発表では基本的にnp.dotの代わりに@演算子を使用
In [4]:
# 本発表では以下を仮定
import scipy.io as spio
import scipy.special as special
import scipy.linalg as la
import scipy.fftpack as fft
import scipy.optimize as opt
import scipy.signal as sg
import scipy.interpolate as interp
import scipy.stats as stats
scipy.io¶
- io
- さまざまな形式のファイルIOを提供するモジュール
- matlabやoctaveのmatファイル
- netcdf, fortranのunformatted seaquential file, matrix market, harwell-boeing, IDL, Arff形式
- 何故かwavファイルのIOもできるが24bit音源が読めないのでハイレゾ音源が扱えない
- さまざまな形式のファイルIOを提供するモジュール
In [5]:
a = np.ones((3, 3))
spio.savemat('file.mat', {'a': a}) # savematには辞書を渡す
data = spio.loadmat('file.mat')
data['a']
Out[5]:
In [6]:
a = np.ones(3)
spio.savemat('file.mat', {'a': a})
print(a)
print(spio.loadmat('file.mat')['a']) # matlabには1次元配列がない
print(spio.loadmat('file.mat', squeeze_me=True)["a"])
- matlabに関してはstructやcell配列も読めるが割愛
scipy.special¶
- さまざまな特殊関数が含まれている
- docstringに色々書かれている
- よく使われると思われるもの
- ベッセル関数
scipy.special.jn()
- 楕円関数
scipy.special.ellpj()
- ガンマ関数
scipy.special.gamma()
、対数ガンマ関数scipy.special.gammaln()
- 誤差関数
scipy.special.erf()
- ベッセル関数
In [7]:
special?
scipy.special.cython_special¶
- cythonバインディングも存在
-
cimport scipy.special.cython_special
でcythonコード上でcimport
-
- 型が付いている
In [11]:
%load_ext cython
In [12]:
%%cython -a
cimport scipy.special.cython_special as csc
cdef:
double x = 1
double complex z = 1 + 1j
double si, ci, rgam
double complex cgam
rgam = csc.gamma(x)
print(rgam)
cgam = csc.gamma(z)
print(cgam)
csc.sici(x, &si, &ci)
print(si, ci)
Out[12]:
- scipy.special.sici
- $\int_0^x \frac{\sin t}{t} dt$と$\gamma + \log (x) + \int_0^x \frac{\cos t - 1}{t} dt$を計算
Tutorial記載の他の内容¶
- cythonのprangeを使うことでGILを外して並列に回す例
- specialに含まれないがnumpy, scipyで容易に実装可能な関数の紹介
- 2値エントロピー関数、ヘビサイド関数、ステップ関数、ランプ関数
scipy.linalg¶
- 線形代数関連の操作を扱うモジュール
numpy.linalgとの違い¶
-
https://www.scipy.org/scipylib/faq.html#why-both-numpy-linalg-and-scipy-linalg-what-s-the-difference
-
scipy.linalg
はLAPACKを用いたより完璧なラッパー - numpy.linalgはFortranコンパイラなしでもビルドでき、LAPACKが無い場合は自前の実装を用いる
- 両者は共通の関数を持つが異なるdocstringを持つ
-
-
scipy.linalg
はnumpy.linalg
に存在しない機能を持つ- LU分解、シュール分解
- 様々な方法による擬似逆行列の計算
- 行列の対数のような行列操作
- 追加の引数を持つもの(
scipy.linalg.eig()
は一般化固有値問題を解くために第二の行列を引数に取ることが可能)
numpy.matrixと2次元numpy.ndarray¶
- numpy.matrix
- ndarrayよりも行列操作に便利なインタフェースを持ったクラス
- 1次元matrixの転置は転置となり、matrixオブジェクト同士の
*
による乗算は行列の乗算となる
- numpy.ndarray
- 1次元ndarrayは
.T
による転置操作は転置にならず、*
による乗算は要素同士の乗算となる
- 1次元ndarrayは
-
scipy.linalg
の操作はどちらのオブジェクトに対しても同一の操作を実現
行列式の計算¶
-
linalg.det
で計算可能 - 行列式は余因子展開によって再帰的に求めることが可能
- $ M_{i,j} = | \textbf{A}_{ij}| $はAの(i,j)小行列式(第i行と第j列を取り除いて得られる行列)$\textbf{A}_{ij}$の行列式
In [13]:
# 行列式の計算
A = np.array([[1, 2],
[3, 4]])
print(la.det(A)) # 1*4-2*3=-2
A = np.array([[3, 2],
[6, 4]])
print(la.det(A) ) # 3*4-2*6=0
print(la.det(np.ones((3, 4)))) # 正方行列ではない
逆行列の計算¶
-
linalg.inv
を用いる
In [14]:
# 逆行列の計算
A = np.array([[1, 2],
[3, 4]])
A_inv = la.inv(A)
print(A_inv)
print(A @ A_inv) # AA^-1 ~= I
print(np.allclose(A @ A_inv, np.eye(2)))
In [15]:
# 特異行列(行列式が0)の場合は逆行列が求められないのでLinAlgError例外を投げる
A = np.array([[3, 2],
[6, 4]])
la.inv(A)
線形方程式を解く¶
-
linalg.solve
を用いる - 下記線形方程式を解くことを考える
- $\textbf{A}\textbf{x}=\textbf{b}$とみなすと$\textbf{x}=\textbf{A}^{-1}\textbf{b}$と計算しても解けるが、速度の観点と数値的な安定性から
linalg.solve
を用いた方が良い
In [16]:
import numpy as np
from scipy import linalg
A = np.array([[1, 2], [3, 4]])
b = np.array([[5], [6]])
print("逆行列による解法")
print(linalg.inv(A) @ b) # 遅い
print((A @ linalg.inv(A) @ b) - b) # 確認
print("solveによる解法")
print(np.linalg.solve(A, b)) # 高速
print((A @ np.linalg.solve(A, b)) - b) # 確認
ノルムの計算¶
-
linalg.norm(a, ord)
で行列やベクトルの様々なノルムを計算可能 - ベクトルに対してはordパラメータで制御
- 行列に対しては$\pm2, \pm1, \pm\inf$, 'fro'(または'f')が指定可能
In [17]:
A = np.array([[1,2],[3,4]])
print(A)
print(la.norm(A))
print(la.norm(A, 'fro')) # デフォルト
print(np.sqrt(np.trace(A.T @ A)))
print(la.norm(A,1)) # L1ノルム(各列の絶対値和のうち最大のもの)
print(la.norm(A,-1)) # L1ノルム(各列の絶対値和のうち最小のもの)
print(la.norm(A,np.inf)) # L∞ノルム(各行の絶対値和のうち最大のもの)
線形最小二乗問題の解法と擬似逆行列¶
-
linalg.lstsq
- 最小二乗問題のソルバー
-
linalg.pinv
- 二乗誤差最小化に基づく擬似逆行列
-
linalg.pinv2
- 特異値分解に基づく擬似逆行列
目標変数$y_i$は説明変数$\textbf{x}_i$, パラメータ$c_j$, モデル関数$f_j (\textbf{x}_i )$を用いて下記のように書けるとする
$$ y_i = \sum_j {c_j f_j (\textbf{x}_i)} + \epsilon_i $$ここで$\epsilon$はデータにおける不確かさを表す。 パラメータ$c_j$を得るために、以下の式で表される二乗誤差をJパラメータ$c_n$で微分して0とおくことで最小化する。
$$ J(\textbf{c}) = \sum_i \left| y_i - \sum_j { c_j f_j (\textbf{x}_i) }\right|^2 $$$$ \frac{\partial J}{\partial c_n^*} = 0 = \sum_i \left( y_i - \sum_j c_j f_j (x_i)\right)(-f_n^*(x_i)) $$即ち $$ \sum_j c_j \sum_i f_j(x_i)f_n^*(x_i) = \sum_i y_i f_n^* (x_i) $$ $$ \textbf{A}^H\textbf{A}\textbf{c}=\textbf{A}^H\textbf{y}$$ ここで $$\left\{ \textbf{A} \right\}_{ij} = f_j (x_i). $$ $\textbf{A}^H\textbf{A}$に逆行列が存在する場合、 $$ \textbf{c} = \left(\textbf{A}^H\textbf{A}\right)^{-1} \textbf{A}^H\textbf{y} = \textbf{A}^\dagger \textbf{y}$$ $\textbf{A}^\dagger$は擬似逆行列と呼ばれる
疑似逆行列¶
- $\textbf{A}$をM行N列の行列とする
- M > Nのとき
- M < Nのとき
- M = Nのとき
- $x_i$=0.1i for i = 1...10に対し、下記のモデルによって生成される$y_i$を考える。 $$ y_i = 5e^{-x_i} + 4x_i $$ 観測値として下記が得られるとき、 $$ z_i = y_i + \epsilon = c_1 e^{-x_i} + c_2 x_i + \epsilon$$ $z_i$と$[e^{-x_i}, x_i]$を用いてモデルパラメータc1とc2を最小二乗法で推定したい
In [18]:
c1, c2 = 5.0, 2.0
i = np.r_[1:11]
xi = 0.1*i # 説明変数
yi = c1*np.exp(-xi) + c2*xi
zi = yi + 0.05 * np.max(yi) * np.random.randn(len(yi)) # 観測値
A = np.c_[np.exp(-xi)[:, np.newaxis],
xi[:, np.newaxis]] # 計画行列
print(A)
In [19]:
# 計画行列と観測値を与えてモデルパラメータを推定
c, resid, rank, sigma = linalg.lstsq(A, zi)
# パラメータ、残差の総和、Aのrank, Aの特異値
print(c, resid, rank, sigma)
xi2 = np.r_[0.1:1.0:100j]
yi2 = c[0]*np.exp(-xi2) + c[1]*xi2
yi_true = c1*np.exp(-xi2) + c2*xi2
plt.plot(xi,zi,'x',xi2,yi2, xi2, yi_true)
plt.axis([0,1.1,3.0,5.5])
plt.xlabel('$x_i$')
plt.title('Data fitting with linalg.lstsq')
plt.show()
固有値分解¶
-
linalg.eig
- 固有値と固有ベクトルを求める
-
linalg.eigvals
- 固有値のみを求める
- エルミート行列または実対象行列の場合は
eigh
,eigvalsh
が存在 - 疎行列の場合は
scipy.sparse.linalg
のeigs
,eighs
In [20]:
A = np.array([[1, 2], [3, 4]])
(l1, l2), v = la.eig(A)
print(l1, l2) # 固有値
print(v) # 各列ベクトルが固有ベクトル
print(la.norm(v, axis=0)) # 固有ベクトルは正規化されている
# Ax = λxとなることを確認
# |1 2| = |v1 v2 ||l1 0| = |l1*v1 l2*v2|
# |3 4| | 0 l2|
print(A@v - v@np.diag((l1,l2)))
特異値分解¶
- 擬似逆行列を求めるときや次元削減に使われる
- Mはrank rのm行n列
- Uはm行m列のユニタリ行列
- $\Sigma$は対角方向に特異値が並んだm行n列行列
- $V^*$はn行n列ユニタリ行列Vの随伴行列
- (m, n) = (m, m) @ (m, n) @ (n, n)
In [21]:
# 正方行列に対する特異値分解
A = np.arange(9).reshape((3, 3)) + np.diag([1, 0, 1])
U, s, Vh = la.svd(A)
Sig = np.diag(s)
print(A)
print(U.shape, s.shape, Sig.shape, Vh.shape)
print(U @ Sig @ Vh)
# 非正方行列に対する特異値分解
A = np.array([[1,2,3],[4,5,6]])
M,N = A.shape
U,s,Vh = linalg.svd(A)
Sig = la.diagsvd(s, M, N)
print("")
print(A)
print(U.shape, s.shape, Sig.shape, Vh.shape)
print(U@Sig@Vh)
scipy.fftpack¶
- 高速フーリエ変換、離散コサイン変換などができる
-
scipy.fftpack.fft
でFFTを実行 -
scipy.fftpack.fftfreq
で周波数軸の値を取得
In [22]:
import IPython.display
In [23]:
fs = 48000.0; nfft = 2048
xs = np.arange(1.0 * fs) # 1[s]
ts = xs / fs
hz = 2000.0
ys = np.sin(2.0 * np.pi * hz * ts) + 0.1 * np.random.randn(int(fs))
fig = plt.figure(1, figsize=(12,4))
ax = fig.add_subplot(1, 1, 1)
ax.plot(ts[:nfft], ys[:nfft])
ax.set_xlabel("time [s]"); ax.set_ylabel("amplitude")
IPython.display.Audio(data=ys, rate=fs)
Out[23]:
In [24]:
# 窓関数を取得
win = sg.get_window("hanning", nfft)
# FFTを実行
Ys = fft.fft(win * ys[:nfft], nfft)[:nfft//2+1]
# 周波数軸を取得
f = fft.fftfreq(nfft, d=1.0/fs)[:nfft//2+1]
f[-1] = fs//2 # デフォルトだと-24kHz
In [25]:
fig = plt.figure(1, figsize=(12,4));
ax = fig.add_subplot(1, 2, 1)
ax.plot(ts[:nfft], win * ys[:nfft])
ax.set_xlabel("time [s]"); ax.set_ylabel("amplitude")
ax = fig.add_subplot(1, 2, 2)
ax.semilogx(f, np.abs(Ys))
ax.set_xlabel("frequency [Hz]"); ax.set_ylabel("amplitude");
scipy.interpolate¶
- 曲線の補間が可能
-
scipy.interpolate.interp1d
で補間関数を生成 - 生成した補間関数に補間点を与えると補間値を算出
In [26]:
# 観測値
xs = np.linspace(0, 1, 10)
ys = np.sin(2.0 * np.pi * xs) + 0.01 * np.random.randn(len(xs))
# 異なる2種類の補間関数の生成
linear_interp = interp.interp1d(xs, ys)
cubic_interp = interp.interp1d(xs, ys, kind='cubic')
# 補間値を算出
xs_new = np.linspace(0, 1, 50)
ys_lin = linear_interp(xs_new)
ys_cubic = cubic_interp(xs_new)
In [27]:
fig = plt.figure(figsize=(12,6)); ax = fig.add_subplot(111)
ax.plot(xs, ys, "x-", label="obserbed")
ax.plot(xs_new, ys_lin, label="linear")
ax.plot(xs_new, ys_cubic, label="cubic")
ax.grid(); ax.legend()
Out[27]:
scipy.signal¶
- 信号処理のためのライブラリ
- フィルタ設計・フィルタリング
- 畳み込み・時間相関計算
- 窓関数
ホワイトノイズに対するローパスフィルタの適用¶
-
numpy.random.randn
でホワイトノイズを生成 -
scipy.signal.firwin
でローパスフィルタを設計 -
scipy.signal.lfilter
でローパスフィルタをホワイトノイズに対して適用
In [28]:
# ホワイトノイズの生成
time_s = 5.0
ys = 0.1 * np.random.randn(int(time_s * fs))
IPython.display.Audio(ys, rate=fs)
Out[28]:
In [29]:
nlpf = 96
cutoff_hz = 4000.0
lpf = sg.firwin(nlpf, cutoff=cutoff_hz, fs=fs)
ys_lpf = sg.lfilter(lpf, [1.0], ys)
IPython.display.Audio(ys_lpf, rate=fs)
Out[29]:
In [30]:
fig = plt.figure(1, figsize=(12,4));
ax = fig.add_subplot(1, 2, 1)
ax.plot(f, 20.0*np.log10(np.abs(fft.fft(ys, nfft)[:nfft//2+1])))
ax.set_xlabel("time [s]"); ax.set_ylabel("power [dB]")
ax = fig.add_subplot(1, 2, 2)
ax.plot(f, 20.0*np.log10(np.abs(fft.fft(ys_lpf, nfft))[:nfft//2+1]))
ax.set_xlabel("frequency [Hz]"); ax.set_ylabel("power [dB]")
Out[30]:
scipy.optimize¶
- 各種最適化アルゴリズムが実装
-
scipy.optimize.curve_fit
で曲線フィッティング -
scipy.optimize.minimize
で最小値を見つける -
scipy.optimize.root
で方程式の根を見つける
In [31]:
# ノイズの乗った正弦波を観測値とする
x_data = np.linspace(-5, 5, num=50)
y_data = 2.9 * np.sin(1.5 * x_data) + np.random.normal(size=50)
def test_func(x, a, b):
return a * np.sin(b * x)
# curve_fitを用いてa, bを推定
params, params_covariance = opt.curve_fit(test_func, x_data, y_data, p0=[2, 2])
print(params, params_covariance)
In [32]:
fig = plt.figure(figsize=(12,4))
ax = fig.add_subplot(111)
ax.plot(x_data, y_data, "x")
ax.plot(x_data, test_func(x_data, params[0], params[1]))
Out[32]:
In [33]:
# 最小値を見つけたい対象
def f(x):
return x**2 + 10*np.sin(x)
# 大域解が-1.3, 局所解が3.8
x = np.arange(-10.0, 10.0, 0.1)
fig = plt.figure(); ax = fig.add_subplot(111)
ax.plot(x, f(x))
Out[33]:
In [34]:
result = opt.minimize(f, x0=0.0) # x0で探索開始点を与える
print(result)
print(result.x)
In [35]:
# nfev(評価回数)が12に減る
opt.minimize(f, x0=0, method="L-BFGS-B")
Out[35]:
In [36]:
# 3.0から始めると局所解に陥る
result = opt.minimize(f, x0=3.0, method="L-BFGS-B")
print(result)
print(result.x)
In [37]:
# `scipy.optimize.basinhopping`を用いて大域解を探す
opt.basinhopping(f, 1.0)
Out[37]:
In [38]:
# 探索範囲に制約を与えることも可能
res = opt.minimize(f, x0=1,
bounds=((0, 10), ))
res.x
Out[38]:
In [39]:
# f=0となる点をoptimize.rootで探す
root = opt.root(f, x0=1)
print(root)
print(root.x)
In [40]:
# もうひとつの根を探す
root = opt.root(f, x0=-3.0)
print(root)
print(root.x)
scipy.stat¶
- 統計や確率分布、統計的検定のためのモジュール
- サンプル点の正規分布へのフィッティング等を紹介
In [41]:
# サンプリングした点のヒストグラム(頻度分布)と確率分布を同時に描画
samples = np.random.normal(size=1000) # sampling
bins = np.arange(-4, 5)
histogram = np.histogram(samples, bins=bins, normed=True)[0] # plot histogram
bins = 0.5*(bins[1:] + bins[:-1])
pdf = stats.norm.pdf(bins) # normは正規分布の意味
fig = plt.figure(); ax = fig.add_subplot(111)
ax.plot(bins, histogram, label="histgram")
ax.plot(bins, pdf, label="probability density")
ax.grid(); ax.legend()
Out[41]:
In [42]:
# 観測値を確率分布にフィッティング
loc, std = stats.norm.fit(samples)
print(loc, std)
xs = np.linspace(-3.0, 3.0, 1000)
f = lambda x: (1.0/np.sqrt(2.0*np.pi*std))*np.exp(-(x - loc)**2/ (2.0 * std**2))
plt.plot(xs, f(xs))
Out[42]:
In [43]:
# 平均、中央値、四分位数
print(np.mean(samples))
print(np.median(samples))
print(stats.scoreatpercentile(samples, 50))
print(stats.scoreatpercentile(samples, 90))
scipy.spatial¶
- 空間データや距離を扱うためのモジュール
- 距離行列を計算する
scipy.spatial.distance
のcdist
,pdist
のみ紹介
In [44]:
from scipy.spatial import distance
coords = [(35.0456, -85.2672),
(35.1174, -89.9711),
(35.9728, -83.9422),
(36.1667, -86.7833)]
print(np.sqrt((coords[0][0] - coords[1][0])**2
+(coords[0][1] - coords[1][1])**2))
distance.cdist(coords, coords, 'euclidean')
Out[44]:
In [45]:
# pdistは対角を除いた上三角成分のみ返す
presult = distance.pdist(coords)
print(presult)
# squareformで距離行列に変換
print(distance.squareform(presult))
scipy.misc¶
- deprecatedな関数を除くと4つのみ
- face
- central_diff_weight
- derivative
- ascent
In [46]:
import scipy.misc
import matplotlib.cm as cm
face = scipy.misc.face()
print(face.shape)
print(face.max())
print(face.dtype)
fig = plt.figure(1)
ax = fig.add_subplot(1, 1, 1)
ax.imshow(face)
plt.show()
In [47]:
face = scipy.misc.face(gray=True)
fig = plt.figure(1)
ax = fig.add_subplot(1, 1, 1)
ax.imshow(face, cmap=cm.gray)
plt.show()
In [48]:
# グレースケールのデモ用画像
ascent = scipy.misc.ascent()
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.imshow(ascent, cmap=cm.gray)
plt.show()
In [49]:
from scipy.misc import derivative
# x0の点におけるn次の中心差分を返す
# n==1の場合はf(x+h)-f(x-h)/2h
# 内部的にcentral_diff_weightを使う
# derivative(func, x0, dx=1.0, n=1, args=(), order=3)
f = lambda x: x**3 + x**2
derivative(f, 1.0, dx=1e-6)
Out[49]:
In [50]:
print((f(1.0 + 1e-6) - f(1.0 - 1e-6)) / (2.0 * 1.e-6))
デモ¶
- scipy.fftpackとscipy.signalを使ったNMFによるモノラル音源分離のデモ
- NMFはscikit-learnの実装を使用
- 音楽情報処理ライブラリlibrosaのデモ音源を使用
-
librosa.decompose.decompose
と同等の処理
NMF¶
- 非負値行列$\textbf{X}$を$\textbf{X} \simeq \textbf{WH} $と分解する手法
- Hを基底行列、Wをアクティベーションと呼ぶ
- scikit-learnのNMFでは下記目的関数を最小化
- 通常最適化には補助関数法が使われる
音響信号における非負行列¶
- 音を短時間波形に切り出した各断片をFFT
- 断片から求めた振幅スペクトルを時間方向に並べたスペクトログラムを行列とみなす
In [51]:
!pip install librosa
In [52]:
import librosa
import librosa.display
In [53]:
y, sr = librosa.load(librosa.util.example_audio_file())
IPython.display.Audio(data=y, rate=sr)
Out[53]:
In [54]:
np.pad(np.arange(5), 2, "reflect")
Out[54]:
In [55]:
# strideトリックを使った信号を短時間波形フレームの配列に変換する関数
from numpy.lib.stride_tricks import as_strided
def split_frames(xs, frame_width=1024, noverlap=2):
nsample = np.alen(xs)
nshift = (frame_width // noverlap)
nframe = int(nsample / nshift) - noverlap + 1
nbyte = xs.dtype.itemsize
# as_strided(xs, ys_shape, new_stride)
xs_split = as_strided(xs, (nframe, frame_width), (nbyte * nshift, nbyte))
return xs_split
In [56]:
# 信号の短時間波形への切り出し
import matplotlib.gridspec as gridspec
fig = plt.figure(1, figsize=(12,8))
gs = gridspec.GridSpec(2, 21)
ax = fig.add_subplot(gs[0, :-1])
xs = np.arange(len(y)) / sr
ax.plot(xs[:sr], y[:sr])
ymax = np.max(y[:sr])
ax.set_xlim([0.0, 1.0]); ax.set_ylim([-ymax, ymax]);
ax.set_xlabel("time [s]"); ax.set_ylabel("amplitude"); ax.grid()
frame_width = sr * 0.1
xs = np.arange(frame_width) / sr
for i, y_cut in enumerate(split_frames(y, int(frame_width), 1)[:10]):
ax = fig.add_subplot(gs[1, 2*i:2*(i+1)])
ax.plot(xs, y_cut)
ax.set_ylim([-ymax, ymax]); ax.grid()
In [57]:
# 切り出し間隔を1/2オーバーラップした場合
import matplotlib.gridspec as gridspec
fig = plt.figure(1, figsize=(12,8))
gs = gridspec.GridSpec(3, 21)
ax = fig.add_subplot(gs[0, :-1])
ax.plot(y[:sr])
ymax = np.max(y[:sr])
ax.set_ylim([-ymax, ymax]); ax.grid()
frame_width = sr * 0.1
half_width = int(frame_width // 2)
xs = np.arange(frame_width)
for i, y_cut in enumerate(split_frames(y, int(frame_width), 2)[:20]):
if i % 2 == 0:
ax = fig.add_subplot(gs[1, i:i+2])
ax.plot(xs[:half_width], y_cut[:half_width])
ax.plot(xs[half_width:], y_cut[half_width:])
else:
ax = fig.add_subplot(gs[2, i:i+2])
ax.plot(xs[half_width:], y_cut[half_width:])
ax.plot(xs[:half_width], y_cut[:half_width])
ax.set_ylim([-ymax, ymax]); ax.grid()
In [58]:
frame_width = int(sr * 0.1)
win = sg.get_window("hanning", frame_width)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(win)
ax.grid()
In [59]:
# 更に各短時間波形に窓掛けを行った場合
import matplotlib.gridspec as gridspec
fig = plt.figure(1, figsize=(12,8))
gs = gridspec.GridSpec(3, 21)
ax = fig.add_subplot(gs[0, :-1])
ax.plot(y[:sr])
ymax = np.max(y[:sr])
ax.set_ylim([-ymax, ymax]); ax.grid()
frame_width = sr * 0.1
half_width = int(frame_width // 2)
win = sg.get_window("hanning", int(frame_width))
xs = np.arange(frame_width)
for i, y_cut in enumerate(split_frames(y, int(frame_width), 2)[:20]):
y_win = win * y_cut
if i % 2 == 0:
ax = fig.add_subplot(gs[1, i:i+2])
ax.plot(xs[:half_width], y_win[:half_width])
ax.plot(xs[half_width:], y_win[half_width:])
else:
ax = fig.add_subplot(gs[2, i:i+2])
ax.plot(xs[half_width:], y_win[half_width:])
ax.plot(xs[:half_width], y_win[:half_width])
ax.set_ylim([-ymax, ymax]); ax.grid()
In [60]:
# 1/4オーバーラップして窓掛けした各波形をそれぞれFFT
nfft = 2048
win = sg.get_window("hanning", nfft)
y_center = np.pad(y, int(nfft//2), "reflect")
fft_list = []
phase_list = []
for y_cut in split_frames(y_center, nfft, 4):
y_end = min(len(y_cut), nfft)
y_win = np.zeros(nfft)
y_win[:y_end] = win[:y_end] * y_cut[:y_end]
Y = fft.fft(y_win, nfft)
fft_list.append(Y[:nfft//2+1])
phase_list.append(np.exp(1.j * np.angle(Y[:nfft//2+1])))
my_D = np.array(fft_list, dtype=np.complex64).T
my_phase = np.array(phase_list, dtype=np.complex64).T
In [61]:
fig = plt.figure(1, figsize=(12,4))
ax = fig.add_subplot(2,1,1)
librosa.display.specshow(librosa.amplitude_to_db(np.abs(my_D), ref=np.max), y_axis="log")
Out[61]:
In [62]:
# 得られたスペクトログラムnp.abs(my_D.T)に対してNMFを実行
from sklearn.decomposition import NMF
model = NMF(n_components=8)
W = model.fit_transform(np.abs(my_D.T))
H = model.components_
components = H.T
activations = W.T
In [63]:
fig = plt.figure(2, figsize=(12,4))
ax = fig.add_subplot(1,2,1)
librosa.display.specshow(activations, x_axis='time')
ax.set_xlabel('Time'); ax.set_ylabel('Component'); ax.set_title('Activations')
ax = fig.add_subplot(1,2,2)
librosa.display.specshow(librosa.amplitude_to_db(components, ref=np.max), y_axis='log')
ax.set_xlabel('Component'); ax.set_ylabel('Frequency'); ax.set_title('Components')
Out[63]:
In [64]:
# 1.e-12以下となっている要素の数をコンポーネントごとに算出
for i in range(8):
print(sum(np.where(components[:, i] < 1.e-12, 1, 0)))
In [65]:
# Play back the reconstruction
# Reconstruct a spectrogram by the outer product of component k and its activation
D_k = components @ activations
# invert the stft after putting the phase back in
#y_k = librosa.istft(D_k * phase)
y_k = librosa.istft(D_k * my_phase)
# And playback
print('Full reconstruction')
IPython.display.Audio(data=y_k, rate=sr)
Out[65]:
In [66]:
components_ = np.zeros(components.shape)
components_[:, 0] = components[:, 0]
components_[:, 2] = components[:, 2]
components_[:, 6] = components[:, 6]
D_k = components_ @ activations
# invert the stft after putting the phase back in
y_k = librosa.istft(D_k * my_phase)
# And playback
print('Partly reconstruction')
IPython.display.Audio(data=y_k, rate=sr)
Out[66]:
まとめ¶
- SciPyの各モジュールとコード片を紹介
- fftpackとsignalで音の信号処理ができる
個人的によく使う関数¶
- scipy.ndimage.interpolation.shift
- 配列を単純にシフトし、要素が無くなったところに指定した値を埋める
- numpyのrollだと要素が回転しまう
In [67]:
from scipy.ndimage.interpolation import shift
xs = np.array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
print(shift(xs, -3, cval=0.0))
print(np.roll(xs, -3))
コメント
Comments powered by Disqus