Spotify Discover Weekly:个性化持续内容生成的巅峰
一、概述
Spotify的"每周新发现"(Discover Weekly)是现代推荐系统的标杆产品,每周一为4亿用户中的每个人生成一个独特的30首歌单。这是一个大规模个性化内容生成系统的完美案例。
核心数据:
- 活跃用户:6亿+
- 曲库规模:1亿+首歌
- 每周生成:数亿个个性化歌单
- 播放时长占比:15%+(用户总听歌时长)
- 成功率:用户平均保存3-5首推荐歌曲
影响:
- 帮助独立音乐人获得曝光(每周数十亿次播放)
- 改变音乐发现方式(从主动搜索到被动推荐)
- 成为Spotify的杀手级功能
二、内容生成哲学
2.1 问题定义
传统困境:
- 曲库太大:1亿首歌,用户无从选择
- 冷启动:新用户没有历史,如何推荐?
- 新鲜感vs熟悉感:如何平衡?
- 长尾发现:如何让小众音乐被发现?
Discover Weekly的目标:
"感觉像是一个懂你的朋友给你制作的歌单"
关键词:
- 个性化:不同用户完全不同
- 新鲜:你没听过但会喜欢的歌
- 惊喜:意外发现
- 周期性:每周一准时更新(仪式感)
2.2 设计原则
1. 持续性机制:
- 用户每周听歌 → 数据更新 → 下周推荐更准
- 正反馈循环:听得越多,推荐越好
2. 探索vs利用(Exploration vs Exploitation):
- 70%安全推荐(高置信度喜欢)
- 30%探索新风格(可能喜欢但不确定)
3. 多样性约束:
- 不全是同一艺术家/专辑
- 风格有变化(不是30首相似歌)
- 新老音乐混合
4. 时效性:
- 包含近期热门(趋势)
- 但不是千篇一律的热歌(个性化)
三、技术架构
3.1 数据收集
用户行为数据
显性反馈:
- 播放/跳过
- 点赞/收藏
- 添加到歌单
- 分享
隐性反馈:
- 播放时长(听完vs听一半)
- 重复播放次数
- 播放时段(早晨vs深夜)
- 设备类型(手机vs桌面vs车载)
- 播放上下文(跑步歌单vs工作歌单)
社交数据:
- 关注的艺术家
- 朋友的歌单
- 协作歌单
音频特征
Spotify音频分析: 每首歌都被分析提取特征
- 可舞性(Danceability):0-1,节奏适合跳舞程度
- 能量(Energy):0-1,强度感知
- 调性(Key):C, C#, D...
- 响度(Loudness):dB
- 语音度(Speechiness):人声vs乐器
- 原声度(Acousticness):电子vs原声
- 器乐度(Instrumentalness):有无人声
- 现场感(Liveness):是否现场录音
- 情绪(Valence):0-1,悲伤vs快乐
- 速度(Tempo):BPM
示例:
{
"track_id": "6y0igZArWVi6Iz0rj35c1Y",
"danceability": 0.735,
"energy": 0.578,
"key": 5,
"loudness": -5.594,
"speechiness": 0.0461,
"acousticness": 0.514,
"instrumentalness": 0.0902,
"liveness": 0.159,
"valence": 0.636,
"tempo": 121.274
}
深度学习特征:
- 卷积神经网络分析音频波形
- 提取数百维隐式特征
- 捕捉人类难以描述的"音乐风格"
元数据
- 艺术家、专辑、发行年份
- 流派标签(多标签)
- 歌词(情感分析)
- 封面图像(视觉风格)
外部数据
- 音乐博客提及
- 社交媒体讨论
- 音乐节阵容
- 电台播放列表
3.2 推荐算法
Spotify使用混合推荐系统(Hybrid Recommender System),结合多种算法:
算法1:协同过滤(Collaborative Filtering)
原理:"喜欢相似歌曲的人,可能喜欢相同的新歌"
矩阵分解:
# 用户-歌曲交互矩阵
# 行=用户,列=歌曲,值=播放次数/评分
import numpy as np
from scipy.sparse.linalg import svds
# 假设矩阵 R (users x songs)
R = load_user_song_matrix() # 6亿 x 1亿,极稀疏
# 分解为 U (users x factors) 和 V (songs x factors)
U, sigma, Vt = svds(R, k=300) # 300潜在因子
# 预测用户u对歌曲s的喜好
def predict(user_id, song_id):
return np.dot(U[user_id], Vt[:, song_id])
# 为用户生成推荐
def recommend_for_user(user_id, n=30):
scores = U[user_id] @ Vt # 对所有歌曲打分
# 排除已听过的
scores[user_listened[user_id]] = -np.inf
return np.argsort(scores)[-n:][::-1]
优化:
- 使用ALS(交替最小二乘)处理大规模稀疏矩阵
- 隐式反馈建模(播放=正例,未播放≠负例)
- 负采样(随机歌曲作为负例)
挑战:
- 冷启动:新用户/新歌曲无交互数据
- 长尾问题:热门歌过度推荐
- 计算量:6亿×1亿的矩阵
算法2:基于内容的过滤(Content-Based Filtering)
原理:"你喜欢的歌曲的特征相似的歌,你可能也喜欢"
音频相似度:
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
# 每首歌的特征向量(音频特征+深度学习embedding)
song_features = load_song_features() # shape: (100M songs, 512 dims)
# 找到与目标歌曲相似的歌
def find_similar_songs(song_id, n=50):
target = song_features[song_id].reshape(1, -1)
similarities = cosine_similarity(target, song_features)[0]
return np.argsort(similarities)[-n-1:-1][::-1] # 排除自己
# 为用户推荐
def recommend_content_based(user_id, n=30):
# 用户最近喜欢的20首歌
liked_songs = get_recent_liked(user_id, 20)
# 对每首找相似歌
candidates = []
for song in liked_songs:
candidates.extend(find_similar_songs(song, 10))
# 聚合并排序
candidate_counts = Counter(candidates)
return [song for song, _ in candidate_counts.most_common(n)]
优点:
- 不依赖其他用户数据
- 能推荐冷门歌(只要音频相似)
- 可解释性强
缺点:
- 容易陷入"回音室"(只推荐相似风格)
- 难以捕捉复杂品味(如"跑步时喜欢电子,睡前喜欢古典")
算法3:自然语言处理(NLP on Text)
数据源:
- 音乐博客文章
- 新闻报道
- 社交媒体讨论
- 歌词
技术:
from transformers import BertModel, BertTokenizer
# 加载预训练BERT模型
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
# 提取艺术家描述的语义向量
def get_artist_embedding(artist_description):
inputs = tokenizer(artist_description, return_tensors='pt', truncation=True, max_length=512)
outputs = model(**inputs)
# 使用[CLS] token的embedding
return outputs.last_hidden_state[0, 0, :].detach().numpy()
# 例子:Taylor Swift的描述
desc = "Taylor Swift is an American singer-songwriter known for narrative songs about her personal life, country and pop music..."
embedding = get_artist_embedding(desc)
# 找到描述相似的艺术家
similarities = cosine_similarity([embedding], all_artist_embeddings)
应用:
- "被描述为类似"的艺术家关联
- 歌词情感 → 推荐氛围相符的歌
- 新兴流派识别(博客高频讨论)
算法4:深度学习(RNN/Transformer for Sequences)
问题:用户听歌是有顺序的,不是孤立的
序列建模:
import torch
import torch.nn as nn
class MusicSequenceModel(nn.Module):
def __init__(self, n_songs, embedding_dim=128, hidden_dim=256):
super().__init__()
self.embedding = nn.Embedding(n_songs, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_dim, batch_first=True)
self.fc = nn.Linear(hidden_dim, n_songs)
def forward(self, song_sequence):
# song_sequence: (batch, seq_len)
embeds = self.embedding(song_sequence) # (batch, seq_len, emb_dim)
lstm_out, _ = self.lstm(embeds)
# 预测下一首歌
logits = self.fc(lstm_out[:, -1, :]) # 使用最后时刻状态
return logits
# 训练数据:用户的播放序列
# 输入:过去20首歌,输出:第21首(实际听的)
# 损失:交叉熵
# 推理:给定用户最近听歌,预测最可能听的下一首
Transformer替代:
- 注意力机制捕捉长程依赖
- "3个月前听的歌"影响"今天的推荐"
实际应用(Spotify 2023年论文):
- 基于BERT的音乐序列模型
- 输入:用户过去3个月播放序列
- 输出:候选歌曲的概率分布
3.3 混合策略
最终推荐流程:
def generate_discover_weekly(user_id):
# 1. 各算法独立生成候选(各100首)
cf_candidates = collaborative_filtering(user_id, 100)
cb_candidates = content_based(user_id, 100)
nlp_candidates = nlp_based(user_id, 100)
seq_candidates = sequence_model(user_id, 100)
# 2. 合并并去重
all_candidates = set(cf_candidates + cb_candidates + nlp_candidates + seq_candidates)
# 3. 重排序(Re-ranking)
scored_candidates = []
for song in all_candidates:
score = weighted_score(
user_id, song,
cf_weight=0.4,
cb_weight=0.3,
nlp_weight=0.1,
seq_weight=0.2
)
scored_candidates.append((song, score))
scored_candidates.sort(key=lambda x: x[1], reverse=True)
# 4. 多样性后处理
final_playlist = []
used_artists = set()
for song, score in scored_candidates:
artist = get_artist(song)
# 同一艺术家最多2首
if used_artists.count(artist) < 2:
final_playlist.append(song)
used_artists.add(artist)
if len(final_playlist) == 30:
break
# 5. 优化排序(歌单内顺序)
final_playlist = optimize_playlist_flow(final_playlist)
return final_playlist
优化歌单流畅度:
def optimize_playlist_flow(songs):
"""
目标:相邻歌曲过渡平滑(能量、速度、调性)
算法:旅行商问题的近似解(贪心或模拟退火)
"""
ordered = [songs[0]] # 从第一首开始
remaining = set(songs[1:])
while remaining:
last_song = ordered[-1]
# 找到与last_song最"兼容"的下一首
next_song = min(remaining, key=lambda s: transition_cost(last_song, s))
ordered.append(next_song)
remaining.remove(next_song)
return ordered
def transition_cost(song1, song2):
"""歌曲过渡的"不适感":数值越小越平滑"""
f1 = get_features(song1)
f2 = get_features(song2)
# 速度差异
tempo_diff = abs(f1['tempo'] - f2['tempo']) / 200.0
# 能量差异
energy_diff = abs(f1['energy'] - f2['energy'])
# 调性兼容性(音乐理论)
key_cost = key_compatibility(f1['key'], f2['key'])
return tempo_diff + energy_diff + key_cost
四、大规模计算挑战
4.1 计算量
每周任务:
- 为2亿活跃用户生成歌单
- 每个用户从1亿首歌中选30首
- 如果暴力计算:2亿 × 1亿 = 2×10^16 次打分
不可行!
4.2 两阶段检索
Stage 1: 候选生成(Candidate Generation)
- 目标:从1亿缩小到几千首
- 方法:快速但粗糙的算法
- ANN(近似最近邻):FAISS库
- 基于规则的过滤(流派、年代)
- 时间:毫秒级
import faiss
# 使用FAISS加速向量检索
index = faiss.IndexFlatIP(512) # 内积索引,512维
index.add(song_embeddings) # 添加1亿首歌的向量
# 为用户生成候选
user_vector = get_user_embedding(user_id)
distances, indices = index.search(user_vector.reshape(1, -1), k=1000)
candidates = indices[0] # 1000首候选歌
Stage 2: 精排(Ranking)
- 目标:从几千缩小到30首
- 方法:复杂的机器学习模型
- 深度神经网络
- GBDT(梯度提升决策树)
- 多目标优化(点击率、播放时长、收藏率)
- 时间:秒级
离线预计算:
- 周日晚批量计算(Spark集群)
- 周一凌晨推送给用户
- 用户打开app时直接加载(无需实时计算)
4.3 分布式架构
数据流:
用户行为日志 → Kafka → Spark Streaming → 特征工程 → Feature Store
↓
音频文件 → 音频分析服务 → 特征向量 → 特征库(Cassandra)
↓
周末批处理(Spark)
↓
推荐生成(TensorFlow on GPU集群)
↓
歌单存储(PostgreSQL)
↓
CDN分发 → 用户客户端
技术栈(公开信息+推测):
- 数据摄入:Kafka
- 流处理:Apache Flink
- 批处理:Apache Spark
- 特征存储:Cassandra + Redis
- 机器学习训练:TensorFlow/PyTorch on TPU/GPU
- 模型服务:TensorFlow Serving
- 数据仓库:Google BigQuery
- 编排:Apache Airflow
- 监控:Prometheus + Grafana
五、产品设计的巧思
5.1 每周一的仪式感
为什么不是每天?
- 用户需要时间听完30首(约2小时)
- 太频繁会降低期待感
- 周一:一周新开始的积极信号
心理学:
- 固定时间 → 习惯养成
- 稀缺性 → 珍惜感
- 新鲜度 → 持续期待
5.2 30首的魔法数字
- 太少(10首):不够探索
- 太多(50首):选择疲劳
- 30首 ≈ 2小时 = 通勤/健身/工作时长
5.3 无法手动刷新
设计决策:用户不能点击"重新生成"
原因:
- 强迫用户体验推荐(而非挑剔)
- 减少服务器负载
- 维护"每周一次"的特殊性
5.4 可见的算法"思考"
界面文案:
- "基于你最近喜欢的 [艺术家名]"
- "因为你播放了 [歌单名]"
价值:
- 可解释性 → 信任
- 教育用户 → 更好的反馈数据
- 人性化 → 情感连接
六、成功指标
6.1 关键指标
参与度(Engagement):
- 打开率:有多少用户点开了歌单
- 播放率:有多少用户至少播放了1首
- 完成率:播放了多少首(平均)
满意度(Satisfaction):
- 收藏率:保存到"我的音乐"的歌曲数
- 重复播放:再次播放推荐的歌
- NPS(净推荐值):用户推荐Spotify的意愿
发现效果(Discovery):
- 新艺术家比例:用户之前没听过的
- 长尾提升:小众歌曲的播放增长
- 风格扩展:用户音乐品味的多样化
商业价值:
- 留存率:Discover Weekly用户的续订率
- 使用时长:占总听歌时长的百分比
- 转化率:免费用户升级付费
6.2 A/B测试实例
测试场景:改变推荐多样性
对照组(Control):
- 70%安全推荐 + 30%探索
实验组(Treatment):
- 60%安全推荐 + 40%探索
结果:
- 短期:实验组跳过率增加10%(用户不适应)
- 长期(4周后):实验组满意度提高5%(发现更多喜欢的歌)
决策:采用实验组,但逐步过渡(避免突然变化)
七、对独立音乐人的影响
7.1 曝光机会
传统问题:
- 唱片公司推广垄断
- 主流电台资源有限
- 社交媒体噪音大
Discover Weekly的民主化:
- 算法不关心名气,只关心相似度
- 长尾歌曲有机会被推荐
- 滚雪球效应:被推荐 → 播放 → 更多推荐
真实案例:
- Børns(独立艺术家):被Discover Weekly推荐后,月播放量从10万暴增到200万
- 许多独立音乐人称Discover Weekly是其主要流量来源
7.2 数据反馈
Spotify for Artists仪表盘:
- 有多少用户通过Discover Weekly发现你
- 哪些歌曲被推荐最多
- 听众地理分布
价值:音乐人可调整创作策略
八、伦理与争议
8.1 过滤泡泡(Filter Bubble)
批评:算法推荐导致用户只接触相似内容
Spotify的平衡:
- 30%探索新风格
- "Blend"功能(混合两个用户的口味)
- "Only You"(展示听歌多样性)
8.2 艺术家收入
问题:流媒体分成低($0.003-0.005/播放)
Discover Weekly的双刃剑:
- ✅ 增加曝光 → 更多播放 → 更多收入
- ❌ 用户期待"免费探索" → 不愿购买专辑
8.3 音乐同质化
担忧:算法导向可能让音乐创作趋同
反驳:
- 数据显示风格多样性在增加
- 算法奖励"独特但相关"的音乐
- 人类策划歌单仍占重要比例
九、技术代码示例
示例1:简化的推荐系统
import numpy as np
from scipy.sparse import csr_matrix
from sklearn.metrics.pairwise import cosine_similarity
class SimpleSpotifyRecommender:
def __init__(self, n_users, n_songs):
self.n_users = n_users
self.n_songs = n_songs
# 用户-歌曲交互矩阵(稀疏)
self.interactions = csr_matrix((n_users, n_songs))
# 歌曲特征矩阵(音频特征)
self.song_features = np.random.randn(n_songs, 50) # 50维特征
def add_interaction(self, user_id, song_id, weight=1.0):
"""记录用户播放行为"""
self.interactions[user_id, song_id] = weight
def collaborative_filtering(self, user_id, n_recommendations=30):
"""协同过滤:找相似用户喜欢的歌"""
# 计算用户相似度
user_similarities = cosine_similarity(
self.interactions[user_id],
self.interactions
)[0]
# 找最相似的50个用户
similar_users = np.argsort(user_similarities)[-51:-1][::-1]
# 聚合他们喜欢的歌
recommendations = {}
for similar_user in similar_users:
similarity = user_similarities[similar_user]
liked_songs = self.interactions[similar_user].nonzero()[1]
for song in liked_songs:
if self.interactions[user_id, song] == 0: # 用户没听过
recommendations[song] = recommendations.get(song, 0) + similarity
# 排序返回
sorted_recs = sorted(recommendations.items(), key=lambda x: x[1], reverse=True)
return [song_id for song_id, _ in sorted_recs[:n_recommendations]]
def content_based(self, user_id, n_recommendations=30):
"""基于内容:找用户喜欢歌曲的相似歌"""
# 用户听过的歌
listened_songs = self.interactions[user_id].nonzero()[1]
if len(listened_songs) == 0:
return []
# 计算所有歌曲与用户听过歌曲的平均相似度
user_profile = self.song_features[listened_songs].mean(axis=0)
similarities = cosine_similarity(
[user_profile],
self.song_features
)[0]
# 排除已听过的
similarities[listened_songs] = -1
# 返回最相似的
return np.argsort(similarities)[-n_recommendations:][::-1].tolist()
def hybrid_recommend(self, user_id, n_recommendations=30):
"""混合推荐"""
cf_recs = self.collaborative_filtering(user_id, n_recommendations * 2)
cb_recs = self.content_based(user_id, n_recommendations * 2)
# 简单合并:CF占60%,CB占40%
combined = []
cf_count = int(n_recommendations * 0.6)
cb_count = n_recommendations - cf_count
combined.extend(cf_recs[:cf_count])
for song in cb_recs:
if song not in combined:
combined.append(song)
if len(combined) >= n_recommendations:
break
return combined[:n_recommendations]
# 使用示例
recommender = SimpleSpotifyRecommender(n_users=1000, n_songs=10000)
# 模拟用户行为
recommender.add_interaction(user_id=42, song_id=100, weight=5.0) # 重复播放
recommender.add_interaction(user_id=42, song_id=205, weight=1.0) # 播放一次
recommender.add_interaction(user_id=42, song_id=1337, weight=3.0) # 收藏
# 生成推荐
discover_weekly = recommender.hybrid_recommend(user_id=42, n_recommendations=30)
print(f"Discover Weekly for User 42: {discover_weekly}")
示例2:音频特征提取(使用Librosa)
import librosa
import numpy as np
def extract_audio_features(audio_file):
"""提取歌曲的音频特征"""
# 加载音频
y, sr = librosa.load(audio_file, duration=30) # 只分析前30秒
# 1. 速度(Tempo)
tempo, _ = librosa.beat.beat_track(y=y, sr=sr)
# 2. 节奏特征
onset_env = librosa.onset.onset_strength(y=y, sr=sr)
pulse = librosa.beat.plp(onset_envelope=onset_env, sr=sr)
# 3. 光谱特征(MFCC)
mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
mfccs_mean = np.mean(mfccs, axis=1)
mfccs_std = np.std(mfccs, axis=1)
# 4. 色度特征(Chroma)
chroma = librosa.feature.chroma_stft(y=y, sr=sr)
chroma_mean = np.mean(chroma, axis=1)
# 5. 光谱对比度
contrast = librosa.feature.spectral_contrast(y=y, sr=sr)
contrast_mean = np.mean(contrast, axis=1)
# 6. 零交叉率(衡量噪声感)
zcr = librosa.feature.zero_crossing_rate(y)
zcr_mean = np.mean(zcr)
# 组合所有特征
features = np.concatenate([
[tempo],
mfccs_mean,
mfccs_std,
chroma_mean,
contrast_mean,
[zcr_mean]
])
return features
# 使用示例
features = extract_audio_features('path/to/song.mp3')
print(f"Feature vector shape: {features.shape}")
# 可以用于计算歌曲相似度
十、关键启示
对持续内容生成系统的启示
-
个性化是规模化的关键:
- 不是生成一个内容给所有人
- 而是为每个人生成独特内容
- 规模:亿级用户 × 周期性更新
-
多算法融合优于单一算法:
- 协同过滤、内容过滤、序列模型、NLP...
- 互补长短,提高鲁棒性
-
数据飞轮效应:
- 用户使用 → 数据积累 → 推荐改进 → 用户更多使用
- 形成护城河(后来者难以追赶)
-
产品设计与算法同等重要:
- 每周一的时机选择
- 30首的数量平衡
- 不可刷新的克制
- 细节决定体验
-
可解释性建立信任:
- "因为你听了XX" 让用户理解推荐
- 透明度增加接受度
-
平衡探索与利用:
- 70%安全推荐(满足期待)
- 30%探索新领域(惊喜发现)
- 这个比例是A/B测试优化的结果
-
两阶段检索是必需的:
- 候选生成(快速筛选)+ 精排(复杂模型)
- 无法在1亿物品上直接应用深度模型
-
周期性创造仪式感:
- 不是"随时可得",而是"每周一"
- 稀缺性 → 价值感
- 固定节奏 → 习惯养成
十一、竞品对比
| 特性 | Spotify Discover Weekly | Apple Music | YouTube Music | Pandora |
|---|---|---|---|---|
| 个性化程度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 更新频率 | 每周 | 每周 | 每天 | 持续 |
| 曲库规模 | 1亿+ | 1亿+ | 1亿+ | 4000万 |
| 算法透明度 | 中 | 低 | 中 | 高 |
| 发现新艺术家能力 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 用户控制度 | 低(不可刷新) | 中 | 高 | 高(点赞/禁止) |
Spotify的优势:
- 最大的协同过滤数据(用户行为)
- 先发优势(2015年推出)
- 品牌效应(用户期待每周一)
十二、未来演进方向
12.1 多模态推荐
- 视觉:封面风格、音乐视频
- 歌词:情感、叙事主题
- 社交:朋友正在听、地理位置热门
12.2 情境感知
- 时间:早晨推荐活力歌曲,晚上推荐舒缓
- 地点:健身房 vs 办公室 vs 家中
- 活动:跑步 vs 学习 vs 开车
- 天气:雨天 vs 晴天
- 情绪:通过设备传感器或用户输入
12.3 对话式推荐
用户:"给我推荐适合周日早晨喝咖啡听的爵士乐"
系统:生成定制歌单
用户:"节奏再慢一点,更放松"
系统:调整推荐
12.4 生成式AI
- AI作曲:根据用户口味生成原创音乐
- AI混音:个性化调整歌曲(EQ、速度)
- 虚拟艺术家:AI创建的音乐人
总结
Spotify Discover Weekly 是大规模个性化持续内容生成的典范:
✅ 真正的个性化:每个用户独特的歌单,不是模板 ✅ 持续性:每周自动更新,10年如一日 ✅ 规模:数亿用户 × 1亿首歌 = 处理极限挑战 ✅ 多算法融合:CF + CB + NLP + DL + 规则 ✅ 产品与技术结合:算法准确 + 体验优秀 ✅ 双赢生态:用户发现新音乐 + 音乐人获得曝光 ✅ 数据飞轮:使用越多,推荐越好
核心洞察: 最好的内容生成系统不仅仅是算法,而是算法、数据、产品、心理学的完美融合。Discover Weekly的成功在于它理解了音乐发现的本质——不是找到"最好"的歌,而是在"熟悉的舒适"和"新鲜的惊喜"之间找到平衡。