{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Prediction","metadata":{"id":"eKdQwW-sv6U-"}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nimport numpy as np\n\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom sklearn.model_selection import train_test_split\nimport pandas as pd\n\nimport random\nimport os\nfrom tqdm import tqdm, trange\nfrom pprint import pprint","metadata":{"id":"_cZosjvHyX4t","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install transformers","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Seed를 설정합니다.\n## https://m.blog.naver.com/vail131/222306329719 참조\n\nrandom.seed(42)\nnp.random.seed(42)\ntorch.manual_seed(42)\ntorch.cuda.manual_seed(42)","metadata":{"id":"T3LFkMbUw069","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## genre에 저장되어있는 숫자와 실제 장르 명의 대응관계입니다.\n\n## 모델을 어떻게 학습시켰는가에 따라, test datset에 존재하는 장르만 사용하도록 \n## id2label, label2 id, num_classes를 변경해도 괜찮습니다.\n\n## e.g. \n## id2label = {'발라드': 0, '록/메탈': 1, '댄스': 2, 'POP': 4,'R&B/Soul': 5, '랩/힙합': 7, '성인가요/트로트': 16}\n## label2id = {value: key for key, value in id2label.items()}\n## num_classes = 7\n\n\nid2label = {\n    '발라드': 0, '록/메탈': 1, '댄스': 2, '포크/블루스': 3,'POP': 4,'R&B/Soul': 5, '-': 6,\n    '랩/힙합': 7, '국내영화': 8, 'CCM': 9, '국내CCM': 10, '클래식': 11,'오페라/성악': 12, '국외영화': 13,\n    '국내드라마': 14, '인디음악': 15, '성인가요/트로트': 16, '일렉트로니카': 17, '뉴에이지': 18,\n    '키즈': 19, '창작동요': 20, '크로스오버': 21, '재즈': 22, '보컬재즈': 23, '애시드/퓨전/팝': 24,\n    '월드뮤직': 25, '애니메이션/웹툰': 26, '만화': 27, '게임': 28, 'J-POP': 29, '중국음악': 30,\n    '샹송/프렌치팝': 31, '컨트리': 32, '국내뮤지컬': 33, '블루스': 34, '국외CCM': 35,\n    '포크': 36, '국외뮤지컬': 37, '가톨릭': 38, '국외드라마': 39, '브라질': 40, '뮤직테라피': 41,\n    '보사노바': 42, '라틴': 43, '국악': 44, '국악가요': 45, '찬송가': 46, '영어동요': 47, '워십': 48,\n    '불교': 49,'자장가': 50, '민요': 51}\nlabel2id = {value: key for key, value in id2label.items()}\nnum_classes = len(id2label)\nprint(f\"num_classes: {num_classes}\")","metadata":{"execution":{"iopub.status.busy":"2023-08-22T08:10:18.142153Z","iopub.execute_input":"2023-08-22T08:10:18.142556Z","iopub.status.idle":"2023-08-22T08:10:18.154859Z","shell.execute_reply.started":"2023-08-22T08:10:18.142525Z","shell.execute_reply":"2023-08-22T08:10:18.153684Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## test df를 불러옵니다.\nTEST_CSV_PATH = f'/kaggle/input/3-aikuthon/test.csv'\ntest_df = pd.read_csv(TEST_CSV_PATH)\n\n## 첫 열의 이름을 'id'로 바꾸어줍니다.\ntest_df = test_df.rename(columns=({'Unnamed: 0':'id'}))\nprint(test_df.shape)\ntest_df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## dataset class를 정의합니다.\nclass SongDatasetTest(Dataset):\n    def __init__(self, df):\n        df = df.reset_index(drop=True)\n        self.song_names = df[\"song_name\"]\n        self.artists = df[\"artist\"]\n        self.lyrics = df[\"lyrics\"]\n        self.id = df[\"id\"]\n\n    def __getitem__(self, idx):\n        song_name = self.song_names[idx]\n        artist = self.artists[idx]\n        lyrics = self.lyrics[idx]\n        id = self.id[idx]\n\n        return {'song_name': song_name,\n                'artist': artist,\n                'lyrics': lyrics,\n                'id': id}\n\n    def __len__(self):\n        return len(self.song_names)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## test_dataset과 dataloader를 정의합니다.\ntest_datasets = SongDatasetTest(test_df)\n\n## Inference 시에는 shuffle=False로 설정해야 출력 순서가 바뀌지 않아 제대로 된 채점이 가능합니다. \n## 단, training 시에는 shuffle=True로 설정해주는 것이 좋습니다.\nBATCH_SIZE_TEST = 256\ntest_dataloader = DataLoader(test_datasets, batch_size = BATCH_SIZE_TEST, shuffle = False)\ntest_datasets[5515]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## 모델 불러오기\n\n## 1. torch.save(model.state_dict(), MODEL_PATH) 과 같이 저장된 모델의 state dict를 불러올 때\n### Training에 사용했던 모델과 tokenizer를 먼저 정의합니다.\n### 여기에서는 transformers 라이브러리에서 제공되는 model / tokenizer를 사용했으나,\n### custom model을 정의하여 사용했을 경우 모델 class를 가져와 instance를 생성한 후, state_dict를 불러와야 합니다.\n\nfrom transformers import BertForSequenceClassification, DistilBertTokenizerFast\nMODEL_PATH = ## model weight path\nMODEL_NAME = ## model name\ntokenizer = DistilBertTokenizerFast.from_pretrained(MODEL_NAME, do_lower_case=False) ## model used in training phase\ntest_model = BertForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=num_classes).to(device)  ## tokenizer used in training phase\n\n### 저장된 모델의 state_dict를 불러옵니다.\ndevice = \"cuda\"\ntest_model.load_state_dict(torch.load(MODEL_PATH))\ntest_model.to(device)\n\n\n## 2. 위에서 훈련시켜 '변수에 저장되어있는' 모델을 사용할 경우\n### 해당 inference baseline 위에서 따로 코드를 작성하여 모델을 학습시켰고, 이를 변수에 저장해두었을 경우\n### 모델을 따로 저장하고 불러오지 않고, 변수에 저장되어있는 모델을 그대로 가져와 사용할 수 있습니다.\n\n# test_model = ## 모델이 저장된 변수","metadata":{"id":"-5XPVlMJvRZW","outputId":"cc44f3a5-e3ab-4ba0-aa61-66f6d71a6923","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## predict\n### 확률이 가장 높게 예측된 index를 result에 모두 저장합니다.\n\nresult = []\nwith torch.no_grad():\n    test_correct_predictions, test_incorrect_predictions, count = 0, 0, 1\n    for data in test_dataloader:\n        inputs = data['lyrics']\n        encoded_inputs = tokenizer(inputs, return_tensors='pt', padding='max_length', truncation=True, max_length=MAX_LEN_TEST).to(device)\n        test_model = test_model.to(device)\n\n        test_model.eval()\n        outputs = test_model(**encoded_inputs).logits\n        _, predicted = torch.max(outputs, 1)\n\n        for i, output in enumerate(outputs):\n            song = data['song_name'][i]\n            artist = data['artist'][i]\n            predicted = torch.topk(output, 1).indices.item()\n            id = data['id'][i].item()\n            \n            \n            output_for_print = {\n              \"Song\": song,\n              \"Artist\": artist,\n              \"Predicted\": label2id[predicted],\n              \"Id\": str(id)\n            }\n\n            output_for_submission = {\n              \"id\": id,\n              \"genre\": predicted\n            }\n\n            result.append(output_for_submission)\n\n            print(f\"{count} / {len(test_datasets)}\")\n            count += 1\n            \n            ### 단순 결과 출력용입니다. 제거해도 괜찮습니다.\n            pprint(output_for_print, sort_dicts=False)","metadata":{"id":"4_J20OgpdU9o","outputId":"96c73cd6-6b47-4195-a584-93319f5dcd49","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## 예측된 값의 리스트를 DataFrame 형식의 df_test로 변환하고, csv file로 저장합니다.\ndf_test = pd.DataFrame(result)\nRESULT_PATH = f'/kaggle/working/prediction_{int(now.timestamp())}.csv'\ndf_test.to_csv(RESULT_PATH, index=False)\ndf_test","metadata":{"id":"PIKnvYOo9zpr","outputId":"c735619c-f32e-417f-9fb5-ff11670aaf85","trusted":true},"execution_count":null,"outputs":[]}]}