Day 1, 2は、以下を実装するコードを作成する。
作成したプログラムが正しいかを確認するため、人工データを作成して検証する。
例を参考にコーディングしてください
Pythonでは以下のようなモジュール・ライブラリを利用する。
numpy
NumpyはPythonの主流なライブラリの一つで、ナムパイと読む。
Python単体で計算をおこなうより簡単に、かつ素早く計算を実行することが可能。
機械学習やデータ分析を中心に様々な分野で利用されている。
Numpy入門
matplotlib
matplotlibはnumpy配列を引数として、図を作成したりできる。
Matplotlib入門
# モジュール
import numpy as np
import matplotlib.pyplot as plt
人工データを作るときには試行のばらつきやノイズを乱数により作成する。
numpyのnp.random
を使い乱数を生成できる。生成されたデータはnumpyデータ型(クラス)のndarrayになる。ndarrayでは計算に有益なメソッドが多く定義されている(X.mean()
など、numpy公式サイト methodsの説明)
# 乱数の生成
""" 乱数の生成 """
import matplotlib.pyplot as plt
import numpy as np
NUM_SAMPLES = 100 # 定数
t = np.arange(NUM_SAMPLES)
# 一様分布(0から1)
noise = np.random.rand(NUM_SAMPLES)
mean_noise = noise.mean()
std_noise = noise.std()
print("mean = ", mean_noise, "std = ", std_noise)
# 正規分布(平均0, 分散1)
noise_normal = np.random.randn(NUM_SAMPLES)
mean_noise_normal = noise_normal.mean()
std_noise_normal = noise_normal.std()
print("mean = ", mean_noise_normal, "std = ", std_noise_normal)
# グラフ表示(標準的な方法)
fig, ax = plt.subplots()
ax.set_title("Artifical datasets")
ax.set_xlabel("Time [s]")
ax.set_ylabel("Amplitude [a.u.]")
ax.plot(noise, label="noise")
ax.plot(noise_normal, label="normalized noise")
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels)
plt.show()
脳波などの生体信号はある周波数帯のデータが強くなったり、機能的な意味があったりする。これを模倣するため、周期的な人工データを生成する。
# -5 ~ 5 まで、100個のデータ(ndarray)を np.linespaceを使って作成
t2 = # 自分で作成, np.linespaceを使う
# Sin信号の生成
y_sin = # 自分で作成
正弦波と乱数を組み合わせて人工時系列データを作成する。
# 例
""" 人工データの生成(1次元) """
import numpy as np
import matplotlib.pyplot as plt
# 関数化, defで関数を定義
def generate_artifical_timeseries(times, fs, freq):
"""
Generating artifical time series signals
y = generate_artifical_timeseries(times, fs, freq):
times: 時間(タイムスタンプ)の配列
fs: sampling frequency(サンプリング周波数)
freq: signal frequency(信号=人工データの周波数)
"""
omega = # 自分で作成、角周波数を定義
data = # 自分で作成、人工データを生成
noise = # 自分で作成、ノイズを生成
output = # 人工データ+ノイズを合成
return output
# サンプリング周波数、人工データの周波数、時間の長さを定義
FS = # [Hz]
F = # [Hz]
TIME = # [s] 秒
# np.arangeで時間データを作成
t = np.arange( )# 中身を自分で作成
# 信号生成
y = generate_artifical_timeseries(t, FS, F)
# 図で確認
fig, ax = plt.subplots()
ax.set_title("generated signal")
ax.set_xlabel("Time [s]")
ax.set_ylabel("Amplitude [a.u.]")
ax.plot(t, y)
plt.show()
信号の分析において、測定した信号同士の特性を比較する手法がある。 しかし、データ毎に信号の平均や分散が異なることが原因で正しく評価できないことがある。 そこで、測定した信号の成分の平均μを0、標準偏差σを1とすることで比較を可能にする処理を標準化(Z-score化)と呼ぶ。 元信号を$x(t)$とするとき、以下の$Z$で表される。
\[\begin{aligned} Z = \frac{x(t)-\mu}{\sigma} \end{aligned}\]下図に標準化の例を示す。 赤線が元信号で青線が標準化後の信号である。
標準化は単位・スケールの違う特徴量を連結して使用する際などに有効であるが、次元間のスケールが統一されている場合や使用する機械学習アルゴリズムによっては必ずしも必要ではない。
np.linespace
で時間軸を生成し、
$y = Aexp\left\lbrace-\frac{(x-\alpha)^2}{2\sigma^2}\right\rbrace$に当てはめて各パラメータに適当な値を入れた信号。
先に作った人工データやランダムデータでも可。
(例)
""" 正規化処理 """
import matplotlib.pyplot as plt
import numpy as np
# 関数の定義
def z_score(data):
"""
dataを入力して正規化したafter_dataを出力
"""
# (コーディング)
mean = # dataの平均値を求める
std = # dataの標準偏差を求める
z_scored = # z-scoreの算出
return z_scored
if __name__ == "__main__":
#(コーディング)
data = # データの作成(例:乱数、先に作った人工データを使用してもOK)
normalized_data = z_score(data)
# 比較のグラフ
fig, ax = plt.subplots()
ax.set_title(" ") # タイトル
ax.set_xlabel(" ") # x軸
ax.set_ylabel(" ") # y軸
ax.plot( ) # 元のデータを表示、labelはoriginalに設定
ax.plot( ) # 正規化したデータを表示、labelはz-scoredに設定
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels) # legendの表示
plt.show()
高速フーリエ変換(FFT)を用いて、特定の周波数帯域のパワーを抽出する。
実数のFFTについてはnumpyのrfftで良い。 https://numpy.org/doc/stable/reference/generated/numpy.fft.rfft.html
その他、FFTに関するメソッドは下記を参照。 https://numpy.org/doc/stable/reference/routines.fft.html#module-numpy.fft https://docs.scipy.org/doc/scipy/reference/fft.html#
np.arange
で時間軸を生成し、np.sin(x)
の$x$に使用して正弦波の時系列データを作成する。(設定した周波数freqに準拠させる)
その信号により高域の適当なノイズを付加したものを処理対象のデータとする。
""" 周波数解析 """
import matplotlib.pyplot as plt
import numpy as np
from scipy import signal
if __name__ == "__main__":
# 10Hzのデータ、ノイズ40Hzの例, 自分でいろいろと試してみてください
ft = 10 # 基本周波数
ft_noise = 40 # ノイズ周波数
fs = 100 # サンプリング周波数
duration = 3 # 時間
dt = 1 / fs # Sampling period
t = np.arange(0, duration, step=dt)
N = len(t) # サンプル数
# 2つの周波数を持つ人工データの作成
signal_orgnl = # 自分で作成
signal_noise = # 自分で作成
signal_add = # 自分で作成, 信号とノイズを合成
# 周波数解析(numpyのrfftを使用)
spectrum =
# FFTの複素数結果を絶対値に変換
spectrum_abs =
# 周波数軸のデータ作成(x軸のメモリ)
fq = np.fft.rfftfreq(N, dt) # rfftを使った場合
# アルファ波を抽出
alpha_power =
# スペクトル表示
fig = plt.figure(figsize=(10, 5))
plt.xlabel("x軸のタイトル", fontsize=14) # xlabelの設定
plt.ylabel("y軸のタイトル", fontsize=14) # ylabelの設定
plt.plot( , , label="signal") # 周波数スペクトルのプロット
plt.tight_layout()
plt.show()
ローパスフィルタは特定の周波数帯域(遮断周波数 $f_c$ )以上の信号を除去するフィルタである。
ここでは、不要な帯域(ノイズ)を除去し、信号成分を明確にすることを目的とする。
下図はローパスフィルタの適用例である。 図右上の一番左の山(4Hz)が信号であり、その他の山を除去したいノイズとして10Hzの遮断周波数を設定している。
うまく適用できれば元の4Hzの成分を得ることができる。
np.arange
で時間軸を生成し、np.sin(x)
の$x$に使用して正弦波の時系列データを作成する。(設定した周波数freqに準拠させる)
その信号により高域の適当なノイズを付加したものを処理対象のデータとする。
""" フィルタ処理 """
# バターワースフィルタ(バンドパス)
import matplotlib.pyplot as plt
import numpy as np
from scipy import signal
def bandpass_butter_filt(sig, fs, fl, fh, N=4):
"""bandpass_butter_filt
Argument
---
sig -> ndarray : input signal
fs -> float : sampling frequency
fl -> float : lowcut frequency
fh -> float : highcut frequency
N -> int : order of filter
Return
---
ret -> ndarray : filtered signal with bandpass butterworth filter
"""
# (コーディング)
return ret
if __name__ == "__main__":
# 10Hzのデータ、ノイズ40Hzの例
ft = 10 # 基本周波数
ft_noise = 40 # ノイズ周波数
fs = 100 # サンプリング周波数
duration = 3 # 時間
# カットオフ周波数の定義
lowcut_f = 5 # [Hz]
highcut_f = 15 # [Hz]
dt = 1 / fs # Sampling period
t = np.arange(0, duration, step=dt)
N = len(t) # サンプル数
# 2つの周波数を持つ人工データの作成
signal_orgnl =
signal_noise =
signal_orgnl =
# フィルタリング処理
signal_filtered = bandpass_butter_filt(signal_orgnl, fs, lowcut_f, highcut_f)
# 周波数解析
spectrum_orgnl =
# FFTの複素数結果を絶対値に変換
spectrum_orgnl =
# 周波数軸のデータ作成
fq = np.fft.rfftfreq(N, dt)
# 周波数解析(フィルタ処理後)
spectrum_filtered =
spectrum_filtered =
# グラフ表示
# 時系列波形の比較
# フィルタ適用後のスペクトル
大きすぎるサンプリング周波数はデータサイズを肥大化させ、各種処理が低速化してしまう。 そのためより低いサンプリング周波数になる様に信号を再サンプリングする事がある(ダウンサンプリング)。
元信号が持つ周波数成分を正確に再現するためには標本化定理(サンプリング定理)より以下の通り元信号が持つ最大の周波数成分$f$ [Hz]の2倍を超える周波数$f_s$ [Hz]によってサンプリングする必要がある。
\[fs > 2f\]例えば、遮断周波数$f_c$のローパスフィルタを通した信号が持つ最大の周波数成分はおよそ$f_c$となるため、再サンプリングに利用する周波数は$f_s > 2f_c$となる。ただし、ローパスフィルタの遮断周波数以降が瞬時に完全に減衰する事は無く遮断周波数以降の周波数成分が多少存在するために余裕を持たせる必要がある。
""" ダウンサンプリング """
import matplotlib.pyplot as plt
import numpy as np
import scipy.signal as signal
def generate_artific_signal(times):
"""適当な信号を生成"""
return ret
if __name__ == "__main__":
# Single trial
FS = 90 # サンプリング周波数
FS_DOWN = 30 # ダウンサンプル後の周波数
DURATION_S = 1 # 時間[s]
downsample_rate = int(np.ceil(FS / FS_DOWN))
n = int(np.floor(FS * DURATION_S)) # サンプル数
n_down = int(np.floor(FS_DOWN * DURATION_S)) # サンプル数(ダウンサンプリング後)
# 人工データの作成
t =
data = generate_artific_signal(t)
# 1. Resampling using scipy.siganal.resample
data_resample =
# 2. 自作ダウンサンプリング
t2 = np.arange(n_down) / FS_DOWN
# データを数個飛ばしに抽出
data_downsample =
# 3. decimation using scipy.signal.decimate
data_decimate =
# 図で確認
fig, ax = plt.subplots()
ax.set_title("downsampling")
ax.set_xlabel("Time [s]")
ax.set_ylabel("Amplitude [a.u.]")
ax.plot(t, data, label="oroginal")
ax.plot(t2, data_downsample, marker="o", label="downsample")
ax.plot(t2, data_resample, marker="s", label="resample")
ax.plot(t2, data_decimate, marker="D", label="decimate")
ax.legend()
plt.show()
測定信号の位相が統一されている場合、それらを加算平均(足し合わせてその回数で割る)することでS/N比を改善することができる。
$N$回の加算平均を行うと、ノイズ成分は$1/\sqrt{N}$になることから、S/N比が$\sqrt{N}$倍改善することになる。
ただし、データの総数が減ることによるデメリットとのトレードオフとなるため、加算平均回数は重要なパラメータとなる。
生体信号に時間経過に応じて発生するトレンド成分を除去するための補正を実装する。 このトレンド(低周波成分)をベースラインと呼ぶ場合がある。ベースラインをどのように定義するかは一概には言えないが、最小二乗法により多項式近似した曲線をベースラインとして生成信号から減算する手法を取り上げる。
最小二乗法による $n$ 次の多項式近似においては、
\[\begin{aligned} y = a_{n}x^{n} + a_{n-1}x^{n-1} + \cdots + a_1x+a_0 \end{aligned}\]と元の信号の誤差の二乗和が最小となるように係数$a$の行列$A$を定める。
$N$次で近似するとき、
\[X = \begin{pmatrix} x_1^N & \cdots & x_1 & 1 \newline x_2^N & \cdots & x_2 & 1 \newline \vdots & \cdots & \vdots & \vdots \newline x_n^N & \cdots & x_n & 1 \end{pmatrix} , A = \begin{pmatrix} a_N \newline \vdots \newline a_0 \end{pmatrix} , Y = \begin{pmatrix} y_1 \newline \vdots \newline y_n \end{pmatrix}\]とおくと、係数行列Aは、
\[\begin{aligned} (X^TX)A = X^TY \end{aligned}\]より、
\[\begin{aligned} A = (X^TX)^{-1}X^TY \end{aligned}\]で求められる。
下図に疑似信号に対する処理の概要を示す。
今回は青線の信号に対して、紫線に示すような傾きとバイアスから構成される線形成分をベースライン(トレンド)として減算する処理を実装する。
トレンド補正が正しく実装されていれば黒線で示されるように元の信号(赤線)と同様の結果が得られる。
np.linespace
で時間軸を生成し、np.sin(x)
の$x$に使用して正弦波の時系列データを作成する。
そこにトレンド成分を付加したものを補正対象の信号として用意する。(簡単なものなら$t$を足せば良い)