質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Matplotlib

MatplotlibはPythonのおよび、NumPy用のグラフ描画ライブラリです。多くの場合、IPythonと連携して使われます。

NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

5回答

2179閲覧

直線データのノイズを除去したい

cream_puff

総合スコア6

Matplotlib

MatplotlibはPythonのおよび、NumPy用のグラフ描画ライブラリです。多くの場合、IPythonと連携して使われます。

NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/07/07 08:55

編集2021/07/07 11:28

前提・実現したいこと

y軸の正方向のノイズを含む直線データからノイズを取り除き、直線を取り出すプログラムをつくっています。ノイズの大きさは1%程度です。直線に対して高速フーリエ変換(FFT)は不適なので、移動平均とスプライン補間による平滑化を試みました。しかし、以下のコードでは取り出したい直線をうまく抽出できず困っています。ノイズを含むy=10-x+noise(グラフのlinear, 青)からノイズを含まないy=10-x(グラフのoriginal, 黒)を取り出すのが目標です。改善点、アドバイス、別のアプローチなどがあれば教えていただけると幸いです。

以下のサイトを参考にしました。
https://snova301.hatenablog.com/entry/2018/10/07/135233

実行結果
実行結果

拡大図
拡大図

該当のソースコード

Python

1import numpy as np 2import matplotlib.pyplot as plt 3from scipy.interpolate import interp1d 4 5 6def make_func(in_x): 7 np.random.seed(0) 8 out_y = 10 - in_x + 0.3*(np.random.rand(in_x.size)) # 10-x に正方向のみの誤差を加える 9 return out_y 10 11 12def spline_interp(in_x, in_y): 13 out_x = np.linspace(np.min(in_x), np.max(in_x), np.size(in_x)*100) # もとのxの個数より多いxを用意 14 func_spline = interp1d(in_x, in_y, kind='cubic') # 3次のスプライン曲線 15 out_y = func_spline(out_x) # func_splineはscipyオリジナルの型 16 17 return out_x, out_y 18 19 20def moving_avg(in_x, in_y): 21 np_y_conv = np.convolve(in_y, np.ones(3)/float(3), mode='valid') 22 out_x_dat = np.linspace(np.min(in_x), np.max(in_x), np.size(np_y_conv)) 23 24 return out_x_dat, np_y_conv 25 26 27def main(): 28 x1 = np.linspace(0, 10, 100) 29 y1 = make_func(x1) 30 31 x2, y2 = spline_interp(x1, y1) 32 33 x3, y3 = moving_avg(x1, y1) 34 x4, y4 = spline_interp(x3, y3) 35 36 plt.plot(x1, 10-x1, color='k', label='original', alpha=0.7) 37 plt.plot(x1, y1, color='b', label='linear', alpha=0.7) 38 plt.plot(x2, y2, color='r', label='spline', alpha=0.7) 39 plt.plot(x4, y4, color='g', label='average + spline', alpha=0.7) 40 plt.legend() 41 plt.show() 42 43 44if __name__ == '__main__': 45 main()

補足情報(FW/ツールのバージョンなど)

python 3.8.8

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jbpb0

2021/07/07 09:24 編集

> linear(青)からoriginal(黒)を取り出す どれ?
cream_puff

2021/07/07 09:42

わかりづらくて申し訳ありません。編集後の内容で伝わりますでしょうか?
jbpb0

2021/07/07 09:50

質問者さんが、グラフの画像をアップロードするのを忘れているのだと思って、質問したのですが もしかしたら、自分でコードを実行して、表示されるグラフを見ろ、ってこと?
jbpb0

2021/07/07 09:52

ノイズの素性は既知なのでしょうか? どんな分布の形状(ガウスノイズ?など)かとか、ノイズの大きさはどれくらいかとか
cream_puff

2021/07/07 11:33

申し訳ありませんでした。ノイズの大きさは1%程度、形状は不明です。測定データなのでノイズの大きさはガウス分布に従うと思いますが、ノイズはy軸正方向に現れる可能性が高いです。
jbpb0

2021/07/08 05:20

ノイズの素性が明らかでは無いのなら、データからノイズの分布形状や大きさを推定して、ノイズの影響によるy+方向の平均オフセットを算出して、最小二乗法等の方法で決めた直線を算出したオスセット分y-方向にずらす、みたいなことをやらないといけないような気がします > ノイズはy軸正方向に現れる可能性が高い 「可能性が高い」ということは、頻度は低いけれどy-方向のノイズも有り得る、ということですか? もしそうなら、質問を編集して、そのように書いてください ppaulさんは、「ノイズが絶対に正の値である」ことを前提に回答を書いてます マイナスのノイズも(頻度は低くても)有り得るなら、話が変わります
ozwk

2021/07/16 05:41

ノイズが正方向なのではなくて、測定データに正のオフセットが乗っているのでは?
guest

回答5

0

ベストアンサー

ノイズが真の値に与える影響をどのように仮定するかによりますが、以下のように仮定します。

y ~ uniform(min = intercept + coef * x , max = intercept + coef * x + shift)

intercept + coef * x の部分は真の値を表し、一様分布の最小値とします。ノイズがshiftであり、何らかの固定値とします。実際の観測値は、真の値とノイズを加えた値の間のどこかに一様分布に従って出現するものとします。

上記のようなモデルにおいてintercept,coef,shiftを推計することができれば求める真値を得ることができます。方法としてはstanを使ってはどうでしょうか。実装は以下になります。

#### 学習結果を安定させるため、1)係数の正負を反転、2)y1の最小値がゼロから始まるように調整 y1_ = -y1 + np.max(y1) import pystan #### pythonからStanをキックするためのモジュール #### モデル本体の定義 stan_code = """ data { int N ; // data数 real X[N] ; // 説明変数 real Y[N] ; // 被説明変数 } parameters { real coef ; // 係数 real intercept ; // 切片 real<lower=0> shift ; // 正方向へのシフト値 } model { for (i in 1:N) { Y[i] ~ uniform(coef * X[i] + intercept ,coef * X[i] + intercept + shift) ; } } """ model = pystan.StanModel(model_code=stan_code) ### モデルのコンパイル #### stan投入用データの編集 stan_data = {'N' : len(x1), 'X' : x1, 'Y' : y1_} #### モデルの学習 result = model.sampling(data=stan_data,iter=6000,chains=1,seed=1234,warmup=3000) #### 学習結果からcoef,intercept,shiftを取得して元の値に戻す Coef = np.mean(result.extract()['coef']) * -1 Intercept = np.mean(result.extract()['intercept']) + np.max(y1) Shift = np.mean(result.extract()['shift']) #### 推計結果の算出 predict = Coef*x1 + Intercept - Shift

上記は、あくまでもノイズが一様分布の場合に限ります。他の分布によるのであれば、stan部分を変更する必要がありますし、場合によってはpreproccessing部分は必要ありません。

投稿2021/07/12 18:27

編集2021/07/14 20:44
R.Shigemori

総合スコア3376

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

移動平均の横軸の値の算出方法がおかしいので、修正例を書いときます
ついでに、平均の長さを簡単に変えられるようにしました

python

1def moving_avg(in_x, in_y, ave_len): 2 if ave_len % 2 == 0: 3 ave_len = ave_len + 1 4 np_y_conv = np.convolve(in_y, np.ones(ave_len)/float(ave_len), mode='valid') 5 #out_x_dat = np.linspace(np.min(in_x), np.max(in_x), np.size(np_y_conv)) 6 ave_len_h = int((ave_len - 1) / 2) 7 out_x_dat = np.linspace(in_x[ave_len_h], in_x[-ave_len_h-1], np.size(np_y_conv)) 8 return out_x_dat, np_y_conv 9 10 11def main(): 12 x1 = np.linspace(0, 10, 100) 13 y1 = make_func(x1) 14 x2, y2 = spline_interp(x1, y1) 15 x3, y3 = moving_avg(x1, y1, 21) 16 x4, y4 = spline_interp(x3, y3) 17 plt.plot(x1, 10-x1, color='k', label='original', alpha=0.7) 18 plt.plot(x1, y1, color='b', label='linear', alpha=0.7) 19 #plt.plot(x2, y2, color='r', label='spline', alpha=0.7) 20 plt.plot(x3, y3, color='r', label='average', alpha=0.7) 21 #plt.plot(x4, y4, color='g', label='average + spline', alpha=0.7) 22 plt.legend() 23 plt.show()

ただし、あくまでも、移動平均のみの修正で、ノイズによるオフセットの補正は入ってません

平均の長さを長くすれば、そこそこ直線っぽくはなります
ただし、直線と分かっているのなら、最小二乗法で直線にフィットする方がいいです
移動平均を勧めているわけではありません
移動平均のグラフ

投稿2021/07/08 07:34

編集2021/07/08 07:42
jbpb0

総合スコア7653

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

cream_puff

2021/07/16 05:20

ありがとうございます。参考にさせていただきます。
guest

0

ノイズが絶対に正の値であるということであれば、以下の方法があります。

python

1import numpy as np 2import matplotlib.pyplot as plt 3 4def make_func(in_x, noise): 5 np.random.seed(0) 6 out_y = 10 - in_x + noise*(np.random.rand(in_x.size)) # 10-x に正方向のみの誤差を加える 7 return out_y 8 9def main(number, noise): 10 x1 = np.linspace(0, 10, number+1) 11 y1 = make_func(x1, noise) 12 xy1 = np.polyfit(x1,y1,1) 13 y2 = np.poly1d(xy1)(x1) 14 y3 = y2 + min(y1-y2) 15 plt.plot(x1, 10-x1, color='k', label='original', alpha=0.7) 16 plt.plot(x1, y1, color='r', label='noise', alpha=0.7) 17 plt.plot(x1, y2, color='b', label='linear', alpha=0.7) 18 plt.plot(x1, y3, color='g', label='adjust', alpha=0.7) 19 plt.legend() 20 plt.show()

number=100、noise=2.0だと少しずれます。
number=100、noise=2.0
number=1000、noise=2.0というようにサンプル数を増やすと、ほぼ元の関数に一致します。
number=1000、noise=2.0

投稿2021/07/07 15:08

ppaul

総合スコア24670

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

cream_puff

2021/07/16 05:21

ありがとうございます。参考にさせていただきます。
guest

0

生方向のみのノイズが乗っている、とするなら、なにをしたところでもとの信号の復元は無理です
普通は正負両方向のノイズが乗っているという前提で移動平均を掛けてノイズを除去するものです

投稿2021/07/07 12:44

y_waiwai

総合スコア88042

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ノイズの量や性質にもよりますが、こんな感じで直線を近似するのはどうでしょうか?

python

1 xy1 = np.polyfit(x1,y1,1) 2 y5 = np.poly1d(xy1)(x1) 3 4 plt.plot(x1, y5, color='r', label='fit', alpha=0.7)

投稿2021/07/07 09:17

TaroToyotomi

総合スコア1449

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

cream_puff

2021/07/07 09:47

早速ありがとうございます。ノイズを含んだまま直線でフィッティングすると取り出したい直線をy軸正方向に平行移動した直線 y = 10 - x + (ノイズの平均値) が出力されます。ノイズを取り除いた y = 10 - x が出力するようにしたいと考えております。
cream_puff

2021/07/07 12:30

ppaulさんありがとうございます。読ませていただきました。標本平均はガウス分布に従うということは、フィッティング結果は y = 10 - x + (ガウス分布) となるということでしょうか。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問