用Python+NetworkX实战:手把手教你用‘传统手艺’搞定社交网络好友推荐
用Python+NetworkX实战:手把手教你用传统图算法实现社交好友推荐
社交网络中的好友推荐一直是互联网产品提升用户粘性的核心功能。与深度学习等"黑箱"方法相比,基于图特征的传统算法具有可解释性强、计算资源消耗低等独特优势。本文将以微信/微博好友关系为场景,使用NetworkX库演示如何通过 共同邻居 、 Adamic-Adar指数 等经典图特征,构建一个可解释的推荐系统。
1. 环境准备与数据建模
在开始之前,我们需要准备Python环境和示例数据集。推荐使用Jupyter Notebook进行交互式开发:
pip install networkx pandas numpy matplotlib scikit-learn
假设我们已经从社交平台API获取了好友关系数据,可以将其建模为无向图。这里用模拟数据演示构建邻接矩阵的过程:
import networkx as nx
import pandas as pd
# 模拟用户好友关系数据
relations = [
("小明", "小红"), ("小明", "小刚"),
("小红", "小美"), ("小刚", "小强"),
("小美", "小丽"), ("小强", "小丽")
]
# 创建无向图
G = nx.Graph()
G.add_edges_from(relations)
使用NetworkX的可视化功能可以直观查看社交网络结构:
import matplotlib.pyplot as plt
plt.figure(figsize=(8,6))
nx.draw(G, with_labels=True, node_color='lightblue',
edge_color='gray', node_size=800)
plt.title("社交网络关系图")
plt.show()
2. 关键图特征计算实战
传统图算法依赖精心设计的特征工程。下面介绍三种在社交网络中表现优异的特征计算方法。
2.1 共同邻居(Common Neighbors)
这是最直观的相似度度量,计算公式简单:
CN(u,v) = |N(u) ∩ N(v)|
其中N(u)表示用户u的好友集合。NetworkX内置了计算方法:
# 计算所有可能的用户对
all_pairs = [(u, v) for u in G.nodes() for v in G.nodes() if u < v]
# 计算共同邻居特征
cn_features = {}
for u, v in all_pairs:
cn_features[(u, v)] = len(list(nx.common_neighbors(G, u, v)))
pd.DataFrame.from_dict(cn_features, orient='index', columns=['CN'])
结果显示小美和小强有1个共同邻居(小丽),而小明和小丽没有共同好友。
2.2 Adamic-Adar指数
该算法改进了共同邻居方法,给予低度数共同好友更高权重:
AA(u,v) = Σ(1/log|N(z)|) ,z∈N(u)∩N(v)
实现代码:
aa_features = {}
for u, v in all_pairs:
aa_features[(u, v)] = nx.adamic_adar_index(G, [(u, v)])[0][2]
pd.DataFrame.from_dict(aa_features, orient='index', columns=['AA'])
2.3 Katz指数
考虑全局网络结构的特征,计算所有长度路径的加权和:
Katz(u,v) = Σβ^l * |paths(u,v) of length l|
β是衰减因子(通常取0.005-0.05):
def katz_index(graph, beta=0.02):
A = nx.adjacency_matrix(graph).todense()
I = np.identity(len(graph.nodes()))
S = np.linalg.inv(I - beta * A) - I
return S
katz_scores = katz_index(G)
3. 构建推荐系统流水线
有了特征后,我们需要将其整合到机器学习流程中。以下是完整实现步骤:
3.1 特征矩阵构建
将多种特征组合成训练数据:
features = []
for u, v in all_pairs:
features.append([
len(list(nx.common_neighbors(G, u, v))),
nx.adamic_adar_index(G, [(u, v)])[0][2],
katz_scores[list(G.nodes()).index(u), list(G.nodes()).index(v)]
])
X = pd.DataFrame(features, columns=['CN', 'AA', 'Katz'])
3.2 标签生成与模型训练
假设我们已经通过业务规则生成了标签(1表示应该推荐,0表示不推荐):
from sklearn.linear_model import LogisticRegression
# 示例标签 (实际应从业务数据获取)
y = [0, 1, 0, 0, 1, 0, 0, 0, 0, 1]
model = LogisticRegression()
model.fit(X, y)
3.3 推荐结果生成
对新用户对进行预测:
def recommend_new_pairs(model, graph, user_list, threshold=0.7):
recommendations = []
for u in user_list:
for v in graph.nodes():
if u != v and not graph.has_edge(u, v):
cn = len(list(nx.common_neighbors(graph, u, v)))
aa = nx.adamic_adar_index(graph, [(u, v)])[0][2]
katz = katz_scores[list(graph.nodes()).index(u), list(graph.nodes()).index(v)]
proba = model.predict_proba([[cn, aa, katz]])[0][1]
if proba >= threshold:
recommendations.append((u, v, proba))
return sorted(recommendations, key=lambda x: -x[2])
recommend_new_pairs(model, G, ['小明', '小美'])
4. 方案优化与生产部署
在实际业务场景中,我们还需要考虑以下优化方向:
4.1 特征工程增强
| 特征类型 | 计算方法 | 适用场景 |
|---|---|---|
| 资源分配指数 | RA = Σ(1/ | N(z) |
| Jaccard系数 | J = | N(u)∩N(v) |
| 偏好依附 | PA = | N(u) |
4.2 负样本策略
社交网络中的隐式负样本(未连接的用户对)需要谨慎处理:
- 随机采样:从无连接的节点对中随机选择
- 基于距离:选择超过2跳的节点对
- 基于时间:选择近期活跃但未连接的用户
4.3 工程化部署建议
# 生产环境推荐使用稀疏矩阵存储
from scipy import sparse
def large_scale_katz_index(graph, beta=0.02, iterations=100):
"""迭代法计算Katz指数,适用于大规模图"""
A = nx.adjacency_matrix(graph)
x = np.ones(A.shape[0])
katz = x.copy()
for _ in range(iterations):
x = beta * A.dot(x)
katz += x
return katz
对于超大规模社交网络(>100万节点),可以考虑:
- 使用Spark GraphX进行分布式计算
- 对图进行社区检测后分块处理
- 采用近似算法降低计算复杂度
5. 传统方法的独特价值
尽管深度学习在推荐系统中大放异彩,传统图算法仍具有不可替代的优势:
- 可解释性 :每个特征都有明确的网络学含义
- 冷启动友好 :不需要大量训练数据
- 计算高效 :适合实时性要求高的场景
- 可组合性 :可以作为深度学习模型的输入特征
在实际项目中,我通常采用混合策略:先用传统方法快速搭建基线系统,再逐步引入深度学习组件。特别是在数据稀疏的业务初期,基于共同邻居的简单方法往往能带来出乎意料的好效果。
更多推荐
所有评论(0)