向量基本运算
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《线性代数的本质》