음파 데이터 분석 기본과 딥러닝 소리 분류기 구현 (구 블로그 글 복구)
- 이번 포스팅으론 소리 데이터를 분석하여 활용하는 기본 개념을 알아보고,
간단한 소리 데이터 분류 문제를 해결하는 실습을 수행하겠습니다.
소리 데이터, 즉 음파의 파형을 분석할 때, 소리라는 데이터가 특별한 것이 아니라, 시각 데이터와 같이 그저 텐서로서 다룰수도 있다는 것을 알아보는 것이 목적입니다.
- 먼저 소리라는 것과 컴퓨터에서 이 데이터의 성질과 형태에 대해 간단히 설명하겠습니다.
(이전에 정리한 음파 데이터 정리글도 추천합니다.)
소리는 모두들 아시다시피 공기를 매질로 하여 청각기관으로 수용할수 있는 부류의 데이터를 의미합니다.
Hz(헤르츠, 초당 진동 수)에 따라 소리의 높낮이가 달라지며,
dB(데시벨)에 따라 소리의 크기가 달라집니다. (10 dB당 실제값이 10배 증가하는 로그함수)
인간의 가청음역대는 통상 최고 20,000 Hz, 130 dB까지입니다.
20,000 Hz를 초과하는 고주파를 초음파라 부르며, 인간이 듣지 못하고 유해하지 않으나,
소리의 크기를 나타내는 데시벨값의 경우 120 dB를 초과하는 크기의 소리는 고막을 크게 해칠 수 있습니다.
보통 인간이 들을 수 있는 최소의 음파는 0 dB로 대략 나뭇잎이 흔들리는 소리이며(사람이 통상 못듣는 볼륨을 기반으로 0이라는 수치를 붙임.),
청력이 매우 좋은 인간은 -15 dB까지도 들을 수 있다고 합니다.
또한, 120 dB은 절대기피 해야 되는 음파의 수치이지, 130이 아니여도 80~90 dB 이상의 음파는 인간의 청력을 해친다고 합니다.
맨홀 공사를 할 때 흔히 듣는 드릴 소리가 약 100dB 이상이라는군요.
1기압의 대기 중에서의 한계 음량은 194 dB.
대기는 194 dB 이상의 음압을 전달할 수 없고,
소리란 공기를 매개로 삼는 진동파이기 때문에, 194 dB를 초과하는 크기의 음파는 소리로써 기능할 수 없으며, 단지 충격파가 된다고 합니다. (그정도 압력이라면 소리라기보다는 압축된 공기 벽을 맞는 느낌이려나...)
전달해주는 매질에 따라 소리의 속도는 당연히 달라집니다.
대기 중의 소리의 속도는 섭씨 15도일 때 340 m/s정도.
기온이 1도 올라갈 때마다 약 0.6 m/s씩 증가한다고 합니다.
(최고/최저 속도가 어디까지 가능한지는 나와있지 않아서 패스.)
인간의 가청역대는 통상적으로 18 ~ 24,000 Hz 정도이나 인간의 가청역대를 벗어난 소리도 인간에게 뭔가 느낌을 전달할 수 있다는 것 같습니다.
이러한 상식을 통해 데이터를 분석하려 할때, 인간적인 분석을 원한다면 위에서 나와있는 가청 음역대를 한정해서 분석을 할수도 있을 것입니다.
- 컴퓨터에서의 소리 (디지털 오디오)
초기에 소리라는 정보를 데이터로 저장하기 위해서는 카세트 테이프라는 것을 사용하였습니다.
소리를 자기(magnetic)의 형태로 저 플라스틱 케이스 안에 기록하고, 그것을 읽어내어 재생하는 개념인데, 이것을 아날로그 방식의 초기 데이터 저장 방식이라 생각하시면 됩니다.
그러니까, 원본이 되는 음파를 감지해서 그대로 신호를 보내는 마이크와 같은 입력장치를 통해 소리 파형이 그대로 저장되고 그대로 출력하는 형태죠.
이제는 저런 방식을 거의 안 씁니다.
다들 아시듯 디지털 방식...
그러니까 데이터를 0과 1로 분류하는 방식으로 데이터를 저장하고, 그것을 아날로그로 해석해서 출력하는 방식의 디지털 저장 방식을 사용합니다.
그러니까 위의 그림으로 보자면, 아날로그 신호가 회로에 입력되면, 0.5, 1, 2.6, 3.7... 이런식으로 있는 그대로 기록이 되는게 아니라, 1과 0으로 표현될수 있는 형태로 저장을 하는 것입니다.
쓸데없는 말을 첨언하자면, 초기에는 파형을 미분하고 이진화 하는 것 같은 저러한 방식으로 데이터 손실이 발생해 제대로된 소리를 느낄수 없다고 하여, 디지털 오디오는 천대받은 적이 있었죠...
어쨌건, 열에도 약하고, 충격에도 약하고, 오히려 왜곡의 가능성이 크고 물리적으로 제한된 아날로그 저장 방식은 현재로는 별로 고려대상이 못되고, 혹여 만나더라도 충분히 디지털화 할수 있는 기반이 쉽게 마련되기에, 여기서는 프로그래밍 적으로 다루기에도 적합한 디지털 오디오 포멧이 중요합니다.
기본적으로 오디오 데이터의 저장은 아날로그와도 흡사합니다.
회로가 처리해주는 분야인데다가 조금 깊어지므로, 그냥 파형을 디지털화한다고 알아두시면 되고,
이에도 마치 이미지 데이터의 화질과 같이 음질이라는게 존재하는데,
화질의 경우는,
이렇게, 이미지를 나타내는 단위 크기가 작고 촘촘하고 많을수록 더 아날로그의 정보를 훼손하지 않으며 정보를 저장할수 있다면,
음질의 경우는,
이러한 것을 생각하시면 됩니다.
마치 미적분 그래프가 생각나는데, 저 곡선에 대한 디지털화가 촘촘할수록 보다 정보 훼손이 덜하겠죠.
- 압축 포멧
이미지 역시 데이터를 있는 그대로 저장하지는 않습니다.
왜냐? 있는 그대로 저장을 했다가는 이미지 파일 한장에 5기가바이트가 넘는 등의 낭비가 일어나기 때문입니다.
압축에는 여러 방식이 존재합니다만 여기선 이미지 압축 방식을 다루지는 않습니다.
간단하게 텍스트 압축으로 예를들면,
'aaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbcc'
[ax12, bx20, cx2]
이렇게 임의로 나타낼수도 있습니다.
무슨 의미냐면, a가 12개 연달아 존재한다, b가 20개 연달아 존재한다, c가 2개 연달아 존재한다...
이런식으로 정보를 압축하고, 추후 복구를 할때는 이를 기반으로 a에 대한 for문을 12번 돌리고 하는 방식으로 데이터를 복구할수도 있죠.
(위 알고리즘의 최악의 상황은 각 문자의 반복이 전혀 없는 데이터는 오히려 데이터가 늘어난다는 단점이 있습니다. 위의 알고리즘은 무손실 알고리즘인데, 경우에 따라서 일부 중요치 않아보이는 데이터를 삭제하거나, 아예 실제 데이터를 저장하기보단 그것을 나타낼수 있는 함수를 구함으로써 용량을 줄이는 손실 알고리즘도 존재합니다.)
포멧이란, 압축을 진행한 파일이 어떤 압축 과정을 거쳤는지에 대한 예시입니다.
이미지의 경우에는 뒤에 .jpg가 붙으면, 이것이 jpg 압축 방식으로 저장된 것이라는 걸 의미하며, .gif는 gif 방식...
이런식이며, 그 말은 즉슨 원래 데이터로 복구를 하려면, 해당 압축 알고리즘에 대한 복구 알고리즘을 선정하여 가져와 사용하라는 뜻이 됩니다.
- 오디오 압축 포멧을 간단히 나열하겠습니다.
1. 조상격 Pulse-Code Modulation(PCM) 포맷
1937년에 만들어진 아날로그 오디오에 가장 가까운 포맷으로,
무압축, 무손실 포멧입니다.
말 그대로 압축을 하지 않고 손실도 없는 포멧이죠.이 포맷은 2가지 Properties를 가지고 있는데,
디지털 오디오에서 빼놓고 이야기할 수 없는 Sample rate 와 Bit depth 입니다.
2. 무압축 무손실 WAV, AIFF
데이터 분석에서 가장 많이 쓰이는 WAV 포멧입니다.
디지털화한 데이터를 실제 압축하지는 않는 무압축 포멧으로,
.wav와 .aif를 구분하는 것은, OS에 달려있습니다.
윈도우에서는 WAV, MacOS에서는 AIFF라는 확장자로 사용하고 해석하는 것 외에 다른 것은 없습니다.
둘다 서로의 OS에서 사용 못하는 것은 아니고, 산업표준은 wav인데, 맥에서 전용 오디오 플레이어에 사용될 포멧으로 aif라는 것을 만든 것 뿐입니다.
알고리즘을 설명하지는 않지만, PCM과 거의 동일하며,
44.1키로헤르츠, (1초 동안 44100번의 샘플링) 16 Bit depth (씨디퀄리티)로 이루어진 파일입니다.
무손실 무압축이다보니 파악도 편하고, 따로 디코딩 과정이 필요한 것도 아닌 날것에 가까운 데이터이기에, 오디오 관련 데이터를 다룰때 가장 많이 사용하는 방식이며, 위에서 말했듯 데이터 분석에도 이걸 사용할 것입니다.
단, 이 역시 손실은 존재하므로, 전문 녹음시에는 다른 비압축 포멧을 사용하는 경우도 많다고 합니다.
3. 압축 무손실 FLAC, ALAC, APE
Free Lossless Audio Codec : FLAC
Apple Lossless Audio Codec : ALAC
Monkey's Audio : APE
일반적으로 .wav 파일의 절반 정도의 사이즈로 압축합니다.
이 압축 포맷들은 사실 산업 표준이 아니라고 합니다.
알고리즘은 스킵하며,
각각의 차이를 간단히 말하자면, 이것 역시 그냥 주로 사용하는 OS의 차이입니다.
위에서부터 윈도우, MacOS고,
APE의 경우엔 , 몽키스 오디오라는 곳에서 개발한 포멧으로 아시기만 하면 되는데,
자주 사용되지는 않습니다.
4. 손실 압축 포맷 : MP3, AAC, WMA, Vorbis
'약간' 손실이 이뤄지는 압축 포멧으로, 포터블 기기 등에 넣어다니기에도 적합하기에 일반인들에게 잘 알려진 포멧입니다.
사람이 들을수 있는 부분을 남겨두고, 필요없는 부분을 최대한 줄이며 압축한 것이 이 포멧이며,
성능은 그다지 차이가 없는데, 데이터 용량이 굉장히 줄어듭니다.
손실 압축은 샘플레이트와 비트뎁스의 차이로 음질을 결정하지 않고
kbit/s 나 kbps 로 정합니다. 'Bit rate' 라고 합니다. 'Bit depth' 와 다른 겁니다.
음원 서비스 업체에서 다운로드해서 음악을 구매할 때 음질 퀄리티를 선택하게 되면 128 kbps, 128 kbit/s 라고 보여지고
최대값으로는 320 kbps, 320 kbit/s 로 보여지는 것을 알고 계실 겁니다. 압축 포맷이면서도 압축 비율을 줄임으로써 음질은 좀 더 높이되 용량은 더 커집니다. 그렇다고 해도 용량이 wav에 비해선 매우 절약됩니다.
음질은 거의 차이가 없다는데, 그래도 예민한 분이 집중하면 약간의 차이가 느껴질수도 있습니다.재생 환경이 좋을수록 그 차이가 더 잘 나타난다고 하죠.
MP3 :
압축 손실 압축에서 가장 잘 알려진 포맷은 .mp3 입니다.
MPEG 1 Audio Layer 3 라는 풀네임이 있는데요.
이슈가 있는 포맷임에도 불구하고 말도 안되게 인기가 있는 포맷입니다.
처음에 나왔을 당시에 cd 플에이어가 아니라 mp3 cd 플레이어가 나올 정도로 처음부터 지금까지 매우 사랑받고 있는 디지털 오디오 포맷으로 압축 손실 포맷은 무압축, 무손실 압축과 음질 차이가 꽤 많이 납니다.
일반 분들의 경우 그 차이를 못 느끼는 경우도 있지만 다양한 환경에서 비교해서 들려주면 그 차이를 확실하게 느낄 수 있습니다.
대신 용량은 매우 줄어들어서 하드용량이 부족하던 때에 씨디가 아니라 음악 파일로서 수 많은 앨범을 소장하는 것들이 가능해졌고, 음악 스트리밍 서비스가 발전할 수 있는 계기가 되기도 했습니다
AAC :
Advanced Audio Coding, 의 약자로 MPEG4 Video 의 오디오로 사용되어지는 포맷입니다.
MP3 를 능가하는 압축 손실 포맷입니다. 그런데 대개 AAC가 MP3 와 동일하다고 보시는 분들이 계십니다. 아닙니다. 동일 bit rate에서 AAC가 더 나은 압축 손실 포맷입니다.
압축도 음질도 mp3 보다 더 낫습니다.
최대 96 키로헤르츠의 48 풀 밴드위쓰를 지원합니다.
여기서부터 아예 mp3와 다릅니다.
음악 파일로는 흔히 안 보이는 듯 하지만
Youtube, iphone, ipod, ipad, Nintendo DSi, Nintendo 3DS, iTunes, DivX Plus Web Player, Playstation3, Playstation Vita, Wii, Sony Walkman Mp3 Android, blackBerry, 는 AAC 를 사용합니다. 모ㄹ셨죠 ?
보이지 않게 매우 선호되는 압축 손실 포맷입니다.
WMA :
이것도 낯설지 않은 포맷이죠 ?
.wma 는 Windows Media Audio 의 약자이고요. 마이크로소프트의 압축 손실 오디오 포맷입니다.
mp3 포맷이 라이센스 문제가 있어서 개발하게 된 포맷입니다.
들어보셨을 DRM 때문인데요. 어쨌거나 애플의 아이튠즈가 DRM 을 채택한 가장 잘 알려진 표준이 되었죠.
저작권 중요합니다 ~ ㅋ
Vorbis :
이건 저도 잘 모르는 포맷인데요.
무료이며 오픈 소스인 손실 압축 포맷입니다.
게임 오디오를 위해서 쓰여지기도 하고, 리눅스 유저쪽에서 잘 알려진 포맷인 거 같습니다.
하드 용량이 충분하지 않았던 시절. 그리고 인터넷이 점점 발전하면서 스트리밍에 대한 대안 방안으로 영상 뿐만이 아니랑 오디오 포맷에서도 많은 개발이 이루어졌습니다.
점점 하드 드라이브의 용량이 커지고 인터넷도 엄청나게 빨라진 지금. 사실 영상이 오디오보다는 어마어마하게 큰 용량을 차지합니다.
그런데도 국내의 디지털 스트리밍 서비스라던지 음악 구매시 다운로드 음질 퀄리티가 너무한 것 아니냐라고 저는 생각하고 있습니다.
영상도 100메가를 기본으로 넘기는데 곡당 최소 압축이어도 무손실 압축으로 제공해야 하는 거 아닌가 싶습니다.
작업을 위해서 사용해야 하는 포맷은 : .WAV, .Aiff
저용량 음악 감상은 : .MP3, .AAC
음질과 용량 모두 적당한 것으로는 : .Flac
추가하면, LP판이라는 것이 있는데, 이것은 아날로그 방식입니다.
영화같은데서 보면 바늘 같은 것을 올려두면 소리가 나는 검은색 원판처럼 생겼죠.
비싼 편이고 그 커다란 판 하나에 곡이 많이 들어있지도 않지만, 아날로그방식이니만큼 WAV보다도 더 손실 없는 그대로의 소리를 들을수 있어서 매니아가 존재하죠.
- 웨이브(.wav) 파일 포멧 분석
우리가 사용할 것은 바로 이 wav 파일이므로 이를 조금 더 자세히 알아봅시다.
wav는 위에서 말했듯 비압축 오디오 파일 포멧입니다.
Waveform audio file format의 약자로, 원리를 알아보자면 오르골을 생각하시면 됩니다.
그냥 소리를 디지털화 해서 저장한 후에, 그것을 해석하는 부분에서 있는 그대로 소리를 연주하면 원래의 소리가 재생이 되는 원리입니다.
위는 wav 파일의 헤더입니다.
파일 포멧이나, 통신 프로토콜처럼 복원을 해야하는 데이터의 경우엔 위와 같이 이 파일이 어떤 종류인지를 나타내주는 메타 정보를 기록한 헤더가 중요합니다.
실제 오디오 파일을 Hex 에디터를 사용하여 열어보면 아래와 같이 데이터가 나열되어 있음을 볼 수 있습니다.
- 오디오 데이터 분석
이제 해볼 오디오 분석 모델은, CNN을 통해 소리의 데이터를 분석할 것입니다.
왜 CNN이냐고 하면, 영상에 CNN을 사용하는 이유와 동일합니다.
하나의 다차원 데이터 패턴에 대해서 그것을 분류하는 문제는, CNN이 적격이며, 만약 그 의미를 알아내는 것이라고 한다면 RNN과 같은 모델을 사용하는 것이죠.
그렇기에 오디오 분석, 음파 분석은 자연어 처리와는 다른 문제이며, 그 이전의 작업이라 생각하시면 됩니다.
(먼저 음성에서 단어를 끄집어내고, 그 단어들을 벡터화 하여 의미를 끄집어내는 작업이 자연어 처리)
오디오 분석은, 영상 분석과 유사한 점도 있고 다른점도 있는데,
일단 여기서 할 것은, 영상 분석에서 하나의 이미지에 대한 topic을 분류하는 것과 같다고 생각하시면 됩니다.
영상에서도 한 이미지 분류가 있고, 그 안에 객체를 탐지해내는 것도 있고, 마스크로 객체를 분리하거나, 이미지를 생성하거나, 제거되거나 가려진 이미지에 들어올만한 이미지를 예측해내는 등의 기술이 존재하는데,
오디오에서도, 한 wav 파일 내의 특징들을 추출하여 전체적으로 어떤 소리 특징이 많이 나타내는가에 따라 분류하는 것, 그리고 여러 소리가 존재한다면 그것들을 각각 잡아내는것, 소리를 생성하거나 자연스럽게 합성해내는 등의 일이 가능합니다.
오디오의 데이터 파일은 역시 이미지와 비슷한 방식으로 2차원축으로 설명이 가능한 형태이므로, 이런 형태에 어울리는 분석 기법인 CNN을 사용하면 꽤나 성능이 좋은 분석이 가능하다고 합니다.
[소리 분류 실습]
- Python Keras 를 사용하여 오디오 파일을 입력하면 데이터를 분석하여 해당 소리가 어떤 것인지를 분류하는 소리 분류기를 만들어보겠습니다.
0. 라이브러리 import
import keras
from keras.layers import Activation, Dense, Dropout, Conv2D, \
Flatten, MaxPooling2D
from keras.models import Sequential
import librosa
import librosa.display
import numpy as np
import pandas as pd
import random
import warnings
warnings.filterwarnings('ignore')
케라스를 사용하며, 쥬피터 노트북을 사용하시거나 혹은 코랩을 사용해도 됩니다.
위와 같은 라이브러리를 사용할 것입니다.
1. 데이터 준비
# Read Data
data = pd.read_csv('code/UrbanSound8K/metadata/UrbanSound8K.csv')
data.head(5)
데이터는, 소리 파일들의 정보가 정리된 csv를 먼저 넣어줍니다.
이 파일의 구조는,
slice_file_name | fsID | start | end | silence | fold | classId | class | |
1 | 100032-3-0-0.wav | 100032 | 0.0 | 0.317551 | 1 | 5 | 3 | dob_bark |
2 | 100263-2-0-117.wav | 100263 | 58.5 | 62.50000 | 1 | 5 | 2 | children_playing |
3 | 100263-2-0-121.wav | 100263 | 60.5 | 64.50000 | 1 | 5 | 2 | children_playing |
4 | 100263-2-0-126.wav | 100263 | 63.0 | 67.00000 | 1 | 5 | 2 | children_playing |
5 | 100263-2-0-137.wav | 100263 | 68.5 | 72.50000 | 1 | 5 | 2 | children_playing |
위와 같습니다.
파일명은 그냥 파일명이고,
fsID는 파일에 대한 개별적인 ID고,
start는 해당 소리의 시작 타임 위치,
end는 해당 소리의 종료 타임 위치,
silence는 해당 소리가 무음이면 0이고 아니면 1
fold는 여기서 사용되는 폴더의 인덱스입니다. 예를들어 fold1, fold2와 같이 각 학습 데이터를 분리해뒀습니다.
classID는 클래스의 인덱스,
class는 클래스명을 의미합니다.
그리고 여기서 탐지하는 소리의 분류 클래스는,
1 Childern playing
2 Dog Barking
3 Jackhammer
4 Running Engine
5 Air conditioner
6 Street music
7 Gun shots
8 Siren
9 Drilling
10 Car Horn
위와 같은 종류가 있습니다.
data.shape
#(8732, 8)
# Get data over 3 seconds long
valid_data = data[['slice_file_name', 'fold' ,'classID', 'class']][ data['end']-data['start'] >= 3 ]
valid_data.shape
# (7468, 4)
그 중에, 적합한 소리를 찾기 위하여 3초 이상 길이를 가지는 데이터에서 필요한 컬럼의 데이터만 추려냅니다.
이 데이터의 샘플을 확인해보고 librosa를 사용해봅시다.
# Example of a Siren spectrogram
y, sr = librosa.load('code/UrbanSound8K/audio/fold6/135160-8-0-0.wav', duration=2.97)
ps = librosa.feature.melspectrogram(y=y, sr=sr)
ps.shape
# (128, 128)
librosa 라이브러리로 해당 오디오들이 존재하는 경로와 오디오 파일을 인자로 넣어주고, duration을 2초 97로 설정합시다.
sampling rate sr은 기본적으로 22050으로 설정되어 있다고 합니다.
위와 같이 하드디스크에서 데이터를 받아와서, librosa.reature.melspectrogram에 각각을 넣어주면, 오디오 데이터를 마치 이미지처럼 만들어 ps에 담을수 있고, 이를 나타내면,
librosa.display.specshow(ps, y_axis='mel', x_axis='time')
이처럼 시간에 대한 오디오 데이터의 특징을 그래프로 볼수 있습니다.
(시간축이 들어가는 것이니, 이는 RNN으로 분석을 해야할것 같지만, 음파 이미지를 모아뒀다가 CNN으로 처리를 합니다. 마치 Transformer 모델의 attention을 사용하는 것 같네요. attention is all you need)
위의 그림은 사이렌 소리가 내포한 음파의 특징을 나타내고,
# Example of a AC spectrogram
y, sr = librosa.load('code/UrbanSound8K/audio/fold1/134717-0-0-19.wav', duration=2.97)
ps = librosa.feature.melspectrogram(y=y, sr=sr)
ps.shape
# (128, 128)
librosa.display.specshow(ps, y_axis='mel', x_axis='time')
이것은 에어컨,
# Example of a children playing spectrogram
y, sr = librosa.load('code/UrbanSound8K/audio/fold9/13579-2-0-16.wav', duration=2.97)
ps = librosa.feature.melspectrogram(y=y, sr=sr)
ps.shape
# (128, 128)
librosa.display.specshow(ps, y_axis='mel', x_axis='time')
이것은 아이들이 웃고 노는 소리입니다.
보시다시피 전부 다른 패턴을 지닌 이미지와 같으며, 128x128 크기의 데이터로 추출이 되는 것을 확인할수 있습니다.
이러면 이미지 분석과 동일하게 하면 되고, 이미지 분석보다 더 쉽게 모델을 만들어서 활용이 가능합니다.
본격적으로 학습 데이터를 준비해봅시다.
valid_data['path'] = 'fold' + valid_data['fold'].astype('str') + '/' + valid_data['slice_file_name'].astype('str')
D = [] # Dataset
for row in valid_data.itertuples():
y, sr = librosa.load('code/UrbanSound8K/audio/' + row.path, duration=2.97)
ps = librosa.feature.melspectrogram(y=y, sr=sr)
if ps.shape != (128, 128): continue
D.append( (ps, row.classID) )
print("Number of samples: ", len(D))
# Number of samples: 7467
먼저 각 폴더 경로와 파일명을 결합한 파일명 리스트를 작성하고,
반복문을 돌며, librosa를 이용해서 ps를 추출하는 것까지 합시다.
유효성 검사로, ps의 크기가 128x128이 되는 것만 ps를 모아두는 데이터셋 리스트에 추가(ps와 classID를 결합한 튜플) 하고, 아니라면 그냥 continue를 합니다.
dataset = D
random.shuffle(dataset)
train = dataset[:7000]
test = dataset[7000:]
X_train, y_train = zip(*train)
X_test, y_test = zip(*test)
# Reshape for CNN input
X_train = np.array([x.reshape( (128, 128, 1) ) for x in X_train])
X_test = np.array([x.reshape( (128, 128, 1) ) for x in X_test])
# One-Hot encoding for classes
y_train = np.array(keras.utils.to_categorical(y_train, 10))
y_test = np.array(keras.utils.to_categorical(y_test, 10))
데이터셋이 갖춰지면, 그것을 random으로 shuffle해줍니다.
그리고 그중 학습 데이터를 7000개, 나머지를 테스트용으로 분리를 하고, 데이터x와 정답레이블y의 튜플로 이루어진 파일을 zip하여 각각의 데이터를 추출합니다.
그리고 케라스 입력층에 넣기 전의 전처리로, 텐서 계산이 되도록, 이의 원소들을 넘파이 배열로 만들어서 최종적인 데이터로 저장합니다.
2. 모델 작성
model = Sequential()
input_shape=(128, 128, 1)
model.add(Conv2D(24, (5, 5), strides=(1, 1), input_shape=input_shape))
model.add(MaxPooling2D((4, 2), strides=(4, 2)))
model.add(Activation('relu'))
model.add(Conv2D(48, (5, 5), padding="valid"))
model.add(MaxPooling2D((4, 2), strides=(4, 2)))
model.add(Activation('relu'))
model.add(Conv2D(48, (5, 5), padding="valid"))
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dropout(rate=0.5))
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(rate=0.5))
model.add(Dense(10))
model.add(Activation('softmax'))
모델링은 간단한 편입니다.
그냥 이미지 분류와 동일하게 하면 되는데, 이미지 크기를 신경써서 만들면 되고, 그냥 위에서처럼 작성하시면 됩니다.
입력층으로 (128,128,1)의 흑백사진처럼 설정하고,
conv->maxpooling->activation의 순서로 3번정도 반복한 후, 그것을 Flatten하시고, FC레이어에 넣어주어 최종적으로 softmax로 분류를 하시면 됩니다.
이건 꽤나 오래된 코드에, 그냥 케라스만 이용한건데, 텐서플로의 경우도 비슷하게 하시면 되고, 최신 트랜드를 반영하기 위해 Batch Normalization을 추가해도 되겠습니다.
3. 학습
model.compile(
optimizer="Adam",
loss="categorical_crossentropy",
metrics=['accuracy'])
model.fit(
x=X_train,
y=y_train,
epochs=12,
batch_size=128,
validation_data= (X_test, y_test))
score = model.evaluate(
x=X_test,
y=y_test)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
옵티마이저는 아담을 사용하시면 되고, loss값은 이진분류이므로 위와 같이...
그리고 에폭을 12, 배치를 128로 하고, 테스트 데이터와 학습 데이터를 모두 넣어주어 fit을 해주면 학습이 진행됩니다.
Train on 7000 samples, validate on 467 samples
Epoch 1/12
7000/7000 [==============================] - 180s 26ms/step - loss: 2.6048 - acc: 0.2070 - val_loss: 2.0851 - val_acc: 0.3233
Epoch 2/12
7000/7000 [==============================] - 181s 26ms/step - loss: 2.0906 - acc: 0.2699 - val_loss: 1.7804 - val_acc: 0.3833
Epoch 3/12
7000/7000 [==============================] - 178s 25ms/step - loss: 1.8462 - acc: 0.3520 - val_loss: 1.5282 - val_acc: 0.4732
Epoch 4/12
7000/7000 [==============================] - 176s 25ms/step - loss: 1.7039 - acc: 0.4081 - val_loss: 1.3940 - val_acc: 0.5096
Epoch 5/12
7000/7000 [==============================] - 183s 26ms/step - loss: 1.5457 - acc: 0.4536 - val_loss: 1.2631 - val_acc: 0.5546
Epoch 6/12
7000/7000 [==============================] - 163s 23ms/step - loss: 1.4177 - acc: 0.5024 - val_loss: 1.1702 - val_acc: 0.6060
Epoch 7/12
7000/7000 [==============================] - 166s 24ms/step - loss: 1.3528 - acc: 0.5274 - val_loss: 1.1287 - val_acc: 0.6167
Epoch 8/12
7000/7000 [==============================] - 150s 21ms/step - loss: 1.2543 - acc: 0.5647 - val_loss: 1.0201 - val_acc: 0.7024
Epoch 9/12
7000/7000 [==============================] - 152s 22ms/step - loss: 1.1813 - acc: 0.5993 - val_loss: 0.9275 - val_acc: 0.7238
Epoch 10/12
7000/7000 [==============================] - 152s 22ms/step - loss: 1.1386 - acc: 0.6130 - val_loss: 0.9540 - val_acc: 0.6938
Epoch 11/12
7000/7000 [==============================] - 151s 22ms/step - loss: 1.0987 - acc: 0.6311 - val_loss: 0.8591 - val_acc: 0.7559
Epoch 12/12
7000/7000 [==============================] - 152s 22ms/step - loss: 0.9981 - acc: 0.6650 - val_loss: 0.8345 - val_acc: 0.7495
467/467 [==============================] - 4s 8ms/step
Test loss: 0.8345382493108958
Test accuracy: 0.7494646677113191
정확도가 대략 74퍼센트가 나오는 것을 볼수 있네요.
그다지 좋다고는 말하지 못하는데, 학습량이 적은 것도 이유가 있습니다.
고로 학습량을 늘려주면 되는데,
실제 데이터를 구하는게 가장 좋지만, 그렇지 못할때 사용하는 방법인 Data Augmentation을 하셔도 됩니다.
4. 데이터 증강
이미지에서는 이미지를 강제로 늘리거나 회전을 시키거나 방향을 반대로 하거나 해서 한 이미지로 다양한 학습 데이터를 만들었죠. 오디오 데이터 역시 동일합니다.
librosa에서 이를 지원합니다.
y, sr = librosa.load('code/UrbanSound8K/audio/fold1/14113-4-0-1.wav', duration=2.97)
y_changed = librosa.effects.time_stretch(y, rate=0.81)
librosa.output.write_wav('code/augmented/fold1/speed_81/14113-4-0-1.wav' ,y_changed, sr)
rate = 1.07 # replace with 0.81 and execute again
for row in valid_data.itertuples():
y, sr = librosa.load('code/UrbanSound8K/audio/' + row.path)
y_changed = librosa.effects.time_stretch(y, rate=rate)
librosa.output.write_wav('code/augmented/fold' + str(row.fold) + '/speed_' + str(int(rate*100)) + '/' + row.slice_file_name ,y_changed, sr)
n_steps = 2 #-1, -2, 2, 1
for row in valid_data.itertuples():
y, sr = librosa.load('code/UrbanSound8K/audio/' + row.path)
y_changed = librosa.effects.pitch_shift(y, sr, n_steps=n_steps)
librosa.output.write_wav('code/augmented/fold' + str(row.fold) + '/ps1_' + str(int(n_steps)) + '/' + row.slice_file_name ,y_changed, sr)
n_steps = 2.5 #-2.5, -3.5, 2.5, 3.5
for row in valid_data.itertuples():
y, sr = librosa.load('code/UrbanSound8K/audio/' + row.path)
y_changed = librosa.effects.pitch_shift(y, sr, n_steps=n_steps)
librosa.output.write_wav('code/augmented/fold' + str(row.fold) + '/ps2_m' + str(int(n_steps*10)) + '/' + row.slice_file_name ,y_changed, sr)
len(D)
# 37310
1-7467 normal samples.
7468-14934 samples Pitch modulated 2.5 semitones higher.
14935-22401 samples Pitch modeulated 2 semitones higher.
22402-29869 samples Slowed down to 0.81.
29869-37310 samples speed up by 1.07
5. 증강 데이터로 학습
dataset = D
random.shuffle(dataset)
train = dataset[:35000]
test = dataset[35000:]
X_train, y_train = zip(*train)
X_test, y_test = zip(*test)
X_train = np.array([x.reshape( (128, 128, 1) ) for x in X_train])
X_test = np.array([x.reshape( (128, 128, 1) ) for x in X_test])
y_train = np.array(keras.utils.to_categorical(y_train, 10))
y_test = np.array(keras.utils.to_categorical(y_test, 10))
model = Sequential()
input_shape=(128, 128, 1)
model.add(Conv2D(24, (5, 5), strides=(1, 1), input_shape=input_shape))
model.add(MaxPooling2D((4, 2), strides=(4, 2)))
model.add(Activation('relu'))
model.add(Conv2D(48, (5, 5), padding="valid"))
model.add(MaxPooling2D((4, 2), strides=(4, 2)))
model.add(Activation('relu'))
model.add(Conv2D(48, (5, 5), padding="valid"))
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dropout(rate=0.5))
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(rate=0.5))
model.add(Dense(10))
model.add(Activation('softmax'))
model.compile(
optimizer="Adam",
loss="categorical_crossentropy",
metrics=['accuracy'])
model.fit(
x=X_train,
y=y_train,
epochs=12,
batch_size=128,
validation_data= (X_test, y_test))
score = model.evaluate(
x=X_test,
y=y_test)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
Train on 35000 samples, validate on 2310 samples
Epoch 1/12
35000/35000 [==============================] - 982s 28ms/step - loss: 1.9844 - acc: 0.3104 - val_loss: 1.4893 - val_acc: 0.4844
Epoch 2/12
35000/35000 [==============================] - 881s 25ms/step - loss: 1.4384 - acc: 0.4950 - val_loss: 1.1614 - val_acc: 0.6251
Epoch 3/12
35000/35000 [==============================] - 947s 27ms/step - loss: 1.1681 - acc: 0.6005 - val_loss: 0.9777 - val_acc: 0.6710
Epoch 4/12
35000/35000 [==============================] - 917s 26ms/step - loss: 0.9994 - acc: 0.6680 - val_loss: 0.8171 - val_acc: 0.7437
Epoch 5/12
35000/35000 [==============================] - 1335s 38ms/step - loss: 0.8709 - acc: 0.7135 - val_loss: 0.7054 - val_acc: 0.7749
Epoch 6/12
35000/35000 [==============================] - 960s 27ms/step - loss: 0.7779 - acc: 0.7450 - val_loss: 0.5937 - val_acc: 0.8091
Epoch 7/12
35000/35000 [==============================] - 919s 26ms/step - loss: 0.6882 - acc: 0.7743 - val_loss: 0.5778 - val_acc: 0.8160
Epoch 8/12
35000/35000 [==============================] - 893s 26ms/step - loss: 0.6560 - acc: 0.7841 - val_loss: 0.5137 - val_acc: 0.8346
Epoch 9/12
35000/35000 [==============================] - 1038s 30ms/step - loss: 0.6122 - acc: 0.7990 - val_loss: 0.4834 - val_acc: 0.8416
Epoch 10/12
35000/35000 [==============================] - 827s 24ms/step - loss: 0.5484 - acc: 0.8187 - val_loss: 0.4364 - val_acc: 0.8502
Epoch 11/12
35000/35000 [==============================] - 828s 24ms/step - loss: 0.5366 - acc: 0.8227 - val_loss: 0.4330 - val_acc: 0.8494
Epoch 12/12
35000/35000 [==============================] - 846s 24ms/step - loss: 0.4991 - acc: 0.8390 - val_loss: 0.5504 - val_acc: 0.8247
2310/2310 [==============================] - 17s 7ms/step
Test loss: 0.5504067881024761
Test accuracy: 0.8246753246753247
결과는 위와 같습니다.
- 이상입니다.
모델 자체도 어렵지 않고, 구현도 어렵지 않습니다.
이러한 지식을 이용하여 실용성 있는 응용 프로그램을 만들려면 개선해야 할 사항이 많을 테지만,
컴퓨터에서 소리라는 데이터를 다루는 방식에 대해 기본적인 지식을 이해하고, 경험하였으므로, 지식에 뼈와 살을 붙여나가면 언젠가 획기적이고 더 좋은 서비스를 구상하고 구현할 수 있을 것입니다.