向量基本运算

package main

import (
    "fmt"
    "math"
)

type Vec3 struct{ X, Y, Z float64 }

func (a Vec3) Add(b Vec3) Vec3 { return Vec3{a.X + b.X, a.Y + b.Y, a.Z + b.Z} }
func (a Vec3) Scale(s float64) Vec3 { return Vec3{a.X * s, a.Y * s, a.Z * s} }
func (a Vec3) Len() float64 { return math.Sqrt(a.X*a.X + a.Y*a.Y + a.Z*a.Z) }
func (a Vec3) Normalize() Vec3 { return a.Scale(1 / a.Len()) }

// 点积:a·b = ax*bx + ay*by + az*bz = |a||b|cosθ
func (a Vec3) Dot(b Vec3) float64 { return a.X*b.X + a.Y*b.Y + a.Z*b.Z }

// 叉积:a×b,垂直于 a 和 b 所在平面
func (a Vec3) Cross(b Vec3) Vec3 {
    return Vec3{
        a.Y*b.Z - a.Z*b.Y,
        a.Z*b.X - a.X*b.Z,
        a.X*b.Y - a.Y*b.X,
    }
}

// 余弦相似度(ML embedding 相似度)
func CosineSim(a, b Vec3) float64 {
    return a.Dot(b) / (a.Len() * b.Len())
}

func main() {
    v1 := Vec3{1, 0, 0}  // X 轴正方向
    v2 := Vec3{0, 1, 0}  // Y 轴正方向

    fmt.Println(v1.Dot(v2))   // 0(垂直)
    fmt.Println(v1.Cross(v2)) // {0 0 1}(Z 轴,右手定则)

    // embedding 相似度
    word1 := Vec3{0.9, 0.1, 0.2}  // "king" 的 embedding
    word2 := Vec3{0.8, 0.2, 0.3}  // "queen" 的 embedding
    fmt.Printf("相似度: %.4f\n", CosineSim(word1, word2))
}

2D 旋转矩阵

绕原点旋转角度 θ 的变换:x' = x cosθ - y sinθy' = x sinθ + y cosθ。写成矩阵形式就是旋转矩阵 R(θ)。组合变换 = 矩阵连乘,先旋转后平移 ≠ 先平移后旋转。

package main

import (
    "fmt"
    "math"
)

type Vec2 struct{ X, Y float64 }

// 2D 旋转:逆时针 angle 弧度
func Rotate2D(v Vec2, angle float64) Vec2 {
    cos, sin := math.Cos(angle), math.Sin(angle)
    return Vec2{
        X: v.X*cos - v.Y*sin,
        Y: v.X*sin + v.Y*cos,
    }
}

// 2D 缩放
func Scale2D(v Vec2, sx, sy float64) Vec2 {
    return Vec2{v.X * sx, v.Y * sy}
}

func main() {
    p := Vec2{1, 0}  // X 轴上的点

    // 旋转 90 度
    rotated := Rotate2D(p, math.Pi/2)
    fmt.Printf("旋转 90°: (%.4f, %.4f)\n", rotated.X, rotated.Y)  // (0, 1)

    // 旋转 45 度
    r45 := Rotate2D(p, math.Pi/4)
    fmt.Printf("旋转 45°: (%.4f, %.4f)\n", r45.X, r45.Y)  // (0.7071, 0.7071)

    // 组合:先缩放 2 倍,再旋转 30 度
    scaled := Scale2D(p, 2, 2)
    combined := Rotate2D(scaled, math.Pi/6)
    fmt.Printf("缩放+旋转: (%.4f, %.4f)\n", combined.X, combined.Y)
}

齐次坐标:统一平移

平移不是线性变换(不保持原点不动),所以不能用 2×2 矩阵表示。齐次坐标给每个 2D 点加第三维 w=1,升维到 3×3 矩阵,平移就变成了线性变换。这是图形渲染管线(OpenGL/Vulkan)里所有变换用 4×4 矩阵的原因。

package main

import "fmt"

// 3x3 齐次变换矩阵(2D 用)
type Mat3 [3][3]float64

func (m Mat3) Apply(x, y float64) (float64, float64) {
    // [a b c] [x]   [ax+by+c]
    // [d e f] [y] = [dx+ey+f]
    // [0 0 1] [1]   [1      ]
    xOut := m[0][0]*x + m[0][1]*y + m[0][2]
    yOut := m[1][0]*x + m[1][1]*y + m[1][2]
    return xOut, yOut
}

// 平移矩阵
func Translation(tx, ty float64) Mat3 {
    return Mat3{{1, 0, tx}, {0, 1, ty}, {0, 0, 1}}
}

func main() {
    t := Translation(3, 4)
    x, y := t.Apply(1, 1)
    fmt.Printf("(1,1) 平移 (3,4) 后: (%.0f, %.0f)\n", x, y)  // (4, 5)
}
推荐做法
  • 点积检查向量方向关系:>0 同向,=0 垂直,<0 反向
  • 余弦相似度用于 embedding 比较时先归一化
  • 复杂变换用矩阵连乘,顺序从右到左
不推荐
  • 叉积只在 3D 中有意义(2D 的「叉积」是标量)
  • 混淆行向量和列向量——明确约定统一用列向量
常见误区
  • 旋转矩阵组合时,矩阵乘法不可交换——先旋转再平移 ≠ 先平移再旋转

判断标准:能解释为什么 cosine similarity 衡量方向相似而非距离 → 掌握本章。

向量是数学对方向的尊重——不只有大小,还有去向。

— 3Blue1Brown《线性代数的本质》