{"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":"code","source":"import os\nimport json\nimport h5py\nimport numpy as np\nimport pandas as pd\nfrom tqdm.notebook import tqdm","metadata":{"execution":{"iopub.status.busy":"2022-06-21T10:00:53.477073Z","iopub.execute_input":"2022-06-21T10:00:53.478377Z","iopub.status.idle":"2022-06-21T10:00:53.484168Z","shell.execute_reply.started":"2022-06-21T10:00:53.478318Z","shell.execute_reply":"2022-06-21T10:00:53.483138Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Preprocess","metadata":{}},{"cell_type":"code","source":"fname_article ='../input/h-and-m-personalized-fashion-recommendations/articles.csv'\narticles = pd.read_csv(fname_article, sep=',')","metadata":{"execution":{"iopub.status.busy":"2022-06-19T13:10:03.776197Z","iopub.execute_input":"2022-06-19T13:10:03.776755Z","iopub.status.idle":"2022-06-19T13:10:04.755127Z","shell.execute_reply.started":"2022-06-19T13:10:03.776724Z","shell.execute_reply":"2022-06-19T13:10:04.754178Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(articles)","metadata":{"execution":{"iopub.status.busy":"2022-06-19T13:10:04.914883Z","iopub.execute_input":"2022-06-19T13:10:04.915265Z","iopub.status.idle":"2022-06-19T13:10:04.923016Z","shell.execute_reply.started":"2022-06-19T13:10:04.915234Z","shell.execute_reply":"2022-06-19T13:10:04.922214Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"articles[['product_type_name']].value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-06-19T13:10:06.90421Z","iopub.execute_input":"2022-06-19T13:10:06.904968Z","iopub.status.idle":"2022-06-19T13:10:06.93385Z","shell.execute_reply.started":"2022-06-19T13:10:06.904933Z","shell.execute_reply":"2022-06-19T13:10:06.933006Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df = articles[['article_id','index_name','product_type_name','section_name','detail_desc']]","metadata":{"execution":{"iopub.status.busy":"2022-06-19T13:10:08.825141Z","iopub.execute_input":"2022-06-19T13:10:08.825487Z","iopub.status.idle":"2022-06-19T13:10:08.834828Z","shell.execute_reply.started":"2022-06-19T13:10:08.825459Z","shell.execute_reply":"2022-06-19T13:10:08.833731Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pd.set_option('display.max_columns', 100)\npd.set_option('display.max_colwidth', 500)\ntrain_df[['detail_desc']].head(5)","metadata":{"execution":{"iopub.status.busy":"2022-06-19T13:10:09.928996Z","iopub.execute_input":"2022-06-19T13:10:09.929371Z","iopub.status.idle":"2022-06-19T13:10:09.944287Z","shell.execute_reply.started":"2022-06-19T13:10:09.929342Z","shell.execute_reply":"2022-06-19T13:10:09.943488Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"article_unique_1 = train_df['index_name'].unique()\narticle_to_idx_1 = {v:k for k,v in enumerate(article_unique_1)}\n\ntemp_1 = train_df['index_name'].map(article_to_idx_1.get).dropna()\nif len(temp_1) == len(train_df):\n    print('no-null')\n    train_df['index_name'] = temp_1\nelse:\n    print('detect null')\n\narticle_unique_2 = train_df['product_type_name'].unique()\narticle_to_idx_2 = {v:k for k,v in enumerate(article_unique_2)}\n\ntemp_2 = train_df['product_type_name'].map(article_to_idx_2.get).dropna()\nif len(temp_2) == len(train_df):\n    print('no-null')\n    train_df['product_type_name'] = temp_2\nelse:\n    print('detect null')\n\narticle_unique_3 = train_df['section_name'].unique()\narticle_to_idx_3 = {v:k for k,v in enumerate(article_unique_3)}\n\ntemp_3 = train_df['section_name'].map(article_to_idx_3.get).dropna()\nif len(temp_3) == len(train_df):\n    print('no-null')\n    train_df['section_name'] = temp_3\nelse:\n    print('detect null')\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2022-06-19T13:10:11.697317Z","iopub.execute_input":"2022-06-19T13:10:11.697837Z","iopub.status.idle":"2022-06-19T13:10:11.867031Z","shell.execute_reply.started":"2022-06-19T13:10:11.697805Z","shell.execute_reply":"2022-06-19T13:10:11.866161Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df","metadata":{"execution":{"iopub.status.busy":"2022-06-19T13:10:14.775499Z","iopub.execute_input":"2022-06-19T13:10:14.775849Z","iopub.status.idle":"2022-06-19T13:10:14.791743Z","shell.execute_reply.started":"2022-06-19T13:10:14.775819Z","shell.execute_reply":"2022-06-19T13:10:14.791Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def checkExist(filePath):\n    img_url = f'../input/h-and-m-personalized-fashion-recommendations/images/0{str(filePath)[:2]}/0{int(filePath)}.jpg'\n    if os.path.isfile(img_url):\n        return True\n    else:\n        return False","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:06:16.517359Z","iopub.execute_input":"2022-06-19T00:06:16.517748Z","iopub.status.idle":"2022-06-19T00:06:16.52531Z","shell.execute_reply.started":"2022-06-19T00:06:16.517711Z","shell.execute_reply":"2022-06-19T00:06:16.523916Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df['imgExist'] = train_df['article_id'].apply(lambda x:checkExist(x))","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:06:16.529105Z","iopub.execute_input":"2022-06-19T00:06:16.52952Z","iopub.status.idle":"2022-06-19T00:12:16.045045Z","shell.execute_reply.started":"2022-06-19T00:06:16.529484Z","shell.execute_reply":"2022-06-19T00:12:16.044202Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df = train_df[train_df['imgExist']==True]\ntrain_df.to_csv(os.path.join('./', 'filteredArticle.csv'), index=False)\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:16.046169Z","iopub.execute_input":"2022-06-19T00:12:16.046978Z","iopub.status.idle":"2022-06-19T00:12:16.827Z","shell.execute_reply.started":"2022-06-19T00:12:16.046937Z","shell.execute_reply":"2022-06-19T00:12:16.826029Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_vc_df(df, col):    \n    vc_df = df[col].value_counts().reset_index()\n    vc_df.columns = [col, 'count']\n    vc_df['percentage'] = (vc_df['count'] / vc_df['count'].sum())*100\n    return vc_df","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:16.828396Z","iopub.execute_input":"2022-06-19T00:12:16.828858Z","iopub.status.idle":"2022-06-19T00:12:16.835101Z","shell.execute_reply.started":"2022-06-19T00:12:16.828809Z","shell.execute_reply":"2022-06-19T00:12:16.834262Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"idx_to_article_1 = {v:k for k,v in article_to_idx_1.items()}\nidx_to_article_2 = {v:k for k,v in article_to_idx_2.items()}\nidx_to_article_3 = {v:k for k,v in article_to_idx_3.items()}","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:16.836637Z","iopub.execute_input":"2022-06-19T00:12:16.837293Z","iopub.status.idle":"2022-06-19T00:12:16.84604Z","shell.execute_reply.started":"2022-06-19T00:12:16.837246Z","shell.execute_reply":"2022-06-19T00:12:16.845169Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"vc_df = get_vc_df(train_df, 'index_name')\nvc_df['name'] = vc_df['index_name'].apply(lambda x: idx_to_article_1[x])\nvc_df.head(10)","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:16.847453Z","iopub.execute_input":"2022-06-19T00:12:16.848106Z","iopub.status.idle":"2022-06-19T00:12:16.867119Z","shell.execute_reply.started":"2022-06-19T00:12:16.84806Z","shell.execute_reply":"2022-06-19T00:12:16.866225Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\nimport sentencepiece as spm # sentencepiece 모듈을 가져온다.\nVOCAB_DIR = './'\n\n# product 칼럼의 상품명을 product.txt 파일명으로 저장한다.\nwith open(os.path.join(VOCAB_DIR, 'detail_desc.txt'), 'w', encoding='utf-8') as f:\n    f.write(train_df['detail_desc'].str.cat(sep='\\n'))\n\n# sentencepiece 모델을 학습시키는 함수이다.\ndef train_spm(txt_path, spm_path,\n              vocab_size=10000, input_sentence_size=100000):  \n    # input_sentence_size: 개수 만큼만 학습데이터로 사용된다.\n    # vocab_size: 사전 크기\n    spm.SentencePieceTrainer.Train(\n        f' --input={txt_path} --model_type=bpe'\n        f' --model_prefix={spm_path} --vocab_size={vocab_size}'\n        f' --input_sentence_size={input_sentence_size}'\n        f' --shuffle_input_sentence=true'\n    )\n\n# product.txt 파일로 sentencepiece 모델을 학습 시킨다. \n# 학습이 완료되면 spm.model, spm.vocab 파일이 생성된다.\ntrain_spm(txt_path=os.path.join(VOCAB_DIR, 'detail_desc.txt'), \n          spm_path=os.path.join(VOCAB_DIR, 'spm')) # spm 접두어\n\n# 센텐스피스 모델 학습이 완료되면 product.txt는 삭제\nos.remove(os.path.join(VOCAB_DIR, 'detail_desc.txt'))\n\n# 필요한 파일이 제대로 생성됐는지 확인\nfor dirname, _, filenames in os.walk(VOCAB_DIR):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:16.868652Z","iopub.execute_input":"2022-06-19T00:12:16.869146Z","iopub.status.idle":"2022-06-19T00:12:19.477274Z","shell.execute_reply.started":"2022-06-19T00:12:16.869111Z","shell.execute_reply":"2022-06-19T00:12:19.475922Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 센텐스피스 모델을 로드한다.\nsp = spm.SentencePieceProcessor()\nsp.Load(os.path.join(VOCAB_DIR, 'spm.model'))\n\n# product 칼럼의 상품명을 분절한 결과를 tokenized_product 칼럼에 저장한다.\ntrain_df['tokens'] = train_df['detail_desc'].map(lambda x: \" \".join(sp.EncodeAsPieces(str(x)) ))\n\ntrain_df[['detail_desc', 'tokens']].head()","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:19.478764Z","iopub.execute_input":"2022-06-19T00:12:19.479363Z","iopub.status.idle":"2022-06-19T00:12:27.456279Z","shell.execute_reply.started":"2022-06-19T00:12:19.479319Z","shell.execute_reply":"2022-06-19T00:12:27.455359Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# csv 포맷으로 저장한다.\ntrain_df.to_csv(os.path.join('./', 'train.csv'), index=False)","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:27.457725Z","iopub.execute_input":"2022-06-19T00:12:27.458096Z","iopub.status.idle":"2022-06-19T00:12:29.100431Z","shell.execute_reply.started":"2022-06-19T00:12:27.458059Z","shell.execute_reply":"2022-06-19T00:12:29.099489Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Cate_model","metadata":{}},{"cell_type":"code","source":"import torch # 파이토치 패키지 임포트\nimport torch.nn as nn # 자주 사용하는 torch.nn패키지를 별칭 nn으로 명명\n# 허깅페이스의 트랜스포머 패키지에서 BertConfig, BertModel 클래스 임포트\nfrom transformers import BertConfig, BertModel\n\nclass CateClassifier(nn.Module):\n    \"\"\"상품정보를 받아서 대/중/소/세 카테고리를 예측하는 모델    \n    \"\"\"\n    def __init__(self, cfg):        \n        \"\"\"\n        매개변수\n        cfg: hidden_size, nlayers 등 설정값을 가지고 있는 변수\n        \"\"\"\n        super(CateClassifier, self).__init__()\n        # 글로벌 설정값을 멤버 변수로 저장\n        self.cfg = cfg\n        # 버트모델의 설정값을 멤버 변수로 저장\n        self.bert_cfg = BertConfig( \n            cfg.vocab_size, # 사전 크기\n            hidden_size=cfg.hidden_size, # 히든 크기\n            num_hidden_layers=cfg.nlayers, # 레이어 층 수\n            num_attention_heads=cfg.nheads, # 어텐션 헤드의 수\n            intermediate_size=cfg.intermediate_size, # 인터미디어트 크기\n            hidden_dropout_prob=cfg.dropout, # 히든 드롭아웃 확률 값\n            attention_probs_dropout_prob=cfg.dropout, # 어텐션 드롭아웃 확률 값 \n            max_position_embeddings=cfg.seq_len, # 포지션 임베딩의 최대 길이\n            type_vocab_size=cfg.type_vocab_size, # 타입 사전 크기\n        )\n        # 텍스트 인코더로 버트모델 사용\n        self.text_encoder = BertModel(self.bert_cfg)\n        \n        self.img_encoder = nn.Linear(cfg.img_feat_size, cfg.hidden_size)#2048,512\n                \n        # 분류기(Classifier) 생성기\n        def get_cls(target_size=1):\n            return nn.Sequential(\n                nn.Linear(cfg.hidden_size*2, cfg.hidden_size),\n                nn.LayerNorm(cfg.hidden_size),\n                nn.Dropout(cfg.dropout),\n                nn.ReLU(),\n                nn.Linear(cfg.hidden_size, target_size),\n            )        \n          \n        # 대 카테고리 분류기\n        self.b_cls = get_cls(cfg.n_b_cls)\n        # 중 카테고리 분류기\n        self.m_cls = get_cls(cfg.n_m_cls)\n        # 소 카테고리 분류기\n        self.s_cls = get_cls(cfg.n_s_cls)\n \n    \n    def forward(self, token_ids, token_mask, token_types, img_feat, label=None):\n        \"\"\"        \n        매개변수\n        token_ids: 전처리된 상품명을 인덱스로 변환하여 token_ids를 만들었음\n        token_mask: 실제 token_ids의 개수만큼은 1, 나머지는 0으로 채움\n        token_types: ▁ 문자를 기준으로 서로 다른 타입의 토큰임을 타입 인덱스로 저장\n        img_feat: resnet50으로 인코딩된 이미지 피처\n        label: 정답 대/중/소/세 카테고리\n        \"\"\"\n        # 전처리된 상품명을 하나의 텍스트벡터(text_vec)로 변환\n        # 반환 튜플(시퀀스 아웃풋, 풀드(pooled) 아웃풋) 중 시퀀스 아웃풋만 사용\n        text_output = self.text_encoder(token_ids, token_mask, token_type_ids=token_types)[0]\n        \n        # 시퀀스 중 첫 타임스탭의 hidden state만 사용. \n        text_vec = text_output[:, 0]\n        \n        # img_feat를 텍스트벡터와 결합하기 앞서 선형변환 적용\n        img_vec = self.img_encoder(img_feat)\n        img_vec = img_vec[:, 0]\n        \n        # 이미지벡터와 텍스트벡터를 직렬연결(concatenate)하여 결합벡터 생성\n        comb_vec = torch.cat([text_vec, img_vec], 1)\n        \n        # 결합된 벡터로 대카테고리 확률분포 예측\n        b_pred = self.b_cls(comb_vec)\n        # 결합된 벡터로 중카테고리 확률분포 예측\n        m_pred = self.m_cls(comb_vec)\n        # 결합된 벡터로 소카테고리 확률분포 예측\n        s_pred = self.s_cls(comb_vec)\n        \n        # 데이터 패러럴 학습에서 GPU 메모리를 효율적으로 사용하기 위해 \n        # loss를 모델 내에서 계산함.\n        if label is not None:\n            # 손실(loss) 함수로 CrossEntropyLoss를 사용\n            # label의 값이 -1을 가지는 샘플은 loss계산에 사용 안 함\n            loss_func = nn.CrossEntropyLoss(ignore_index=-1)\n            # label은 batch_size x 4를 (batch_size x 1) 4개로 만듦\n            b_label, m_label, s_label = label.split(1, 1)\n            # 대카테고리의 예측된 확률분포와 정답확률 분포의 차이를 손실로 반환\n            b_loss = loss_func(b_pred, b_label.view(-1))\n            # 중카테고리의 예측된 확률분포와 정답확률 분포의 차이를 손실로 반환\n            m_loss = loss_func(m_pred, m_label.view(-1))\n            # 소카테고리의 예측된 확률분포와 정답확률 분포의 차이를 손실로 반환\n            s_loss = loss_func(s_pred, s_label.view(-1))\n            # 대/중/소/세 손실의 평균을 낼 때 실제 대회 평가방법과 일치하도록 함\n            loss = (b_loss + 1.2*m_loss + 1.3*s_loss)/3  \n        else: # label이 없으면 loss로 0을 반환\n            loss = b_pred.new(1).fill_(0)      \n        \n        # 최종 계산된 손실과 예측된 대/중/소/세 각 확률분포를 반환\n        return loss, [b_pred, m_pred, s_pred]","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:29.101927Z","iopub.execute_input":"2022-06-19T00:12:29.102316Z","iopub.status.idle":"2022-06-19T00:12:35.909592Z","shell.execute_reply.started":"2022-06-19T00:12:29.102281Z","shell.execute_reply":"2022-06-19T00:12:35.908692Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 사진 인코딩..","metadata":{}},{"cell_type":"code","source":"img_indices = [623245001]\nimg_url = f'../input/h-and-m-personalized-fashion-recommendations/images/0{str(img_indices[0])[:2]}/0{int(img_indices[0])}.jpg'\n\nimport matplotlib.pyplot as plt\nimg = plt.imread(img_url)\nR, G, B = img[:,:,0], img[:,:,1], img[:,:,2]\nimgGray = 0.2989 * R + 0.5870 * G + 0.1140 * B\nprint(imgGray[:,:,].shape)\nplt.imshow(imgGray, cmap='gray') # 흑백으로 보고 싶을 땐, plt.imshow(a, cmap='gray')","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:35.910897Z","iopub.execute_input":"2022-06-19T00:12:35.911742Z","iopub.status.idle":"2022-06-19T00:12:36.355075Z","shell.execute_reply.started":"2022-06-19T00:12:35.911701Z","shell.execute_reply":"2022-06-19T00:12:36.354336Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.decomposition import PCA\nimgList = train_df['article_id'].tolist()\nprint(len(imgList))","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:36.356456Z","iopub.execute_input":"2022-06-19T00:12:36.357048Z","iopub.status.idle":"2022-06-19T00:12:36.744675Z","shell.execute_reply.started":"2022-06-19T00:12:36.357011Z","shell.execute_reply":"2022-06-19T00:12:36.743854Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def returnUrl(name):\n    img_url = f'../input/h-and-m-personalized-fashion-recommendations/images/0{str(name)[:2]}/0{int(name)}.jpg'\n    try:\n        img = plt.imread(img_url)\n        imgGray = 0\n        if img.ndim>2:\n            R, G, B = img[:,:,0], img[:,:,1], img[:,:,2]\n            imgGray = 0.2989 * R + 0.5870 * G + 0.1140 * B\n        if img.ndim<3:\n            plt.imshow(img, cmap='gray')\n            print(img.ndim)\n        if type(imgGray) is int:\n            imgGray = np.array(imgGray)\n        imgGray = imgGray.reshape(imgGray.size,1,1)\n        return imgGray\n    except Exception as e:    # 모든 예외의 에러 메시지를 출력할 때는 Exception을 사용\n        print('예외가 발생했습니다.', e)\n        img_url = f'../input/h-and-m-personalized-fashion-recommendations/images/0{str(name)[:2]}'\n        for dirname, _, filenames in os.walk(img_url):\n            if not str(article_pd[i]) in str(article_pd[i]):\n                print(i, end=' ')\n        \n#df = dfs['article_id'][:300].apply(lambda x:returnUrl(x))","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:36.745813Z","iopub.execute_input":"2022-06-19T00:12:36.746767Z","iopub.status.idle":"2022-06-19T00:12:36.757232Z","shell.execute_reply.started":"2022-06-19T00:12:36.746725Z","shell.execute_reply":"2022-06-19T00:12:36.756248Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"flattenImg = returnUrl(108775015)\nflattenImg = flattenImg[::300]\nflattenImg = flattenImg[:5000]\nif len(flattenImg)<6144:\n    print(flattenImg.shape, end='')\n    pad = [0] * (6144 - len(flattenImg))\n    flattenImg = flattenImg.reshape(1, len(flattenImg))\n    flattenImg =flattenImg[0].tolist()\n    flattenImg = np.array(flattenImg + pad)\n    flattenImg = flattenImg.reshape(len(flattenImg), 1)\n    print(flattenImg.shape)","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:36.758744Z","iopub.execute_input":"2022-06-19T00:12:36.759179Z","iopub.status.idle":"2022-06-19T00:12:36.828688Z","shell.execute_reply.started":"2022-06-19T00:12:36.759142Z","shell.execute_reply":"2022-06-19T00:12:36.827622Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Cate_dataset","metadata":{}},{"cell_type":"code","source":"import torch # 파이토치 패키지 임포트\nfrom torch.utils.data import Dataset # Dataset 클래스 임포트\nimport h5py # h5py 패키지 임포트\nimport re # 정규식표현식 모듈 임포트 \n\nclass CateDataset(Dataset):\n    \"\"\"데이터셋에서 학습에 필요한 형태로 변환된 샘플 하나를 반환\n    \"\"\"\n    def __init__(self, df_data, img_h5_path, token2id, tokens_max_len=64, type_vocab_size=30):\n        print(\"CateDataset:\",len(df_data))\n        \"\"\"        \n        매개변수\n        df_data: 상품타이틀, 카테고리 등의 정보를 가지는 데이터프레임\n        img_h5_path: img_feat가 저장돼 있는 h5 파일의 경로\n        token2id: token을 token_id로 변환하기 위한 맵핑 정보를 가진 딕셔너리\n        tokens_max_len: tokens의 최대 길이. 상품명의 tokens가 이 이상이면 잘라서 버림\n        type_vocab_size: 타입 사전의 크기\n        \"\"\"  \n        self.tokens = df_data['tokens'].values # 전처리된 상품명\n        self.img_indices = df_data['article_id'].values\n        self.img_h5_path = img_h5_path \n        self.tokens_max_len = tokens_max_len        \n        self.labels = df_data[['index_name','product_type_name','section_name']].values\n        self.token2id = token2id \n        self.p = re.compile('▁[^▁]+') # ▁기호를 기준으로 나누기 위한 컴파일된 정규식\n        self.type_vocab_size = type_vocab_size\n  \n        \n    def __getitem__(self, idx):\n\n        \"\"\"\n        데이터셋에서 idx에 대응되는 샘플을 변환하여 반환        \n        \"\"\"\n        if idx >= len(self):\n            raise StopIteration\n\n        # idx에 해당하는 상품명 가져오기. 상품명은 문자열로 저장돼 있음\n        tokens = self.tokens[idx]\n        if not isinstance(tokens, str):\n            tokens = ''\n\n        # 상품명을 ▁기호를 기준으로 분리하여 파이썬 리스트로 저장\n        # \"▁직소퍼즐 ▁1000 조각 ▁바다 거북 의 ▁여행 ▁pl 12 75\" =>\n        # [\"▁직소퍼즐\", \"▁1000 조각\", \"▁바다 거북 의\", \"▁여행\", \"▁pl 12 75\"]\n        tokens = self.p.findall(tokens)\n\n        # ▁ 기호 별 토큰타입 인덱스 부여\n        # [\"▁직소퍼즐\", \"▁1000 조각\", \"▁바다 거북 의\", \"▁여행\", \"▁pl 12 75\"] =>\n        # [     0     ,     1    1  ,    2     2  2 ,     3   ,   4  4   4 ]\n        token_types = [type_id for type_id, word in enumerate(tokens) for _ in word.split()]       \n        tokens = \" \".join(tokens) # ▁기호로 분리되기 전의 원래의 tokens으로 되돌림\n\n        # 토큰을 토큰에 대응되는 인덱스로 변환\n        # \"▁직소퍼즐 ▁1000 조각 ▁바다 거북 의 ▁여행 ▁pl 12 75\" =>\n        # [2291, 784, 2179, 3540, 17334, 30827, 1114, 282, 163, 444]\n        # \"▁직소퍼즐\" => 2291\n        # \"▁1000\" => 784\n        # \"조각\" => 2179\n        # ...\n        token_ids = [self.token2id[tok] if tok in self.token2id else 0 for tok in tokens.split()]\n\n        # token_ids의 길이가 max_len보다 길면 잘라서 버림\n        if len(token_ids) > self.tokens_max_len:\n            token_ids = token_ids[:self.tokens_max_len]      \n            token_types = token_types[:self.tokens_max_len]\n\n        # token_ids의 길이가 max_len보다 짧으면 짧은만큼 PAD값 0 값으로 채워넣음\n        # token_ids 중 값이 있는 곳은 1, 그 외는 0으로 채운 token_mask 생성\n        token_mask = [1] * len(token_ids)\n        token_pad = [0] * (self.tokens_max_len - len(token_ids))\n        token_ids += token_pad\n        token_mask += token_pad\n        token_types += token_pad # max_len 보다 짧은만큼 PAD 추가\n\n        # h5파일에서 이미지 인덱스에 해당하는 img_feat를 가져옴\n        # 파이토치의 데이터로더에 의해 동시 h5파일에 동시접근이 발생해도\n        # 안정적으로 img_feat를 가져오려면 아래처럼 매번 h5py.File 호출필요\n        # with h5py.File(self.img_h5_path, 'r') as img_feats:\n        # img_feat = img_feats['img_feat'][self.img_indices[idx]]\n\n        #print(token_ids, token_mask, token_types, img_feat)\n        import matplotlib.pyplot as plt\n\n        img_url = f'../input/h-and-m-personalized-fashion-recommendations/images/0{str(self.img_indices[idx])[:2]}/0{int(self.img_indices[idx])}.jpg'\n        flattenImg = returnUrl(self.img_indices[idx])\n        flattenImg = flattenImg[::300]\n        if len(flattenImg)<6144:\n            pad = [0] * (6144 - len(flattenImg))\n            flattenImg = flattenImg.reshape(1, len(flattenImg))\n            flattenImg =flattenImg[0].tolist()\n            flattenImg = np.array(flattenImg + pad)\n            flattenImg = flattenImg.reshape(len(flattenImg), 1)\n        if len(flattenImg)<6144:\n            print(len(flattenImg))\n        img_feat = flattenImg[:6144]\n        img_feat = img_feat.reshape(-1,2048)   \n    \n        # 넘파이(numpy)나 파이썬 자료형을 파이토치의 자료형으로 변환\n        token_ids = torch.LongTensor(token_ids)\n        token_mask = torch.LongTensor(token_mask)\n        token_types = torch.LongTensor(token_types)\n\n        try:\n            # token_types의 타입 인덱스의 숫자 크기가 type_vocab_size 보다 작도록 바꿈\n            token_types[token_types >= self.type_vocab_size] = self.type_vocab_size-1 \n            img_feat = torch.FloatTensor(img_feat)\n        except Exception as e: \n            print(e)\n\n        # 대/중/소/세 라벨 준비\n        label = self.labels[idx]    \n        try:\n            label = torch.LongTensor(label)\n        except Exception as e:\n            print(e)\n            \n        # 크게 3가지 텍스트 입력, 이미지 입력, 라벨을 반환한다.\n        return token_ids, token_mask, token_types, img_feat, label\n\n    def __len__(self):\n        \"\"\"\n        tokens의 개수를 반환한다. 즉, 상품명 문장의 개수를 반환한다.\n        \"\"\"\n        return len(self.tokens)","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:36.830362Z","iopub.execute_input":"2022-06-19T00:12:36.830799Z","iopub.status.idle":"2022-06-19T00:12:36.8513Z","shell.execute_reply.started":"2022-06-19T00:12:36.830758Z","shell.execute_reply":"2022-06-19T00:12:36.850297Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train.py","metadata":{}},{"cell_type":"code","source":"import os\nimport time\nimport math\nimport torch\nimport random\nimport argparse\nimport numpy as np\nimport pandas as pd\nfrom torch.utils.data import DataLoader\nfrom sklearn.model_selection import StratifiedKFold, KFold\nfrom transformers import AdamW, get_linear_schedule_with_warmup\n\nimport warnings\nwarnings.filterwarnings(action='ignore')\n\n# 전처리된 데이터가 저장된 디렉터리\nDB_PATH=f'./'\n\n# 토큰을 인덱스로 치환할 때 사용될 사전 파일이 저장된 디렉터리 \nVOCAB_DIR=os.path.join(DB_PATH, 'vocab')\n\n# 학습된 모델의 파라미터가 저장될 디렉터리\nMODEL_PATH=f'./'\n\n# 미리 정의된 설정 값\nclass CFG:\n    learning_rate=3.0e-4 # 러닝 레이트\n    batch_size=1024 # 배치 사이즈\n    num_workers=4 # 워커의 개수\n    print_freq=100 # 결과 출력 빈도\n    start_epoch=0 # 시작 에폭\n    num_train_epochs=9 # 학습할 에폭수\n    warmup_steps=100 # lr을 서서히 증가시킬 step 수 100->50\n    max_grad_norm=10 # 그래디언트 클리핑에 사용\n    weight_decay=0.01\n    dropout=0.2 # dropout 확률\n    hidden_size=512 # 은닉 크기\n    intermediate_size=256 # TRANSFORMER셀의 intermediate 크기\n    nlayers=2 # BERT의 층수\n    nheads=8 # BERT의 head 개수\n    seq_len=32 # 토큰의 최대 길이\n    n_b_cls = 10 + 1 # 대카테고리 개수\n    n_m_cls = 131 + 1 # 중카테고리 개수\n    n_s_cls = 56 + 1 # 소카테고리 개수\n    vocab_size = 32000 # 토큰의 유니크 인덱스 개수\n    img_feat_size = 2048 # 이미지 피처 벡터의 크기\n    type_vocab_size = 30 # 타입의 유니크 인덱스 개수\n    csv_path = os.path.join(DB_PATH, 'train.csv')\n    h5_path = os.path.join(DB_PATH, 'train_img_feat.h5')\n    seed = 42 ","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:36.853105Z","iopub.execute_input":"2022-06-19T00:12:36.853777Z","iopub.status.idle":"2022-06-19T00:12:36.872149Z","shell.execute_reply.started":"2022-06-19T00:12:36.853738Z","shell.execute_reply":"2022-06-19T00:12:36.871232Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def calc_cate_acc(pred, label):\n    \"\"\"\n    대/중/소/세 카테고리별 정확도와 전체(overall) 정확도를 반환\n    전체 정확도는 대회 평가 방법과 동일한 가중치로 계산\n    \"\"\"\n    b_pred, m_pred, s_pred= pred    \n    _, b_idx = b_pred.max(1)\n    _, m_idx = m_pred.max(1)\n    _, s_idx = s_pred.max(1)\n        \n    b_acc = (b_idx == label[:, 0]).sum().item() / (label[:, 0]>0).sum().item()\n    m_acc = (m_idx == label[:, 1]).sum().item() / (label[:, 1]>0).sum().item()\n            \n    s_acc = (s_idx == label[:, 2]).sum().item() / ((label[:, 2]>0).sum().item()+1e-06)  \n    o_acc = (b_acc + 1.2*m_acc + 1.3*s_acc)/3\n    return o_acc, b_acc, m_acc, s_acc\n\n\ndef save_checkpoint(state, model_path, model_filename, is_best=False):\n    print('saving cust_model ...')\n    if not os.path.exists(model_path):\n        os.makedirs(model_path)\n    torch.save(state, os.path.join(model_path, model_filename))\n    if is_best:\n        torch.save(state, os.path.join(model_path, 'best_' + model_filename))\n\n\nclass AverageMeter(object):\n    \"\"\"Computes and stores the average and current value\"\"\"\n    def __init__(self):\n        self.reset()\n\n    def reset(self):\n        self.val = 0\n        self.avg = 0\n        self.sum = 0\n        self.count = 0\n\n    def update(self, val, n=1):\n        self.val = val\n        self.sum += val * n\n        self.count += n\n        self.avg = self.sum / self.count\n\n\ndef asMinutes(s):\n    m = math.floor(s / 60)\n    s -= m * 60\n    return '%dm %ds' % (m, s)\n\n\ndef timeSince(since, percent):\n    now = time.time()\n    s = now - since\n    es = s / (percent)\n    rs = es - s\n    return '%s (remain %s)' % (asMinutes(s), asMinutes(rs))","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:36.873242Z","iopub.execute_input":"2022-06-19T00:12:36.875508Z","iopub.status.idle":"2022-06-19T00:12:36.89125Z","shell.execute_reply.started":"2022-06-19T00:12:36.875477Z","shell.execute_reply":"2022-06-19T00:12:36.89052Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train(train_loader, model, optimizer, epoch, scheduler):\n    print('len(train_loader)', len(train_loader))\n    \"\"\"    \n    한 에폭 단위로 학습을 시킵니다.\n    매개변수\n    train_loader: 학습 데이터셋에서 배치(미니배치) 불러옵니다.\n    model: 학습될 파라미터를 가진 딥러닝 모델\n    optimizer: 파라미터를 업데이트 시키는 역할\n    scheduler: learning_rate를 감소시키는 역할\n    \"\"\"\n    print(\"Now Train!\")\n    # AverageMeter는 지금까지 입력 받은 전체 수의 평균 값 반환 용도\n    batch_time = AverageMeter()     # 한 배치처리 시간 집계\n    data_time = AverageMeter()      # 데이터 로딩 시간 집계\n    losses = AverageMeter()         # 손실 값 집계\n    o_accuracies = AverageMeter()   # 대회 평가 방법으로 집계\n    b_accuracies = AverageMeter()   # 대카테고리 정확도 집계\n    m_accuracies = AverageMeter()   # 중카테고리 정확도 집계\n    s_accuracies = AverageMeter()   # 소카테고리 정확도 집계\n    \n    sent_count = AverageMeter()     # 문장 처리 개수 집계\n    \n    # 학습 모드로 교체\n    model.train()\n    \n    start = end = time.time()\n    \n    # train_loader에서 반복해서 학습용 배치 데이터를 받아옵니다.\n    # CateDataset의 __getitem__() 함수의 반환 값과 동일한 변수 반환\n    for step, (token_ids, token_mask, token_types, img_feat, label) in enumerate(train_loader):\n\n        # 데이터 로딩 시간 기록\n        data_time.update(time.time() - end)\n\n        # 배치 데이터의 위치를 CPU메모리에서 GPU메모리로 이동\n#         token_ids, token_mask, token_types, img_feat, label = (\n#             token_ids.cuda(), token_mask.cuda(), token_types.cuda(), \n#             img_feat.cuda(), label.cuda())\n   \n        batch_size = token_ids.size(0)   \n\n        # model은 배치 데이터를 입력 받아서 예측 결과 및 loss 반환\n        # model은 인스턴스이나 __call__함수가 추가돼 함수처럼 호출이 가능합니다. \n        # CateClassifier의 __call__ 함수 내에서 forward 함수가 호출됩니다. \n        \n        loss, pred = model(token_ids, token_mask, token_types, img_feat, label)\n        loss = loss.mean() # Multi-GPU 학습의 경우 mean() 호출 필요\n\n        # loss 값을 기록\n        losses.update(loss.item(), batch_size)\n\n        # 역전파 수행\n        loss.backward()\n\n        # CFG.max_grad_norm 이상의 값을 가지는 그래디언트 값 클리핑\n        grad_norm = torch.nn.utils.clip_grad_norm_(model.parameters(), CFG.max_grad_norm)\n\n        scheduler.step()    # 스케쥴러로 learning_rate 조절\n        optimizer.step()    # 옵티마이저로 파라미터 업데이터\n        optimizer.zero_grad() # 옵티마이저 내의 그래디언트 초기화\n\n        # 소요시간 측정\n        batch_time.update(time.time() - end)\n        end = time.time()\n\n        sent_count.update(batch_size)\n\n            \n        # CFG.print_freq 주기대로 결과 로그를 출력\n        if step % CFG.print_freq == 0 or step == (len(train_loader)-1):\n            # 대/중/소/세가 예측된 pred와 정답 label로 정확도 계산 및 집계\n            o_acc, b_acc, m_acc, s_acc = calc_cate_acc(pred, label)\n            o_accuracies.update(o_acc, batch_size)\n            b_accuracies.update(b_acc, batch_size)\n            m_accuracies.update(m_acc, batch_size)\n            s_accuracies.update(s_acc, batch_size)\n            \n            print('Epoch: [{0}][{1}/{2}] '\n                  'Data {data_time.val:.3f} ({data_time.avg:.3f}) '\n                  'Elapsed {remain:s} '\n                  'Loss: {loss.val:.3f}({loss.avg:.3f}) '\n                  'OAcc: {o_acc.val:.3f}({o_acc.avg:.3f}) '\n                  'BAcc: {b_acc.val:.3f}({b_acc.avg:.3f}) '\n                  'MAcc: {m_acc.val:.4f}({m_acc.avg:.3f}) '\n                  'SAcc: {s_acc.val:.3f}({s_acc.avg:.3f}) '                  \n                  'Grad: {grad_norm:.4f}  '\n                  'LR: {lr:.6f}  '\n                  'sent/s {sent_s:.0f} '\n                  .format(\n                   epoch, step+1, len(train_loader),\n                   data_time=data_time, loss=losses,\n                   o_acc=o_accuracies, b_acc=b_accuracies, m_acc=m_accuracies,\n                   s_acc=s_accuracies, \n                   remain=timeSince(start, float(step+1)/len(train_loader)),\n                   grad_norm=grad_norm,\n                   lr=scheduler.get_lr()[0],                   \n                   sent_s=sent_count.avg/batch_time.avg\n                   ))\n    # 학습 동안 집계된 결과 반환\n    return (losses.avg, o_accuracies.avg, b_accuracies.avg, m_accuracies.avg, \n            s_accuracies.avg) ","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:36.892554Z","iopub.execute_input":"2022-06-19T00:12:36.893029Z","iopub.status.idle":"2022-06-19T00:12:36.91153Z","shell.execute_reply.started":"2022-06-19T00:12:36.892995Z","shell.execute_reply":"2022-06-19T00:12:36.910695Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def validate(valid_loader, model):\n    \"\"\"    \n    한 에폭 단위로 검증합니다.\n    매개변수\n    valid_loader: 검증 데이터셋에서 배치(미니배치) 불러옵니다.\n    model: train 함수에서 학습된 딥러닝 모델\n    \"\"\"    \n    batch_time = AverageMeter()     # 한 배치처리 시간 집계\n    data_time = AverageMeter()      # 데이터 로딩 시간 집계\n    losses = AverageMeter()         # 손실 값 집계\n    o_accuracies = AverageMeter()   # 대회 평가 방법으로 집계\n    b_accuracies = AverageMeter()   # 대카테고리 정확도 집계\n    m_accuracies = AverageMeter()   # 중카테고리 정확도 집계\n    s_accuracies = AverageMeter()   # 소카테고리 정확도 집계\n    \n    sent_count = AverageMeter()     # 문장 처리 개수 집계\n    \n    # 평가(evaluation) 모드로 교체\n    # 드롭아웃이나 배치정규화가 일관된 값을 내도록 함\n    model.eval()\n\n    start = end = time.time()\n    try:    \n        for step, (token_ids, token_mask, token_types, img_feat, label) in enumerate(valid_loader):\n            # 데이터 로딩 시간 기록\n            data_time.update(time.time() - end)\n\n            # 배치 데이터의 위치를 CPU메모리에서 GPU메모리로 이동\n#             token_ids, token_mask, token_types, img_feat, label = (\n#                 token_ids.cuda(), token_mask.cuda(), token_types.cuda(), \n#                 img_feat.cuda(), label.cuda())\n\n            batch_size = token_ids.size(0)\n\n            # with문 내에서는 그래디언트 계산을 하지 않도록 함\n            with torch.no_grad():\n                # model은 배치 데이터를 입력 받아서 예측 결과 및 loss 반환\n                loss, pred = model(token_ids, token_mask, token_types, img_feat, label)\n                loss = loss.mean()\n\n            # loss 값을 기록\n            losses.update(loss.item(), batch_size)\n\n            # 소요시간 측정\n            batch_time.update(time.time() - end)\n            end = time.time()\n\n            sent_count.update(batch_size)\n\n            # CFG.print_freq 주기대로 결과 로그를 출력\n            if step % CFG.print_freq == 0 or step == (len(valid_loader)-1):\n                o_acc, b_acc, m_acc, s_acc = calc_cate_acc(pred, label)\n                o_accuracies.update(o_acc, batch_size)\n                b_accuracies.update(b_acc, batch_size)\n                m_accuracies.update(m_acc, batch_size)\n                s_accuracies.update(s_acc, batch_size)\n\n                print('TEST: {0}/{1}] '\n                      'Data {data_time.val:.3f} ({data_time.avg:.3f}) '\n                      'Elapsed {remain:s} '\n                      'Loss: {loss.val:.4f}({loss.avg:.4f}) '\n                      'OAcc: {o_acc.val:.3f}({o_acc.avg:.3f}) '\n                      'BAcc: {b_acc.val:.3f}({b_acc.avg:.3f}) '\n                      'MAcc: {m_acc.val:.4f}({m_acc.avg:.3f}) '\n                      'SAcc: {s_acc.val:.3f}({s_acc.avg:.3f}) '\n                      'sent/s {sent_s:.0f} '\n                      .format(\n                       step+1, len(valid_loader),\n                       data_time=data_time, loss=losses,\n                       o_acc=o_accuracies, b_acc=b_accuracies, m_acc=m_accuracies,\n                       s_acc=s_accuracies,\n                       remain=timeSince(start, float(step+1)/len(valid_loader)),\n                       sent_s=sent_count.avg/batch_time.avg\n                       ))\n        # 검증 동안 집계된 결과 반환\n        return (losses.avg, o_accuracies.avg, b_accuracies.avg, m_accuracies.avg, \n                s_accuracies.avg)\n    except Exception as e:\n        print(\"val=>\",e)","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:36.913041Z","iopub.execute_input":"2022-06-19T00:12:36.913471Z","iopub.status.idle":"2022-06-19T00:12:36.930197Z","shell.execute_reply.started":"2022-06-19T00:12:36.913433Z","shell.execute_reply":"2022-06-19T00:12:36.929447Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train_main(FOLD):\n    \n    # 랜덤 시드를 설정하여 매 코드를 실행할 때마다 동일한 결과를 얻게 합니다.\n    os.environ['PYTHONHASHSEED'] = str(CFG.seed)\n    random.seed(CFG.seed)\n    np.random.seed(CFG.seed)\n    torch.manual_seed(CFG.seed)    \n    torch.cuda.manual_seed(CFG.seed)\n    torch.backends.cudnn.deterministic = True\n    \n    # 전처리된 데이터를 읽어옵니다.\n    print('loading ...')\n    train_df = pd.read_csv(CFG.csv_path, dtype={'tokens':str})    \n    train_df['img_idx'] = train_df.index # 몇 번째 행인지 img_idx 칼럼에 기록\n   \n    # KFold을 사용해 데이터셋을 학습셋(train_df)과 검증셋(valid_df)으로 나눕니다.\n    folds = KFold(n_splits=5, random_state=CFG.seed, shuffle=True)\n    train_idx, valid_idx = list(folds.split(train_df.values))[FOLD]\n    \n    valid_df = train_df.iloc[valid_idx]\n    train_df = train_df.iloc[train_idx]\n    \n    # 토큰을 대응되는 인덱스로 치환할 때 사용될 딕셔너리를 로딩합니다.\n    vocab = [line.split('\\t')[0] for line in open(os.path.join(DB_PATH, 'spm.vocab'), encoding='utf-8').readlines()]\n    token2id = dict([(w, i) for i, w in enumerate(vocab)])\n    print('loading ... done')\n    \n    # 학습에 적합한 형태의 샘플을 가져오는 CateDataset의 인스턴스를 만듭니다.\n    train_db = CateDataset(train_df, CFG.h5_path, token2id, \n                                       CFG.seq_len, CFG.type_vocab_size)\n    valid_db = CateDataset(valid_df, CFG.h5_path, token2id, \n                                       CFG.seq_len, CFG.type_vocab_size)\n     \n    # 여러 개의 워커로 빠르게 배치(미니배치)를 생성하도록 DataLoader로 \n    # CateDataset 인스턴스를 감싸 줍니다.    \n    train_loader = DataLoader(\n        train_db, batch_size=CFG.batch_size, shuffle=True, drop_last=True,\n        num_workers=CFG.num_workers, pin_memory=True)\n    \n    valid_loader = DataLoader(\n        valid_db, batch_size=CFG.batch_size, shuffle=False,\n        num_workers=CFG.num_workers, pin_memory=False)\n    \n    # 카테고리 분류기 모델을 생성합니다.\n    model = CateClassifier(CFG)\n    \n    # 모델의 파라미터를 GPU메모리로 옮깁니다.\n#     model.cuda()    \n    \n    # 모델의 파라미터 수를 출력합니다.\n    def count_parameters(model):\n        return sum(p.numel() for p in model.parameters() if p.requires_grad)\n    print('parameters: ', count_parameters(model))\n    \n#     n_gpu = torch.cuda.device_count()\n#     print('n_gpu:',n_gpu)\n\n    # 학습 동안 수행될 총 스텝 수\n    # 데이터셋을 배치크기로 나눈 것이 1에폭 동안 스텝 수\n    # 총 스텝 수 = 1에폭 스텝 수 * 총 에폭 수\n    num_train_optimization_steps = int(\n        len(train_db) / CFG.batch_size) * (CFG.num_train_epochs)\n    print('num_train_optimization_steps', num_train_optimization_steps)    \n\n    # 파라미터 그룹핑 정보 생성\n    # 가중치 감쇠(weight decay) 미적용 파라미터 그룹과 적용 파라미터로 나눔\n    param_optimizer = list(model.named_parameters())    \n    no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']\n    optimizer_grouped_parameters = [\n        {'params':[p for n, p in param_optimizer if not any(nd in n for nd in no_decay)],\n         'weight_decay': 0.01},\n        {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], \n         'weight_decay': 0.0}\n    ]\n    \n    # AdamW 옵티마이저 생성\n    optimizer = AdamW(optimizer_grouped_parameters,\n                           lr=CFG.learning_rate,\n                           weight_decay=CFG.weight_decay,                           \n                           )\n\n    # learning_rate가 선형적으로 감소하는 스케줄러 생성\n    scheduler = get_linear_schedule_with_warmup(optimizer, \n                                                num_warmup_steps=CFG.warmup_steps,\n                                                num_training_steps=num_train_optimization_steps)\n    print('use WarmupLinearSchedule ...')\n    \n    def get_lr():    \n        return scheduler.get_lr()[0]\n    \n    log_df = pd.DataFrame() # 에폭 별 실험결과 로그를 저장할 데이터 프레임\n    curr_lr = get_lr()    \n    print(f'initial learning rate:{curr_lr}')\n    \n    # (num_train_epochs - start_epoch) 횟수 만큼 학습을 진행합니다.\n    for epoch in range(CFG.start_epoch, CFG.num_train_epochs):\n        \n        # 한 에폭의 결과가 집계된 한 행을 반환합니다.\n        def get_log_row_df(epoch, lr, train_res, valid_res):\n            log_row = ''\n            print(epoch, lr, train_res, valid_res)\n            try:\n                log_row = {'EPOCH':epoch, 'LR':lr,\n                           'TRAIN_LOSS':train_res[0], 'TRAIN_OACC':train_res[1],\n                           'TRAIN_BACC':train_res[2], 'TRAIN_MACC':train_res[3],\n                           'TRAIN_SACC':train_res[4],\n                           'VALID_LOSS':valid_res[0], 'VALID_OACC':valid_res[1],\n                           'VALID_BACC':valid_res[2], 'VALID_MACC':valid_res[3],\n                           'VALID_SACC':valid_res[4],\n                           }\n            except Exception as e:\n                print('get_log_row_df=>e',e)\n            return pd.DataFrame(list(log_row))             \n        \n        \n        # 학습을 진행하고 loss나 accuracy와 같은 결과를 반환합니다.\n        train_res = train(train_loader, model, optimizer, epoch, scheduler)\n \n        # 검증을 진행하고 loss나 accuracy와 같은 결과를 반환합니다.\n        valid_res = validate(valid_loader, model)\n        curr_lr = get_lr()\n        print(f'set the learning_rate: {curr_lr}')\n        \n        log_row_df = get_log_row_df(epoch, curr_lr, train_res, valid_res)\n        # log_df에 결과가 집계된 한 행을 추가합니다.\n        log_df = log_df.append(log_row_df, sort=False)\n        print(log_df.tail(10)) # log_df의 최신 10개 행만 출력합니다.\n        \n        # 모델의 파라미터가 저장될 파일의 이름을 정합니다.\n        curr_model_name = (f'b{CFG.batch_size}_h{CFG.hidden_size}_'\n                            f'd{CFG.dropout}_l{CFG.nlayers}_hd{CFG.nheads}_'\n                            f'ep{epoch}_s{CFG.seed}_fold.pt')\n        # torch.nn.DataParallel로 감싸진 경우 원래의 model을 가져옵니다.\n        model_to_save = model.module if hasattr(model, 'module') else model  \n        \n    print('training done')\n\n    # 모델의 파라미터를 저장합니다.\n    save_checkpoint({\n        'epoch': epoch + 1,\n        'arch': 'transformer',\n        'state_dict': model_to_save.state_dict(),\n        'log': log_df,\n        },\n        MODEL_PATH, curr_model_name,\n    )","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:12:36.933727Z","iopub.execute_input":"2022-06-19T00:12:36.934176Z","iopub.status.idle":"2022-06-19T00:12:36.961914Z","shell.execute_reply.started":"2022-06-19T00:12:36.934139Z","shell.execute_reply":"2022-06-19T00:12:36.960878Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_main(0)","metadata":{"execution":{"iopub.status.busy":"2022-06-16T00:04:46.803984Z","iopub.execute_input":"2022-06-16T00:04:46.804609Z","iopub.status.idle":"2022-06-16T00:31:42.728791Z","shell.execute_reply.started":"2022-06-16T00:04:46.804572Z","shell.execute_reply":"2022-06-16T00:31:42.726577Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"code","source":"import os\nos.environ['OMP_NUM_THREADS'] = '24'\nos.environ['NUMEXPR_MAX_THREADS'] = '24'\nimport math\nimport glob\nimport json\nimport torch\nimport time\nimport random\nimport numpy as np\nimport pandas as pd\nfrom torch.utils.data import DataLoader\nimport warnings\nwarnings.filterwarnings(action='ignore')\nimport argparse\n\n# 전처리된 데이터가 저장된 디렉터리\nDB_DIR = '../input/h-and-m-personalized-fashion-recommendations'\n\n# 토큰을 인덱스로 치환할 때 사용될 사전 파일이 저장된 디렉터리 \nVOCAB_DIR = os.path.join('./', 'vocab')\n\n# 학습된 모델의 파라미터가 저장될 디렉터리\nMODEL_DIR = './'\n\n# 제출할 예측결과가 저장될 디렉터리\nSUBMISSION_DIR = './'\n\n\n# 미리 정의된 설정 값\nclass CFG:    \n    batch_size=1024 # 배치 사이즈\n    num_workers=1 # 워커의 개수\n    print_freq=100 # 결과 출력 빈도    \n    warmup_steps=100 # lr을 서서히 증가시킬 step 수        \n    hidden_size=512 # 은닉 크기\n    dropout=0.2 # dropout 확률\n    intermediate_size=256 # TRANSFORMER셀의 intermediate 크기\n    nlayers=2 # BERT의 층수\n    nheads=8 # BERT의 head 개수\n    seq_len=32 # 토큰의 최대 길이\n    n_b_cls = 10 + 1 # 대카테고리 개수\n    n_m_cls = 131 + 1 # 중카테고리 개수\n    n_s_cls = 56 + 1 # 소카테고리 개수\n    vocab_size = 32000 # 토큰의 유니크 인덱스 개수\n    img_feat_size = 2048 # 이미지 피처 벡터의 크기\n    type_vocab_size = 30 # 타입의 유니크 인덱스 개수\n    csv_path = os.path.join('./', 'train.csv')# 아직 dev데이터셋없음\n    h5_path = os.path.join('./', 'train_img_feat.h5')","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:13:44.656273Z","iopub.execute_input":"2022-06-19T00:13:44.656665Z","iopub.status.idle":"2022-06-19T00:13:44.667567Z","shell.execute_reply.started":"2022-06-19T00:13:44.656634Z","shell.execute_reply":"2022-06-19T00:13:44.666604Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def inference_main():\n  \n    CFG.seed =  41     \n\n    # 랜덤 시드를 설정하여 매 코드를 실행할 때마다 동일한 결과를 얻게 함\n    os.environ['PYTHONHASHSEED'] = str(CFG.seed)\n    random.seed(CFG.seed)\n    np.random.seed(CFG.seed)\n    torch.manual_seed(CFG.seed)    \n    torch.cuda.manual_seed(CFG.seed)\n    torch.backends.cudnn.deterministic = True\n    \n    # 전처리된 데이터를 읽어옵니다.\n    print('loading ...')\n    dev_df = pd.read_csv(CFG.csv_path, dtype={'tokens':str})     \n    dev_df['img_idx'] = dev_df.index\n    img_h5_path = CFG.h5_path\n    \n    vocab = [line.split('\\t')[0] for line in open(os.path.join('./', 'spm.vocab'), encoding='utf-8').readlines()]\n    token2id = dict([(w, i) for i, w in enumerate(vocab)])    \n    print('loading ... done')\n        \n    # 찾아진 모델 파일의 개수만큼 모델을 만들어서 파이썬 리스트에 추가함\n    model_list = []\n    # args.model_dir에 있는 확장자 .pt를 가지는 모든 모델 파일의 경로를 읽음\n    model_path_list = glob.glob(os.path.join('./', '*.pt'))\n    # 모델 경로 개수만큼 모델을 생성하여 파이썬 리스트에 추가함\n    for model_path in model_path_list:\n        model = CateClassifier(CFG)\n        if model_path != \"\":\n            print(\"=> loading checkpoint '{}'\".format(model_path))\n            checkpoint = torch.load(model_path)        \n            state_dict = checkpoint['state_dict']                \n            model.load_state_dict(state_dict, strict=True)  \n            print(\"=> loaded checkpoint '{}' (epoch {})\"\n                  .format(model_path, checkpoint['epoch']))\n#         model.cuda()\n#         n_gpu = torch.cuda.device_count()\n#         if n_gpu > 1:\n#             model = torch.nn.DataParallel(model)\n        model_list.append(model)\n    if len(model_list) == 0:\n        print('Please check the model directory.')\n        return\n    \n    # 모델의 파라미터 수를 출력합니다.\n    def count_parameters(model):\n        return sum(p.numel() for p in model.parameters() if p.requires_grad)\n    print('parameters: ', count_parameters(model_list[0]))    \n    \n    # 모델의 입력에 적합한 형태의 샘플을 가져오는 CateDataset의 인스턴스를 만듦\n    dev_db = CateDataset(dev_df, img_h5_path, token2id, CFG.seq_len, \n                                       CFG.type_vocab_size)\n    \n    # 여러 개의 워커로 빠르게 배치(미니배치)를 생성하도록 DataLoader로 \n    # CateDataset 인스턴스를 감싸 줌\n    dev_loader = DataLoader(\n        dev_db, batch_size=CFG.batch_size, shuffle=False,\n        num_workers=CFG.num_workers, pin_memory=True)    \n    \n    # dev 데이터셋의 모든 상품명에 대해 예측된 카테고리 인덱스를 반환\n    pred_idx = inference(dev_loader, model_list)\n\n    # dev 데이터셋의 상품ID별 예측된 카테고리를 붙여서 제출 파일을 생성하여 저장\n    cate_cols = ['index_name', 'product_type_name', 'section_name'] \n    dev_df[cate_cols] = pred_idx\n    os.makedirs(SUBMISSION_DIR, exist_ok=True)\n    submission_path = os.path.join(SUBMISSION_DIR, 'dev.csv')\n    dev_df[['article_id', 'index_name', 'product_type_name', 'section_name', 'detail_desc']].to_csv(submission_path)\n    \n    print('done')\n\n\ndef inference(dev_loader, model_list):\n    \"\"\"\n    dev 데이터셋의 모든 상품명에 대해 여러 모델들의 예측한 결과를 앙상블 하여 정확도가 개선된\n    카테고리 인덱스를 반환\n    \n    매개변수\n    dev_loader: dev 데이터셋에서 배치(미니배치) 불러옴\n    model_list: args.model_dir에서 불러온 모델 리스트 \n    \"\"\"\n    batch_time = AverageMeter()\n    data_time = AverageMeter()    \n    sent_count = AverageMeter()\n    \n    # 모딜 리스트의 모든 모델을 평가(evaluation) 모드로 작동하게 함\n    for model in model_list:\n        model.eval()\n\n    start = end = time.time()\n    \n    # 배치별 예측한 대/중/소/세 카테고리의 인덱스를 리스트로 가짐\n    pred_idx_list = []\n    \n    # dev_loader에서 반복해서 배치 데이터를 받음\n    # CateDataset의 __getitem__() 함수의 반환 값과 동일한 변수 반환\n    for step, (token_ids, token_mask, token_types, img_feat, _) in enumerate(dev_loader):\n        # 데이터 로딩 시간 기록\n        data_time.update(time.time() - end)\n        \n        # 배치 데이터의 위치를 CPU메모리에서 GPU메모리로 이동\n#         token_ids, token_mask, token_types, img_feat = (\n#             token_ids.cuda(), token_mask.cuda(), token_types.cuda(), img_feat.cuda())\n        \n        batch_size = token_ids.size(0)\n        \n        # with문 내에서는 그래디언트 계산을 하지 않도록 함\n        with torch.no_grad():\n            pred_list = []\n            # model 별 예측치를 pred_list에 추가합니다.\n            for model in model_list:\n                _, pred = model(token_ids, token_mask, token_types, img_feat)\n                pred_list.append(pred)\n            \n            # 예측치 리스트를 앙상블 하여 하나의 예측치로 만듦\n            pred = ensemble(pred_list)\n            # 예측치에서 카테고리별 인덱스를 가져옴\n            pred_idx = get_pred_idx(pred)\n            # 현재 배치(미니배치)에서 얻어진 카테고리별 인덱스를 pred_idx_list에 추가\n            pred_idx_list.append(pred_idx.cpu())\n            \n        # 소요시간 측정\n        batch_time.update(time.time() - end)\n        end = time.time()\n\n        sent_count.update(batch_size)\n\n        if step % CFG.print_freq == 0 or step == (len(dev_loader)-1):\n            print('TEST: {0}/{1}] '\n                  'Data {data_time.val:.3f} ({data_time.avg:.3f}) '\n                  'Elapsed {remain:s} '                  \n                  'sent/s {sent_s:.0f} '\n                  .format(\n                   step+1, len(dev_loader), batch_time=batch_time,                   \n                   data_time=data_time,\n                   remain=timeSince(start, float(step+1)/len(dev_loader)),\n                   sent_s=sent_count.avg/batch_time.avg\n                   ))\n    \n    # 배치별로 얻어진 카테고리 인덱스 리스트를 직렬연결하여 하나의 카테고리 인덱스로 변환\n    pred_idx = torch.cat(pred_idx_list).numpy()\n    return pred_idx\n\n# 예측치의 각 카테고리 별로 가장 큰 값을 가지는 인덱스를 반환함\ndef get_pred_idx(pred):\n    b_pred, m_pred, s_pred= pred # 대/중/소/세 예측치로 분리\n    _, b_idx = b_pred.max(1) # 대카테고리 중 가장 큰 값을 가지는 인덱스를 변수에 할당\n    _, m_idx = m_pred.max(1) # 중카테고리 중 가장 큰 값을 가지는 인덱스를 변수에 할당\n    _, s_idx = s_pred.max(1) # 소카테고리 중 가장 큰 값을 가지는 인덱스를 변수에 할당\n    \n    # 대/중/소/세 인덱스 반환\n    pred_idx = torch.stack([b_idx, m_idx, s_idx], 1)    \n    return pred_idx\n\n\n# 예측된 대/중/소/세 결과들을 앙상블함\n# 앙상블 방법으로 간단히 산술 평균을 사용\ndef ensemble(pred_list):\n    b_pred, m_pred, s_pred= 0, 0, 0  \n    for pred in pred_list:\n        # softmax를 적용해 대/중/소/세 각 카테고리별 모든 클래스의 합이 1이 되도록 정규화\n        # 참고로 정규화된 pred[0]은 대카테고리의 클래스별 확률값을 가지는 확률분포 함수라 볼 수 있음\n        b_pred += torch.softmax(pred[0], 1)\n        m_pred += torch.softmax(pred[1], 1)\n        s_pred += torch.softmax(pred[2], 1)\n    b_pred /= len(pred_list)    # 모델별 '대카테고리의 정규화된 예측값'들의 평균 계산\n    m_pred /= len(pred_list)   # 모델별 '중카테고리의 정규화된 예측값'들의 평균 계산\n    s_pred /= len(pred_list)    # 모델별 '소카테고리의 정규화된 예측값'들의 평균 계산 \n    \n    # 앙상블 결과 반환 \n    pred = [b_pred, m_pred, s_pred]    \n    return pred\n\n\nclass AverageMeter(object):\n    \"\"\"Computes and stores the average and current value\"\"\"\n    def __init__(self):\n        self.reset()\n\n    def reset(self):\n        self.val = 0\n        self.avg = 0\n        self.sum = 0\n        self.count = 0\n\n    def update(self, val, n=1):\n        self.val = val\n        self.sum += val * n\n        self.count += n\n        self.avg = self.sum / self.count\n\n\ndef asMinutes(s):\n    m = math.floor(s / 60)\n    s -= m * 60\n    return '%dm %ds' % (m, s)\n\n\ndef timeSince(since, percent):\n    now = time.time()\n    s = now - since\n    es = s / (percent)\n    rs = es - s\n    return '%s (remain %s)' % (asMinutes(s), asMinutes(rs))","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:13:47.47341Z","iopub.execute_input":"2022-06-19T00:13:47.473782Z","iopub.status.idle":"2022-06-19T00:13:47.509866Z","shell.execute_reply.started":"2022-06-19T00:13:47.473749Z","shell.execute_reply":"2022-06-19T00:13:47.508971Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"inference_main()","metadata":{"execution":{"iopub.status.busy":"2022-06-19T00:13:52.124492Z","iopub.execute_input":"2022-06-19T00:13:52.124877Z","iopub.status.idle":"2022-06-19T00:13:52.639763Z","shell.execute_reply.started":"2022-06-19T00:13:52.124845Z","shell.execute_reply":"2022-06-19T00:13:52.638804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dev_result ='./dev.csv'\ndevresult = pd.read_csv(dev_result, sep=',')\ndevresult","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"idx_to_article_1 = {v:k for k,v in article_to_idx_1.items()}\nj = [idx_to_article_1[i] for i in devresult['index_name']]\ndevresult['index_name'] = j\n\nidx_to_article_2 = {v:k for k,v in article_to_idx_2.items()}\nj2 = [idx_to_article_2[i] for i in devresult['product_type_name']]\ndevresult['product_type_name'] = j2\n\nidx_to_article_3 = {v:k for k,v in article_to_idx_3.items()}\nj3 = [idx_to_article_3[i] for i in devresult['section_name']]\ndevresult['section_name'] = j3\n\ndevresult.to_csv('SHOW.csv')\ndevresult","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# SHOW","metadata":{}},{"cell_type":"code","source":"show_article ='../input/h-and-m-personalized-fashion-recommendations/articles.csv'\narticle_set = pd.read_csv(show_article, sep=',')\narticle_set = article_set[['article_id', 'index_name', 'product_type_name', 'section_name', 'detail_desc']]\narticle_set\nshow_article ='../input/h-and-m-personalized-fashion-recommendations/articles.csv'","metadata":{"execution":{"iopub.status.busy":"2022-06-21T10:55:27.57188Z","iopub.execute_input":"2022-06-21T10:55:27.57229Z","iopub.status.idle":"2022-06-21T10:55:28.287349Z","shell.execute_reply.started":"2022-06-21T10:55:27.572251Z","shell.execute_reply":"2022-06-21T10:55:28.286253Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dev_result ='../input/category-result/SHOW.csv'\ndevresult = pd.read_csv(dev_result, sep=',')\ndevresult = devresult[['article_id', 'index_name', 'product_type_name', 'section_name', 'detail_desc']]\ndevresult","metadata":{"execution":{"iopub.status.busy":"2022-06-21T10:55:30.089325Z","iopub.execute_input":"2022-06-21T10:55:30.08967Z","iopub.status.idle":"2022-06-21T10:55:30.387983Z","shell.execute_reply.started":"2022-06-21T10:55:30.089642Z","shell.execute_reply":"2022-06-21T10:55:30.386953Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def collectError():\n    err_list = checkError()\n    dataframe = pd.DataFrame(err_list)\n    dataframe.to_csv('error.csv')\n\ndef checkError():\n    errorList = []\n    count = 0 \n    for i in range(len(devresult)):\n        if count>1 and (count%1000)==0:\n            print(count, end=' ')\n        tem = article_set[article_set['article_id'] == devresult['article_id'][i]]\n        if (tem['index_name'] == devresult['index_name'][i]).bool() == False | (tem['product_type_name'] == devresult['product_type_name'][i]).bool() == False| (tem['section_name'] == devresult['section_name'][i]).bool()  == False:\n            errorList.append(True)\n        else:\n            errorList.append(False)\n        count+=1\n    return errorList\n\ndef checkError2(item):\n    item = item[0]\n    tem = article_set[article_set['article_id'] == item]\n    tem2 = devresult[devresult['article_id'] == item]\n    if (tem['index_name'] == tem2['index_name']).bool() == False | (tem['product_type_name'] == tem2['product_type_name']).bool() == False| (tem['section_name'] == tem2['section_name']).bool()  == False:\n            return item\n    return","metadata":{"execution":{"iopub.status.busy":"2022-06-21T10:55:32.587288Z","iopub.execute_input":"2022-06-21T10:55:32.587662Z","iopub.status.idle":"2022-06-21T10:55:32.597072Z","shell.execute_reply.started":"2022-06-21T10:55:32.587632Z","shell.execute_reply":"2022-06-21T10:55:32.596102Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"compare = checkError()\nsave_to_file = devresult[compare]","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"save_to_file = pd.DataFrame(save_to_file)\nsave_to_file.to_csv(\"checkErrors_fin.csv\")","metadata":{},"execution_count":null,"outputs":[]}]}