AI编程 · 架构思考 · 技术人生

程序员数学07:线性代数 - 推荐系统

#程序员数学扫盲课
智谱 GLM,支持多语言、多任务推理。从写作到代码生成,从搜索到知识问答,AI 生产力的中国解法。

本文是《程序员数学扫盲课》系列文章

← 上一篇:程序员数学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]

四、小结

这篇文章的核心观点:

  1. 向量就是”一组数字”,可以表示多维特征(用户兴趣、商品属性)
  2. 余弦相似度是推荐系统的核心算法,通过计算向量夹角判断相似度
  3. 协同过滤利用”物以类聚”原理,找相似用户或相似物品做推荐
  4. 矩阵是批量处理的利器,用户-物品评分矩阵是推荐系统的基础数据结构
  5. 线性代数是推荐系统的数学基础:Netflix、淘宝、抖音的推荐算法都基于此

记住这个对照表:

概念 数学定义 Go实现 应用场景
向量 一组数字 []float64 用户画像、商品特征
点积 对应位置相乘求和 sum += a[i] * b[i] 计算相似度
余弦相似度 (A·B)/(|A||B|) CosineSimilarity() 推荐系统核心
矩阵 二维数组 [][]float64 用户-物品评分

实战建议:
– 用户画像用向量表示,每个维度是一个兴趣标签
– 推荐系统优先用余弦相似度,比欧氏距离更稳定
– 协同过滤适合冷启动后的成熟系统,新用户用内容推荐

下次面试官问”推荐系统怎么实现”,你就可以回答:用余弦相似度计算用户兴趣向量的相似度,找到Top-K相似用户,推荐他们喜欢但目标用户没见过的物品


参考资料
– 《推荐系统实践》:协同过滤算法
– Netflix Prize:推荐系统竞赛
– Spark MLlib:分布式推荐系统实现


返回系列总览

👉 程序员数学扫盲课:完整课程大纲

赞(0)
未经允许不得转载:Toy's Tech Notes » 程序员数学07:线性代数 - 推荐系统
免费、开放、可编程的智能路由方案,让你的服务随时随地在线。

评论 抢沙发

十年稳如初 — LocVPS,用时间证明实力

10+ 年老牌云主机服务商,全球机房覆盖,性能稳定、价格厚道。

老品牌,更懂稳定的价值你的第一台云服务器,从 LocVPS 开始