이번 포스팅에서는 주기성을 가지는 센서 시계열 데이터, 음향 데이터, 신호 처리 데이터 등에서 많이 사용되는 스펙트럼 분석 (spectral analysis) 에 대해서 소개하겠습니다. 

 

많은 시계열 데이터는 주기적인 패턴 (periodic behavior) 을 보입니다. 스펙트럼 분석 은 시계열 데이터에 내재되어 있는 주기성(periodicity) 을 찾아내는 분석기법으로서, spectral analysis, spectrum analysis, frequency domain analysis 등의 이름으로 불립니다. 

 

스펙트럼 분석을 할 때 우리는 먼저 시계열 데이터를 시간 영역(time domain)에서 주파수 영역(frequency domain)로 변환을 합니다. 시계열의 공분산스펙트럼 확률밀도함수 (spectral density function) 로 알려진 함수로 재표현될 수 있습니다. 스펙트럼 밀도함수는 시계열과 다른 주파수의 사인/코사인 파동 간의 제곱 상관계수로 구하는 주기도(periodogram) 를 사용하여 추정할 수 있습니다. (Venables & Ripley, 2002)

 

아래의 그림은 시계열(time series)과 스펙트럼(spectrum, frequency) 간의 관계를 직관적으로 이해할 수 있게 도와줍니다. 

 

[ Time Series vs. Spectrum/ Frequency, Fourier Transform(FT) vs. Inverse FT ]

 

time-series vs. spectrum

 

 

참고로, 주파수(frequency)는 단위 시간 당 반복되는 이벤트가 발생하는 회수를 의미합니다. 주파수를 측정하는데 사용되는 단위인 Hz (Hertz, 헤르쯔) 는 전자기파의 존재를 처음으로 증명한 Heinrich Rudolf Hertz 의 이름에서 따온 것인데요, 가령 2 Hz 는 1초에 2번의 반복 사이클이 발생하는 시계열의 경우, 4 Hz 는 1초에 4번의 반복 사이클이 발행하는 시계열의 주파수에 해당합니다. (아래 그림 예시 참조)

 

frequency (* https://rfriend.tistory.com)

 

 

 

주기성을 띠는 시계열 데이터나 공간 데이터로 부터 주파수 (spectrum, frequency) 를 계산하거나, 반대로 주파수로 부터 원래의 데이터로 변환할 때 고속 푸리에 변환 (Fast Fourier Transform, 줄여서 FFT) 알고리즘을 많이 사용합니다.  시계열을 주파수로 변환하는 과정 자체를 Fourier Transform 이라고 하며, 이산 푸리에 변환 (Discrete Fourier Transform, DFT) 은 시계열 값을 다른 주파수의 구성요소들로 분해함으로써 구할 수 있습니다. 지금까지 가장 널리 사용되는 고속 푸리에 변환 (FFT) 알고리즘은 Cooley-Tukey 알고리즘 입니다.  이것은 분할-정복 알고리즘 (divide-and-conqer algorithm) 으로서, 반복적으로(recursively) 원래의 시계열 데이터를 훨씬 적은 개수의 주파수로 분할합니다. 

 

[ Fast Fourier Transform(FFT) vs. Inverse FFT]

FFT vs. Inverse FFT

 

 

이제 Python 의 spectrum 모듈을 사용해서 스펙트럼 분석을 해보겠습니다. 터미널에서 $ pip install spectrum 으로 먼저 spectrum 모듈을 설치해줍니다. 

 

## installment of spectrum python library at a terminal
$ pip install spectrum

 

 

spectrum 모듈에서 Periodogramm data_cosine 메소드를 불러옵니다.

예제로 사용할 샘플데이터로서, data_cosine() 메소드를 사용해서 200 Hz 주파수 (freq=200) 의 주기성을 띠는 코사인 신호(cosine signal)에 백색 잡음 (amplitude 0.1) 도 포함되어 있는 이산형 샘플 데이터 1,024개 (N=1024, sampling=1024) 를 생성해보겠습니다. 

 

## -- importing modules
from spectrum import Periodogram, data_cosine

## generating a toy data sets
## data contains a cosine signal with a frequency of 200Hz 
## buried in white noise (amplitude 0.1). 
## The data has a length of N=1024 and the sampling is 1024Hz.

data = data_cosine(N=1024, A=0.1, sampling=1024, freq=200)

## plotting the generated data
plt.rcParams['figure.figsize'] = [18, 8]
plt.plot(data)
plt.show()

 

 

위의 시계열 그래프가 1,024개 데이터 포인트가 모두 포함되다보니 코사인 신호의 주기성이 눈에 잘 안보이는데요, 앞 부분의 50개만 가져다가 시도표로 그려보면 아래처럼 코사인 파동 모양의 주기성을 띠는 데이터임을 눈으로 확인할 수 있습니다. 

 

plt.rcParams['figure.figsize'] = [18, 8]
plt.plot(data[:50])
plt.show()

cosine signal waves

 

 

spectrum 모듈의 Periodogram() 메소드에 위에서 생성한 샘플 데이터를 적용해서 periodogram 객체를 만든 후에 run() 함수를 실행시켜서 스펙트럼을 추정할 수 있고, p.plot() 으로 간단하게 추정한 스펙트럼을 시각화할 수 있습니다. 

 

## the Power Spectrum Estimation method provided in spectrum.
## creating an object Periodogram. 
p = Periodogram(data, sampling=1024)

## running the spectrum estimation
p.run() # or p()

p.plot(marker='o')

 

 

스펙트럼 분석 결과 중 개수(N), 주파수의 세기 (power of spectrum dendity) 속성값을 확인할 수 있습니다.  

 

## some information are stored and can be retrieved later on.
p.N
[Out] 1024

p.psd

# [Out] array([1.06569152e-04, 1.74100855e-03, 3.93335837e-03, 1.10485152e-03,
#        8.28499278e-03, 7.64848301e-04, 2.94332240e-03, 7.35029918e-03,
#        1.21273520e-03, 3.72342384e-05, 1.87058542e-03, 4.35817479e-03,
#        6.60803332e-04, 3.01094523e-03, 3.14914528e-03, 3.40040266e-03,
#        1.70112710e-04, 4.04579597e-04, 1.73102721e-03, 5.17048610e-03,
#        5.89537834e-03, 3.43004928e-03, 2.60894164e-03, 2.29556960e-03,
#        6.23836725e-03, 7.35588289e-03, 3.88793797e-03, 2.25691304e-03,
#        ... 중간 생략함 ...
#        2.57852156e-03, 1.23483225e-03, 1.49139300e-03, 9.09082081e-04,
#        5.24999741e-04])

 

 

우리가 위에서 data_cosine(N=1024, A=0.1, sampling=1024, freq=200) 메소드를 사용해서 200 Hz 의 데이터를 생성하였기 때문에, 이 샘플 데이터로부터 주파수를 분해하면 200 Hz 가 되어야 할텐데요, 스펙트럼 분석 결과도 역시 200 Hz 에서 스파이크가 엄청 높게 튀는군요. 

 

plt.rcParams['figure.figsize'] = [10, 6]
plt.plot(p.psd)
plt.show()

 

 

* R 을 이용한 스펙트럼 분석은 https://rfriend.tistory.com/64 를 참고하시기 바랍니다. 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

데이터 변환 방법으로서

(1) 표준화

(2) 정규분포화

(3) 범주화

     - 이산형화

     - 이항변수화

(4) 개수 축소

(5) 차원 축소

     - 주성분분석

     - 요인분석 

(6) 시그널 데이터 변환

   - 푸리에 변환 (FFT: Fast Fourier Transform)

    - 웨이블릿 변환(Wavelet Transform)

의 6개 구분 중에서

 

전파, 진동, 소리, 파도, 빛 등 시간(time domain)에 따라 주기성(periodicity)을 띠면서 파형을 형성하는 데이터를 주파수(spectrum domain) 대역별로 세기로 변환하는 푸리에 변환(Fourier Transform)에 대해서 알아보겠습니다.  참고로, 푸리에(Jean-Baptiste Joseph Fourier, 1768~1830)는 프랑스의 수학자이자 물리학자로서, 푸리에 변환은 바로 이분의 이름을 딴 것이랍니다.  저 아래에 애니메이션의 오른쪽 하단에 사진이 푸리에가 되겠습니다.

 

 

 

푸리에 변환은 물리나 공업 분야에서 폭넓게 사용되고 있기에, 특히 시그널 데이터, 기계 데이터, 센서 데이터 등의 주기성을 띤 데이터를 분석하는 분이라면 반드시 알아야 할 변환이라고 하겠습니다.

 

푸리에 변환을 이해하려면 삼각함수(특히 사인함수, 코사인함수), 미적분, 함수의 사칙연산, 함수의 직교성, 푸리에 급수와 계수 등에 대해서 알아야 하는데요, 어려운 수학 공식은 다른 교재를 참고하시기 바라며, 이번 포스팅에서는 직관적으로 이해할 수 있는 그림과 예시를 들어서 가급적 쉽게 설명을 해보겠습니다.

 

아래의 그림처럼 시간에 따른 진폭 데이터를 => 주파수별 세기의 데이터로 변환하는 작업을 푸리에변환이라고 하며, 그 반대를 푸리에역변환이라고 합니다.

 

 

[ 푸리에변환과 푸리에역변환 ]

 

 

 

먼저, 일정한 괘도를 회전하는 운동을 하는 시간함수로 나타내는 법에 대해서 알아보겠습니다.  아래 애니메이션의 왼쪽이 일정한 속도로 반지름이 일정한 괘도를 회전하는 운정이 되겠구요, 오른쪽이 이 회전운동을 시간축에 옮겨놓았을 때의 모양입니다.  전형적인 코사인(cosine) 형태를 띠고 있습니다.

 

* 출처: http://www.di.fc.ul.pt/~jpn/r/fourier/fourier.html

 

 

 

아래는 왼쪽의 반지름 거리와 시간 주기가 다른 4개의 회전운동을 오른쪽에 시간 축에 진폭을 나타낸 그래프가 되겠습니다.  아래 보는 것처럼 회전운동은 시간을 축으로 해서 진폭이 변화하는 값을 사인(sine) 또는 코사인(cosine) 함수로 나타낼 수 있습니다.

 

* 출처: http://www.di.fc.ul.pt/~jpn/r/fourier/fourier.html

 

 

 

이렇게 주기성을 띤 회전운동을 시간함수로 나타낼 수 있는데요, 이 시간함수는 사실 여러개의 주파수를 띤 시간함수들이 합해진 것입니다.  아래 예시로 든 그림에서는 주기(주파수)가 다른 3개의 시간함수가 함쳐져서 1개의 시간함수를 형성하고 있는데요, 주파수별로 필터링을 해서(주파수 성분을 구한다고 함) 세기가 큰 (peaks) 주파수를 헤아리면 되겠습니다. 시간함수를 알면 주파수 스펙트럼을 구할 수 있고, 주파수 스펙트럼을 알면 이들을 합쳐서 시간함수를 구할 수 있게 됩니다.

 

 

[ 시간함수와 주파수 스펙트럼의 관계 ]

 

* 그림 출처: aragec.com556

 

 

주파수는 1초에 파동 cycle이 몇 번 반복되느냐를 나타내는 말로서, 단위는 Hz(헤르츠)를 사용합니다.  아래의 3개의 파형을 예로 들면, 3개 파형 모두 진폭은 -1 ~ +1 로 동일한 반면에 주기는 모두 다릅니다. (즉, 주파수가 모두 다름)  첫번째 파형은 1초에 2회 주기이므로 주파수는 2Hz, 두번째 파형은 1초에 4회 주기이므로 4Hz, 세번째 파형은 1초에 6회 주기이므로 6Hz 주파수가 되겠습니다.

 

 

[ 주기와 진폭 ]

 

참고로, 악기 음 조율할 때 사용하는 소리굽쇠는 440Hz 의 '라'음을 낸답니다. 1초에 주기가 440회 진동이 발생한다는 뜻입니다. 서울/경기 지역의 '별이 빛나는 밤에' 라디오 주파수가 95.9kHz 인데요, 이는 전파가 1초에 9만 5천9백번 진동한다는 의미입니다.  음악에서는 주파수가 낮을 수록(현이 굷고 길수록) 저음이 나고, 주파수가 높을 수록(현이 가늘고 짧을 수록) 고음이 납니다.

 

이제 R을 가지고 1초에 2pi 만큼을 단위 시간 구간으로 해서 진폭과 주기를 달리한 4개의 사인함수 그래프도 그려보고, FFT (Fast Fourier Transform) 변환 실습을 해보도록 하겠습니다.

 

> # 사인함수 파라미터 설정 > x <- seq(0, 2*pi, by=pi/100) > > amp.1 <- 2 # 진폭(amplitude) 2 > amp.2 <- 2 # 진폭 2 > amp.3 <- 5 # 진폭 5 > amp.4 <- 5 # 진폭 5 > > wav.1 <- 1 # 주기(wave-length, cycle) 1 > wav.2 <- 2 # 주기 2 > wav.3 <- 3 # 주기 3 > wav.4 <- 7 # 주기 7 > > # 사인함수 생성 > signal.1 <- amp.1*sin(wav.1*x) # 진폭 2 & 주기 1인 사인함수 > signal.2 <- amp.2*sin(wav.2*x) # 진폭 2 & 주기 2인 사인함수 > signal.3 <- amp.3*sin(wav.3*x) # 진폭 5 & 주기 3인 사인함수 > signal.4 <- amp.4*sin(wav.4*x) # 진폭 5 & 주기 7인 사인함수 >

 

 

4개의 사인함수를 각각 순서대로 그려보면 아래와 같습니다. 첫번째와 두번째 그래프는 진폭(높이, y축)이 '2'로서 동일하고, 세번째와 네번째 그래프는 진폭이 '5'로서 동일합니다.  1초에 몇번의 주기가 있는지, 즉 주파수에 해당하는 주기는 순서대로 1, 2, 3, 7로서 뒤로 갈수록 증가하는 사인함수 그래프로서, 주파수가 커질수록 1초당 주기 갯수가 많아집니다 (진동 회수가 증가).

 

> # 사인함수 시간에 따른 그래프 > par(mfrow = c(1,4)) > plot(x, signal.1, type='l', ylim=c(-5,5)); abline(h=0, lty=3) # 진폭 2 & 주기 1인 사인함수 > plot(x, signal.2, type='l', ylim=c(-5,5)); abline(h=0, lty=3) # 진폭 2 & 주기 2인 사인함수 > plot(x, signal.3, type='l', ylim=c(-5,5)); abline(h=0, lty=3) # 진폭 5 & 주기 3인 사인함수 > plot(x, signal.4, type='l', ylim=c(-5,5)); abline(h=0, lty=3) # 진폭 5 & 주기 7인 사인함수



 

 

다음으로, 위 4개의 시간에 따른 사인함수를 합한 후에 그래프로 나타내보겠습니다.  위 4개의 개별 시간에 따른 사인함수가 주기성을 띠므로 아래의 1개로 합쳐진 시간함수도 일정한 주기성을 띠게 됩니다.  (푸리에 변환은 지금 하는 작업과는 거꾸로, 여러개의 시간함수들이 합쳐진 시간함수를 개별 시간함수들로 분해 해서 각 개별 시간함수들의 주파수 성분을 구하는 것입니다.  푸리에 변환할 때는 파형이 주기성을 띤다고 가정하고 변환을 진행합니다.) 

 



> # 사인함수 4개 합치기 (sine function summation) > signal.1234 <- signal.1 + signal.2 + signal.3 + signal.4 > head(signal.1234, n=30) [1] 0.000000 1.749660 3.442051 5.022470 6.441235 7.655888 8.633062 9.349913 9.795059 9.968964 [11] 9.883774 9.562593 9.038247 8.351592 7.549453 6.682284 5.801674 4.957812 4.197038 3.559593 [21] 3.077684 2.773944 2.660382 2.737852 2.996074 3.414214 3.961977 4.601179 5.287703 5.973757 > > # 사인함수 4개 합친 그래프 그리기 > par(mfrow = c(1,1)) > plot(x, signal.1234, type='l', main = "Sum of signal.1&2&3&4", + xlab = "Time", ylab = "Amplitude") > abline(h=0, lty=3)
 

 

 

 

R로 푸리에 변환할 때는 stats 패키지의 fft() 함수를 사용합니다.  fft()함수를 적용한 값을 관측치 개수의 반개((N-1)/2 = (201-1)/2 = 100 )로 나누어서 표준화를 시켜주고, abs()함수를 적용해 절대값을 취하게 됩니다.  plot()으로 그래프를 그려보면 좌우로 대칭인 그래프가 그려지는데요, 절반을 기점으로 해서 똑같은 대칭 그래프라서 왼쪽의 값(그래프)만을 사용하면 되겠습니다.  

 

이 예제에서는 주파수가 1Hz, 2Hz, 3Hz, 7Hz 짜리 4개의 사인함수를 더한 것이었으므로 1Hz, 2Hz,  3Hz, 7Hz 지점에서 피크(peak)를 치는 스펙트럼을 보여줄 겁니다.  따라서 1~20Hz 까지만 뽑아서 자세히 본 그래프가 두번째 그래프가 되겠습니다. 

 

> # 푸리에 변환 (FFT: Fast Fourier Transform)
> library(stats) > N <- length(x) > > fft_x_abs <- abs(fft(signal.1234)/((N-1)/2)) # 표준화, 절대값 > plot(fft_x_abs, type="h") >






 
> # 앞의 주파수 20개만 그래프 그려보기 > plot(fft_x_abs[1:20], type="h")





 

 

참고로, Python 을 이용한 스펙트럼 분석은 https://rfriend.tistory.com/690 를 참고하시기 바랍니다. 

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

728x90
반응형
Posted by Rfriend
,