具有不同坐标原点的坐标向量的基础的编程更改(python/general 数学)

Programmatical Change of basis for coordinate vectors with different origin of coordinates (python/general maths)

我有一个支持向量机,它使用决策超平面将我的数据一分为二(出于可视化目的,这是一个具有三个维度的示例数据集),如下所示: 现在我想改变基础,使超平面平躺在 x/y 平面上,这样从每个样本点到决策超平面的距离就是它们的 z 坐标。

为此,我知道我需要更改基础。 SVM 的超平面由它们的系数(3d 向量)和截距(标量)给出,使用(据我所知)数学平面的一般形式:ax+by+cz=d,其中 a、b、c 为系数的坐标和 d 是截距。当绘制为 3d-Vector 时,系数是垂直于平面的矢量(在图像中是青色线)。

现在 change of basis:如果没有截距,我可以假设作为系数的向量是我新基的一个向量,另一个可以是平面上的随机向量第三个是两者的简单叉积,得到三个正交向量,可以作为变换矩阵的列向量。 下面代码中使用的 z 函数来自平面一般形式的简单术语重排:ax+by+cz=d <=> z=(d-ax-by)/c:

z_func = lambda interc, coef, x, y: (interc-coef[0]*x -coef[1]*y) / coef[2]
def generate_trafo_matrices(coefficient, z_func):
    normalize = lambda vec: vec/np.linalg.norm(vec)
    uvec2 = normalize(np.array([1, 0, z_func(1, 0)]))
    uvec3 = normalize(np.cross(uvec1, uvec2))
    back_trafo_matrix = np.array([uvec2, uvec3, coefficient]).T 
    #in other order such that its on the xy-plane instead of the yz-plane
    trafo_matrix = np.linalg.inv(back_trafo_matrix)
    return trafo_matrix, back_trafo_matrix

然后将此转换矩阵应用于所有点,如下所示:

def _transform(self, points, inverse=False):
    trafo_mat = self.inverse_trafo_mat if inverse else self.trafo_mat              
    points = np.array([trafo_mat.dot(point) for point in points])        
    return points

现在,如果截距为零,那将完美运行,并且飞机将在 xy 轴上保持平坦。然而,一旦我有截距!=零,飞机就不再平坦了:

我理解是这样,因为这不是简单的基础变化,因为我的另一个基础的坐标原点不在(0,0,0)而是在不同的地方(超平面可能是在任何时候穿过系数向量),但我尝试将截距添加到转换中都没有得到正确的结果:

def _transform(self, points, inverse=False):
    trafo_mat = self.inverse_trafo_mat if inverse else self.trafo_mat      
    intercept = self.intercept if inverse else -self.intercept
    ursprung_translate = trafo_mat.dot(np.array([0,0,0])+trafo_matrix[:,0]*intercept)
    points = np.array([point+trafo_matrix[:,0]*intercept for point in points])
    points = np.array([trafo_mat.dot(point) for point in points])        
    points = np.array([point-ursprung_translate for point in points])
    return points

例如是错误的。我在 Whosebug 上而不是在数学 StackExchange 上问这个问题,因为我认为我无法将相应的数学转换成代码,我很高兴我能做到这一点。

我已经创建了一个 github 要点,其中包含用于执行转换的代码并在 https://gist.github.com/cstenkamp/0fce4d662beb9e07f0878744c7214995, which can be launched using Binder under the link https://mybinder.org/v2/gist/jtpio/0fce4d662beb9e07f0878744c7214995/master?urlpath=lab%2Ftree%2Fchange_of_basis_with_translate.ipynb 处创建绘图,如果有人想使用代码本身的话。

感谢任何帮助!

这里的问题是你的平面是仿射space,不是向量space,所以你不能使用通常的变换矩阵公式。

仿射 space 中的坐标系由原点和基(放在一起称为 仿射框架 )给出。比如你的原点是O,那么M点在仿射坐标系中的坐标就是OM向量在仿射坐标系中的坐标。

如您所见,“正常”R^3 space 是仿射 space 的特例,其中原点为 (0,0,0)。

一旦我们确定了这些,我们就可以在仿射 spaces 中使用框架变化公式:如果我们有两个仿射框架 R = (O, b)R' = (O', b'),则基础变化公式对于点 M 是:M(R') = base_change_matrix_from_b'_to_b * (M(R) - O'(R))O'(R) O' 在 R 定义的坐标系中的坐标)。

在我们的例子中,我们尝试从原点为 (0,0,0) 的帧出发,然后 规范基础,到一个框架,其中原点是 (0,0,0) 在平面上的正交投影,并且基础是,例如,在您的初始 post.

中描述的那个

让我们执行这些步骤:

首先,我们将定义一个 Plane class 让我们的生活更轻松一些:

from dataclasses import dataclass
import numpy as np

@dataclass
class Plane:
    a: float
    b: float
    c: float
    d: float
    
    @property
    def normal(self):
        return np.array([self.a, self.b, self.c])
    
    def __contains__(self, point:np.array):
        return np.isclose(self.a*point[0] + self.b*point[1] + self.c*point[2] + self.d, 0)
    
    def project(self, point):
        x,y,z = point
        k = (self.a*x + self.b*y + self.c*z + self.d)/(self.a**2 + self.b**2 + self.c**2)
        return np.array([x - k*self.a, y-k*self.b, z-k*self.c])
   
    
    def z(self, x, y):
        return (- self.d - self.b*y - self.a*x)/self.c

然后我们可以实现 make_base_changer,它将 Plane 作为输入,return 2 个 lambda 函数执行正向和反向变换(采用和 return一个点)。你应该可以测试

def normalize(vec):
    return vec/np.linalg.norm(vec)
def make_base_changer(plane):
    uvec1 = plane.normal
    uvec2 = [0, -plane.d/plane.b, plane.d/plane.c]
    uvec3 = np.cross(uvec1, uvec2)
    transition_matrix = np.linalg.inv(np.array([uvec1, uvec2, uvec3]).T)
    
    origin = np.array([0,0,0])
    new_origin = plane.project(origin)
    forward  = lambda point: transition_matrix.dot(point - new_origin)
    backward = lambda point: np.linalg.inv(transition_matrix).dot(point) + new_origin
    return forward, backward