<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Pytorch量化之圖像超分量化,附實(shí)例與code

          共 14988字,需瀏覽 30分鐘

           ·

          2021-02-05 22:11

          ↑ 點(diǎn)擊藍(lán)字?關(guān)注極市平臺

          作者丨Happy
          來源丨AIWalker
          編輯丨極市平臺

          極市導(dǎo)讀

          ?

          作者使用圖像超分為例提供了一個(gè)完整的可復(fù)現(xiàn)的量化示例,以EDSR為例,模型大小73%,推理速度提升了40%左右,視覺效果幾乎無損。?>>加入極市CV技術(shù)交流群,走在計(jì)算機(jī)視覺的最前沿

          最近作者在嘗試進(jìn)行圖像超分的INT8量化,發(fā)現(xiàn):pytorch量化里面的坑真多,遠(yuǎn)不如TensorFlow的量化好用。不過花了點(diǎn)時(shí)間終于還是用pytorch把圖像超分模型完成了量化,以EDSR為例,模型大小73%,推理速度提升40%左右(PC端),視覺效果幾乎無損,定量指標(biāo)待補(bǔ)充。有感于網(wǎng)絡(luò)上介紹量化的博客一堆,但真正有幫助的較少,所以Happy會盡量以圖像超分為例提供一個(gè)完整的可復(fù)現(xiàn)的量化示例。

          背景

          量化在不同領(lǐng)域有不同的定義,而在深度學(xué)習(xí)領(lǐng)域,量化有兩個(gè)層面的意義:(1) 存儲量化,即更少的bit來存儲原本需要用浮點(diǎn)數(shù)(一般為FP32)存儲的tensor;(2) 計(jì)算量化,即用更少的bit來完成原本需要基于浮點(diǎn)數(shù)(一般為FP32,F(xiàn)P16現(xiàn)在也是常用的一種)完成的計(jì)算。量化一般有這樣兩點(diǎn)好處:

          • 更小的模型體積,理論上減少為FP32模型的75%左右,從筆者不多的經(jīng)驗(yàn)來看,往往可以減少73%;
          • 更少的內(nèi)存訪問與更快的INT8計(jì)算,從筆者的幾個(gè)簡單嘗試來看,一般可以加速40%左右,這個(gè)還會跟平臺相關(guān)。

          對于量化后模型而言,其部分或者全部tensor(與量化方式、量化op的支持程度有關(guān))將采用INT類型進(jìn)行計(jì)算,而非量化前的浮點(diǎn)類型。量化對于底層的硬件支持、推理框架等要求還是比較高的,目前X86CPU,ARMCPU,Qualcomm DSP等主流硬件對量化都提供了支持;而NCNN、MACE、MNN、TFLite、Caffe2、TensorRT等推理框架也都對量化提供了支持,不過不同框架的支持度還是不太一樣,這個(gè)就不細(xì)說了,感興趣的同學(xué)可以自行百度一下。

          筆者主要用Pytorch進(jìn)行研發(fā),所以花了點(diǎn)精力對其進(jìn)行了一些研究&嘗試。目前Pytorch已經(jīng)更新到了1.7版本,基本上支持常見的op,可以參考如下:

          • Activation:ReLU、ReLU6、Hardswish、ELU;
          • Normalization:BatchNorm、LayerNorm、GroupNorm、InstanceNorm;
          • Convolution:Conv1d、Conv2d、Conv3d、ConvTranspose1d、ConvTranspose2d、Linear;
          • Other:Embedding、EmbeddingBag。

          目前Pytorch支持的量化有如下三種方式:

          • Post Training Dynamic Quantization:動態(tài)量化,推理過程中的量化,這種量化方式常見諸于NLP領(lǐng)域,在CV領(lǐng)域較少應(yīng)用;
          • Post Training Static Quantization:靜態(tài)量化,訓(xùn)練后靜態(tài)量化,這是CV領(lǐng)域應(yīng)用非常多的一種量化方式;
          • Quantization Aware Training:感知量化,邊訓(xùn)練邊量化,一種比靜態(tài)量化更優(yōu)的量化方式,但量化時(shí)間會更長,但精度幾乎無損。

          注:筆者主要關(guān)注CV領(lǐng)域,所以本文也將主要介紹靜態(tài)量化與感知量化這種方式。

          Tensor量化

          要實(shí)現(xiàn)量化,那么就不可避免會涉及到tensor的量化,一般來說,量化公式可以描述如下:

          目前Pytorch中的tensor支持int8/uint8/int32等類型的數(shù)據(jù),并同時(shí)scale、zero_point、quantization_scheme等量化信息。這里,我們給出一個(gè)tensor量化的簡單示例:

          x = torch.rand(3, 3)print(x)x = torch.quantize_per_tensor(x, scale=0.2, zero_point=3, dtype=torch.quint8)print(x)print(x.int_repr())

          一個(gè)參考輸出如下所示:

          注1:藍(lán)框?yàn)樵嫉母↑c(diǎn)數(shù)據(jù),紅框?yàn)閠ensor的量化信息,綠框則對應(yīng)了量化后的INT8數(shù)值。

          注2:量化不可避免會出現(xiàn)精度損失,這個(gè)損失與scale、zero_point有關(guān)。

          在量化方面,Tensor一般有兩種量化模式:per tensor與per channel。對于PerTensor而言,它的所有數(shù)值都按照相同方式進(jìn)行scale和zero_point處理;而對于PerChannel而言,它有多種不同的scale和zero_point參數(shù),這種方式的量化精度損失更少。

          Post Training Static Quantization

          靜態(tài)量化一般有兩種形式:(1) 僅weight量化;(2) weight與activation同時(shí)量化。對于第一種“僅weight量化”而言,只針對weight量化可以使得模型參數(shù)所占內(nèi)存顯著減小,但在實(shí)際推理過程中仍需要轉(zhuǎn)換成浮點(diǎn)數(shù)進(jìn)行計(jì)算;而第二種“weight與activation同時(shí)量化”則不僅對weight進(jìn)行量化,還需要結(jié)合校驗(yàn)數(shù)據(jù)進(jìn)行activation的量化。第一種的量化非常簡單,這里略過,本文僅針對第二種方式進(jìn)行介紹。

          Pytorch的靜態(tài)量化一把包含五個(gè)步驟:

          • fuse_model:該步驟用來對可以融合的op進(jìn)行融合,比如Conv與BN的融合、Conv與ReLU的融合、Conv與BN以及ReLU的融合、Linear與BN的融合、Linear與BN以及ReLU的融合。目前Pytorch已經(jīng)內(nèi)置的融合code:
          fuse_modules(model,?modules_to_fuse,?inplace=False,?fuser_func=fuse_known_modules,?fuse_custom_config_dict=None)

          在完成融合后,第一個(gè)op將被替換會融合后的op,而其他op則會替換為nn.Identity。

          • qconfig:該步驟用于設(shè)置用于模型量化的方式,它將插入兩個(gè)observer,一個(gè)用于監(jiān)測activation,一個(gè)用于監(jiān)測weight??紤]到推理平臺的不同,pytorch提供了兩種量化配置:針對x86平臺的fbgemm以及針對arm平臺的qnnpack

          不同平臺的量化配置方式存在些微的區(qū)別,大概如下:

          backendactivationweight
          • Prepare:該步驟用于給每個(gè)支持量化的模塊插入Observer,用于收集數(shù)據(jù)并進(jìn)行量化數(shù)據(jù)分析。以activation為例,它將根據(jù)所喂入數(shù)據(jù)統(tǒng)計(jì)min_val與max_val,一般觀察幾個(gè)次迭代即可,然后根據(jù)所觀察到數(shù)據(jù)進(jìn)行統(tǒng)計(jì)分析得到scale與zero_point。
          • Feed Data:為了更好的獲得activation的量化參數(shù)信息,我們需要一個(gè)合適大小的校驗(yàn)數(shù)據(jù),并將其送入到前述模型中。這個(gè)就比較簡單了,就按照模型驗(yàn)證方式往里面送數(shù)據(jù)就可以了。
          • Convert:在完成前述四個(gè)步驟后,接下來就需要將完成量化的模型轉(zhuǎn)換為量化后模型了,這個(gè)就比較簡單了,通過如下命令即可。
          torch.quantization.convert(model, inplace=True)

          該過程本質(zhì)上就是用量化OP替換模型中的費(fèi)量化OP,比如用nnq.Conv2d替換nn.Conv2d, nnq.ConvReLU2d替換nni.ConvReLU2d(注:這是Conv與ReLU的合并)。之前的量化op以及對應(yīng)的被替換op列表如下:

          DEFAULT_STATIC_QUANT_MODULE_MAPPINGS = {    QuantStub: nnq.Quantize,    DeQuantStub: nnq.DeQuantize,    nn.BatchNorm2d: nnq.BatchNorm2d,    nn.BatchNorm3d: nnq.BatchNorm3d,    nn.Conv1d: nnq.Conv1d,    nn.Conv2d: nnq.Conv2d,    nn.Conv3d: nnq.Conv3d,    nn.ConvTranspose1d: nnq.ConvTranspose1d,    nn.ConvTranspose2d: nnq.ConvTranspose2d,    nn.ELU: nnq.ELU,    nn.Embedding: nnq.Embedding,    nn.EmbeddingBag: nnq.EmbeddingBag,    nn.GroupNorm: nnq.GroupNorm,    nn.Hardswish: nnq.Hardswish,    nn.InstanceNorm1d: nnq.InstanceNorm1d,    nn.InstanceNorm2d: nnq.InstanceNorm2d,    nn.InstanceNorm3d: nnq.InstanceNorm3d,    nn.LayerNorm: nnq.LayerNorm,    nn.LeakyReLU: nnq.LeakyReLU,    nn.Linear: nnq.Linear,    nn.ReLU6: nnq.ReLU6,    # Wrapper Modules:    nnq.FloatFunctional: nnq.QFunctional,    # Intrinsic modules:    nni.BNReLU2d: nniq.BNReLU2d,    nni.BNReLU3d: nniq.BNReLU3d,    nni.ConvReLU1d: nniq.ConvReLU1d,    nni.ConvReLU2d: nniq.ConvReLU2d,    nni.ConvReLU3d: nniq.ConvReLU3d,    nni.LinearReLU: nniq.LinearReLU,    nniqat.ConvBn1d: nnq.Conv1d,    nniqat.ConvBn2d: nnq.Conv2d,    nniqat.ConvBnReLU1d: nniq.ConvReLU1d,    nniqat.ConvBnReLU2d: nniq.ConvReLU2d,    nniqat.ConvReLU2d: nniq.ConvReLU2d,    nniqat.LinearReLU: nniq.LinearReLU,    # QAT modules:    nnqat.Linear: nnq.Linear,    nnqat.Conv2d: nnq.Conv2d,}

          在完成模型量化后,我們就要考慮量化模型的推理了。其實(shí)量化模型的推理與浮點(diǎn)模型的推理沒什么本質(zhì)區(qū)別,最大的區(qū)別有這么兩點(diǎn):

          • 量化節(jié)點(diǎn)插入:需要在網(wǎng)絡(luò)的forward里面插入QuantStub與DeQuantSub兩個(gè)節(jié)點(diǎn)。一個(gè)非常簡單的參考示例,摘自torchvision.model.quantization.resnet.py。
          class QuantizableResNet(ResNet):
          def __init__(self, *args, **kwargs): super(QuantizableResNet, self).__init__(*args, **kwargs)
          self.quant = torch.quantization.QuantStub() self.dequant = torch.quantization.DeQuantStub()
          def forward(self, x): x = self.quant(x) # Ensure scriptability # super(QuantizableResNet,self).forward(x) # is not scriptable x = self._forward_impl(x) x = self.dequant(x) return x

          • op替換:需要將模型中的Add、Concat等操作替換為支持量化的FloatFunctional,可參考如下示例。
          class QuantizableBasicBlock(BasicBlock):    def __init__(self, *args, **kwargs):        super(QuantizableBasicBlock, self).__init__(*args, **kwargs)        self.add_relu = torch.nn.quantized.FloatFunctional()
          def forward(self, x): identity = x
          out = self.conv1(x) out = self.bn1(out) out = self.relu(out)
          out = self.conv2(out) out = self.bn2(out)
          if self.downsample is not None: identity = self.downsample(x)
          out = self.add_relu.add_relu(out, identity)
          return out

          準(zhǔn)備工作

          在真正開始量化之前,我們需要準(zhǔn)備好要進(jìn)行量化的模型,本文以EDSR-baseline模型為基礎(chǔ)進(jìn)行。所以大家可以直接下載官方預(yù)訓(xùn)練模型,EDSR的Pytorch官方實(shí)現(xiàn)code連接如下:

          github.com/thstkdgus35/EDSR-PyTorch

          EDSRx4-baseline預(yù)訓(xùn)練模型下載連接如下:

          https://cv.snu.ac.kr/research/EDSR/models/edsr_baseline_x4-6b446fab.pt

          除了要準(zhǔn)備上述預(yù)訓(xùn)練模型與code外,我們還需要準(zhǔn)備校驗(yàn)數(shù)據(jù),在這里筆者采用的DIV2K數(shù)據(jù),該數(shù)據(jù)集下載鏈接如下:

          https://cv.snu.ac.kr/research/EDSR/DIV2K.tar


          模型轉(zhuǎn)換

          正如上一篇文章所介紹的,在量化之前需要對模型進(jìn)行op融合操作,而EDSR官方的實(shí)現(xiàn)code是對于融合操作是不太方便的,所以筆者對EDSR進(jìn)行了一些實(shí)現(xiàn)上的調(diào)整。調(diào)整成如下形式(注:這里的實(shí)現(xiàn)code部分參數(shù)寫成了固定參數(shù)):

          class ResBlock(nn.Module):    def __init__(self, channels=64):        super(ResBlock, self).__init__()        self.conv1 = nn.Conv2d(channels, channels, 3, 1, 1)        self.relu = nn.ReLU(inplace=True)        self.conv2 = nn.Conv2d(channels, channels, 3, 1, 1)
          def forward(self, x): identity = x conv1 = self.conv1(x) relu = self.relu(conv1) conv2 = self.conv2(relu)
          output = conv2 + identity return output
          class EDSR(nn.Module): def __init__(self, num_blocks=16, num_features=64, block=ResBlock): super(EDSR, self).__init__() self.head = nn.Conv2d(3, num_features, 3, 1, 1) body = [ block(num_features) for _ in range(num_blocks) ] body.append(nn.Conv2d(num_features, num_features, 3, 1, 1)) self.body = nn.Sequential(*body) self.tail = nn.Sequential( nn.Conv2d(num_features, num_features * 4, 3, 1, 1), nn.PixelShuffle(upscale_factor=2), nn.Conv2d(num_features, num_features * 4, 3, 1, 1), nn.PixelShuffle(upscale_factor=2), nn.Conv2d(num_features, 3, 3, 1, 1) )
          def forward(self, x, **kwargs): x = self.head(x) res = self.body(x) res += x x = self.tail(res) return x

          也許有同學(xué)會說,模型轉(zhuǎn)換后原始的預(yù)訓(xùn)練模型還能導(dǎo)入嗎?直接導(dǎo)入肯定是不行的,checkpoint的key發(fā)生了變化,所以我們需要對下載的checkpoint進(jìn)行一下簡單的轉(zhuǎn)換。checkpoint的轉(zhuǎn)換code如下(注:這些轉(zhuǎn)換可以都是寫死的,已經(jīng)確認(rèn)過的):

          checkpoint = torch.load("edsr_baseline_x4-6b446fab.pt", map_location='cpu')newStateDict = OrderedDict()
          for key, val in checkpoint.items(): if 'head' in key: newStateDict[key.replace('.0.', '.')] = val elif 'mean' in key: continue # newStateDict[key] = val elif 'tail' in key: if '.0.0.' in key: newStateDict[key.replace('.0.0.', '.0.')] = val elif '.0.2.' in key: newStateDict[key.replace('.0.2.', '.2.')] = val else: newStateDict[key.replace('.1.', '.4.')] = val elif 'body' in key: if '.body.0.' in key: newStateDict[key.replace(".body.0.", '.conv1.')] = val elif '.body.2.' in key: newStateDict[key.replace(".body.2.", '.conv2.')] = val elif "16" in key: newStateDict[key] = valtorch.save(newStateDict, "edsr-baseline-fp32.pth.tar")

          對比原始code的同學(xué)應(yīng)該會發(fā)現(xiàn):EDSR中的add_mean與sub_mean不見了。是的,筆者將add_mean與sub_mean移到了網(wǎng)絡(luò)外面,不對其進(jìn)行量化,具體為什么這樣做,見后面的介紹。

          除了上述操作外,我們還需要提供前述EDSR實(shí)現(xiàn)的量化版本模型,這個(gè)沒太多需要介紹的,直接看code(主要體現(xiàn)在三點(diǎn):插入量化節(jié)點(diǎn)(即QuantStub與DequantStub)、add轉(zhuǎn)換(即FloatFunctional)、fuse_model模塊(即fuse_model函數(shù))):

          class QuantizableResBlock(ResBlock):    def __init__(self, *args, **kwargs):        super(QuantizableResBlock, self).__init__(*args, **kwargs)        self.add = FloatFunctional()
          def forward(self, x): identity = x
          conv1 = self.conv1(x) relu = self.relu(conv1) conv2 = self.conv2(relu)
          output = self.add.add(identity, conv2) return output
          def fuse_model(self): fuse_modules(self, ['conv1', 'relu'], inplace=True)
          class QuantizableEDSR(EDSR): def __init__(self, *args, **kwargs): super(QuantizableEDSR, self).__init__(*args, **kwargs)
          self.quant = QuantStub() self.dequant = DeQuantStub() self.add = FloatFunctional()
          def forward(self, x): x = self.quant(x) x = self.head(x) res = self.body(x) res = self.add.add(res, x) x = self.tail(res) x = self.dequant(x) return x
          def fuse_model(self): for m in self.modules(): if type(m) == QuantizableResBlock: m.fuse_model()

          模型量化

          在上一篇文章中,我們也介紹了PTSQ的幾個(gè)步驟(額外包含了模型的構(gòu)建與保存)。

          • init: 模型的定義、預(yù)訓(xùn)練模型加載、inplace操作替換為非inplace操作;
          • config:定義量化時(shí)的配置方式,這里以fbgemm為例,它的activation量化方式為Historam,weight量化方式為per_channel;
          • fuse:模型中的op融合,比如相鄰的Conv+ReLU融合,Conv+BN+ReLU融合等等;
          • prepare: 量化前的準(zhǔn)備工作,也就是對每個(gè)需要進(jìn)行量化的op插入Observer;
          • feed: 送入校驗(yàn)數(shù)據(jù),前面插入的Observer會針對這些數(shù)據(jù)進(jìn)行量化前的信息統(tǒng)計(jì);
          • convert:用于在將非量化op轉(zhuǎn)換成量化op,比如將nn.Conv2d轉(zhuǎn)換成nnq.Conv2d, 同時(shí)會根據(jù)Observer所觀測的信息進(jìn)行nnq.Conv2d中的量化參數(shù)的統(tǒng)計(jì),包含scale、zero_point、qweight等;
          • save:用于保存量化好的模型參數(shù).

          Init

          模型的創(chuàng)建與預(yù)訓(xùn)練模型,這個(gè)比較簡單了,直接上code(注:PTSQ模式下模型應(yīng)當(dāng)是eval模式)。

          checkpoint = torch.load("edsrx4-baseline-fp32.pth.tar")model = QuantizableEDSR(block=QuantizableResBlock)model.load_state_dict(checkpoint)_replace_relu(model)model.eval()

          config

          這個(gè)步驟主要是為了指定與推理引擎搭配的一些量化方式,比如X86平臺應(yīng)該采用fbgemm方式進(jìn)行量化,而ARM平臺則應(yīng)當(dāng)采用qnnpack方式量化。本文主要是在PC端進(jìn)行,所以選擇了fbgemm進(jìn)行,相關(guān)配置信息如下:

          backend = 'fbgemm'torch.backends.quantized.engine = backendmodel.qconfig = torch.quantization.QConfig(    activation=default_histogram_observer,                            weight=default_per_channel_weight_observer)

          Fuse&Prepare

          Fuse與Prepare兩個(gè)步驟的作用主要是

          • 進(jìn)行OP的融合,比如Conv+ReLU的融合,Conv+BN+ReLU的融合,這個(gè)可以見前述實(shí)現(xiàn)code中的'fuse_model',pytorch目前提供了幾種類型的融合。我們只需知道就可以了,這塊不用太過關(guān)心,兩行code就可以完成:
          model.fuse_model()torch.quantization.prepare(model, inplace=True)

          • 插入Observer,在每個(gè)需要進(jìn)行量化的op中插入Observer,不同的量化方式會有不同的Observer,它將對喂入的校驗(yàn)數(shù)據(jù)進(jìn)行統(tǒng)計(jì),比如統(tǒng)計(jì)數(shù)據(jù)的最大值、最小值、直方圖分布等等。

          Feed

          這個(gè)步驟需要采用校驗(yàn)數(shù)據(jù)喂入到上述準(zhǔn)備好的模型中,這個(gè)就比較簡單了,按照常規(guī)模型的測試方式處理就可以了,參考code如下:

          注:筆者這里用了100張數(shù)據(jù),這個(gè)用全部也可以,不過耗時(shí)會更長meanBGR = torch.FloatTensor((0.4488, 0.4371, 0.4040)).view(3, 1, 1) * 255data_root = "${DIV2K_train_LR_bicubic/X4}"for index in range(1, 100):    image_path = os.path.join(data_root, f"{index:04d}.png")    inputs = preprocess(image_path)    inputs -= meanBGR
          with torch.no_grad(): output = model(inputs)

          Convert&Save

          在完成前面幾個(gè)步驟后,我們就可以將浮點(diǎn)類型的模型進(jìn)行量化了,這個(gè)只需要一行code就可以。在轉(zhuǎn)換過程中,它會將nn.Conv2d這類浮點(diǎn)類型op轉(zhuǎn)換成量化版op:nnq.Conv2d。

          torch.quantization.convert(model, inplace=True)torch.save(model.state_dict(),?"edsrx4-baseline-qint8.pth.tar")
          經(jīng)過上面的幾個(gè)步驟,我們就完成了EDSR模型的INT8量化,也將其進(jìn)行了保存。
          也就是說完成了初步的量化工作,因?yàn)榻酉聛淼臏y試論證很關(guān)鍵,如果量化損失
          很嚴(yán)重也不行的。

          量化模型測試

          接下來,我們對上述量化好的模型進(jìn)行一下測試看看效果。量化模型的調(diào)用code如下(與常規(guī)模型的調(diào)用有一點(diǎn)點(diǎn)的區(qū)別):

          def fp32edsr(block=ResBlock, pretrained=None):    model = EDSR(block=block)    if pretrained:        state_dict = torch.load(pretrained, map_location="cpu")        model.load_state_dict(state_dict)    return model
          def qint8edsr(block=QuantizableResBlock, pretrained=None, quantize=False): model = QuantizableEDSR(block=block) _replace_relu(model)
          if quantize: backend = 'fbgemm' quantize_model(model, backend) else: assert pretrained in [True, False]
          if pretrained: state_dict = torch.load(pretrained, map_location="cpu") model.load_state_dict(state_dict)
          return model
          def quantize_model(model, backend): if backend not in torch.backends.quantized.supported_engines: raise RuntimeError("Quantized backend not supported ") torch.backends.quantized.engine = backend model.eval()
          _dummy_input_data = torch.rand(1, 3, 64, 64)
          # Make sure that weight qconfig matches that of the serialized models if backend == 'fbgemm': model.qconfig = torch.quantization.QConfig( activation=torch.quantization.default_histogram_observer, weight=torch.quantization.default_per_channel_weight_observer) elif backend == 'qnnpack': model.qconfig = torch.quantization.QConfig( activation=torch.quantization.default_histogram_observer, weight=torch.quantization.default_weight_observer)
          model.fuse_model() torch.quantization.prepare(model, inplace=True) model(_dummy_input_data) torch.quantization.convert(model, inplace=True)

          從上面code可以看到:相比fp32模型,量化模型多了兩步驟:

          • replace=True的op替換為replace=False的op;
          • 模型的最簡單量化版本,完成初步的op替換。

          結(jié)合上述code,我們就可以直接對DIV2K數(shù)據(jù)進(jìn)行測試了,測試的部分code摘錄如下:

          index = 1image_path = os.path.join(data_root, f"{index:04d}.png")inputs = preprocess(image_path)inputs -= meanBGR
          with torch.no_grad(): output1 = model(inputs) output2 = fmodel(inputs)
          output1 += meanBGRoutput2 += meanBGR
          show1 = post_process(output1)cv2.imwrite(f"results/{index:03d}-init8.png", show1)show2 = post_process(output2)cv2.imwrite(f"results/{index:03d}-fp32.png", show2)

          上圖給出了DIV2K訓(xùn)練集中0016的兩種模型的效果對比,左圖為FP32模型的超分效果,右圖為INT8量化模型的超分效果??梢钥吹剑毫炕竽P驮谛Ч鲜且曈X無損的(就是說:量化損失導(dǎo)致的效果下降不可感知)??偠灾?strong style="font-weight: bold;color: black;">量化前后模型大小減少73%,推理延遲減少43%。

          注意事項(xiàng)

          1. 為什么要將add_mean與sub_mean移到網(wǎng)絡(luò)外面不參與量化呢?

          從我們的量化對比來看,將其移到外面效果更佳??赡芤哺鷄dd_mean與sub_mean中的參數(shù)有關(guān),兩者只是簡單的均值處理, 這個(gè)地方的量化會導(dǎo)致weight值出現(xiàn)較大偏差,進(jìn)而影響后續(xù)的量化精度。

          1. 在量化方式方面,該如何選擇呢?

          在量化方式方面,activation支持:HistogramObserver,MinMaxObserver,, weight支持:PerChannelMinMaxObserver,MinMaxObserver. 從我們的量化對比來看,Histogram+PerChannelMinMax這種組合要比MinMaxObserver+PerChannelMinMax更佳。下圖給出了DIV2K訓(xùn)練集中0018數(shù)據(jù)采用第二種量化組合效果對比,可以感知到明顯的量化損失。


          參考文章

          1. 如何使用PyTorch的量化功能?(https://mp.weixin.qq.com/s/wzAgIS1Omm-K-4-tx68CCQ)

          2. PyTorch模型量化工具學(xué)習(xí)(https://zhuanlan.zhihu.com/p/144025236)

          3. Pytorch實(shí)現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)訓(xùn)練量化(https://zhuanlan.zhihu.com/p/164901397)



          推薦閱讀



          添加極市小助手微信(ID : cvmart2),備注:姓名-學(xué)校/公司-研究方向-城市(如:小極-北大-目標(biāo)檢測-深圳),即可申請加入極市目標(biāo)檢測/圖像分割/工業(yè)檢測/人臉/醫(yī)學(xué)影像/3D/SLAM/自動駕駛/超分辨率/姿態(tài)估計(jì)/ReID/GAN/圖像增強(qiáng)/OCR/視頻理解等技術(shù)交流群:月大咖直播分享、真實(shí)項(xiàng)目需求對接、求職內(nèi)推、算法競賽、干貨資訊匯總、與?10000+來自港科大、北大、清華、中科院、CMU、騰訊、百度等名校名企視覺開發(fā)者互動交流~
          △長按添加極市小助手

          △長按關(guān)注極市平臺,獲取最新CV干貨

          覺得有用麻煩給個(gè)在看啦~??
          瀏覽 129
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天澡天天日天天射天天舔天天爽爽爽 | 国产 日韩 欧美视频 | 亚洲自拍中文字幕 | 睛唱久久久久久久 | 国产二线在线观看 |