細粒度相關 - Learning to Zoom: a Saliency-Based Sampling Layer for Neural Networks - 1 - 論文學習


 

 

Learning to Zoom: a Saliency-Based Sampling Layer for Neural Networks

Abstract

我們為卷積神經網絡引入了一個基於顯著性的扭曲(distortion)層,這有助於改善給定任務的輸入數據的空間采樣。我們的可微層可以作為預處理塊添加到現有的任務網絡中,並以端到端方式完全訓練。該層的作用是有效地估計如何從原始數據中采樣,以提高任務性能。例如,對於一個圖像分類任務的原始數據的大小可能高達幾百萬像素,但任務網絡所需的輸入圖像要小得多,我們層學習如何能更好地從底層高分辨率數據上以比均勻采樣更能保存任務相關信息的方式進行采樣。這就產生了一種扭曲的、像漫畫一樣的中間圖像的效果,在中間圖像中,有助於提高任務性能的特殊元素被放大和誇大。與空間變換器網絡等替代方法不同,我們提出的層受圖像顯著性的啟發,從均勻下采樣數據有效計算,並在不確定性下優雅地退化為均勻采樣策略。我們將該層應用於改進現有的用於人眼注視估計和細粒度對象分類任務的網絡。代碼可見:https://github.com/recasens/Saliency-Sampler

 

1 Introduction

許多現代用於計算機視覺的神經網絡模型都有輸入尺寸限制[1,2,3,4]。這些限制的存在有各種各樣的原因。通過限制輸入分辨率,可以控制訓練和測試期間所需的時間和計算量,並從GPU上的高效批處理訓練中受益。在某些數據集上,限制輸入特征維數也可以通過改進訓練樣本對輸入空間的覆蓋來提高性能。

當目標輸入尺寸小於原始數據集中的圖像時,標准的方法是對輸入圖像進行均勻下采樣。也許最著名的例子是在 ImageNet Large Scale Visual Recognition Challenge [5]上訓練分類器時常用的224×224像素輸入,盡管原始數據集中存在一系列圖像大小——可能高達數百萬像素。

雖然均勻下采樣在許多情況下簡單有效,但對於需要不同空間分辨率和位置信息的任務來說,它可能是有損的。在這種情況下,必須在必要的(可能是不同的)尺寸和位置對突出的區域進行抽樣。人類通過掃視目光來完成這些任務,以利用高度敏銳的中央凹視覺和粗糙的周邊視覺來收集必要的信息。人們也曾嘗試賦予機器類似形式的抽樣行為。傳統計算機視覺的一個流行的例子是SIFT[6],在特征提取之前,將關鍵點定位在空間和圖像尺度內。近年來,區域proposal網絡在目標檢測[7]中得到了廣泛的應用。為了更緊密地模仿人類的視覺系統,人們正在開發與任務相關的順序注意機制,以允許以高分辨率處理許多場景區域(見例[8,9,10])。然而,這些方法放棄了一些使機器視覺具有吸引力的處理速度,並增加了proposal生成和評估任務完成的復雜性。

在這項工作中,我們引入了一個基於顯著性的采樣層:一個簡單的插件模塊,它可以附加到任何有輸入限制的網絡的開頭,並用於以特定任務的方式改進下采樣。如圖1所示,給定目標圖像輸入大小,我們的顯著性采樣器學習將目標中的像素分配到潛在圖像中對當前任務特別重要的區域。這樣做,扭曲圖像輸入層,創建一個圖像的變形版本,強調任務相關的部分,然后抑制無關緊要的部分,類似於漫畫的臉試圖放大的一個人的身份的那部分信息,使他們從平均水平中脫穎而出。

我們的層包括一個連接到一個的采樣器顯著映射估計器(saliency map),該采樣器根據圖像區域的相對顯著值改變采樣密度。由於該層被設計成完全可微的,它可以在任何傳統網絡之前插入,並經過端到端訓練。與順序注意模型不同[9,10,11,12],計算是在顯著性采樣器的單一通道中以恆定的計算成本執行的。

我們將我們的方法應用到小對象的發現或細粒度細節是重要的任務中(見圖2),並一致發現在基線網絡上添加我們的層得到了性能改進。

 

 

2 Related Work

我們將相關工作分為三大類:注意機制、基於顯著性的方法和自適應圖像采樣方法。

Attention mechanisms:  注意力已被廣泛用於提高CNN的性能。Jaderberg等[13]介紹了空間轉換網絡(STN),是一個用來從輸入圖像估計參數化轉換的層,以撤銷有害(nuisance)圖像變化(如來自rigid目標分類任務的姿勢),從而提高模型的泛化。在他們的工作中,作者提出了三種類型的轉換可以學習:affine, projective 和thin plate spline (TPS)。盡管我們的方法也對輸入圖像應用了轉換,但我們的應用程序是完全不同的:我們不試圖撤消變化,如局部平移或旋轉;相反,我們嘗試動態地改變分辨率以適應輸入圖像中更具任務顯著性的區域。雖然我們的方法可以封裝在[13]的TPS方法中,但我們隱式地防止了極端轉換和折疊,這對於基於TPS的空間轉換器很容易發生(這也使得非參數采樣map難以直接估計)。我們相信,這有助於防止戲劇性的失敗,因此有助於使模塊更容易學習。

Dai等人[14]引入的可變形卷積網絡(Deformable convolutional networks,DCNs)具有與STNs相似的動機。他們表明,卷積層可以學習動態調整它們的接受域,以適應輸入特征,並提高對有害因素的不變性。他們的建議包括用可變形層替換CNN中的任何標准卷積層,可變形層學習根據輸入估計到標准核采樣位置的偏移量。我們注意到我們的工作有四個主要不同之處。首先,他們的方法從與原始CNN架構相同的低分辨率輸入中采樣,而我們的顯著性采樣器被設計為從任何可用的分辨率中采樣,允許它在可用時利用更高分辨率的數據。其次,我們的方法通過顯著性映射來估計樣本域,顯著性映射在訓練全卷積神經網絡[15]時已經自然出現了。我們發現,就像在DCN中一樣直接估計局部空間偏移要困難得多。第三,我們的方法可以在不修改的情況下應用於現有的訓練過的網絡,而DCNs需要通過變換可變形的卷積來改變網絡配置。最后,我們的方法以顯著性映射和變形圖像的形式產生人類可讀的輸出,這允許容易的視覺檢查和調試。我們注意到,我們提出的顯著性采樣器和DCNs並不是互斥的:我們的顯著性采樣器旨在跨尺度空間進行有效采樣,並可能利用可變形的卷積層來幫助建模局部幾何變化。與變形網絡一樣,Li等人[16]提出了一種使用非平方卷積的編碼器-解碼器結構。與[13]中一樣,它們直接預測這些轉換的參數化,而不是使用顯著性映射。

Saliency-based methods: CNN已經被證明可以自然地將注意力引導到輸入數據的任務顯著區域。Zhou等人[15]發現CNN在分類任務中使用圖像的有限部分來告知他們的決策。他們提出使用Class Activation Maps(CAM)作為一種機制來定位圖像中的物體,在訓練過程中沒有明確的位置反饋。Rosenfeld等人[19]提出了一種迭代的方法來裁剪圖像的相關區域進行細粒度分類。它們生成一個CAM來突出顯示網絡最常用的區域,以做出最終決定。這些區域被用來裁剪圖像的一部分,並生成一個新的CAM,然后突出顯示網絡使用的圖像區域,以通知最終的預測。正如在[15]中介紹的那樣,CAM需要使用一種特殊的全卷積架構。為了克服這一限制,[20]引入了一種基於梯度的方法來生成CAMs。他們的方法可以用來理解各種各樣的網絡。在我們的工作中,我們通過鼓勵網絡更多地添加這些區域,來利用CNN自然定位任務顯著區域的能力。

Adaptive image sampling methods:  解決這一問題的另一種可能的方法是在多尺度策略中預先設計某些特征檢測器。這種方法通常是在解決一個特定的問題時使用的特性對人類來說非常清楚的情況下使用的。例如,為了解決移動設備顯示器上的視線跟蹤問題,Khosla等人[21]提出了iTracker方法,一種基於RGB圖像的視線估計系統。他們的系統使用該設備前置攝像頭的圖像,並使用單獨的探測器提取出眼睛和人臉的高分辨率圖像。沿這條線的另一個例子是Wang等人的[22],他們生成輸入圖像在不同尺度上的特征,然后選擇最好的特征,產生最終的輸出。

自適應圖像采樣也被用於計算機圖形學[23]中的圖像重定向。不像在我們的例子中,采樣圖像僅作為解決另一個問題的中間表示,重新定位的目標是變形圖像以適應新的形狀,並保留對人類觀察者重要的內容,同時避免可見的變形。與我們的概念類似,這可以由顯著性[24]驅動,並將其表述為能量最小化[25]或Finite Element Method[26]問題。

 

3 Saliency Sampler

表示任意大小的高分辨率圖像,表示適合於任務網絡(圖1)的大小為MxN像素的低分辨率圖。通常, CNNs將輸入圖像的大小重置為,而不利用的像素的相應重要性。但是,如果我們的任務更需要某一個圖像區域的信息,那么密集地采樣該區域可能會獲得更好效果。saliency sampler首先分析,然后按感知重要性的比例對的區域進行采樣。在這樣做的過程中,該模型可以獲得一些提高分辨率的好處,而無需顯著增加的計算負擔或過度擬合的風險。

采樣過程可分為兩個階段。在第一階段,使用CNN生成saliency map。這張map是特定於任務的,因為不同的任務可能需要關注不同的圖像區域。第二階段,根據saliency map對最重要的圖像區域進行采樣。

 

3.1 Saliency Network

saliency網絡f從低分辨率圖像中生成saliency map S :。在該階段網絡的選擇是很靈活的,根據任務而變化。對於s的所有選擇,我們在最后一層使用一個softmax操作去歸一化輸出map。

 

3.2 Sampling Approach

接下來,sampler g使用saliency map S和全分辨率圖做為輸入計算,得到一個和有着相同維度的圖像,其從采樣得到,且S中高權重區域將被一個更大的圖像范圍表示(如圖3)。在該節中,我們將討論g采用的形式,使用更適用於CNNs的那個。在所有情況中,我們計算采樣圖和原始圖之間的映射,然后使用在[13]中介紹的grid sampler。該映射能夠能夠被寫成標准形式,如兩個函數u(x,y)和v(x,y),即u(x,y)對應原圖的x值,v(x,y)對應原圖的y值,用來將原圖(u(x,y),v(x,y))位置的像素映射到重采樣圖J的(x,y)位置)。

u和v設計的主要目的是按比例映射像素到saliency map復制給它們的歸一化權重。假設u(x,y)和v(x,y)的x和y范圍從0到1。一個該問題的確切近似將如下式去尋找u和v:

然而,求出u和v等價於求出將S(x, y)的分布集轉換成均勻分布的變量的變化。這個問題已經被廣泛地探索過,通常的解決方案在計算上是非常昂貴的[27]。因此,我們需要采取一種適合CNNs使用的替代方法。

我們的方法受到每個像素使用力拉扯其他像素的想法的啟發(如圖3)。如果我們添加距離核,則兩個函數可以表示為:

對於函數u和v,這個公式具有某些可取的性質,特別是:

Sampled areas: 高顯著性區域的采樣更密集,因為具有高顯著性質量的像素會吸引其他像素。注意,內核k可以作為一個正則化器,以避免所有像素收斂到相同值的極端情況。在我們所有的實驗中,我們使用一個參數σ設置為saliency map度的三分之一的高斯核,我們發現這在各種設置中工作得很好。

Convolutional form:  這種形式允許我們用簡單的卷積來計算u和v,這是整個系統效率的關鍵。這一層可以很容易地添加到標准CNN中,並通過反向傳播保持訓練所需的可微性。

注意,等式2和等式3中的公式有一個不可取的偏向於圖像中心的采樣偏好。我們通過填充saliency map的邊框值來避免這種影響。

 代碼

saliency_sampler.py

import argparse
import os
import shutil
import time
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.backends.cudnn as cudnn
import torch.distributed as dist
import torch.optim
import torchvision.transforms as transforms
import torchvision.models as models
import torch.nn.functional as F
import torchvision.utils as vutils
import torchvision.models as models
import numpy as np
import random



def makeGaussian(size, fwhm = 3, center=None):
    """ Make a square gaussian kernel.

    size is the length of a side of the square
    fwhm is full-width-half-maximum, which
    can be thought of as an effective radius.
    """

    x = np.arange(0, size, 1, float)
    y = x[:,np.newaxis]

    if center is None:
        x0 = y0 = size // 2
    else:
        x0 = center[0]
        y0 = center[1]

    return np.exp(-4*np.log(2) * ((x-x0)**2 + (y-y0)**2) / fwhm**2)


class Saliency_Sampler(nn.Module):
    def __init__(self,task_network,saliency_network,task_input_size,saliency_input_size):
        super(Saliency_Sampler, self).__init__()
        
        self.hi_res = task_network
        self.grid_size = 31
        self.padding_size = 30
        self.global_size = self.grid_size+2*self.padding_size
        self.input_size = saliency_input_size
        self.input_size_net = task_input_size
        self.conv_last = nn.Conv2d(256,1,kernel_size=1,padding=0,stride=1)
        gaussian_weights = torch.FloatTensor(makeGaussian(2*self.padding_size+1, fwhm = 13))

        # Spatial transformer localization-network
        self.localization = saliency_network
        self.filter = nn.Conv2d(1, 1, kernel_size=(2*self.padding_size+1,2*self.padding_size+1),bias=False)
        self.filter.weight[0].data[:,:,:] = gaussian_weights

        self.P_basis = torch.zeros(2,self.grid_size+2*self.padding_size, self.grid_size+2*self.padding_size)
        for k in range(2):
            for i in range(self.global_size):
                for j in range(self.global_size):
                    #k = 0時,為(j-self.padding_size)/(self.grid_size-1.0)
                    #k=1時,為(i-self.padding_size)/(self.grid_size-1.0)
                    #k=0時,每一行都相同,前[0:30]是[-1,0], [30:60]是[0,1],[60:]是[1,2]
                    ##k=1時,每一列都相同,前[0:30]是[-1,0], [30:60]是[0,1],[60:]是[1,2]
                    self.P_basis[k,i,j] = k*(i-self.padding_size)/(self.grid_size-1.0)+(1.0-k)*(j-self.padding_size)/(self.grid_size-1.0)


    def create_grid(self, x):
        #x : 相當於論文中等式2和等式3的S(x',y')* k((x,y),(x',y')),是權重
        #P : 相當於論文中等式2和等式3的[x',y']
        # P = torch.autograd.Variable(torch.zeros(1,2,self.grid_size+2*self.padding_size, self.grid_size+2*self.padding_size).cuda(),requires_grad=False)
        P = torch.autograd.Variable(torch.zeros(1,2,self.grid_size+2*self.padding_size, self.grid_size+2*self.padding_size).cpu(),requires_grad=False)
        # print("P size is : ", P.size()) #torch.Size([1, 2, 91, 91])
        P[0,:,:,:] = self.P_basis
        #從[1, 2, 91, 91]擴展為[batch_size, 2, 91, 91]
        P = P.expand(x.size(0),2,self.grid_size+2*self.padding_size, self.grid_size+2*self.padding_size)
        # print("P size is : ", P.size()) #torch.Size([5, 2, 91, 91])

        x_cat = torch.cat((x,x),1) #[batch_size, 2, 91, 91]
        # print("x_cat size is : ", x_cat.size()) #torch.Size([5, 2, 91, 91])
        #得到的是論文中等式2的分母
        p_filter = self.filter(x) #輸入[batch_size, 1, 91, 91],輸出[batch_size, 1, 31, 31]
        # print("p_filter is : ", p_filter)
        # print("p_filter size is : ", p_filter.size()) #torch.Size([5, 1, 31, 31])


        #得到的是論文中等式2和等式3的分子
        x_mul = torch.mul(P,x_cat).view(-1,1,self.global_size,self.global_size) #[batch_size*2, 1, 91, 91]
        # print("x_mul size is : ", x_mul.size()) #torch.Size([10, 1, 91, 91])
        #filter()輸入[batch_size*2, 1, 91, 91], 輸出[batch_size*2, 1, 31, 31]
        #然后重置為[batch_size, 2, 31, 31]
        all_filter = self.filter(x_mul).view(-1,2,self.grid_size,self.grid_size)
        # print("all_filter size is : ", all_filter.size()) #torch.Size([5, 2, 31, 31])

        # x_filter是u(x,y)的分子,y_filter是v(x,y)的分子
        x_filter = all_filter[:,0,:,:].contiguous().view(-1,1,self.grid_size,self.grid_size) #[batch_size, 1, 31, 31]
        y_filter = all_filter[:,1,:,:].contiguous().view(-1,1,self.grid_size,self.grid_size) #[batch_size, 1, 31, 31]
        # print("y_filter size is : ", y_filter.size()) #torch.Size([5, 1, 31, 31])

        #值的范圍是[0,1]
        x_filter = x_filter/p_filter #u(x,y)
        y_filter = y_filter/p_filter #v(x,y)
        # print("y_filter is : ", y_filter)
        # print("y_filter max is : ", y_filter.max()) #tensor(1.0341, grad_fn=<MaxBackward1>)
        # print("y_filter min is : ", y_filter.min()) #tensor(-0.0268, grad_fn=<MinBackward1>)

        #將值的范圍從[0,1]改為[-1,1]
        xgrids = x_filter*2-1
        ygrids = y_filter*2-1
        # print("ygrids max is : ", ygrids.max()) #tensor(1.0200, grad_fn=<MaxBackward1>)
        # print("ygrids min is : ", ygrids.min()) #tensor(-1.0502, grad_fn=<MinBackward1>)
        xgrids = torch.clamp(xgrids,min=-1,max=1) #將里面的值的范圍控制在[-1,1]
        ygrids = torch.clamp(ygrids,min=-1,max=1)


        xgrids = xgrids.view(-1,1,self.grid_size,self.grid_size)
        ygrids = ygrids.view(-1,1,self.grid_size,self.grid_size)

        grid = torch.cat((xgrids,ygrids),1) #[batch_size, 2, 31, 31]

        grid = nn.Upsample(size=(self.input_size_net,self.input_size_net), mode='bilinear')(grid) #上采樣為[batch_size, 2, 224, 224]

        grid = torch.transpose(grid,1,2) #[batch_size, 224, 2, 224]
        print("grid size is : ", grid.size()) #torch.Size([5, 224, 2, 224])
        grid = torch.transpose(grid,2,3) #[batch_size, 224, 224, 2]
        print("grid size is : ", grid.size()) #torch.Size([5, 224, 224, 2])

        return grid

    def forward(self, x,p):
        x_low = nn.AdaptiveAvgPool2d((self.input_size,self.input_size))(x) #先均勻下采樣得到低分辨率圖,[batch_size,3,224,224]
        # print('x_low size is : ', x_low.size()) #torch.Size([5, 3, 224, 224])
        xs = self.localization(x_low) #得到saliency map,當輸入是[batch_size,3,224,224]時,輸出是[batch_size, 256, 14, 14]
        # print('xs size is : ', xs.size()) #torch.Size([5, 256, 14, 14])
        xs = nn.ReLU()(xs)
        xs = self.conv_last(xs) #得到saliency map,channels=1,即[batch_size, 1, 14, 14]
        # print('xs size is : ', xs.size()) #torch.Size([5, 1, 14, 14])
        xs = nn.Upsample(size=(self.grid_size,self.grid_size), mode='bilinear')(xs) #上采樣為[batch_size, 1, 31, 31]
        # print('xs size is : ', xs.size()) #torch.Size([5, 1, 31, 31])
        xs = xs.view(-1,self.grid_size*self.grid_size) #重置大小為[batch_size, 31*31]
        # print('xs size is : ', xs.size()) #torch.Size([5, 961])
        xs = nn.Softmax()(xs) #得到每個像素的權重
        # print('xs size is : ', xs.size()) #torch.Size([5, 961])
        xs = xs.view(-1,1,self.grid_size,self.grid_size) #再重置為[batch_size, 1, 31, 31]
        # print('xs size is : ', xs.size()) #torch.Size([5, 1, 31, 31])
        #對圖像或者張量的邊緣進行鏡像對稱的填充,大小為[batch_size, 1, self.grid_size+2*self.padding_size, self.grid_size+2*self.padding_size]
        #即[batch_size, 1, 91, 91]
        xs_hm = nn.ReplicationPad2d(self.padding_size)(xs) #避免等式2和3偏向於圖像中心的采樣偏好
        # print('xs_hm size is : ', xs_hm.size()) #torch.Size([5, 1, 91, 91])

        grid = self.create_grid(xs_hm)

        #輸入為x = [batch_size, 3, x_size, x_size], grid=[batch_size, 224, 224, 2],grid是歸一化后的結果,值范圍為[-1,1]
        #輸出x_sampled為[batch_size, 3, 224, 224]
        #grid的[n,h,w]指定的2維表示(x,y),意思是輸入x的(x,y)位置的像素插到輸出x_sampled的x_sampled[n,:,h,w]上
        x_sampled = F.grid_sample(x, grid) #得到重采樣的圖像

        if random.random()>p: #均勻采樣
            s = random.randint(64, 224)
            x_sampled = nn.AdaptiveAvgPool2d((s,s))(x_sampled)
            x_sampled = nn.Upsample(size=(self.input_size_net,self.input_size_net),mode='bilinear')(x_sampled)

        x = self.hi_res(x_sampled)

        return x,x_sampled,xs


if __name__ == '__main__':
    from saliency_network import saliency_network_resnet18
    from resnet import resnet101
    model_v = Saliency_Sampler(resnet101(),saliency_network_resnet18(),224,224)
    import torch
    from torch.autograd import Variable
    input_ = Variable(torch.randn((5,3,800,800)))
    x,x_sampled,xs = model_v(input_, 1)
View Code

 

3.3 Training with the Saliency Sampler

當網絡需要更高分辨率輸入的更有信息的子采樣時,saliency sampler可以插入到任何卷積神經網絡ft中。由於該模塊是端到端可微的,我們可以用標准的優化技術訓練整個pipeline。我們的完整pipeline包括四個步驟(見圖1):

1.獲得圖的一個低分辨率版本

2.圖像被用於saliency 網絡 fs去計算saliency map ,圖像中與任務相關的區域被賦予更高的權重

3.根據saliency map ,使用deterministic grid sampler g去采樣高分辨率圖像,獲得重采樣圖像,其與有着相同的分辨率

4.原始任務網絡ft被用於計算最后的輸出 (使用的輸入是重采樣圖像J)

fs和ft都有可學習的參數,因此可以針對特定任務聯合訓練。我們發現,在訓練過程的開始階段,對重新采樣的任務網絡輸入圖像進行模糊處理是有幫助的。它迫使顯著性采樣器更深入地放大圖像,以便進一步放大小的細節,否則會被隨后的模糊破壞。這甚至對移除模糊后的模型的最終性能都是有益的。

 

4 Experiments

省略

 

5 Discussion

添加我們的saliency sampler對於那些特征小且稀疏,或出現在多個圖像尺度的圖像任務來說是十分有利的。如果另一個感興趣的點受到影響,在放大區域附近引入的變形可能會潛在地阻止網絡產生強烈的變形。這對文本識別等任務可能是有害的。在實踐中,我們觀察到學習過程能夠很好地處理這種情況,因為它能夠在不影響注視預測性能的情況下放大兩眼。這是特別有趣的,因為這項任務需要保存圖像中的幾何信息。該方法被證明比其他修改空間采樣的方法更容易訓練,如Spatial Transformer Networks [13] 或Deformable Convolutional Networks [14]。由於這些方法無法為其抽樣策略找到合適的參數,因此這些方法的性能往往接近基線。saliency map引入的非均勻放大方法也允許在空間域上可變性縮放。正如我們在細粒度分類任務中觀察到的那樣,與一致放大的感興趣區域crops相比,這一點加上端到端優化會帶來性能上的好處。與iTracker[21]的情況不同,我們在任務中不需要關於相關圖像特征的先驗知識。

 

6 Conclusion

我們提出了saliency sampler —— 一種新的CNNs層,它可以適應圖像采樣策略來提高任務性能,同時為給定的圖像處理任務保留內存分配和計算效率。我們已經證明了我們的技術在定位和聚焦圖像特征方面的有效性,這些特征對於注視跟蹤和細粒度目標識別的任務很重要。該方法很容易集成到現有的模型中,並且可以以端到端的方式有效地訓練。與其他一些圖像變換技術不同,我們的方法不限於預先定義的重要區域的數量或大小,它可以在整個圖像域重新分配采樣密度。同時,我們的技術通過單個標量注意力映射實現參數化,使其對由於折疊或奇點導致的圖像不可恢復的退化具有魯棒性。這導致在需要恢復小圖像特征,如眼睛或相關動物物種之間的細微差別的問題上的卓越表現。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM