{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Fine Tuning"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport torch\nimport torchvision\nimport torch.nn as nn\nimport torch.functional as F\nimport matplotlib.pyplot as plt\nfrom torch.utils.data import DataLoader,Dataset\nfrom torchvision import datasets,models,transforms\nfrom PIL import Image\nfrom sklearn.model_selection import StratifiedShuffleSplit\ntorch.version","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"os.listdir(\"../input\")","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","collapsed":true,"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":false},"cell_type":"markdown","source":"## 注意事项"},{"metadata":{},"cell_type":"markdown","source":"1.新数据集和原始数据集类似，那么直接可以微调最后一个FC层或者重新指定一个分类器；<p>\n2.如果差异比较大，可以从模型的中部开始训练，只对最后几层进行fine_tuning；<p>\n3.如果差异比较大，并且上面的方法不可行，最好重新训练模型，只将预训练的模型作为新模型的一个初始化数据；<p>\n4.新数据集的大小一定要与原始数据集相同<p>\n5.如果数据集大小不相同的话，可以在最后的fc层之前添加卷积或者pool层，但是这样做会使得精度大幅度下降；<p>\n6.对于不同的层可以设置不同的学习率；<p>"},{"metadata":{},"cell_type":"markdown","source":"## 微调实例"},{"metadata":{"trusted":true},"cell_type":"code","source":"all_labels_df=pd.read_csv(\"../input/labels.csv\")\nall_labels_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"定义两个字典"},{"metadata":{"trusted":true},"cell_type":"code","source":"breeds=all_labels_df.breed.unique()\nbreed2idx=dict((breed,idx) for idx,breed in enumerate(breeds))\nidx2breed=dict((idx,breed) for idx,breed in enumerate(breeds))\nprint(len(breeds))   # 这个数据有120个类别","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"把类别标号添加到原始表格中"},{"metadata":{"trusted":true},"cell_type":"code","source":"all_labels_df[\"label_idx\"]=[breed2idx[b] for b in all_labels_df.breed]\nall_labels_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"由于我们的数据集是官方指定的格式，所以我们自己定义一个数据集"},{"metadata":{"trusted":true},"cell_type":"code","source":"class DogDataset(Dataset):\n    def __init__(self,label_df,img_path,transform=None):\n        self.label_df=label_df\n        self.img_path=img_path\n        self.transform=transform\n        \n    def __len__(self):\n        \"\"\"放回数据集的长度\"\"\"\n        return self.label_df.shape[0]\n    \n    def __getitem__(self,idx):\n        \"\"\"读取图片和标签\"\"\"\n        label=self.label_df.label_idx[idx]\n        id_img=self.label_df.id[idx]\n        img_P=os.path.join(self.img_path,id_img)+\".jpg\"\n        img=Image.open(img_P)\n        \n        if self.transform:\n            img=self.transform(img)\n            \n        return img,label","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"定义一些超参数"},{"metadata":{"trusted":true},"cell_type":"code","source":"IMG_SIZE=224   # resnet50的输入是224的，所以需要将图片统一大小\nBATCH_SIZE=256   # 每个批次输入的图片的数量\nIMG_MEAN=[0.485,0.456,0.406]\nIMG_STD=[0.229,0.224,0.225]\nCUDA=torch.cuda.is_available()\nDEVICE=torch.device(\"cuda\" if CUDA else \"cpu\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"定义数据和图片变换规则"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_transform=transforms.Compose([\n    transforms.Resize(IMG_SIZE),   # 改变图片大小\n    transforms.RandomResizedCrop(IMG_SIZE),  # 首先随机裁减，然后在转换成规定图片大小\n    transforms.RandomHorizontalFlip(),  # 以0.5的概率随机水平翻转\n    transforms.RandomRotation(30),  # 旋转\n    transforms.ToTensor(),  # 转换成张量\n    transforms.Normalize(IMG_MEAN,IMG_STD)  # 标准化\n])\n\nval_transform=transforms.Compose([\n    transforms.Resize(IMG_SIZE),\n    transforms.CenterCrop(IMG_SIZE),\n    transforms.ToTensor(),\n    transforms.Normalize(IMG_MEAN,IMG_STD)\n])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"我们这里切割10%的数据作为训练时的验证数据"},{"metadata":{"trusted":true},"cell_type":"code","source":"# 使用分层抽样切分训练集和验证集\ndataset_name=[\"train\",\"valid\"]\nstra_splt=StratifiedShuffleSplit(n_splits=1,test_size=0.1,random_state=0)\ntrain_index,val_index=next(iter(stra_splt.split(all_labels_df.id,all_labels_df.breed)))\ntrain_df=all_labels_df.iloc[train_index,:].reset_index()\nval_df=all_labels_df.iloc[val_index,:].reset_index()\nprint(len(train_df))\nprint(len(val_df))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"使用官方的dataloader载入数据"},{"metadata":{"trusted":true},"cell_type":"code","source":"image_tarnsforms={\"train\":train_transform,\"valid\":val_transform}\n\ntrain_dataset=DogDataset(train_df,\"../input/train\",transform=image_tarnsforms[\"train\"])\nvalid_dataset=DogDataset(val_df,\"../input/train\",transform=image_tarnsforms[\"valid\"])\nimage_dataset={\"train\":train_dataset,\"valid\":valid_dataset}\n\nimage_dataloader={x:DataLoader(image_dataset[x],batch_size=BATCH_SIZE,shuffle=True,num_workers=0) for x in dataset_name}\ndatasize={x:len(image_dataset[x]) for x in dataset_name}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"配置我们的网络，由于ImageNet识别的是1000个类别，而我们的数据集只有100个类别。"},{"metadata":{"trusted":true},"cell_type":"code","source":"model_ft=models.resnet50(pretrained=True) # 自动下载官方的预训练模型\n# 将所有的层都先冻结\nfor param in model_ft.parameters():\n    param.requires_grad=False\n\n# 打印全连接层的信息\nprint(model_ft.fc)\nnum_fc_ftr=model_ft.fc.in_features  # 获取全连接层的输入\nmodel_ft.fc=nn.Linear(num_fc_ftr,len(breeds))  # 定义一个新的全连接层\nmodel_ft=model_ft.to(DEVICE)  # 将网络放到设备中\nprint(model_ft)  # 最后打印一下模型","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"设置训练参数"},{"metadata":{"trusted":true},"cell_type":"code","source":"criterion=nn.CrossEntropyLoss()\noptimizer=torch.optim.Adam([{\"params\":model_ft.fc.parameters()}],lr=0.001)  # 指定新加的fc层的学习率","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for data in image_dataloader[\"train\"].dataset:\n    x,y=data\n    plt.imshow(x)\n    plt.title(y)\n    break","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"定义训练函数"},{"metadata":{"trusted":true},"cell_type":"code","source":"def train(model,device,train_loader,epoch):\n    model.train()\n    for batch_idx,data in enumerate(train_loader):\n        x,y=data\n        x=x.to(device)\n        y=y.to(device)\n        optimizer.zero_grad()\n        y_hat=model(x)\n        loss=criterion(y_hat,y)\n        loss.backward()\n        optimizer.step()\n    print ('Train Epoch: {}\\t Loss: {:.6f}'.format(epoch,loss.item()))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"定义测试函数"},{"metadata":{"trusted":true},"cell_type":"code","source":"def test(model,device,test_loader):\n    model.eval()\n    test_loss=0\n    correct=0\n    with torch.no_grad():\n        for i,data in enumerate(test_loader):\n            x,y=data\n            x=x.to(device)\n            y=y.to(device)\n            optimizer.zero_grad()\n            y_hat=model(x)\n            test_loss+=criterion(y_hat,y).item()\n            pred=y_hat.max(1,keepdim=True)[1]\n            correct+=pred.eq(y.view_as(pred)).sum().item()\n    test_loss/=len(test_loader.dataset)\n    print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n        test_loss, correct, len(valid_dataset),\n        100. * correct / len(valid_dataset)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"训练9次，看看效果"},{"metadata":{"trusted":true},"cell_type":"code","source":"for epoch in range(1,9):\n    %time train(model_ft,DEVICE,image_dataloader[\"train\"],epoch)\n    test(model_ft,DEVICE,image_dataloader[\"valid\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"我们每次都需要将一张图片在全部网络中进行计算，而且每次结果都是一样的，这样浪费了很多计算资源。下面我们将这些不进行反向传播或者说不更新参数层的计算结果保存下来，这样我们以后使用的时候就可以直接将这些结果输入到以这些结果构建的新的网络层中，省去了计算时间，这样如果只训练全连接层，cpu就能完成了。"},{"metadata":{"trusted":true},"cell_type":"markdown","source":"## 固定层的向量导出"},{"metadata":{"trusted":true},"cell_type":"code","source":"# hook\nin_list=[]  # 存放所有的输出\ndef hook(module,input,output):\n    for i in range(input[0].size(0)):\n        in_list.append(input[0][i].cpu().numpy())","execution_count":94,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model_ft.avgpool.register_forward_hook(hook)","execution_count":95,"outputs":[{"output_type":"execute_result","execution_count":95,"data":{"text/plain":"<torch.utils.hooks.RemovableHandle at 0x7f939361b198>"},"metadata":{}}]},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\nwith torch.no_grad():\n    for batch_idx,data in enumerate(image_dataloader[\"train\"]):\n        x,y=data\n        x=x.to(DEVICE)\n        y=y.to(DEVICE)\n        y_hat=model_ft(x)","execution_count":98,"outputs":[{"output_type":"stream","text":"CPU times: user 1min 9s, sys: 16.6 s, total: 1min 26s\nWall time: 1min 31s\n","name":"stdout"}]},{"metadata":{"trusted":true},"cell_type":"code","source":"features=np.array(in_list)","execution_count":103,"outputs":[{"output_type":"error","ename":"MemoryError","evalue":"","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mMemoryError\u001b[0m                               Traceback (most recent call last)","\u001b[0;32m<ipython-input-103-1246aade0a8e>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfeatures\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0min_list\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      2\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msave\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"/kaggle/working/features\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mfeatures\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mMemoryError\u001b[0m: "]}]},{"metadata":{"trusted":true},"cell_type":"code","source":"np.save(\"/kaggle/working/features\",features)","execution_count":105,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"features.shape","execution_count":107,"outputs":[{"output_type":"execute_result","execution_count":107,"data":{"text/plain":"(9199, 2048, 7, 7)"},"metadata":{}}]},{"metadata":{},"cell_type":"markdown","source":"这样，我们就只需要将这个数据读出来，再输入到我们之后的分类器中就可以，或者更高级的分类器，如SVM中。"}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.4","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}