{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"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        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","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.read_csv(\"/kaggle/input/global-wheat-detection/train.csv\")\ntrain","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import re # regular expression\n\n# 컬럼 추가 x,y,w,h\ntrain[\"x\"]=-1 \ntrain[\"y\"]=-1\ntrain[\"w\"]=-1\ntrain[\"h\"]=-1\n\n# 바운딩 박스 처리하는 함수 만들기 // 문자열 처리\n# train[\"bbox\"][0] # series 접근\ndef expand_bbox(x):\n    a = np.array(re.findall(\"[0-9]+[.]?[0-9]*\", x)) # 숫자가 여러번,'.'한번\n    if len(a)==0:\n        a = [-1,-1,-1,-1]\n    return a\n\ntrain[[\"x\",\"y\",\"w\",\"h\"]] = np.stack(train['bbox'].apply(expand_bbox)) # np.stack에 어레이를 넣어야 4개의 컬럼(x,y,w,h)에 데이터를 넣을 수 있따!\n\ntrain","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 꼭 해줘야 하는 코드\n# 문자열 -> 숫자형\ntrain.dtypes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train[[\"x\",\"y\",\"w\",\"h\"]] = train[[\"x\",\"y\",\"w\",\"h\"]].astype(np.float32)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train.dtypes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"image_ids = train[\"image_id\"].unique()\nimage_ids","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 이미지 개수\nlen(image_ids)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_ids = image_ids[:2700]\nvalid_ids = image_ids[2700:]\n\ntrain_ids","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"x_valid = train[train[\"image_id\"].isin(valid_ids)]\nx_valid","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"x_train = train[train[\"image_id\"].isin(train_ids)]\nx_train","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"x_train.dtypes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"x_train.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"x_valid.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from torch.utils.data import Dataset\n\nclass WheatDataset(Dataset):\n\n    def __init__(self, dataframe, image_dir, transforms=None):\n        super().__init__()\n\n        self.image_ids = dataframe['image_id'].unique()\n        self.df = dataframe\n        self.image_dir = image_dir\n        self.transforms = transforms\n\n    def __getitem__(self, index):\n        image_id = self.image_ids[index]\n        \n        # detection에 추가되는 내용 -> 바운딩 박스에 대한 내용\n        records = self.df[self.df[\"image_id\"]==image_id] # 같은 id만 뽑는다\n        \n        image = cv2.imread(f'{self.image_dir}/{image_id}.jpg', cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32) # BGR->RGB\n        image /= 255.0\n        \n        boxes = records[[\"x\",\"y\",\"w\",\"h\"]].values # 데이터프레임의 값을 array로 바꾸기\n        \n        boxes[:,2] = boxes[:,2]+boxes[:,0] # xmax 만들기 // \"x\": xmin\n        boxes[:,3] = boxes[:,3]+boxes[:,1] # ymax 만들기\n        \n        # 넓이 구하기 // 회귀 성능 높일때 쓴다\n        area = (boxes[:,2]-boxes[:,0])*(boxes[:,3]-boxes[:,1]) # element-wise 연산!!!\n        area = torch.as_tensor(area,dtype=torch.float32) # tensor로 바꿔야 학습이 가능하다!!\n        \n        # 정답 유형 // 모두 배경 or 수많은 바운딩 박스들\n        iscrowd = torch.zeros((records.shape[0]),dtype=torch.int64) # shape[0]: 클래스 개수\n        labels = torch.ones((records.shape[0]),dtype=torch.int64)\n        \n        # dictionary 만들기\n        target = {}\n        target['boxes'] = boxes\n        target['labels'] = labels # 모두 클래스인 경우\n        target['area'] = area\n        target['iscrowd'] = iscrowd # 모두 배경인 경우\n        \n        if self.transforms:\n            sample = {\"image\":image,\"bboxes\":target[\"boxes\"],\"labels\":target[\"labels\"]}\n            tensor_sample = self.transforms(**sample)\n            tensor_sample = tensor_sample[\"image\"]\n            \n            # permute(1, 0): 축을 바꾼다는 의미\n            target[\"boxes\"] = torch.stack(tuple(map(torch.tensor, zip(*sample['bboxes'])))).permute(1, 0)\n            \n            return tensor_sample,target,image_id # 여기가 데이터로더를 불렀을 때 나오는 값들\n          \n    def __len__(self):\n        return self.image_ids.shape[0] # 데이터 개수","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from albumentations.pytorch.transforms import ToTensorV2\nimport albumentations as al\n\n# 참고: https://albumentations.ai/docs/examples/example_bboxes/ \ndef get_train_transforms():\n    # 0.5 확률로 flip\n    return al.Compose([al.Flip(0.5),ToTensorV2()],\n                     bbox_params={\"format\":\"pascal_voc\",\"label_fields\":[\"labels\"]})\n    \ndef get_valid_transforms():\n    return al.Compose([ToTensorV2()],\n                     bbox_params={\"format\":\"pascal_voc\",\"label_fields\":[\"labels\"]})","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# dataloader 만들기 // train, valid 따로!\n# loss\n# requires_grad 공부해오기! // 학습 축적할 수 있도록 하는 옵션\n# to(device)는 이미지,레이블,model에 적용시킨다!!!!!!!!","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def Collate_fn(batch):\n    return tuple(zip(*batch))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from torch.utils.data import DataLoader\n\ntrain_dataset = WheatDataset(dataframe=x_train, image_dir=\"/kaggle/input/global-wheat-detection/train/\", transforms=get_train_transforms()) # 괄호 있어야한다\nvalid_dataset = WheatDataset(dataframe=x_valid, image_dir=\"/kaggle/input/global-wheat-detection/train/\", transforms=get_valid_transforms()) # 경로는 동일하다\n\n# 인수 5개\nbatch_size = 8\ntrain_dataloader = DataLoader(train_dataset,batch_size=batch_size,shuffle=True,num_workers=4,collate_fn=Collate_fn) # 괄호 없어야 한다\nvalid_dataloader = DataLoader(valid_dataset,batch_size=batch_size,shuffle=True,num_workers=4,collate_fn=Collate_fn) # valid의 배치크기는 달라도 된다","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nimport torchvision\nimport torch\n\n# pretrained_backbone은 학습 구조를 말하는 것!\nmodel = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True) # pretrained(가중치)은 전이학습 여부 -> 기존 가중치를 활용해 이어서 학습하겠다! // 데이터 도메인이 달라도 상관없다!\nmodel.roi_heads.box_predictor = FastRCNNPredictor(1024,2) # 마지막 출력층을 1024->2로 바꾼다\n\n# gpu향\ndevice = torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\")\n\nmodel.to(device)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"params = [x for x in model.parameters() if x.requires_grad] # 학습 가능한 파라미터(층;Layer)에 대해서 리스트 생성\noptimizer = torch.optim.SGD(params,lr=0.005,momentum=0.9) # SGD도 모멘텀 인수 있다\nnum_epochs = 5","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# loss 계산할 때 사용하는 클래스\nclass Averager:\n    def __init__(self):\n        self.current_total = 0.0 # loss 저장되는 변수\n        self.iterations = 0.0 # 배치 횟수 -> step\n\n    def send(self, value):\n        self.current_total += value\n        self.iterations += 1\n\n    @property # hidden element 접근하는 방법(decorator)\n    def value(self):\n        if self.iterations == 0:\n            return 0\n        else:\n            return 1.0 * self.current_total / self.iterations\n\n    def reset(self):\n        self.current_total = 0.0\n        self.iterations = 0.0\n        \nloss_hist = Averager()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import cv2\nitr=0\nlr_scheduler = None\nfor epoch in range(num_epochs):\n    loss_hist.reset() # loss를 reset\n    \n    for images, targets, image_ids in train_dataloader: # 데이터셋 클래스 확인!\n        \n        images = list(x.to(device) for x in images)\n        targets = [{k: v.to(device) for k, v in t.items()} for t in targets] \n\n        # 학습된 애들 저장\n        loss_dict = model.forward(images, targets) # 배치 입력\n\n        losses = sum(loss for loss in loss_dict.values()) # 여기 코드 참고!!!!\n        loss_value = losses.item() # 4가지  -> 아래 코드 연습에 있음\n\n        loss_hist.send(loss_value) # send는 Averager 클래스에 들어있는 함수 // 학습에 직접 쓰려고 하는 변수는 아님\n\n        # 분류, 분할 등의 학습할 때 필요한 3가지 단계\n        optimizer.zero_grad() # 가중치 초기화 해야 새로운 기울기를 얻을 수 있음\n        losses.backward() # 역전파 -> 가중치와 편향이 계산된다\n        optimizer.step() # 역전파를 이용해서 가중치와 편향이 `업데이트` 됨\n\n        if itr % 50 == 0:\n            print(f\"Iteration #{itr} loss: {loss_value}\")\n\n        itr += 1\n    \n    # update the learning rate\n    if lr_scheduler is not None:\n        lr_scheduler.step()\n\n    print(f\"Epoch #{epoch} loss: {loss_hist.value}\")   # Loss 출력","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 모델(가중치) 저장\ntorch.save(model.state_dict(),\"best_rcnn.pth\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"cut과 mix가 학습을 방해하지 않고 오히려 더 잘 되는 이유\n> 원본 + cut&mix 사용! (ml에서 pca 항을 추가하는 것과 같은 방법 -> 학습할 내용을 추가한다!)\n\ncutout 쓰면 정확도가 올라가는 이유\n> 학습이 어려워져서 정확도가 올라간다(모델의 다양성이 올라간다(앙상블 효과))\n> 원본도 학습에 사용된다!(1/3확률로 cutout을 사용한다 등의 옵션으로 활용)\n\ncutmix 쓰면 정확도가 올라가는 이유 -> 가장 도움이 된다\n> 레이블을 2개 이상을 줘서 각 클래스에 대한 디테일한 정보를 학습할 수 있다\n\nmixup"},{"metadata":{},"cell_type":"markdown","source":"# 코드 연습"},{"metadata":{"trusted":true},"cell_type":"code","source":"# 학습 가능한 값(loss)들이 dictionary도 들어가 있음\nloss_dict","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"loss_dict.values()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sum(loss for loss in loss_dict.values())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sum(loss for loss in loss_dict.values()).item()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import torch\n\nx = torch.FloatTensor([1,4])\ny = torch.FloatTensor([2,5])\nz = torch.FloatTensor([3,6])\n\n# axis=0으로 쌓기!!\nprint(torch.stack([x,y,z]))\nprint(torch.stack([x,y,z]).permute(1, 0))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import torch\n\nx = torch.FloatTensor([1,4])\ny = torch.FloatTensor([2,5])\nz = torch.FloatTensor([3,6])\n\n# axis=1으로 쌓기!!\ntorch.stack([x,y,z],dim=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"s = {\"bboxes\":[[1,2],[3,4],[5,6]]}\ns","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"s[\"bboxes\"]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# unpacking 안한 것!\nlist(zip(s[\"bboxes\"]))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# unpacking 한 것!\nlist(zip(*s[\"bboxes\"]))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"list(map(torch.tensor,zip(*s[\"bboxes\"])))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"tuple(map(torch.tensor,zip(*s[\"bboxes\"])))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 우리가 원하는 순서가 아님!\ntorch.stack(list(map(torch.tensor,zip(*s[\"bboxes\"]))))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"torch.stack(list(map(torch.tensor,zip(*s[\"bboxes\"]))),dim=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"torch.stack(list(map(torch.tensor,zip(*s[\"bboxes\"])))).permute(1,0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 언패킹하지 않아서 차원이 하나 더 생김\n# 실패!\ntorch.stack(list(map(torch.tensor,zip(s[\"bboxes\"]))))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}