[學習筆記] Tangent Distance


Tangent Distance

簡介

切空間距離可以用在KNN方法中度量距離,其解決的是圖像經過有限變換之后還能否被分類正確,例如。對一張數字為5的手寫數字圖片,將其膨脹后得到圖像p1,此時KNN還應認為p1與原圖接近,即距離較近,而不是距離其他類別較近。而Tangent Distance較好的解決了經過圖像變換后距離度量的問題,其通過梯度下降算法在切空間中優化求得被分類向量和原始圖像及其經過變換后圖像的空間中最近的點。

Question

對於上面這張圖,我們使用傳統的歐式距離,極有可能將左邊的圖分類成4,而不是9。這暴露出一個問題,傳統的歐式距離對旋轉、縮放、平移等等變換是不魯棒的,這就告訴我們我們需要一個新的對於常規變換魯棒的距離評估標准。

Tangent Vector

切空間向量是如何定義的呢?簡單的說,切空間向量的切是指在參數變化方向上的切,即輸入圖像/向量關於參數的微分。要理解這個,首先我們要先將圖像變換數學化(為將問題簡單化,我們認為只有一種變換,即旋轉變換):

\[x_{t} = F(x;a) \]

上式表示輸入圖像x旋轉a角度變為xt,我們怎么求旋轉變換關於角度a的微分呢?很簡單:

\[T = \lim_{a \to 0}x_t - x = \lim_{a \to 0} F(x;a) - x \]

這個定義,也就是圖像關於a的微分,也就是切空間向量的定義。

那么這個切空間向量有什么用呢,很有用!

我們可以在切線方向也就是切空間里去搜索一種最合適的變換,也就是得到一個最合適的a,使得變換后的圖像與原圖的歐式距離最小。

一個簡單的想法就是窮舉出所有的經過變換后的圖像,我們將圖片每隔1度旋轉增廣一張,那么肯定能找到一張與所需要分類的圖片距離最近的圖像。

當然,切空間距離可不是這么簡單,上面的想法是讓a離散化的想法,實際上a是連續的,我們可以通過梯度下降法來求得一個最為合適的參數a,從而找到最合適的距離。

Tangent Distance

上面我們定義了切空間向量,下面是切空間距離的定義:

\[D_{tan}(x,y) = \min_a [||x + Ta - y||] \]

這個定義可以這么理解,T由於是參數方向上的微分,乘以系數a然后加上x就是在參數方向上的變換,a取0度,就是不旋轉,a取15度就是旋轉15度。而a是參數,通過最小化與需要判別圖像的歐式距離來得到參數a,繼而得到切空間距離。

顯然這里優化過程是需要用到梯度下降算法的。

將上式擴展為多種變換,就是對T的定義擴展為矩陣,比如有r種變換,圖像像素維度d,則T矩陣就是r x d的。a就是d x 1維度。

Gradient Descent

簡答推導一下這里梯度下降的公式:

\[\frac {\partial(||x + Ta - y||)}{\partial a} = \frac{\partial(x + Ta - y)^T(x + Ta - y)}{\partial a}\\ = \frac{\partial}{\partial a}(x^TTa + a^TT^Tx + a^TT^TTa - A^TT^Ty - y^TTa)\\ =2T^Tx + 2T^TTa - 2T^Ty\\ =2T^T(x + Ta - y) \]

好了,上面推導完了梯度,可以開始寫代碼了。

Coding

import cv2 as cv
import numpy as np
import copy
class GradientDescent():
    def __init__(self,T):
        self.T = T #(2,784)
    def __call__(self,x,y):
        r,d = self.T.shape # (2,784)
        a = np.ones(shape = (r,1)) # (2,1)
        t = 0
        while True:
            b = copy.copy(a)
            # (784,2).dot (2,1) -> (784,1) -> (2,1)
            a = a - 0.0005 * self.T.dot(x + self.T.T.dot(a) - y)
            t += 1
            #print(a,b)
            if np.sqrt(np.mean((b-a)**2)) < 0.0001 or t > 5000:
                break
        return a,self.T


class TanhDistance():
    def __init__(self,frame,transforms = None):
        self.vectors = []
        h,w = frame.shape
        self.hw = h*w
        if transforms is not None:
            for transform in transforms:
                t = transform(frame) # (h,w)
                self.vectors.append(np.reshape(t,(h*w,)) - np.reshape(frame,(h*w,)))
        self.gradientDescent = GradientDescent(np.array(self.vectors)) # r,28*28
    def __call__(self,x,y):
        x = np.reshape(x,(self.hw,1))
        y = np.reshape(y,(self.hw,1))
        a,T = self.gradientDescent(x,y) # (28*28,1)
        
        return np.sqrt(np.mean((x + T.T.dot(a) - y)**2)) 
def get_transforms(frame):
    h,w = frame.shape
    transformations = []
    # rotate
    delta_theta = 5
    M = cv.getRotationMatrix2D(((w-1)/2.0,(h-1)/2.0),delta_theta,1)
    transformations.append(lambda x:cv.warpAffine(x,M,(w,h)))

    # shift
    delta_x = 2
    delta_y = 0
    M = np.float32([[1,0,delta_x],[0,1,delta_y]])
    transformations.append(lambda x:cv.warpAffine(x,M,(w,h)))
    
    return transformations

if __name__ == "__main__":
    img = cv.imread("/home/xueaoru/圖片/0000.jpg")
    gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
    gray = cv.resize(gray,(28,28))/255
    transforms = get_transforms(gray)
    metric = TanhDistance(gray,transforms)

    img2 = cv.imread("/home/xueaoru/圖片/000.jpg")
    gray2 = cv.cvtColor(img2,cv.COLOR_BGR2GRAY)
    gray2 = cv.resize(gray2,(28,28))/255 

    print("tan distance:{}".format(metric(gray,gray2)))
    print("l2 distance:{}".format(np.sqrt(np.mean((gray - gray2)**2))))
    #for transform in transforms:
    #    print(transform(gray))

所用圖片:

距離結果:

tangent distance:0.3062723225969733

l2 distance:0.336102326896069Q


免責聲明!

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



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