web-dev-qa-db-ja.com

pythonの符号変更を効率的に検出

私はこの男がしたことを正確にしたいです:

Python-符号の変更を数える

しかし、私はそれを超高速で実行するために最適化する必要があります。手短に言えば、時系列を取り、それが交差するたびにゼロと交差する(符号が変わる)ことを伝えたいと思います。ゼロクロッシングの間の時間を記録したいと思います。これは実際のデータ(32ビット浮動小数点)であるため、正確にゼロである数値がすべてあるとは思えないため、それは重要ではありません。現在、タイミングプログラムを実施しているので、結果を測定して誰が勝つかを確認します。

私の解決策は(マイクロ秒)を与えます:

open data       8384
sign data       8123
zcd data        415466

ご覧のとおり、ゼロクロス検出器は遅い部分です。これが私のコードです。

import numpy, datetime

class timer():
    def __init__(self):
        self.t0 = datetime.datetime.now()
        self.t = datetime.datetime.now()
    def __call__(self,text='unknown'):
        print text,'\t',(datetime.datetime.now()-self.t).microseconds
        self.t=datetime.datetime.now()

def zcd(data,t):
    sign_array=numpy.sign(data)
    t('sign data')
    out=[]
    current = sign_array[0]
    count=0
    for i in sign_array[1:]:
        if i!=current:
            out.append(count)
            current=i
            count=0
        else: count+=1
    t('zcd data')
    return out

def main():
    t = timer()
    data = numpy.fromfile('deci.dat',dtype=numpy.float32)
    t('open data')
    zcd(data,t)

if __name__=='__main__':
    main()
30
chriscauley

何について:

import numpy
a = [1, 2, 1, 1, -3, -4, 7, 8, 9, 10, -2, 1, -3, 5, 6, 7, -10]
zero_crossings = numpy.where(numpy.diff(numpy.sign(a)))[0]

出力:

> zero_crossings
array([ 3,  5,  9, 10, 11, 12, 15])

つまり、zero_crossingsには、ゼロクロッシングが発生する要素のインデックスbeforeが含まれます。要素afterが必要な場合は、その配列に1を追加するだけです。

62
Jim Brissom

Jay Borsethが述べたように、受け入れられた回答は0を含む配列を正しく処理しません。

私は使用を提案します:

import numpy as np
a = np.array([-2, -1, 0, 1, 2])
zero_crossings = np.where(np.diff(np.signbit(a)))[0]
print(zero_crossings)
# output: [1]

A)numpy.signbit()を使用する方がnumpy.sign()よりも少し速いので、実装が単純であるため、私は推測し、b)入力配列のゼロを正しく処理しています。

しかし、おそらく1つの欠点があります。入力配列がゼロで開始および停止する場合、最初ではゼロクロッシングが見つかりますが、最後ではありません...

import numpy as np
a = np.array([0, -2, -1, 0, 1, 2, 0])
zero_crossings = np.where(np.diff(np.signbit(a)))[0]
print(zero_crossings)
# output: [0 2]
28
Dominik Neise

ゼロクロッシングをカウントし、コードからさらに数ミリ秒を絞る別の方法は、nonzeroを使用して符号を直接計算することです。 dataの1次元配列があると仮定します。

_def crossings_nonzero_all(data):
    pos = data > 0
    npos = ~pos
    return ((pos[:-1] & npos[1:]) | (npos[:-1] & pos[1:])).nonzero()[0]
_

あるいは、ゼロ交差の特定の方向(たとえば、正から負へ)のゼロ交差を数えたい場合、これはさらに高速です。

_def crossings_nonzero_pos2neg(data):
    pos = data > 0
    return (pos[:-1] & ~pos[1:]).nonzero()[0]
_

私のマシンでは、これらはwhere(diff(sign))メソッドよりも少し高速です(20サイクル、全部で40の交差を含む10000サインサンプルの配列のタイミング):

_$ python -mtimeit 'crossings_where(data)'
10000 loops, best of 3: 119 usec per loop

$ python -mtimeit 'crossings_nonzero_all(data)'
10000 loops, best of 3: 61.7 usec per loop

$ python -mtimeit 'crossings_nonzero_pos2neg(data)'
10000 loops, best of 3: 55.5 usec per loop
_
10
lmjohns3

aに値0が含まれている場合、Jim Brissomの回答は失敗します。

import numpy  
a2 = [1, 2, 1, 1, 0, -3, -4, 7, 8, 9, 10, -2, 1, -3, 5, 6, 7, -10]  
zero_crossings2 = numpy.where(numpy.diff(numpy.sign(a2)))[0]  
print zero_crossings2  
print len(zero_crossings2)  # should be 7

出力:

[ 3  4  6 10 11 12 13 16]  
8  

ゼロクロッシングの数は7でなければなりませんが、sign()は、0が渡された場合は0、正の場合は1、負の値の場合は-1を返すため、diff()はゼロを含む遷移を2回カウントします。

代替案は次のとおりです。

a3 = [1, 2, 1, 1, 0, -3, -4, 7, 8, 9, 10, 0, -2, 0, 0, 1, 0, -3, 0, 5, 6, 7, -10]  
s3= numpy.sign(a3)  
s3[s3==0] = -1     # replace zeros with -1  
zero_crossings3 = numpy.where(numpy.diff(s3))[0]  
print s3  
print zero_crossings3  
print len(zero_crossings3)   # should be 7

これは正しい答えを与えます:

[ 3  6 10 14 15 18 21]
7
8
Jay Borseth

時間を計りたいですか?それとも、できるだけ速くしたいですか?

タイミングは簡単です。それを何億回も実行し、それをストップウォッチし、そして何百万で割ります。

それをできるだけ速くするために、あなたがする必要があるのは、何が時間がかかっているのか、そしてあなたがより良い方法でできることを見つけることです。私は1)ランダムポーズテクニック、または2)シングルステップテクニックのいずれかを使用しています。

3
Mike Dunlavey

私は人々がソリューションでdiffを頻繁に使用しているのを見ていますが、xorははるかに高速で、ブールの場合も結果は同じです(diffを使用すると非推奨の警告が表示されるという事実もそうかもしれません... :) )次に例を示します。

positive = a2 > 0
np.where(np.bitwise_xor(positive[1:], positive[:-1]))[0]

それが私のために比較するためにそれが約1.5時間速くなると測定する時間:)

Edgeケースを気にしない場合は、使用する方がよい場合があります

positive = np.signbit(a2)

しかし、ポジティブ= a2> 0は、signbitよりも高速で(かつクリーン)、かつ0をチェックしているように見えます(たとえば、positive = np.bitwise_or(np.signbit(a2)、np.logical_not(a2))の方が遅い...)

1
ntg

特定のアプリケーションに適した別の方法は、式np.diff(np.sign(a))の評価を拡張することです。

この式が特定の場合にどのように反応するかを比較すると:

  1. ゼロなしの立ち上がり交差:np.diff(np.sign([-10, 10]))array([2])を返します
  2. ゼロとの立ち上がり交差:np.diff(np.sign([-10, 0, 10]))array([1, 1])を返します
  3. ゼロなしの下降交差:np.diff(np.sign([10, -10]))array([-2])を返します
  4. ゼロとの立ち下がり交差:np.diff(np.sign([10, 0, -10]))array([-1, -1])を返します

したがって、1と2で返されたパターンについてnp.diff(...)を評価する必要があります。

sdiff = np.diff(np.sign(a))
rising_1 = (sdiff == 2)
rising_2 = (sdiff[:-1] == 1) & (sdiff[1:] == 1)
rising_all = rising_1
rising_all[1:] = rising_all[1:] | rising_2

およびケース3.および4.の場合:

falling_1 = (sdiff == -2) #the signs need to be the opposite
falling_2 = (sdiff[:-1] == -1) & (sdiff[1:] == -1)
falling_all = falling_1
falling_all[1:] = falling_all[1:] | falling_2

この後、私たちは簡単にインデックスを見つけることができます

indices_rising = np.where(rising_all)[0]
indices_falling = np.where(falling_all)[0]
indices_both = np.where(rising_all | falling_all)[0]

このアプローチは「遅い」ループを使用せずに管理できるため、妥当な速度である必要があります。

これは、他のいくつかの回答のアプローチを組み合わせたものです。

0
lagru