web-dev-qa-db-ja.com

Pythonベースライン修正ライブラリ

現在いくつかのラマンスペクトルデータを使用していますが、蛍光のゆがみによって引き起こされたデータを修正しようとしています。下のグラフを見てください。

enter image description here

私は私が望むものを達成するのにかなり近いです。ご覧のとおり、私はすべてのデータに多項式を適合させようとしていますが、実際には多項式を極小値に適合させているだけです。

理想的には、元のデータから差し引くと次のような結果になる多項式フィッティングが必要です。

enter image description here

これをすでに行う組み込みのライブラリはありますか?

そうでない場合、私に推奨できる簡単なアルゴリズムはありますか?

16
Sparrowcide

私は私の質問に対する答えを見つけました。これに遭遇したすべての人のために共有しています。

2005年にP. EilersとH. Boelensによって "Asymmetric Least Squares Smoothing"と呼ばれるアルゴリズムがあります。この論文は無料で、Googleで見つけることができます。

def baseline_als(y, lam, p, niter=10):
  L = len(y)
  D = sparse.csc_matrix(np.diff(np.eye(L), 2))
  w = np.ones(L)
  for i in xrange(niter):
    W = sparse.spdiags(w, 0, L, L)
    Z = W + lam * D.dot(D.transpose())
    z = spsolve(Z, w*y)
    w = p * (y > z) + (1-p) * (y < z)
  return z
23
Sparrowcide

次のコードはPython 3.6で動作します。

これは、受け入れられた正解から適応され、密行列diff計算(メモリの問題を簡単に引き起こす可能性があります)を回避し、rangexrangeではない)を使用します。

import numpy as np
from scipy import sparse
from scipy.sparse.linalg import spsolve

def baseline_als(y, lam, p, niter=10):
  L = len(y)
  D = sparse.diags([1,-2,1],[0,-1,-2], shape=(L,L-2))
  w = np.ones(L)
  for i in range(niter):
    W = sparse.spdiags(w, 0, L, L)
    Z = W + lam * D.dot(D.transpose())
    z = spsolve(Z, w*y)
    w = p * (y > z) + (1-p) * (y < z)
  return z
6
jpantina

これは古い質問であることは知っていますが、数か月前に偶然見つけて、spicy.sparseルーチンを使用して同等の回答を実装しました。

# Baseline removal                                                                                            

def baseline_als(y, lam, p, niter=10):                                                                        

    s  = len(y)                                                                                               
    # assemble difference matrix                                                                              
    D0 = sparse.eye( s )                                                                                      
    d1 = [numpy.ones( s-1 ) * -2]                                                                             
    D1 = sparse.diags( d1, [-1] )                                                                             
    d2 = [ numpy.ones( s-2 ) * 1]                                                                             
    D2 = sparse.diags( d2, [-2] )                                                                             

    D  = D0 + D2 + D1                                                                                         
    w  = np.ones( s )                                                                                         
    for i in range( niter ):                                                                                  
        W = sparse.diags( [w], [0] )                                                                          
        Z =  W + lam*D.dot( D.transpose() )                                                                   
        z = spsolve( Z, w*y )                                                                                 
        w = p * (y > z) + (1-p) * (y < z)                                                                     

    return z

乾杯、

ペドロ。

1
Pedro Fluxa

最近、私はこの方法を使用する必要がありました。回答のコードはうまく機能しますが、明らかにメモリを使いすぎます。だから、これが最適化されたメモリ使用量の私のバージョンです。

def baseline_als_optimized(y, lam, p, niter=10):
    L = len(y)
    D = sparse.diags([1,-2,1],[0,-1,-2], shape=(L,L-2))
    D = lam * D.dot(D.transpose()) # Precompute this term since it does not depend on `w`
    w = np.ones(L)
    W = sparse.spdiags(w, 0, L, L)
    for i in range(niter):
        W.setdiag(w) # Do not create a new matrix, just update diagonal values
        Z = W + D
        z = spsolve(Z, w*y)
        w = p * (y > z) + (1-p) * (y < z)
    return z

以下のベンチマークによると、これも約1.5倍高速です。

%%timeit -n 1000 -r 10 y = randn(1000)
baseline_als(y, 10000, 0.05) # function from @jpantina's answer
# 20.5 ms ± 382 µs per loop (mean ± std. dev. of 10 runs, 1000 loops each)

%%timeit -n 1000 -r 10 y = randn(1000)
baseline_als_optimized(y, 10000, 0.05)
# 13.3 ms ± 874 µs per loop (mean ± std. dev. of 10 runs, 1000 loops each)

注1:元の記事では次のように述べています。

アルゴリズムの基本的な単純さを強調するために、反復回数は10に固定されています。実際のアプリケーションでは、重みに変化があるかどうかを確認する必要があります。そうでない場合、収束は達成されています。

つまり、反復を停止するためのより正しい方法は、||w_new - w|| < tolerance

注2:別の有用な引用(@glycoaddictのコメントから)は、パラメーターの値を選択する方法を示しています。

2つのパラメーターがあります。非対称性のpと滑らかさのλです。どちらも手元のデータに合わせて調整する必要があります。一般に、0.001≤p≤0.1は正のピークを持つ信号の場合に適切な選択であり、102≤λ≤109ですが、例外が発生する場合があります。いずれの場合でも、対数λに対してほぼ線形であるグリッド上でλを変化させる必要があります。多くの場合、適切なパラメータ値を取得するには、目視検査で十分です。

1
Rustam Guliev