引言
本篇笔记为coggle 2月打卡任务,正好也在学习cs224w,干脆就一起做了,以下是任务列表:
任务名称 | 难度 |
---|---|
任务1:图属性与图构造 | 低、1 |
任务2:图查询与遍历 | 低、2 |
任务3:节点中心性与应用 | 中、2 |
任务4:图节点嵌入算法(DeepWalk/node2vec) | 高、3 |
任务5:图节点嵌入算法:LINE/SDNE | 高、3 |
任务6:图节点嵌入算法:GraphGAN | 高、3 |
task1 图属性与图构造
- 步骤1:导入networkx
- 步骤2:加载Wiki数据集,包含来自 19 个类别的 2405 个文档和它们之间的 17981个边。
- 步骤3:使用networkx构造上述有向图
import pandas as pd
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
# 两列,分别为节点id,节点类别
group = pd.read_csv('http://mirror.coggle.club/dataset/graph-wiki/group.txt.zip', sep='\t', header=None)
# 两列,分别为出发节点id,目的节点id
graph = pd.read_csv('http://mirror.coggle.club/dataset/graph-wiki/graph.txt.zip', sep='\t', header=None)
g = nx.DiGraph()
# 只添加前100条边
g.add_edges_from(graph.values[:100])
nx.draw_spring(g)
# 添加所有数据
g = nx.DiGraph()
g.add_edges_from(graph.values[:])
task2 图查询与遍历
- 步骤1:使用networkx对Wiki数据集进行如下统计
- 节点个数、边个数
- 节点度平均
- 存在指向自身节点的个数
- 步骤2:对节点1397进行深度和广度遍历,设置搜索最大深度为5
- 步骤3:判断节点1573与节点1397之间是否存在联通性
# 边个数 节点个数
g.number_of_edges(), g.number_of_nodes()
# 度均值
np.mean([x[1] for x in list(g.degree())])
# 对节点1397的深度5内进行深度和广度遍历
nx.dfs_tree(g, 1397, 5).nodes()
nx.bfs_tree(g, 1397, 5).nodes()
# 节点1573与节点1397之间的路径
list(nx.connectivity.node_disjoint_paths(g, 1573, 1397))
"""
[[1573, 2337, 489, 708, 1525, 1397],
[1573, 280, 696, 1610, 1445, 1397],
[1573, 445, 2395, 750, 1397],
[1573, 933, 1455, 1401, 1412, 1397]]
"""
task3 节点中心性与应用
本task的数据为:
# 文章1
'''
一纸四季报,令芯片巨头英特尔一夜间股价重挫近6.5%,市值蒸发80亿美元,再度被AMD反超。
这份严重缩水的财报显示,英特尔在去年四季度营收大降32%至140亿美元,是2016年以来最低单季收入;净利润由三季度的10.2亿美元转为近7亿美元净亏损;毛利率更从2021年四季度的53.6%大幅下降至39.2%。
此番业绩“跳水”并非英特尔一家的一时失利,在全球PC出货量整体下滑的背景下,包括英特尔、AMD、英伟达、高通在内的芯片企业,均在过去一年里出现不同程度的收入与利润下滑,但英特尔的确是其中的重灾区。
'''
# 文章2
'''
2021年,成都地区生产总值已经超过1.99万亿元,距离2万亿门槛仅咫尺之间。在去年遭受多轮疫情冲击及高温限电冲击的不利影响下,2022年,成都市实现地区生产总值20817.5亿元,按可比价格计算,比上年增长2.8%。
成都因此成为第7个跨过GDP2万亿门槛的城市。目前,GDP万亿城市俱乐部中形成了4万亿、3万亿、2万亿和万亿这四个梯队。上海和北京在2021年跨过了4万亿,深圳2021年跨过了3万亿,重庆、广州、苏州和成都则是2万亿梯队。
在排名前十的城市中,预计武汉将超过杭州。武汉市政府工作报告称,预计2022年武汉地区生产总值达到1.9万亿元左右。而杭州市统计局公布的数据显示,杭州2022年地区生产总值为18753亿元。受疫情影响,武汉在2020年GDP排名退居杭州之后。
'''
# 文章3
'''
据报道,美国证券交易委员会(SEC)与特斯拉首席执行官埃隆·马斯克之间又起波澜。SEC正对马斯克展开调查,主要审查内容是,马斯克是否参与了关于特斯拉自动驾驶软件的不恰当宣传。
据知情人士透露,该机构正在调查马斯克是否就驾驶辅助技术发表了不恰当的前瞻声明。
特斯拉在2014年首次发布了其自动驾驶辅助功能,公司声称该功能可以让汽车在车道内自动转向、加速和刹车。目前所有特斯拉车辆都内置了该软件。
'''
步骤1:筛选度最大的Top10个节点,并对节点深度1以内的节点进行可视化;
# 筛选度最大的Top10个节点,并对节点深度1以内的节点进行可视化;
g_degree = pd.DataFrame(g.degree()).sort_values(by=1)
g_degree = g_degree.iloc[-10:]
selected_nodes = []
for node in g_degree[0].values:
selected_nodes += list(nx.dfs_tree(g, node, 1).nodes())
nx.draw_spring(g.subgraph(selected_nodes), node_size=3)
步骤2:使用PageRank筛选Top10个节点,并对节点深度1以内的节点进行可视化;
# 使用PageRank筛选Top10个节点,并对节点深度1以内的节点进行可视化
g_pagerank = pd.DataFrame.from_dict(nx.pagerank(g), orient='index')
g_pagerank = g_pagerank.sort_values(by=0)
g_pagerank = g_pagerank.iloc[-10:]
selected_nodes = []
for node in g_pagerank[0].index:
selected_nodes += list(nx.dfs_tree(g, node, 1).nodes())
nx.draw_spring(g.subgraph(selected_nodes), node_size=3)
步骤3:文本关键词提取算法RAKE
这里以文章一为例,先将上述文章1的内容规整一下:
content = "一纸四季报,令芯片巨头英特尔一夜间股价重挫近6.5%,市值蒸发80亿美元,再度被AMD反超。这份严重缩水的财报显示,英特尔在去年四季度营收大降32%至140亿美元,是2016年以来最低单季收入;净利润由三季度的10.2亿美元转为近7亿美元净亏损;毛利率更从2021年四季度的53.6%大幅下降至39.2%。此番业绩“跳水”并非英特尔一家的一时失利,在全球PC出货量整体下滑的背景下,包括英特尔、AMD、英伟达、高通在内的芯片企业,均在过去一年里出现不同程度的收入与利润下滑,但英特尔的确是其中的重灾区。"
然后使用RAKE算法对上述内容进行提取,并计算单词打分 wordDegree (w) / wordFrequency (w),以及统计每个文章Top10关键词:
import jieba
from collections import Counter
g2 = nx.Graph()
words = jieba.lcut(content)
words = [x for x in words if len(x) > 1]
for i in range(len(words)-2):
for j in range(i-2, i+2):
if i == j:
continue
g2.add_edge(words[i], words[j])
g2_node_gree = dict(g2.degree())
word_counter = dict(Counter(words))
g2_node_gree = pd.DataFrame.from_dict(g2_node_gree, orient='index')
g2_node_gree.columns = ['degree']
g2_node_gree['freq'] = g2_node_gree.index.map(word_counter)
g2_node_gree['score'] = g2_node_gree['degree'] / g2_node_gree['freq']
g2_node_gree.sort_values(by='score').index[-10:]
"""
['32%', '140', '2016', '以来', '最低', '单季', '收入', '利润', '缩水', '净利润']
"""
其余两个就不再详述,这里贴出结果为:
# ['增长', '2.8%', '成都', '2020', '成为', '超过', 'GDP2', '目前', '可比价格', '因此']
# ['主要', '审查', '内容', '参与', '关于', '软件', '所有', '宣传', '人士', '恰当']
步骤4:文本关键词提取算法TextRank
这里跟步骤三一样,直接给出代码为:
g2 = nx.Graph()
words = jieba.lcut(content)
words = [x for x in words if len(x) > 1]
for i in range(len(words)-2):
for j in range(i-2, i+2):
if i == j:
continue
g2.add_edge(words[i], words[j])
g2_node_gree = pd.DataFrame.from_dict(nx.pagerank(g2), orient='index')
g2_node_gree.columns = ['degree']
g2_node_gree = g2_node_gree.sort_values(by='degree')
三篇文章提取统计的结果为:
# ['39.2%', '四季', '一纸', 'AMD', '下滑', '四季度', '收入', '芯片', '亿美元', '英特尔']
# ['万亿元', '成都', '地区', '2021', '城市', '杭州', '武汉', '2022', '生产总值', '万亿']
# ['功能', '是否', '辅助', '调查', '恰当', 'SEC', '驾驶', '自动', '马斯克', '特斯拉']
下图是task任务下插入的一张图,但我不太清楚这张图怎么画的,就以上信息好像不怎么够,提取出来的6个关键词列表好像除了根据算法认定的词权重外似乎是没多大关系?之后再研究一下:
当然,这里我尝试了一些简单的方式,比如我看到有直接用相关性的,这里我大概修改了一下代码,为:
G = nx.Graph()
keywords_list = [
['39.2%', '四季', '一纸', 'AMD', '下滑', '四季度', '收入', '芯片', '亿美元', '英特尔'],
['功能', '是否', '辅助', '调查', '恰当', 'SEC', '驾驶', '自动', '马斯克', '特斯拉'],
['万亿元', '成都', '地区', '2021', '城市', '杭州', '武汉', '2022', '生产总值', '万亿'],
['32%', '140', '2016', '以来', '最低', '单季', '收入', '利润', '缩水', '净利润'],
['增长', '2.8%', '成都', '2020', '成为', '超过', 'GDP2', '目前', '可比价格', '因此'],
['主要', '审查', '内容', '参与', '关于', '软件', '所有', '宣传', '人士', '恰当']
]
# 定义一个函数来计算两个关键词列表之间的相关性
def similarity(list1, list2):
# 计算两个列表之间的交集和并集
intersection = set(list1) & set(list2)
union = set(list1) | set(list2)
# 计算交集与并集之比作为相关性度量
return len(intersection) / len(union)
# 添加节点和边
for keywords in keywords_list:
G.add_nodes_from(keywords)
for i in range(len(keywords_list)-1):
for j in range(i+1, len(keywords_list)):
# 计算两个关键词列表之间的相关性,并作为边的权重
weight = similarity(keywords_list[i], keywords_list[j])
# 只有当相关性大于0时才添加边
if weight > 0:
G.add_edge(keywords_list[i][0], keywords_list[j][0], weight=weight)
nx.draw(G, with_labels=True, font_weight='bold',
node_color='skyblue',
edge_color='gray')
plt.show()
有些丑,没有上图好看,就不贴出来献丑了,emmm,可以自己跑跑试试,因为我是两个算法针对三段文章提取出来的,所以就直接取交并,而没有用到分词出来的权重,虽然结果感觉上差不多,还是需要词之间的相关性,这个不知道怎么定义。
task4 图节点嵌入算法:DeepWalk/node2vec
这里两个算法的原理,我已经在前面两篇文章中做了详细推导与说明,为:
CS224W课程学习笔记(三):DeepWalk算法原理与说明
CS224W课程学习笔记(四):node2vec算法原理与说明
deepwalk
首先写出随机游走的采样序列算法:
import random
def deepwalk(G, walk_length):
nodes = G.nodes()
history_walks = []
# 对于每个节点
for node in nodes:
# 从当前节点开始
random_walk_length = [node]
# 开始游走
for i in range(walk_length-1):
# 找到节点邻居
neighbors = list(G.neighbors(node))
# 排除已经游走的邻居
neighbors = list(set(neighbors) - set(random_walk_length))
if len(neighbors) == 0:
break
# 随机挑选邻居
random_neighbor = random.choice(neighbors)
random_walk_length.append(random_neighbor)
# 从下一个邻居继续游走
node = random_neighbor
# 此节点的游走路径
history_walks.append(random_walk_length)
return history_walks
然后我们加载wiki数据集,并用50维做嵌入,生成节点类别:
g = nx.DiGraph()
# 构件图
g.add_edges_from(graph.values[:])
# 游走
history_walks = deepwalk(g, 100)
from gensim.models import Word2Vec
# 训练word2vec
w2v = Word2Vec(history_walks, vector_size=50, window=5)
# 节点类别,因为word2vec有单独节点次序
node_group = group.iloc[list(w2v.wv.key_to_index.keys())][1].values
"""
array([18, 18, 4, ..., 11, 2, 17])
"""
根据上面得到的节点类别做降维与使用TSNE算法可视化,对于TSNE算法,我在deepwalk笔记中对该概念和PCA降维进行了对比,具体可以看上述我引用出来的第三篇,这里不再详述,那代码为:
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2)
tsne_data = tsne.fit_transform(w2v.wv.vectors)
import matplotlib.cm as cm
x = np.arange(20)
ys = [i+x+(i*x)**2 for i in range(20)]
colors = cm.rainbow(np.linspace(0, 1, len(ys)))
plt.scatter(
tsne_data[:, 0],
tsne_data[:, 1],
color=colors[node_group]
)
然后我们可以使用节点嵌入向量 + 逻辑回归进行训练,并记录验证集准确率:
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
# 划分数据集
x_tr, x_val, y_tr, y_val = train_test_split(
w2v.wv.vectors, node_group, test_size=0.2, stratify=node_group
)
# 模型训练与验证,准确率0.535
model = LogisticRegression()
model.fit(x_tr, y_tr)
model.score(x_val, y_val)
"""
0.5642023346303502
"""
还行,比预先版本的0.535要高0.03,因为逻辑回归没有给默认参数,这点随机性可以理解,毕竟分高了,emmm。
node2vec
node2vec是一种综合考虑DFS邻域和BFS邻域的graph embedding方法。简单来说,可以看作是deepwalk的一种扩展,是结合了DFS和BFS随机游走的deepwalk。node2vec依然采用随机游走的方式获取顶点的近邻序列,不同的是node2vec采用的是一种有偏的随机游走。
这里将上述的labels和graph改动一下格式,因为原来是以zip文件转格式而来并且是以制表符为分隔,先wget下载文件后,解压运行如下代码:
import pandas as pd
df = pd.read_table("graph.txt", sep="\t")
df2 = pd.read_table("label.txt", sep="\t")
# 将制表符替换成空格
df = df.replace("\t", " ")
df.to_csv("train.txt",sep=" ",index=False)
df.to_csv("test.txt",sep=" ",index=False)
然后根据我上面第四篇笔记中,浅梦大佬开发的ge
包进行训练,代码改为:
import numpy as np
from ge.classify import read_node_label, Classifier
from ge import Node2Vec
from sklearn.linear_model import LogisticRegression
import matplotlib.pyplot as plt
import networkx as nx
from sklearn.manifold import TSNE
def evaluate_embeddings(embeddings):
# group = pd.read_csv('http://mirror.coggle.club/dataset/graph-wiki/group.txt.zip', sep='\t', header=None)
X, Y = read_node_label('test.txt')
tr_frac = 0.8
print("Training classifier using {:.2f}% nodes...".format(
tr_frac * 100))
clf = Classifier(embeddings=embeddings, clf=LogisticRegression())
clf.split_train_evaluate(X, Y, tr_frac)
def plot_embeddings(embeddings,):
X, Y = read_node_label('test.txt')
emb_list = []
for k in X:
emb_list.append(embeddings[k])
emb_list = np.array(emb_list)
model = TSNE(n_components=2)
node_pos = model.fit_transform(emb_list)
color_idx = {}
for i in range(len(X)):
color_idx.setdefault(Y[i][0], [])
color_idx[Y[i][0]].append(i)
for c, idx in color_idx.items():
plt.scatter(node_pos[idx, 0], node_pos[idx, 1], label=c)
plt.legend()
plt.show()
使用逻辑回归进行测试,得到的分数为0.5883,即:
{'micro': 0.5883575883575883, 'macro': 0.459881682897527, 'samples': 0.5883575883575883, 'weighted': 0.5816291785521392, 'acc': 0.5883575883575883}
task5 图节点嵌入算法:LINE/SDNE
步骤1:使用LINE对Wiki数据集节点嵌入,维度为50维
步骤2:每个group中20%的节点作为验证集,剩余的作为训练集
步骤3:使用节点嵌入向量 + 逻辑回归进行训练,并记录验证集准确率
步骤4:使用SDNE重复上述操作
步骤5:使用t-SNE将节点的LINE/SDNE特征降维,绘制散点图,节点颜色使用group进行区分
task6 图节点嵌入算法:GraphGAN
- 步骤1:使用GraphGAN对Wiki数据集节点嵌入,维度为50维
- 步骤2:每个group中20%的节点作为验证集,剩余的作为训练集
- 步骤3:使用节点嵌入向量 + 逻辑回归进行训练,并记录验证集准确率
- 步骤4:使用t-SNE将节点的GraphGAN特征降维,绘制散点图,节点颜色使用group进行区分