평활화 (Smoothing)
평활화는 신호에서 단기적 변동을 제거하여 장기적 추세를 드러내려는 연산이다. 예를 들어, 주식 가격의 일일 변화를 플로팅하면 노이즈가 많아 보일 것이다. 평활화 연산자를 사용하면 가격이 시간이 지남에 따라 전반적으로 상승하는지 하락하는지 더 쉽게 확인할 수 있다. 일반적인 평활화 알고리즘은 이동 평균 (moving average)이다. 이동 평균은 특정 n 값에 대해 이 n 개 값 의 평균을 계산한다.
평활화 연산은 사운드 신호에도 적용된다. 예를 들어, 440Hz의 사각파 (square wave)의 고조파는 천천히 떨어지므로 많은 고주파 (high-frequency) 성분이 포함된다.
signal = thinkdsp.SquareSignal(freq=440)
wave = signal.make_wave(duration=1, framerate=44100)
segment = wave.segment(duration=0.01)
wave는 신호의 1초 세그먼트로 잡는다. 이 신호의 이동 평균을 계산하려면 11개의 요소로 창 (window)을 만들고, 각 요소의 합이 1이 되도록 정규화한다.
window = np.ones(11)
window /= sum(window)
이제, 창에 wave 배열을 곱하여 처음 11개 요소의 평균을 계산할 수 있다.
ys = segment.ys
N = len(ys)
padded = thinkdsp.zero_pad(window, N)
prod = padded * ys
sum(prod)
padded는 segment.ys와 길이가 같도록 창의 끝에 0을 추가한 것이다. prod는 window와 wave 배열의 곱이다. 원소별 곱의 합은 배열의 처음 11개 원소의 평균이다. 이 원소들은 모두 -1이므로 평균은 -1이다. 평활화된 신호의 다음 요소를 계산하려면 window를 오른쪽으로 이동하고 두 번째부터 시작하여 wave 배열의 다음 11개 요소의 평균을 계산한다.
rolled = np.roll(rolled, 1)
prod = rolled * ys
sum(prod)
결과는 다시 -1이다. 이동 평균을 계산하기 위해 이 과정을 반복하고 결과를 smoothed라는 이름의 배열에 저장한다.
smoothed = np.zeros_like(ys)
rolled = padded
for i in range(N):
smoothed[i] = sum(rolled * ys)
rolled = np.roll(rolled, 1)
rolled는 padded의 복사본으로, 루프를 통과할 때마다 오른쪽으로 한 요소씩 이동한다. 루프 내부에서 세그먼트를 rolled로 곱하여 11개 요소를 선택한 다음 더한다.
그림의 회색 선은 원래 신호이고, 어두운 선은 매끄럽게 처리된 신호이다. 매끄럽게 처리된 신호는 window의 선두 모서리가 첫 번째 transition에 도달하면 ramp up을 시작하고, 창이 transition을 교차하면 수평을 이룬다. 결과적으로 전환이 덜 갑작스럽고 모서리가 덜 날카로워진다. 매끄럽게 처리된 신호를 들으면 덜 윙윙거리고 (buzzy) 약간 흐릿하게 (slightly muffled) 들린다.