{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":77776,"databundleVersionId":8541572,"sourceType":"competition"}],"dockerImageVersionId":30698,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        continue\n        #print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-05-14T02:43:57.567835Z","iopub.execute_input":"2024-05-14T02:43:57.568434Z","iopub.status.idle":"2024-05-14T02:43:58.558201Z","shell.execute_reply.started":"2024-05-14T02:43:57.568401Z","shell.execute_reply":"2024-05-14T02:43:58.557016Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 랜덤 시드 고정\nimport random\nimport librosa\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\nimport time\n\nseed = 42\n\nrandom.seed(seed)\nnp.random.seed(seed)\nos.environ[\"PYTHONHASHSEED\"] = str(seed)","metadata":{"execution":{"iopub.status.busy":"2024-05-14T02:43:58.560013Z","iopub.execute_input":"2024-05-14T02:43:58.567406Z","iopub.status.idle":"2024-05-14T02:43:58.599497Z","shell.execute_reply.started":"2024-05-14T02:43:58.567351Z","shell.execute_reply":"2024-05-14T02:43:58.598176Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 0. 음악 데이터 재생","metadata":{}},{"cell_type":"code","source":"# Ipython 라이브러리를 이용해 학습에 사용될 음성을 직접 들어볼 수 있습니다.\nfrom IPython.display import Audio, display\n\nfilename = '/kaggle/input/2024-ml-project4/train/train_000.wav'\ny, sr = librosa.load(filename)\naudio_wdt = Audio(data=y,rate=sr)\ndisplay(audio_wdt)","metadata":{"execution":{"iopub.status.busy":"2024-05-14T02:43:58.605847Z","iopub.execute_input":"2024-05-14T02:43:58.609755Z","iopub.status.idle":"2024-05-14T02:44:10.912349Z","shell.execute_reply.started":"2024-05-14T02:43:58.609704Z","shell.execute_reply":"2024-05-14T02:44:10.911314Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 1. 학습 및 제출에 사용할 csv 파일 불러오기","metadata":{}},{"cell_type":"code","source":"# 학습할 음악들의 장르 정답 csv 파일 불러오기\ntrain_info_csv = pd.read_csv('/kaggle/input/2024-ml-project4/train_labels.csv')\n\n# 제출에 사용할 csv 파일 불러오기\nsubmit = pd.read_csv('/kaggle/input/2024-ml-project4/submit.csv')\ntest_info_csv = submit['id']","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 어떤 장르들을 분류해야 하는지 확인해보기\ntrain_info_csv['genre'].unique()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 2. 데이터 불러오기 및 Handcrafted-Feature 추출","metadata":{}},{"cell_type":"markdown","source":"## Empty Module #1: extract_rhythm_features()\n첫 번째 Empty Module에서는 rhythm 도메인의 두 가지 feature인 autocorrelation tempogram과 fourier tempogram을 추출합니다.  \n단, 첫 번째 Empty Module에서는 **librosa.load() 함수를 통해 얻은 time-series (y, sr)를 입력으로 주어 두 feature를 얻는 것을 목표**로 합니다.\n\n(1-1) **librosa.load()**: extract_rhythm_features() 함수의 인자로 넘겨 받은 file_path에 대하여 1초 당 22050개의 샘플을 추출한 time-series를 load합니다.  \n(1-2) **librosa.onset.onset_strength()**: 앞서 load한 time-series를 함수의 입력으로 주어 onset_envelope를 추출합니다.  \n(1-3) **librosa.feature.tempogram()**: 앞서 추출한 onset_envelope와 sr을 함수의 입력으로 주어 autocorrelation tempogram feature를 추출합니다.  \n(1-4) autocorrelation tempogram에 대해 시간 축으로 평균을 내고 절대값을 취해 복소수를 제거함으로써 tempogram_feature를 얻습니다.   \n\n각 단계별 세부 내용은 아래 함수를 참고하시기 바랍니다.","metadata":{}},{"cell_type":"code","source":"# Empty Module #1\ndef extract_rhythm_features(file_path):\n    # 반환할 feature list 선언\n    feature = []\n    \n    # (1-1) librosa.load(): extract_rhythm_features() 함수의 인자로 넘겨 받은 file_path에 대하여 1초 당 22050개의 샘플을 추출한 time-series를 load합니다.\n    y, sr =\n    \n    # (1-2) librosa.onset.onset_strength(): 앞서 load한 time-series를 함수의 입력으로 주어 onset_envelope를 추출합니다.\n    onset_envelope = \n    \n    # autocorrelation tempogram\n    # (1-3) librosa.feature.tempogram(): 앞서 추출한 onset_envelope와 sr을 함수의 입력으로 주어 autocorrelation tempogram feature를 추출합니다. 함수의 입력 변수를 잘 확인하세요.\n    tempogram =                          # (384, 1293)\n    \n    # (1-4) autocorrelation tempogram에 대해 시간 축으로 평균을 내고 절대값을 취해 복소수를 제거함으로써 tempogram_feature를 얻습니다.\n    tempogram_feature =                  # (384, 1293) -> (384, ), 복소수를 없애기 위해 절대값 처리\n    \n    feature = tempogram_feature\n    \n    return feature","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Empty Module #2: extract_spectral_features()\n두 번째 Empty Module에서는 spectral 도메인의 두 가지 feature인 MFCC와 chromagram을 추출합니다.  \n단, 두 번째 Empty Module에서는 **librosa 라이브러리의 함수를 통해 spectrogram, power_spectrogram, melspectrogram, melspectrogram_db를 순차적으로 얻고,  \n이를 입력으로 주어 두 feature를 얻는 것을 목표**로 합니다.\n\n(2-1) **librosa.load()**: extract_spectral_features() 함수의 인자로 넘겨 받은 file_path에 대하여 1초 당 22050개의 샘플을 추출한 time-series를 load합니다.  \n(2-2) **librosa.stft()**: 앞서 load한 time-series를 함수의 입력으로 주어 stft를 추출하고, 절대값을 취해 spectrogram을 추출합니다.  \n(2-3) 앞서 얻은 spectrogram에 제곱 연산을 취해 power_spectrogram을 얻습니다.  \n(2-4) **librosa.feature.melspectrogram()**: 앞서 얻은 power_spectrogram을 함수의 입력으로 주어 melspectrogram을 추출합니다.  \n(2-5) **librosa.power_to_db()**: 앞서 얻은 melspectrogram을 함수의 입력으로 주어 db scale로 변환된 melspectrogram_db를 추출합니다.  \n(2-6) **librosa.feature.chroma_stft()**: (2-3)에서 얻은 power_spectrogram을 함수의 입력으로 주어 chromagram을 추출합니다.  \n(2-7) chromagram에 대해 시간 축으로 평균을 내어 feature로 사용할 수 있도록 변환합니다.  \n(2-8) **librosa.feature.mfcc()**: (2-5)에서 얻은 melspectrogram_db를 함수의 입력으로 주어 mfcc를 추출합니다.  \n(2-9) mfcc에 대해 시간 축으로 평균을 내어 feature로 사용할 수 있도록 변환합니다.  \n(2-10) chromagram과 mfcc를 하나의 feature로 반환할 수 있도록 concatenate를 수행합니다.\n\n각 단계별 세부 내용은 아래 함수를 참고하시기 바랍니다.","metadata":{}},{"cell_type":"code","source":"# Empty Module #2\ndef extract_spectral_features(file_path):\n    # 반환할 feature list 선언\n    feature = []\n    \n    # (2-1) librosa.load(): extract_spectral_features() 함수의 인자로 넘겨 받은 file_path에 대하여 1초 당 22050개의 샘플을 추출한 time-series를 load합니다.  \n    y, sr = \n\n    # (2-2) librosa.stft(): 앞서 load한 time-series를 함수의 입력으로 주어 stft를 추출하고, 절대값을 취해 복소수를 제거한 spectrogram을 추출합니다.  \n    # * 이 때, 93ms의 물리적 간격으로 나뉘어 spectrogram이 생성될 수 있도록 하이퍼파라미터 n_fft를 조절합니다. 어떠한 값을 넣어야 하는지는 공식 문서를 참고하세요.\n    spectrogram = \n    # (2-3) 앞서 얻은 spectrogram에 제곱 연산을 취해 power_spectrogram을 얻습니다.  \n    power_spectrogram = \n    # (2-4) librosa.feature.melspectrogram(): 앞서 얻은 power_spectrogram을 함수의 입력으로 주어 melspectrogram을 추출합니다.  \n    melspectrogram = \n    # (2-5) librosa.power_to_db(): 앞서 얻은 melspectrogram을 함수의 입력으로 주어 db scale로 변환된 melspectrogram_db를 추출합니다.  \n    melspectrogram_db =\n    \n    # chromagram\n    # (2-6) librosa.feature.chroma_stft(): (2-3)에서 얻은 power_spectrogram을 함수의 입력으로 주어 chromagram을 추출합니다. 함수의 입력 변수를 잘 확인하세요.\n    chromagram =                  # (12, 1293)\n    # (2-7) chromagram에 대해 시간 축으로 평균을 내어 chromagram_feature를 얻습니다. \n    chromagram_feature =          # (12, 1293) -> (12, )\n     \n    # mfcc\n    # (2-8) librosa.feature.mfcc(): (2-5)에서 얻은 melspectrogram_db를 함수의 입력으로 주어 mfcc를 추출합니다. 함수의 입력 변수를 잘 확인하세요.\n    mfcc =                       # (20, 1293)\n    # (2-9) mfcc에 대해 시간 축으로 평균을 내어 mfcc_feature를 얻습니다.  \n    mfcc_feature =               # (20, )\n    \n    # (2-10) chromagram과 mfcc를 하나의 feature로 반환할 수 있도록 concatenate를 수행합니다.\n    feature =  \n\n    return feature","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def feature_loader(data_info, split=None, rootpath=None, domain=None):\n    split = split.upper()\n    info_dict = {}\n    \n    if split=='TRAIN':\n        train_path = os.path.join(rootpath, 'train')\n        file_list = data_info['id']\n        label_list = data_info['genre']\n        \n        for file, label in zip(tqdm(file_list), label_list):\n            # 손상된 wav파일 제외\n            if file == 'train_412.wav':\n                continue   \n                \n            file_dict = {}     \n            file_dict['label'] = label\n            \n            file_path = os.path.join(train_path, file)\n            if domain == 'spectral':\n                features = extract_spectral_features(file_path)\n            elif domain == 'rhythm':\n                features = extract_rhythm_features(file_path)\n            else:\n                raise Exception(\"Check domain\")\n                \n            file_dict['features'] = features\n            info_dict[file] = file_dict\n            \n        return info_dict\n        \n    elif split=='TEST':\n        test_path = os.path.join(rootpath, 'test')\n        file_list = data_info\n        \n        for file in tqdm(file_list):\n            file_dict = {}\n            file_path = os.path.join(test_path, file)\n            \n            if domain == 'spectral':\n                features = extract_spectral_features(file_path)\n            elif domain == 'rhythm':\n                features = extract_rhythm_features(file_path)\n            else:\n                raise Exception(\"Check domain\")\n                \n            file_dict['features'] = features\n            info_dict[file] = file_dict\n            \n        return info_dict\n    \n    else:\n        raise Exception(\"Check split\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"rootpath = '/kaggle/input/2024-ml-project4'\n\n# 'spectral' or 'rhythm'\ndomain = 'spectral'\n\n# 선택한 domain의 feature 추출, dictionary 반환 받기\ntrain_data = feature_loader(train_info_csv, split='train', rootpath=rootpath, domain=domain)\ntest_data = feature_loader(test_info_csv, split='test', rootpath=rootpath, domain=domain) ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 3. 학습, 테스트 데이터 구축 및 분류기 설계","metadata":{}},{"cell_type":"code","source":"# dictionary에 담겨 있던 feature와 라벨들을 학습에 사용하기 위해 list에 담아줍니다.\n\nx_train = []\ny_train = []\n\nfor key in train_data.keys():\n    x_train.append(train_data[key]['features'])\n    y_train.append(train_data[key]['label'])\n    \nx_train = np.asarray(x_train) # (799, feature_size)\ny_train = np.asarray(y_train) # (799, )","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 음악 장르는 string으로 되어있으므로 학습을 위해 LabelEncoding을 진행합니다.\n\nfrom sklearn.preprocessing import LabelEncoder\n\nle = LabelEncoder()\ny_train = le.fit_transform(y_train)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# dictionary에 담겨 있던 feature들을 테스트에 사용하기 위해 list에 담아줍니다.\n\nx_test = []\n\nfor key in test_data.keys():\n    x_test.append(test_data[key]['features'])\n    \nx_test = np.asarray(x_test) # (200, feature_size)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Empty Module #3: RandomForestClassifier(random_state=seed)\n세 번째 Empty Module에서는 앞서 얻은 feature를 활용할 분류기를 설계합니다.  \n베이스라인의 분류기는 RandomForestClassifier(random_state=seed)로, 분류기에 대한 별다른 하이퍼파라미터 튜닝을 진행하지 않은 성능입니다.\n\n분류기를 선언하고 앞서 얻은 feature와 라벨로 fit, predict하는 과정을 구현하시면 됩니다.","metadata":{}},{"cell_type":"code","source":"# Empty Module #3\nfrom sklearn.ensemble import RandomForestClassifier\n\n# 분류기 선언\nrfc =\n\n# 분류기 fit\n\n\n# 예측\npred_rfc = ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submit['genre'] = le.inverse_transform(pred_rfc)\nsubmit.to_csv(f'{domain}_feature_baseline.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 4. train data에 대한 성능 확인 (Optional)\n임의의 validation set을 만들어 성능을 측정해보는 것도 좋은 방법입니다.","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\nfrom sklearn.metrics import accuracy_score\n\ntrain_pred = rfc.predict(x_train)\n\nprint(f\"Accuracy score: {accuracy_score(y_train, train_pred)*100}\")\nprint(confusion_matrix(y_train, train_pred))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 5. 성능을 올릴 수 있는 방법\n\n**0) 베이스라인에서 사용하는 feature가 음성 데이터의 무엇을 추출하는 것인지 확인**\n\n1) librosa 라이브러리에서 제공하는 또 다른 feature 활용\n\n2) 베이스라인에서 사용했던 spectral feature와 rhythm feature의 조합\n\n3) 최종 feature를 만들 때 베이스라인 코드처럼 단순 concat이 아닌 다른 방법\n\n** 이외에도 음성 데이터 추출 시 하이퍼파라미터 튜닝, 데이터 전처리, 데이터 증강 기법, 분류기 최적화 등 다양한 방법이 존재하지만 텀프로젝트의 목적 상 1)을 위주로 진행하시기 바랍니다.","metadata":{}}]}