sgd 参数 详解_代码笔记--PC-DARTS代码详解
DARTS是可微分網絡架構搜搜索,PC-DARTS是DARTS的拓展,通過部分通道連接的方法在網絡搜索過程中減少計算時間的內存占用。接下來將會結合論文和開源代碼來詳細介紹PC-DARTS。
1 總體框架
在PC-DARTS的具體實現過程主要分為兩個步驟:一是網絡架構搜索;二是網絡訓練,其中第二個部分的內容與一般深度學習任務相同,這里將會詳細介紹網絡架構具體是如何通過機器自動搜索出來的。
所有的NAS方法主要由搜索空間、搜索策略決定,一般流程如圖1所示。
圖1搜索空間定義了網絡架構是在離散的還是連續的空間中進行搜索,以及在多大的空間中進行搜索,理論上希望搜索空間能夠涵蓋網絡的所有可能性,這樣才可能找到最優的網絡結構。
搜索策略定義了在某個特定的搜索空間中如何去尋找最優解,為了便于理解,我們可以類比于一般機器學習中的網絡參數優化方法(SGD、ADAM等)
1.1 搜索空間
圖21.1.1 限定條件
前面我們提到理論上搜索空間應該盡可能大,但在實現過程中需要考慮到時間和設備的約束,搜索空間一般都是在特定前提條件下的有限子集空間。PC-DARTS的前提條件有以下幾點:
性能較好的卷積神經網絡由特定數量和特定結構的基本單元堆疊而成
每個基本單元由六個節點(網絡層)組成,其中前兩個節點為輸入層固定不變,四個節點為中間節點需要通過搜索確定
每個基本單元的輸入為前兩個基本單元的輸出(即每個基本單元與前兩個基本單元相連)
每個基本單元的輸出為其中間節點計算結果在深度通道上的連接(concat)
從以上限定條件中不難發現,搜索空間實際上就是四個節點之間數據的流動方式(operations)組成的空間,圖2中(a)中的問號即表示待確定的操作。
1.1.2 重要概念
DARTS的搜索空間是連續的,在連續的搜索空間中由兩個重要的概念:混合操作(MixedOp)和超網絡(SuperNet)。
一般網絡的每一層代表著一種操作,這個操作可能是卷積、池化、激活等函數,但在超網絡中,每一層網絡是由多種操作組合起來的超參數,每一種操作將會對應一個系數
,混合操作的表示公式如下:混合操作公式基本單元的初始結構可以由圖2中(b)表示,不同顏色的線條表示不同的操作,由這種基本單元構成的網絡稱為超網絡。
PC-DARTS對DARTS做了進一步的修改,該方法將網絡提取的特征在通道維度上進行1/4采樣,只對計算1/4通道的特征進行處理,處理后的特征再與剩余的特征進行連接(concat),計算公式如下:
部分通道連接的公式部分通道連接有好處也有壞處,好處是能夠減少網絡搜索過程中的內存占用,壞處是弱參數操作(pool、skip connect)在搜索過程中可能更容易被選擇,為此方法提出了邊緣歸一化來抑制該現象。邊緣歸一化為基本單元中的第i層網絡的每一個輸入分配了一個β參數,公式表示如下:
1.2 搜索策略
梯度下降的搜索策略,把架構參數
視為待優化的參數(類比于一般機器學習方法中的網絡參數),每完成一個epoch的網絡參數訓練之后,固定網絡參數,根據評估結果進行一次架構參數的更新,更新的方法同樣類似于網絡參數的更新。2 重要思想
混合操作和超網絡使搜索空間變為一個連續可導的空間,并構建了相應的架構參數,因此架構參數的梯度信息能夠有效地指導網絡的搜索過程。
3 代碼詳解
3.1基本流程
· 代碼下載:https://github.com/yuhuixu1993/PC-DARTS
主要文件及功能
· train_search.py :在制定的搜索空間和搜索策略下,通過更新各個operation的權重搜索最終組成網絡的cell
· train.py &test.py :對由cell組成的網絡進行訓練和測試
1.1 train_search.py 重要代碼詳解
部分參數解釋
'--init_channels' # 初始特征通道數,隨著網絡的加深,特征通道數將會成倍增漲 '--layers' # 進行cell的搜索時,網絡框架由8個cell組成 '--drop_path_prob' #減少搜索過程中的計算時間以及內存占用的一個參數,在使用到的時候會說明 '--grad_clip' #梯度裁剪,用于解決梯度爆炸的問題 '--train_portion' # 網絡架構搜索過程中以5 5的比例劃分訓練集和測試集 '--arch_learning_rate' # 架構參數學習率,用于更新網絡架構參數主函數:
criterion = nn.CrossEntropyLoss() #設置損失函數criterion = criterion.cuda() #配置到gpu上model = Network(args.init_channels, CIFAR_CLASSES, args.layers, criterion)#構建一個超網絡model = model.cuda()#網絡配置到gpu上...... # 設置優化器optimizer = torch.optim.SGD(model.parameters(), # 第一個參數是優化器更新的參數args.learning_rate, # 第二個參數是學習率momentum=args.momentum, weight_decay=args.weight_decay) ...... # 數據劃分為訓練和驗證集,并打包成有序的結構train_queue = torch.utils.data.DataLoader(train_data, batch_size=args.batch_size,sampler=torch.utils.data.sampler.SubsetRandomSampler(indices[:split]),pin_memory=True, num_workers=2)valid_queue = torch.utils.data.DataLoader(train_data, batch_size=args.batch_size,sampler=torch.utils.data.sampler.SubsetRandomSampler(indices[split:num_train]),pin_memory=True, num_workers=2) # 學習率更新函數scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, float(args.epochs), eta_min=args.learning_rate_min) # 在Architecture中創建架構參數和架構參數更新函數architect = Architect(model, args) # 經歷50個epoch后搜索完畢 for epoch in range(args.epochs): ......genotype = model.genotype() # 打印當前epoch 的cell的網絡結構logging.info('genotype = %s', genotype) # train函數實現了超網絡的訓練和搜索,在15個epoch內只對網絡參數進行更新,# 15個epoch后每更新一次網絡參數就更新一次架構參數train_acc, train_obj = train(train_queue, valid_queue, model, architect, criterion, optimizer, lr,epoch) ......utils.save(model, os.path.join(args.save, 'weights.pt')) # 保存網絡train函數:
def train(train_queue, valid_queue, model, architect, criterion, optimizer, lr,epoch):objs = utils.AvgrageMeter() # 用于保存loss的值top1 = utils.AvgrageMeter() # 前1預測正確的概率top5 = utils.AvgrageMeter() # 前5預測正確的概率for step, (input, target) in enumerate(train_queue): # 每個step取出一個batch(256個數據對)model.train()n = input.size(0)input = Variable(input, requires_grad=False).cuda() # 圖片數據target = Variable(target, requires_grad=False).cuda(async=True) # 標簽# get a random minibatch from the search queue with replacementinput_search, target_search = next(iter(valid_queue)) # 用于架構參數更新的一個batchinput_search = Variable(input_search, requires_grad=False).cuda()target_search = Variable(target_search, requires_grad=False).cuda(async=True)if epoch>=15:architect.step(input, target, input_search, target_search, lr, optimizer, unrolled=args.unrolled)# 15個epoch之后每個batch更新一次架構參數 ......return top1.avg, objs.avginfer函數:在最后一個epoch后打印驗證集計算結果
def infer(valid_queue, model, criterion):objs = utils.AvgrageMeter()top1 = utils.AvgrageMeter()top5 = utils.AvgrageMeter()model.eval()for step, (input, target) in enumerate(valid_queue):input = Variable(input, volatile=True).cuda()target = Variable(target, volatile=True).cuda(async=True)logits, _ = model(input) # 計算出預測結果loss = criterion(logits, target)prec1, prec5 = utils.accuracy(logits, target, topk=(1, 5))n = input.size(0)objs.update(loss.data[0], n)top1.update(prec1.data[0], n)top5.update(prec5.data[0], n)if step % args.report_freq == 0:logging.info('valid %03d %e %f %f', step, objs.avg, top1.avg, top5.avg)return top1.avg, objs.avg1.2 Network :構建由混合operation組成的超網絡
class Network(nn.Module):# args.init_channels, CIFAR_CLASSES, args.layers, criteriondef __init__(self, C, num_classes, layers, criterion, steps=4, multiplier=4, stem_multiplier=3):super(Network, self).__init__()self._C = C # 初始通道數self._num_classes = num_classes # 數據集總分類類型self._layers = layers # 網絡總共由layers個基本單元(cell)組成self._criterion = criterion # 損失函數self._steps = steps #一個基本單元內有4個節點需要進行operation操作的搜索self._multiplier = multiplierC_curr = stem_multiplier*C # 當前Sequential模塊的輸出通道數self.stem = nn.Sequential( # 一個Sequential表示一個序列模塊nn.Conv2d(3, C_curr, 3, padding=1, bias=False), # 二維卷積層, 輸入的尺度是(N, C_in,H,W),輸出尺度(N,C_out,H_out,W_out)# C_in,C_out,卷積核大小,groups:卷積核個數,默認為1個nn.BatchNorm2d(C_curr) # 輸入特征通道數)C_prev_prev, C_prev, C_curr = C_curr, C_curr, C #通道數更新self.cells = nn.ModuleList() # 創建一個空modulelist類型數據reduction_prev = False # 之前連接的是否是衰減層,false為否for i in range(layers): # 遍歷8個基本單元模塊,構建相應的cellif i in [layers//3, 2*layers//3]: # 只在特定的層使用縮減單元,縮減單元增大通道數,減小尺寸C_curr *= 2 # 縮減一次通道數乘2reduction = True # 使用了縮減單元,reduction置為trueelse:reduction = False# list中增加一個normal 或者reduction cell,cell屬于normal或者redunction由參數決定cell = Cell(steps, multiplier, C_prev_prev, C_prev, C_curr, reduction, reduction_prev)# cell的具體功能后續會說reduction_prev = reductionself.cells += [cell] # 在modulelist中增加一個cellC_prev_prev, C_prev = C_prev, multiplier*C_curr # 更新當前通道數self.global_pooling = nn.AdaptiveAvgPool2d(1) # 構建一個平均池化層self.classifier = nn.Linear(C_prev, num_classes) # 構建一個線性分類器self._initialize_alphas() # 架構參數初始化def new(self):model_new = Network(self._C, self._num_classes, self._layers, self._criterion).cuda()for x, y in zip(model_new.arch_parameters(), self.arch_parameters()):x.data.copy_(y.data)return model_new對應關鍵公式
# forward定義了數據前向傳播的具體計算過程def forward(self, input):s0 = s1 = self.stem(input)for i, cell in enumerate(self.cells):if cell.reduction: # reduce和normal模塊分別進行weights = F.softmax(self.alphas_reduce, dim=-1) # 首先將架構參數中的操作系數用softmax進行歸一化n = 3start = 2weights2 = F.softmax(self.betas_reduce[0:2], dim=-1) # 邊緣歸一化架構參數β也用softmax進行歸一化for i in range(self._steps-1):end = start + ntw2 = F.softmax(self.betas_reduce[start:end], dim=-1)start = endn += 1weights2 = torch.cat([weights2,tw2],dim=0)else:weights = F.softmax(self.alphas_normal, dim=-1)n = 3start = 2weights2 = F.softmax(self.betas_normal[0:2], dim=-1)for i in range(self._steps-1):end = start + ntw2 = F.softmax(self.betas_normal[start:end], dim=-1)start = endn += 1weights2 = torch.cat([weights2,tw2],dim=0)s0, s1 = s1, cell(s0, s1, weights,weights2) # 實際計算和更新輸出數據out = self.global_pooling(s1) #全局池化logits = self.classifier(out.view(out.size(0),-1)) #分類器輸出結果return logits1.2.1 cell:初始基本單元的構建
class Cell(nn.Module):def __init__(self, steps, multiplier, C_prev_prev, C_prev, C, reduction, reduction_prev):super(Cell, self).__init__()self.reduction = reductionif reduction_prev: # 前兩層固定不參與搜索,然和根據之前所連cell類型進行設定self.preprocess0 = FactorizedReduce(C_prev_prev, C, affine=False)else:self.preprocess0 = ReLUConvBN(C_prev_prev, C, 1, 1, 0, affine=False)self.preprocess1 = ReLUConvBN(C_prev, C, 1, 1, 0, affine=False)self._steps = steps # 每個cell中有4個節點的連接狀態待確定self._multiplier = multiplierself._ops = nn.ModuleList() # 構建operation的modulelistself._bns = nn.ModuleList()for i in range(self._steps):#順序構建四個節點的混合operationfor j in range(2+i):stride = 2 if reduction and j < 2 else 1op = MixedOp(C, stride)#MixedOp構建一個混合操作self._ops.append(op)#混合操作添加到ops # cell中的計算過程,前向傳播時自動調用def forward(self, s0, s1, weights,weights2):s0 = self.preprocess0(s0)s1 = self.preprocess1(s1)states = [s0, s1]offset = 0for i in range(self._steps):# 順序計算四個節點的輸出并添加到states中,每一個節點的輸入為之前節點的輸出s = sum(weights2[offset+j]*self._ops[offset+j](h, weights[offset+j]) for j, h in enumerate(states))offset += len(states)states.append(s)return torch.cat(states[-self._multiplier:], dim=1)#后四個節點進行concat作為輸出1.2.2 混合操作的實現
def channel_shuffle(x, groups):# 特征的通道隨機亂序batchsize, num_channels, height, width = x.data.size()channels_per_group = num_channels // groups # 返不大于結# reshapex = x.view(batchsize, groups, channels_per_group, height, width)x = torch.transpose(x, 1, 2).contiguous()# flattenx = x.view(batchsize, -1, height, width)return xclass MixedOp(nn.Module): # 獲得混合操作def __init__(self, C, stride):super(MixedOp, self).__init__()self._ops = nn.ModuleList()self.mp = nn.MaxPool2d(2,2)#最大池化操作 # 形成每一層的操作for primitive in PRIMITIVES:#PRIMITIVES中存儲了各種操作的名稱op = OPS[primitive](C //4, stride, False)#OPS中存儲了各種操作的定義if 'pool' in primitive:op = nn.Sequential(op, nn.BatchNorm2d(C //4, affine=False))self._ops.append(op)def forward(self, x, weights):#channel proportion k=4 dim_2 = x.shape[1]xtemp = x[ : , : dim_2//4, :, :]xtemp2 = x[ : , dim_2//4:, :, :]temp1 = sum(w * op(xtemp) for w, op in zip(weights, self._ops)) # 只對前1/4的特征進行計算#reduction cell needs pooling before concatif temp1.shape[2] == x.shape[2]:ans = torch.cat([temp1,xtemp2],dim=1)else:#如果是reduction cell,則沒有經過計算的特征大小需要進行變化,變換的方法是最大池化ans = torch.cat([temp1,self.mp(xtemp2)], dim=1)ans = channel_shuffle(ans,4) #對輸出的特征在通道維度上打亂#ans = torch.cat([ans[ : , dim_2//4:, :, :],ans[ : , : dim_2//4, :, :]],dim=1)#except channe shuffle, channel shift also worksreturn ans1.3 architect:架構參數
def _concat(xs):return torch.cat([x.view(-1) for x in xs])class Architect(object):def __init__(self, model, args):self.network_momentum = args.momentumself.network_weight_decay = args.weight_decayself.model = modelself.optimizer = torch.optim.Adam(self.model.arch_parameters(), # 架構參數優化函數 lr=args.arch_learning_rate, betas=(0.5, 0.999), weight_decay=args.arch_weight_decay) # 實現學習率優化def step(self, input_train, target_train, input_valid, target_valid, eta, network_optimizer, unrolled):self.optimizer.zero_grad()if unrolled:self._backward_step_unrolled(input_train, target_train, input_valid, target_valid, eta, network_optimizer)else:self._backward_step(input_valid, target_valid)self.optimizer.step() # 實現架構參數的更新def _backward_step(self, input_valid, target_valid):loss = self.model._loss(input_valid, target_valid)loss.backward()總結
以上是生活随笔為你收集整理的sgd 参数 详解_代码笔记--PC-DARTS代码详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ie浏览器 杂项样式错乱
- 下一篇: sudo命令_如何用一条sudo命令让标