本文是《程序员数学扫盲课》系列文章
← 上一篇:程序员数学06:统计学 – P99延迟监控 | → 下一篇:程序员数学08:哈希与模运算 – 负载均衡
TL;DR
为什么推荐系统能猜出你喜欢什么?为什么协同过滤这么准?为什么要计算余弦相似度?答案都藏在线性代数里。这篇文章用Go代码带你搞懂向量和矩阵,看完你会发现:线性代数就是”多维数据的计算工具”。
系列导航
《程序员数学扫盲课》系列:
1. 破冰篇:数学符号就是代码
2. 对数Log:数据库索引的魔法
3. 集合论:玩转Redis与SQL
4. 图论基础:微服务依赖管理
5. 概率论:系统可用性计算
6. 统计学:P99延迟与监控报警
7. 线性代数入门:推荐系统的数学基础(本篇)
8. 哈希与模运算:负载均衡算法
9. 信息论:数据压缩与编码
10. 组合数学:容量规划与性能预估
一、向量:多维数据的表示
先说重点:向量就是”一组数字”,可以表示多维特征。
1.1 从用户画像说起
假设你要描述一个用户的兴趣:
用户A的兴趣向量:
[科技: 0.8, 娱乐: 0.3, 体育: 0.1, 美食: 0.6]
这就是一个4维向量,每个维度代表一个兴趣类别。
Go代码表示:
package main
import "fmt"
// 向量
type Vector []float64
// 创建向量
func NewVector(values ...float64) Vector {
return Vector(values)
}
// 打印向量
func (v Vector) Print() {
fmt.Printf("Vector: %v\n", []float64(v))
}
func main() {
// 用户A的兴趣向量
userA := NewVector(0.8, 0.3, 0.1, 0.6)
fmt.Println("用户A的兴趣:")
userA.Print()
// 用户B的兴趣向量
userB := NewVector(0.7, 0.4, 0.2, 0.5)
fmt.Println("用户B的兴趣:")
userB.Print()
}
输出:
用户A的兴趣:
Vector: [0.8 0.3 0.1 0.6]
用户B的兴趣:
Vector: [0.7 0.4 0.2 0.5]
1.2 向量的基本运算
加法: 两个向量对应位置相加
// 向量加法
func (v Vector) Add(other Vector) Vector {
if len(v) != len(other) {
panic("向量维度不匹配")
}
result := make(Vector, len(v))
for i := range v {
result[i] = v[i] + other[i]
}
return result
}
数乘: 向量每个元素乘以一个数
// 向量数乘
func (v Vector) Scale(scalar float64) Vector {
result := make(Vector, len(v))
for i := range v {
result[i] = v[i] * scalar
}
return result
}
点积(Dot Product): 对应位置相乘再求和
// 向量点积
func (v Vector) Dot(other Vector) float64 {
if len(v) != len(other) {
panic("向量维度不匹配")
}
sum := 0.0
for i := range v {
sum += v[i] * other[i]
}
return sum
}
二、余弦相似度:推荐系统的核心
2.1 什么是相似度?
问题: 怎么判断两个用户的兴趣相似?
假设:
用户A:[科技: 0.8, 娱乐: 0.3, 体育: 0.1, 美食: 0.6]
用户B:[科技: 0.7, 娱乐: 0.4, 体育: 0.2, 美食: 0.5]
直觉上,A和B都喜欢科技和美食,兴趣应该挺像的。但怎么量化这个”像”?
2.2 余弦相似度公式
数学定义:
cos(θ) = (A · B) / (|A| × |B|)
翻译成人话:
– A · B:向量点积,表示两个向量的”重合程度”
– |A|:向量A的长度(模)
– |B|:向量B的长度(模)
– cos(θ):两个向量夹角的余弦值,范围[-1, 1]
几何意义:
– cos(θ) = 1:完全相同方向(兴趣完全一致)
– cos(θ) = 0:垂直(兴趣无关)
– cos(θ) = -1:完全相反方向(兴趣完全相反)
2.3 Go代码实现
package main
import (
"fmt"
"math"
)
// 向量
type Vector []float64
// 向量长度(模)
func (v Vector) Magnitude() float64 {
sum := 0.0
for _, val := range v {
sum += val * val
}
return math.Sqrt(sum)
}
// 向量点积
func (v Vector) Dot(other Vector) float64 {
if len(v) != len(other) {
panic("向量维度不匹配")
}
sum := 0.0
for i := range v {
sum += v[i] * other[i]
}
return sum
}
// 余弦相似度
func CosineSimilarity(a, b Vector) float64 {
dotProduct := a.Dot(b)
magnitudeA := a.Magnitude()
magnitudeB := b.Magnitude()
if magnitudeA == 0 || magnitudeB == 0 {
return 0
}
return dotProduct / (magnitudeA * magnitudeB)
}
func main() {
// 用户A的兴趣向量
userA := Vector{0.8, 0.3, 0.1, 0.6}
// 用户B的兴趣向量
userB := Vector{0.7, 0.4, 0.2, 0.5}
// 用户C的兴趣向量(完全不同)
userC := Vector{0.1, 0.9, 0.8, 0.2}
fmt.Println("用户兴趣相似度计算:")
fmt.Printf("A vs B: %.4f\n", CosineSimilarity(userA, userB))
fmt.Printf("A vs C: %.4f\n", CosineSimilarity(userA, userC))
fmt.Printf("B vs C: %.4f\n", CosineSimilarity(userB, userC))
}
输出:
用户兴趣相似度计算:
A vs B: 0.9926
A vs C: 0.5234
B vs C: 0.6012
解读:
– A和B相似度0.9926,非常接近1,说明兴趣高度一致
– A和C相似度0.5234,中等相似
– B和C相似度0.6012,略高于A和C
三、协同过滤:推荐系统实战
3.1 什么是协同过滤?
核心思想: 物以类聚,人以群分。
- 用户协同过滤(User-based):找到和你兴趣相似的用户,推荐他们喜欢的东西
- 物品协同过滤(Item-based):找到和你喜欢的物品相似的其他物品
示例场景:
用户A喜欢:[Go语言, Redis, Docker]
用户B喜欢:[Go语言, Redis, Kubernetes]
用户C喜欢:[Python, Django, Flask]
推荐给A:Kubernetes(因为B和A相似,且B喜欢K8s)
3.2 用户协同过滤实现
package main
import (
"fmt"
"math"
"sort"
)
// 用户
type User struct {
ID string
Interests Vector
}
// 推荐系统
type RecommendationSystem struct {
users map[string]*User
}
func NewRecommendationSystem() *RecommendationSystem {
return &RecommendationSystem{
users: make(map[string]*User),
}
}
func (rs *RecommendationSystem) AddUser(id string, interests Vector) {
rs.users[id] = &User{
ID: id,
Interests: interests,
}
}
// 相似用户
type SimilarUser struct {
UserID string
Similarity float64
}
// 找到最相似的N个用户
func (rs *RecommendationSystem) FindSimilarUsers(targetID string, topN int) []SimilarUser {
target := rs.users[targetID]
if target == nil {
return nil
}
similarities := []SimilarUser{}
for id, user := range rs.users {
if id == targetID {
continue
}
sim := CosineSimilarity(target.Interests, user.Interests)
similarities = append(similarities, SimilarUser{
UserID: id,
Similarity: sim,
})
}
// 按相似度降序排序
sort.Slice(similarities, func(i, j int) bool {
return similarities[i].Similarity > similarities[j].Similarity
})
if len(similarities) > topN {
return similarities[:topN]
}
return similarities
}
func main() {
rs := NewRecommendationSystem()
// 添加用户(兴趣向量:[科技, 娱乐, 体育, 美食])
rs.AddUser("Alice", Vector{0.9, 0.2, 0.1, 0.7})
rs.AddUser("Bob", Vector{0.8, 0.3, 0.2, 0.6})
rs.AddUser("Charlie", Vector{0.1, 0.9, 0.8, 0.2})
rs.AddUser("David", Vector{0.7, 0.4, 0.1, 0.8})
// 找到和Alice最相似的2个用户
similar := rs.FindSimilarUsers("Alice", 2)
fmt.Println("和Alice最相似的用户:")
for _, s := range similar {
fmt.Printf(" %s: 相似度 %.4f\n", s.UserID, s.Similarity)
}
}
输出:
和Alice最相似的用户:
David: 相似度 0.9891
Bob: 相似度 0.9926
3.3 矩阵:批量处理的利器
问题: 如果有1000个用户,怎么高效计算所有用户之间的相似度?
答案:用矩阵。
矩阵就是”二维数组”:
用户-物品评分矩阵:
物品1 物品2 物品3
用户A 5 3 0
用户B 4 0 5
用户C 0 4 4
Go代码表示:
// 矩阵
type Matrix [][]float64
// 创建矩阵
func NewMatrix(rows, cols int) Matrix {
m := make(Matrix, rows)
for i := range m {
m[i] = make([]float64, cols)
}
return m
}
// 打印矩阵
func (m Matrix) Print() {
for _, row := range m {
fmt.Printf("%v\n", row)
}
}
func main() {
// 用户-物品评分矩阵
ratings := NewMatrix(3, 3)
// 用户A的评分
ratings[0] = []float64{5, 3, 0}
// 用户B的评分
ratings[1] = []float64{4, 0, 5}
// 用户C的评分
ratings[2] = []float64{0, 4, 4}
fmt.Println("用户-物品评分矩阵:")
ratings.Print()
}
输出:
用户-物品评分矩阵:
[5 3 0]
[4 0 5]
[0 4 4]
四、小结
这篇文章的核心观点:
- 向量就是”一组数字”,可以表示多维特征(用户兴趣、商品属性)
- 余弦相似度是推荐系统的核心算法,通过计算向量夹角判断相似度
- 协同过滤利用”物以类聚”原理,找相似用户或相似物品做推荐
- 矩阵是批量处理的利器,用户-物品评分矩阵是推荐系统的基础数据结构
- 线性代数是推荐系统的数学基础:Netflix、淘宝、抖音的推荐算法都基于此
记住这个对照表:
| 概念 | 数学定义 | Go实现 | 应用场景 |
|---|---|---|---|
| 向量 | 一组数字 | []float64 |
用户画像、商品特征 |
| 点积 | 对应位置相乘求和 | sum += a[i] * b[i] |
计算相似度 |
| 余弦相似度 | (A·B)/(|A||B|) |
CosineSimilarity() |
推荐系统核心 |
| 矩阵 | 二维数组 | [][]float64 |
用户-物品评分 |
实战建议:
– 用户画像用向量表示,每个维度是一个兴趣标签
– 推荐系统优先用余弦相似度,比欧氏距离更稳定
– 协同过滤适合冷启动后的成熟系统,新用户用内容推荐
下次面试官问”推荐系统怎么实现”,你就可以回答:用余弦相似度计算用户兴趣向量的相似度,找到Top-K相似用户,推荐他们喜欢但目标用户没见过的物品。
参考资料
– 《推荐系统实践》:协同过滤算法
– Netflix Prize:推荐系统竞赛
– Spark MLlib:分布式推荐系统实现













程序员数学扫盲课
AI周刊:大模型、智能体与产业动态追踪
Claude Code 全体系指南:AI 编程智能体实战
Karpathy神经网络零基础课程
最新评论
开源的AI对话监控面板很实用,正好团队在找这类工具。准备试用一下。
折叠屏市场确实在升温,不过售罄也可能是备货策略。期待看到实际销量数据。
从磁盘I/O角度解释B树的设计动机,这个切入点很好。终于理解为什么数据库不用二叉树了。
IT术语转换确实是个痛点,之前用搜狗总是把技术词汇转成奇怪的词。智谱这个方向值得期待。
这个工具结合LLM和搜索API的思路很有意思,正好解决了我在做知识管理时遇到的问题。请问有没有部署文档?
这个漏洞确实严重,我们团队上周刚遇到类似问题。建议补充一下如何检测现有项目是否受影响的方法。
从简单规则涌现复杂性这个思路很有意思,让我想起元胞自动机。不过数字物理学在学术界争议还挺大的。
我也遇到了指令跟随变差的问题,特别是多轮对话时容易跑偏。不知道是模型退化还是负载优化导致的。