음악을 듣다보면 참 신비로운 소리들이 많습니다. 피아노 소리처럼 익숙한 음색도 있지만, 스틸 드럼처럼 낯선듯 익숙한듯 뭔지 모를 음색들도 많지요. 종종 듣다보면 그거 참 신기한 음색인데 어떤 악기인지는 감도 안 잡힐때가 있습니다. 이것 참 지식인에 음악을 올려서 무슨 악기냐고 물어볼 수도 없고, 궁금함에서만 멈춰야한 적이 있었는데요, 딥러닝으로 핫한 시대에 맞춰 소리에 따라 악기를 분류해주는 모델을 만들어보면 좋겠다는 생각이 들었습니다. 이 포스팅은 그 기나긴 대장정의 첫 걸음입니다.
딥 러닝 모델을 만드는 건 어렵지 않습니다. 데이터만 충분히 있다면요. 문제는 악기별로 음색을 분류해서 녹음해놓은 데이터셋을 찾아보기 어렵다는 것입니다. 단, 실제 악기를 녹음해놓은 데이터셋은 많지 않지만, 가짜 악기(virtual instrument, 가상 악기)는 널려 있습니다. 작곡이 컴퓨터의 영역으로 들어오면서 소프트웨어를 통해 악기의 소리를 합성하고자 하는 노력이 계속 있었는데, 이 노력 덕분에 오늘날 수많은 가상 악기 소프트웨어와 MIDI 시퀀서들이 탄생하게 되었습니다. 그래서 악기 하나 없이 컴퓨터만을 통해 음악을 만드는 것이 가능한 세상이지요.
따라서 가상 악기의 도움을 받아 악기별 음색 데이터셋을 만들기로 했습니다. 일반 MIDI표준에는 총 128개의 악기가 등록되어 있고, MIDI를 재생하는 프로그램들은 Soundfont라는 음색 데이터 포맷을 이용해 전자신호에서 128가지 악기 소리를 합성해냅니다. 따라서 이들을 최대한 활용하면 되겠습니다. 오픈 소스 MIDI 편집 프로그램인 MuseScore에서 이용가능한 soundfont 파일을 구했구요, Soundfont를 해석하여 소리로 재생하기 위해서 FluidSynth라는 오픈소스 라이브러리를 이용했습니다. 기나긴 삽질 후의 산출물만 정리해서 공유드릴게요.
다음은 FluidSynth를 이용해 128종류의 악기 + 46개의 타악기 소리를 생성하는 코드입니다.
위 코드를 돌리면 output.wav라는 파일이 생성될 겁니다. (생성된 output.wav 파일을 공유해드리고 싶으나 용량이 큰 관계로 Google drive 링크로 대체합니다) 그 파일에는 2초 간격으로 128종 악기별로 50개의 음이 저장되어 있고, 46개의 타악기 소리가 들어가 있을 겁니다. 이렇게 소리를 생성했으면 이를 처리하여 딥러닝 모델에 넣기 적합한 형태로 변형해봅시다.
wav는 매 순간의 음압을 측정하여 그 수치를 저장한 형태이기 때문에 그 자체로 음악을 분석하기에 적합하지 않습니다. 왜냐면 우리는 음의 높이와 세기를 듣는것이지 순간의 음압을 듣는게 아니기 때문입니다. 이 때문에 푸리에 변환과 같은 변환 기법을 이용하여 시간 축의 데이터를 주파수 축의 데이터로 바꿔줘야할 필요가 있습니다. 단, 푸리에 변환 대신 푸리에 변환의 사촌쯤 되는 Constant-Q 변환을 사용할 겁니다. 이 변환은 주파수 축이 로그 단위로 변환되고, 각 주파수에 따라 해상도가 다양하게 처리되기 때문에(저주파는 저해상도, 고주파는 고해상도) 음악을 처리하는 데에 푸리에 변환보다 유리하다고 알려져 있습니다. 저는 파이썬 librosa의 cqt 구현을 이용해 wav 파일을 주파수 대역으로 변환하였습니다.
CQT를 스펙토그램으로 그리면 X축은 시간, Y축은 주파수 대역이 됩니다. 즉, 모든 악기 소리는 2차원의 그림으로 표현될 수 있는 거지요. 따라서 이미지 분석에 널리 쓰이는 CNN 모델을 악기 소리 분류에도 써보기로 결정했습니다.
3x3 크기의 필터를 2층을 쌓고, 마지막은 3x24 필터를 쌓았습니다. 마지막에서 주파수 대역 길이를 24로 길게 잡은건 다양한 배음 정보를 잡는게 악기 분류에 도움이 될거라고 판단했습니다. 시간 정보보다는 주파수 대역 정보가 중요할 테니깐요. 그리고 최종적으로 fully connected 레이어를 통과시켜서 128개의 악기 및 46개의 타악기 소리에 대해 분류하도록 했습니다. 아주 단순한 모형입니다.
그럼 바로 training을 시작해봅시다.
전체 데이터를 불러와 랜덤으로 섞고 90%는 훈련 데이터, 10%는 평가 데이터로 사용했습니다.
matplotlib를 이용해서 결과를 시각화해봅시다. GM 0~127번까지의 악기에 46종의 퍼커션 소리 이름을 gm.list.txt 파일에 담아두었는데요, 위 코드를 실행하려면 해당 파일이 필요합니다. 별건 아니지만 첨부해드리니 필요하다면 다운 받아 쓰시면 되겠습니다.
테스트셋에 대한 정확도는 98.9%가 나왔습니다. 예상보다 훨씬 높네요! 모델에겐 너무 쉬운 문제였을지도 모릅니다. 틀린 사례에 대해서만 Confusion Matrix를 그려서 오류 분석을 해보았습니다. X축이 실제 라벨, Y축이 예측된 라벨입니다. Acoustic Grand Piano와 Bright Acoustic Piano를 헷갈렸고, Tenor Sax와 Baritone Sax를 헷갈렸네요. 헷갈린 목록을 정리해보면 다음과 같습니다.
CNN모델 피셜 헷갈리는 악기 소리 묶음
(Acoustic Grand Piano, Bright Acoustic Piano)
(Tenor Sax, Baritone Sax)
(Woodblock, Low Wood Block)
그리고 애매하면 다 Tuba라고 분류했습니다...
또한 타악기 쪽에서 오류가 발생했는데 이는 데이터가 너무 적고, 타악기 소리 특성한 배음보다는 노이즈 비슷한 소리가 많이 잡히기 때문인걸로 보입니다. 좀더 현실 악기에 가까운 데이터를 구해 보충할 수 있다면 좋겠군요. 생각보다 성능이 잘 나오는걸 보니 과제를 좀 더 어렵게해서 악기 종류와 음 높이를 함께 맞추도록 해보는것도 좋을 거 같습니다.
[C++] EigenRand 0.3.0: 다변량 분포 추가 (1) | 2020.10.17 |
---|---|
[C++] EigenRand: Eigen용 Random Library 개발 (0) | 2020.06.27 |
[Python] Segmented Least Squares를 이용해 구간 나누기 (0) | 2019.02.27 |
[c++] 빠른 log sigmoid 계산 (0) | 2019.01.02 |
[Python] 임의의 웹 페이지에서 텍스트를 추출하기 (1) | 2018.11.04 |
[Python] 호환용 한자를 통합 한자로 변환하기 (2) | 2018.10.28 |
댓글 영역