From 6fd93c9e1d8b147ab82c1c2b9950122671740561 Mon Sep 17 00:00:00 2001 From: estomm Date: Wed, 23 Feb 2022 16:19:23 +0800 Subject: [PATCH] =?UTF-8?q?netowkx=E5=92=8Cpyg=E7=9A=84=E6=95=99=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Python/networkx/0 基础使用.md | 283 ++++++++++++++ Python/networkx/1 图的视图.md | 53 +++ Python/networkx/1.ipynb | 225 ++++++++++++ Python/networkx/2 图生成器.md | 69 ++++ Python/networkx/3 分析图形.md | 30 ++ Python/networkx/4 绘制图形.md | 22 ++ Python/networkx/5 导入导出.md | 77 ++++ pytorch/PyG/1 数据.md | 217 +++++++++++ pytorch/PyG/2 模型.md | 95 +++++ pytorch/PyG/3 训练.md | 429 ++++++++++++++++++++++ pytorch/PyG/image/2022-02-22-15-04-25.png | Bin 0 -> 57749 bytes pytorch/官方教程/00 Pytorch 概述.md | 20 +- 工作日志/2022年2月16日-今日计划.md | 10 + 13 files changed, 1520 insertions(+), 10 deletions(-) create mode 100644 Python/networkx/0 基础使用.md create mode 100644 Python/networkx/1 图的视图.md create mode 100644 Python/networkx/1.ipynb create mode 100644 Python/networkx/2 图生成器.md create mode 100644 Python/networkx/3 分析图形.md create mode 100644 Python/networkx/4 绘制图形.md create mode 100644 Python/networkx/5 导入导出.md create mode 100644 pytorch/PyG/1 数据.md create mode 100644 pytorch/PyG/2 模型.md create mode 100644 pytorch/PyG/3 训练.md create mode 100644 pytorch/PyG/image/2022-02-22-15-04-25.png create mode 100644 工作日志/2022年2月16日-今日计划.md diff --git a/Python/networkx/0 基础使用.md b/Python/networkx/0 基础使用.md new file mode 100644 index 00000000..6cb90688 --- /dev/null +++ b/Python/networkx/0 基础使用.md @@ -0,0 +1,283 @@ +# 教程 + +> 参考文献 +> * [官方文档](https://www.osgeo.cn/networkx/tutorial.html) + +## 0 简单介绍 + +### 图的类型 + +* Graph类是无向图的基类,无向图能有自己的属性或参数,不包含重边,允许有回路,节点可以是任何hash的python对象,节点和边可以保存key/value属性对。该类的构造函数为Graph(data=None,**attr),其中data可以是边列表,或任意一个Networkx的图对象,默认为none;attr是关键字参数,例如key=value对形式的属性。 +* MultiGraph是可以有重边的无向图,其它和Graph类似。其构造函数MultiGraph(data=None, *attr)。 +* DiGraph是有向图的基类,有向图可以有数自己的属性或参数,不包含重边,允许有回路;节点可以是任何hash的python对象,边和节点可含key/value属性对。该类的构造函数DiGraph(data=None,**attr),其中data可以是边列表,或任意一个Networkx的图对象,默认为none;attr是关键字参数,例如key=value对形式的属性。 +* MultiDiGraph是可以有重边的有向图,其它和DiGraph类似。其构造函数MultiDiGraph(data=None, *attr) + + +### 有向图 + + +这个 DiGraph 提供了附加的属性,DiGraph.out_edges , DiGraph.in_degree , DiGraph.predecessors() , DiGraph.successors() 等。为了使算法能够轻松地与两个类一起工作,有向版本的 neighbors() 等于 successors() 虽然 degree 报告 in_degree 和 out_degree 尽管有时会觉得不一致。 + +```py +DG = nx.DiGraph() +DG.add_weighted_edges_from([(1, 2, 0.5), (3, 1, 0.75)]) +DG.out_degree(1, weight='weight') +0.5 +DG.degree(1, weight='weight') +1.25 +list(DG.successors(1)) +[2] +list(DG.neighbors(1)) +[2] +``` + +有向图和无向图的转换 + +```py +Graph.to_undirected() +H = nx.Graph(G) # create an undirected graph H from a directed graph G +``` + + +### 多重图 + +多重图 +NetworkX为允许任意节点对之间存在多个边的图形提供类。这个 MultiGraph 和 MultiDiGraph 类允许您两次添加相同的边缘,可能使用不同的边缘数据。这对某些应用程序来说可能很强大,但许多算法在此类图上没有很好的定义。如果结果定义明确,例如: MultiGraph.degree() 我们提供功能。否则,您应该以一种使测量定义良好的方式转换为标准图。 + +```py +MG = nx.MultiGraph() +MG.add_weighted_edges_from([(1, 2, 0.5), (1, 2, 0.75), (2, 3, 0.5)]) +dict(MG.degree(weight='weight')) +{1: 1.25, 2: 1.75, 3: 0.5} +GG = nx.Graph() +for n, nbrs in MG.adjacency(): + for nbr, edict in nbrs.items(): + minvalue = min([d['weight'] for d in edict.values()]) + GG.add_edge(n, nbr, weight = minvalue) + +nx.shortest_path(GG, 1, 3) +[1, 2, 3] +``` + + + +## 1 增加 + +### 创建图形 +创建一个没有节点和边的空图形。 + +```py +>>> +import networkx as nx +G = nx.Graph() +``` +根据定义,a Graph 是一组节点(顶点)和已识别的节点对(称为边、链接等)的集合。在NetworkX中,节点可以是任何 hashable 对象,例如文本字符串、图像、XML对象、另一个图形、自定义节点对象等 + + +### 添加节点 + +图 G 可以通过多种方式生长。NetworkX包括许多图形生成器功能和以多种格式读取和写入图形的工具。我们先来看看简单的操作。 + +* 一次添加一个节点, + +```py +G.add_node(1) +``` + +* 从任何 iterable 容器,如列表 + +```py +G.add_nodes_from([2, 3]) +``` + +* 如果容器产生2个元组形式,还可以添加节点和节点属性 (node, node_attribute_dict) :: + +```py +G.add_nodes_from([ + (4, {"color": "red"}), + (5, {"color": "green"}), +]) +``` + +* 一个图中的节点可以合并到另一个图中 + +```py +H = nx.path_graph(10) +G.add_nodes_from(H) +``` + +* G 现在包含的节点 H 作为节点 G . 相反,您可以使用图表 H 作为一个节点 G . +``` +G.add_node(H) +``` + + +### 添加边缘 + +* G 也可以通过一次添加一个边来生长, + +``` +G.add_edge(1, 2) +e = (2, 3) +G.add_edge(*e) # unpack edge tuple* +``` + +* 通过添加边列表, + +``` +G.add_edges_from([(1, 2), (1, 3)]) +``` + +* 我们添加新的节点/边缘,忽略已经存在的节点和边 +``` +>>> +G.add_edges_from([(1, 2), (1, 3)]) +G.add_node(1) # 已经存在1节点,不再重复添加 +G.add_edge(1, 2) # 已经存在1,2边,不再重复添加 +G.add_node("spam") # adds node "spam" +G.add_nodes_from("spam") # adds 4 nodes: 's', 'p', 'a', 'm' +G.add_edge(3, 'm') +``` + +* 在这个阶段,图表 G 由8个节点和3个边组成,如下所示: + +```py +>>> +G.number_of_nodes() +8 +G.number_of_edges() +3 +``` + +## 2 删除 +### 删除元素 +可以用与添加类似的方式从图形中删除节点和边。 +* Graph.remove_node() +* Graph.remove_nodes_from() +* Graph.remove_edge() +* Graph.remove_edges_from() + +``` +>>> +G.remove_node(2) +G.remove_nodes_from("spam") +list(G.nodes) +[1, 3, 'spam'] +G.remove_edge(1, 3) +``` + + +### 构造函数 +图形对象不必以增量方式构建——指定图形结构的数据可以直接传递给各种图形类的构造函数。当通过实例化一个图形类来创建一个图结构时,可以用几种格式指定数据。 + +``` +G.add_edge(1, 2) +H = nx.DiGraph(G) # create a DiGraph using the connections from G +list(H.edges()) +[(1, 2), (2, 1)] +edgelist = [(0, 1), (1, 2), (2, 3)] +H = nx.Graph(edgelist) +``` + +## 3 查找 +### 访问边缘和邻居 +* 除了视图 Graph.edges 和 Graph.adj ,可以使用下标表示法访问边和邻居。 + +```py +G = nx.Graph([(1, 2, {"color": "yellow"})]) +G[1] # same as G.adj[1] +AtlasView({2: {'color': 'yellow'}}) +G[1][2] +{'color': 'yellow'} +G.edges[1, 2] +{'color': 'yellow'} +``` + +* 如果边已经存在,可以使用下标表示法获取/设置边的属性。 + +```py +G.add_edge(1, 3) +G[1][3]['color'] = "blue" +G.edges[1, 2]['color'] = "red" +G.edges[1, 2] +{'color': 'red'} +``` + +* 使用 G.adjacency() 或 G.adj.items() . 注意,对于无向图,邻接迭代可以看到每个边两次。 + +```py +FG = nx.Graph() +FG.add_weighted_edges_from([(1, 2, 0.125), (1, 3, 0.75), (2, 4, 1.2), (3, 4, 0.375)]) +for n, nbrs in FG.adj.items(): + for nbr, eattr in nbrs.items(): + wt = eattr['weight'] + if wt < 0.5: print(f"({n}, {nbr}, {wt:.3})") +(1, 2, 0.125) +(2, 1, 0.125) +(3, 4, 0.375) +(4, 3, 0.375) +``` + +* 使用边缘属性可以方便地访问所有边缘。 + +```py +for (u, v, wt) in FG.edges.data('weight'): + if wt < 0.5: + print(f"({u}, {v}, {wt:.3})") +(1, 2, 0.125) +(3, 4, 0.375) +``` + + + +## 4 修改 +向图形、节点和边添加属性 + +诸如权重、标签、颜色或任何您喜欢的python对象等属性都可以附加到图形、节点或边上。 + +每个图、节点和边都可以在关联的属性字典中保存键/值属性对(键必须是可哈希的)。默认情况下,这些属性为空,但可以使用 add_edge , add_node 或直接操作命名的属性字典 G.graph , G.nodes 和 G.edges 对于图 G . + +### 图形属性 + +创建新图形时分配图形属性 + +``` +G = nx.Graph(day="Friday") +G.graph +{'day': 'Friday'} +``` +或者您可以稍后修改属性 + +``` +G.graph['day'] = "Monday" +G.graph +{'day': 'Monday'} +``` +### 节点属性 +使用添加节点属性 add_node() , add_nodes_from() 或 G.nodes + +```py +G.add_node(1, time='5pm') +G.add_nodes_from([3], time='2pm') +G.nodes[1] +{'time': '5pm'} +G.nodes[1]['room'] = 714 +G.nodes.data() +NodeDataView({1: {'time': '5pm', 'room': 714}, 3: {'time': '2pm'}}) +``` + + +请注意,将节点添加到 G.nodes 不将其添加到图表中,使用 G.add_node() 添加新节点。同样适用于边缘。 + +### 边缘属性 +使用添加/更改边缘属性 add_edge() , add_edges_from() 或下标符号。 + +```py +G.add_edge(1, 2, weight=4.7 ) +G.add_edges_from([(3, 4), (4, 5)], color='red') +G.add_edges_from([(1, 2, {'color': 'blue'}), (2, 3, {'weight': 8})]) +G[1][2]['weight'] = 4.7 +G.edges[3, 4]['weight'] = 4.2 +``` + +特殊属性 weight 应该是数字,因为它被需要加权边缘的算法使用。 \ No newline at end of file diff --git a/Python/networkx/1 图的视图.md b/Python/networkx/1 图的视图.md new file mode 100644 index 00000000..0e26617b --- /dev/null +++ b/Python/networkx/1 图的视图.md @@ -0,0 +1,53 @@ +# 图的视图 + +## 视图 +### 概述 +* 各种视图提供了图上节点、边、邻接矩阵和度本身的各种基础方法。这些视图提供对属性的迭代,以及成员查询和数据属性查找。 +* 视图引用图形数据结构,因此对图形的更改反映在视图中。这类似于Python3中的字典视图。是一种图表示的数据结构效率非常高,全是字典。 +* 如果要在迭代时更改图形,则需要使用例如 for e in list(G.edges): . +* 视图提供了类似集合的操作,例如联合和交集, +* 视图提供了类似dict的数据属性查找和迭代,使用 G.edges[u, v]['color'] 和 for e, datadict in G.edges.items(): . 方法 G.edges.items() 和 G.edges.values() + + +## 2 常见的视图 + +```py +__all__ = [ + "NodeView", #只包含节点的视图 + "NodeDataView", # 包含节点和属性的视图 + + "EdgeView", # 只保函边的视图 + "OutEdgeView", + "InEdgeView", + "EdgeDataView", # 包含边和属性的视图 + "OutEdgeDataView", + "InEdgeDataView", + + + "DegreeView", # 度的视图 + "DiDegreeView", + "InDegreeView", + "OutDegreeView", + "MultiDegreeView", + "DiMultiDegreeView", + "InMultiDegreeView", + "OutMultiDegreeView", + + "adjacentView", # 邻接视图 + "AtlasView", # 邻接属性视图 +] +``` + +### 节点视图 + + +### 边视图 + +### 邻接视图 + +### 度视图 + + + + + diff --git a/Python/networkx/1.ipynb b/Python/networkx/1.ipynb new file mode 100644 index 00000000..e708dc10 --- /dev/null +++ b/Python/networkx/1.ipynb @@ -0,0 +1,225 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "import networkx as nx\n", + "G = nx.Graph()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "G.add_nodes_from([1,2,3])\n", + "G.add_edge(3,4)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4]\n", + "[(3, 4)]\n" + ] + } + ], + "source": [ + "print(G.nodes())\n", + "print(G.edges())" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":3: UserWarning: Matplotlib is currently using ps, which is a non-GUI backend, so cannot show the figure.\n", + " plt.show()\n" + ] + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "nx.draw(G)\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1, 2, 0.125)\n", + "(2, 1, 0.125)\n", + "(3, 4, 0.375)\n", + "(4, 3, 0.375)\n" + ] + } + ], + "source": [ + "FG = nx.Graph()\n", + "FG.add_weighted_edges_from([(1, 2, 0.125), (1, 3, 0.75), (2, 4, 1.2), (3, 4, 0.375)])\n", + "for n, nbrs in FG.adj.items():\n", + " for nbr, eattr in nbrs.items():\n", + " wt = eattr['weight']\n", + " if wt < 0.5: print(f\"({n}, {nbr}, {wt:.3})\")" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1, 2, 0.125)\n", + "(3, 4, 0.375)\n" + ] + } + ], + "source": [ + "for (u, v, wt) in FG.edges.data('weight'):\n", + " if wt < 0.5:\n", + " print(f\"({u}, {v}, {wt:.3})\")" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ItemsView(AdjacencyView({1: {2: {'weight': 0.5}}, 2: {}, 3: {1: {'weight': 0.75}}}))\n", + "[3]\n", + "[2]\n" + ] + } + ], + "source": [ + "DG = nx.DiGraph()\n", + "DG.add_weighted_edges_from([(1, 2, 0.5), (3, 1, 0.75)])\n", + "DG.out_degree(1, weight='weight')\n", + "print(DG.adj.items())\n", + "print(list(DG.predecessors(1)))\n", + "print(list(DG.successors(1)))" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[0 1 0 1 0 0 1 1 0 0]\n", + " [1 0 1 1 1 1 0 1 0 1]\n", + " [0 1 0 0 1 1 1 1 0 1]\n", + " [0 0 1 0 0 0 0 1 1 1]\n", + " [1 0 0 1 1 1 0 1 1 1]\n", + " [0 0 0 0 0 0 0 0 1 1]\n", + " [0 0 1 0 0 0 1 1 1 1]\n", + " [1 0 1 0 0 1 1 0 0 0]\n", + " [1 1 1 1 1 0 1 0 0 0]\n", + " [1 1 1 0 0 0 0 0 0 1]]\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "a = np.random.randint(0, 2, size=(10, 10))\n", + "print(a)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "D = nx.DiGraph(a)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0., 1., 0., 1., 0., 0., 1., 1., 0., 0.],\n", + " [1., 0., 1., 1., 1., 1., 0., 1., 0., 1.],\n", + " [0., 1., 0., 0., 1., 1., 1., 1., 0., 1.],\n", + " [0., 0., 1., 0., 0., 0., 0., 1., 1., 1.],\n", + " [1., 0., 0., 1., 1., 1., 0., 1., 1., 1.],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 1., 1.],\n", + " [0., 0., 1., 0., 0., 0., 1., 1., 1., 1.],\n", + " [1., 0., 1., 0., 0., 1., 1., 0., 0., 0.],\n", + " [1., 1., 1., 1., 1., 0., 1., 0., 0., 0.],\n", + " [1., 1., 1., 0., 0., 0., 0., 0., 0., 1.]])" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "D.nodes()\n", + "D.edges()\n", + "nx.to_numpy_array(D)" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "5ef0042cb263260037aa2928643ae94e240dd3afaec7872ebebe4f07619ddd0c" + }, + "kernelspec": { + "display_name": "Python 3.8.8 ('ml')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Python/networkx/2 图生成器.md b/Python/networkx/2 图生成器.md new file mode 100644 index 00000000..1d790bda --- /dev/null +++ b/Python/networkx/2 图生成器.md @@ -0,0 +1,69 @@ +# 图形生成器和图形操作 + +## 1 应用经典图形操作 + + +| subgraph \(G ,N启动) | 返回在nbunch中的节点上诱导的子图。 | +|---|---| +| union (g,h) [, rename, name] ) | 返回图g和h的并集。 | +| disjoint_union (g,h) | 返回图G和图H的不相交的并集。 | +| cartesian_product (g,h) | 返回g和h的笛卡尔积。 | +| compose (g,h) | 返回由h组成的g的新图。 | +| complement (g) | 返回g的图补。 | +| create_empty_copy (g) [, with_data] ) | 返回图形G的副本,并删除所有边。 | +| to_undirected [(图)] | 返回图表的无向视图 graph . | +| to_directed [(图)] | 返回图形的定向视图 graph . | + + +## 2 经典生成小图 + + +| petersen_graph \ [create_using] ) | 返回彼得森图。 | +|---|---| +| tutte_graph \ [create_using] ) | 返回图特图。 | +| sedgewick_maze_graph \ [create_using] ) | 返回一个带有循环的小迷宫。 | +| tetrahedral_graph \ [create_using] ) | 返回3-正则柏拉图四面体图。 | + + +## 3 对经典图形使用生成器 + + +| complete_graph n(n) [, create_using] ) | 返回完整图形 K_n 具有n个节点。 | +|---|---| +| complete_bipartite_graph (N1,N2) [, create_using] ) | 返回完整的二部图 K_{{n_1,n_2}} . | +| barbell_graph (M1,M2) [, create_using] ) | 返回杠铃图:由路径连接的两个完整图。 | +| lollipop_graph (m,n) [, create_using] ) | 返回棒棒糖图; K_m 连接到 P_n . | + + +```py +K_5 = nx.complete_graph(5) +K_3_5 = nx.complete_bipartite_graph(3, 5) +barbell = nx.barbell_graph(10, 10) +lollipop = nx.lollipop_graph(10, 20) +``` + + +## 4 使用随机图形生成器 + + +| erdos_renyi_graph (n,p) [, seed, directed] ) | 返回一个$G{n,p}$随机图,也称为Erdős-Rényi图或二项式图。 | +|---|---| +| watts_strogatz_graph (n,k,p) [, seed] ) | 返回Watts–Strogaz小世界图。 | +| barabasi_albert_graph (n,m) [, seed] ) | 根据barab_si–albert优先连接模型返回随机图。 | +| random_lobster \(N、P1、P2)[, seed] ) | 返回随机龙虾图。 | + + +```py +er = nx.erdos_renyi_graph(100, 0.15) +ws = nx.watts_strogatz_graph(30, 3, 0.1) +ba = nx.barabasi_albert_graph(100, 5) +red = nx.random_lobster(100, 0.9, 0.9) +``` + +使用常用的图形格式读取存储在文件中的图形,如边列表、邻接列表、gml、graphml、pickle、leda等。 + +``` +nx.write_gml(red, "path.to.file") +mygraph = nx.read_gml("path.to.file") +``` +有关图形格式的详细信息,请参见 读写图表 关于图形生成器函数,请参见 图形生成器 \ No newline at end of file diff --git a/Python/networkx/3 分析图形.md b/Python/networkx/3 分析图形.md new file mode 100644 index 00000000..ab225298 --- /dev/null +++ b/Python/networkx/3 分析图形.md @@ -0,0 +1,30 @@ +# 分析图形 + + + +## 基础处理 + +分析图形的结构 G 可以使用各种图论函数进行分析,例如: + +```py +G = nx.Graph() +G.add_edges_from([(1, 2), (1, 3)]) +G.add_node("spam") # adds node "spam" +list(nx.connected_components(G)) +[{1, 2, 3}, {'spam'}] +sorted(d for n, d in G.degree()) +[0, 1, 1, 2] +nx.clustering(G) +{1: 0, 2: 0, 3: 0, 'spam': 0} +``` + +一些具有大输出的函数迭代(节点、值)2元组。这些很容易存储在 dict 结构,如果你愿意的话。 + +```py +sp = dict(nx.all_pairs_shortest_path(G)) +sp[3] +{3: [3], 1: [3, 1], 2: [3, 1, 2]} +``` + +## 图算法 + diff --git a/Python/networkx/4 绘制图形.md b/Python/networkx/4 绘制图形.md new file mode 100644 index 00000000..86f5b31b --- /dev/null +++ b/Python/networkx/4 绘制图形.md @@ -0,0 +1,22 @@ +# 图形绘制 + + +networkx主要不是一个图形绘制包,而是一个带有matplotlib的基本绘图,以及一个使用开源graphviz软件包的接口。这些是 networkx.drawing 模块,如果可能,将导入。 + +首先导入Matplotlib的绘图接口(Pylab也可以工作) + +```py +import matplotlib.pyplot as plt +``` + +测试是否导入 networkx.drawing 抽签成功 G 使用其中之一 + +``` +G = nx.petersen_graph() +plt.subplot(121) + +nx.draw(G, with_labels=True, font_weight='bold') +plt.subplot(122) + +nx.draw_shell(G, nlist=[range(5, 10), range(5)], with_labels=True, font_weight='bold') +``` \ No newline at end of file diff --git a/Python/networkx/5 导入导出.md b/Python/networkx/5 导入导出.md new file mode 100644 index 00000000..ccbecb70 --- /dev/null +++ b/Python/networkx/5 导入导出.md @@ -0,0 +1,77 @@ +读写图表 +邻接表 +邻接表 +networkx.readwrite.adjlist.read_adjlist +networkx.readwrite.adjlist.write_adjlist +networkx.readwrite.adjlist.parse_adjlist +networkx.readwrite.adjlist.generate_adjlist +多行邻接列表 +多行相邻列表 +networkx.readwrite.multiline_adjlist.read_multiline_adjlist +networkx.readwrite.multiline_adjlist.write_multiline_adjlist +networkx.readwrite.multiline_adjlist.parse_multiline_adjlist +networkx.readwrite.multiline_adjlist.generate_multiline_adjlist +边缘列表 +边缘列表 +networkx.readwrite.edgelist.read_edgelist +networkx.readwrite.edgelist.write_edgelist +networkx.readwrite.edgelist.read_weighted_edgelist +networkx.readwrite.edgelist.write_weighted_edgelist +networkx.readwrite.edgelist.generate_edgelist +networkx.readwrite.edgelist.parse_edgelist +GEXF +格式 +networkx.readwrite.gexf.read_gexf +networkx.readwrite.gexf.write_gexf +networkx.readwrite.gexf.generate_gexf +networkx.readwrite.gexf.relabel_gexf_graph +GML +networkx.readwrite.gml.read_gml +networkx.readwrite.gml.write_gml +networkx.readwrite.gml.parse_gml +networkx.readwrite.gml.generate_gml +networkx.readwrite.gml.literal_destringizer +networkx.readwrite.gml.literal_stringizer +泡菜 +腌渍图 +networkx.readwrite.gpickle.read_gpickle +networkx.readwrite.gpickle.write_gpickle +图形ML +图形ML +networkx.readwrite.graphml.read_graphml +networkx.readwrite.graphml.write_graphml +networkx.readwrite.graphml.generate_graphml +networkx.readwrite.graphml.parse_graphml +JSON +JSON数据 +networkx.readwrite.json_graph.node_link_data +networkx.readwrite.json_graph.node_link_graph +networkx.readwrite.json_graph.adjacency_data +networkx.readwrite.json_graph.adjacency_graph +networkx.readwrite.json_graph.cytoscape_data +networkx.readwrite.json_graph.cytoscape_graph +networkx.readwrite.json_graph.tree_data +networkx.readwrite.json_graph.tree_graph +networkx.readwrite.json_graph.jit_data +networkx.readwrite.json_graph.jit_graph +LEDA +格式 +networkx.readwrite.leda.read_leda +networkx.readwrite.leda.parse_leda +YAML +YAML +networkx.readwrite.nx_yaml.read_yaml +networkx.readwrite.nx_yaml.write_yaml +闪光灯6 +图形6 +SARSE6 +帕吉克 +帕吉克 +networkx.readwrite.pajek.read_pajek +networkx.readwrite.pajek.write_pajek +networkx.readwrite.pajek.parse_pajek +networkx.readwrite.pajek.generate_pajek +地理信息系统 +整形器 +networkx.readwrite.nx_shp.read_shp +networkx.readwrite.nx_shp.write_shp \ No newline at end of file diff --git a/pytorch/PyG/1 数据.md b/pytorch/PyG/1 数据.md new file mode 100644 index 00000000..50594cdd --- /dev/null +++ b/pytorch/PyG/1 数据.md @@ -0,0 +1,217 @@ + +## 1 图数据结构 + +图说起来也很简单,就是两个核心点,一个是图节点(nodes/vertics),一个是边(edges/links)表示节点之间的连接关系 + +总体而言,图可以是不规整的(irregular),对比而言,平时我们看到的图片都是规整的(regular),可以表示成矩阵或者向量。问题在于设计数据结构如何储存图,一般有两种方案 + +### 矩阵表示 + +又细分成两种 + +1. 用邻接矩阵(adjacency matrix),度矩阵(degree matrix), 拉普拉斯矩阵(Laplacian matrix)去表示, 衍生出各种对拉普拉斯矩阵的操作,比如图傅里叶变换(graph fourier transform), 也有稀疏邻接矩阵 + +2. 用关联矩阵(incidence matrix)表示,行表示节点,列表示边, 和这个相关的例如:超图(hypergraph) + +这(两)种方式的缺点在于使用内存大, 矩阵维度和节点数目N挂钩。但是图的连接常常是稀疏的(sparse),也就是邻接矩阵中很多元素都是0(两个node没有连接关系),这些0元素会占据大量存储空间,使效率很低下。尤其是大型网络图,都不会把图完整的表示成一个矩阵。 + + + +### 邻接表 +邻接表(Adjacency list),也是数据领域常用的存储图方式,比如将边表示成节点对,成为一个2*N_edges的matrix,第一行表示source node, 第二行表示target node。这样的好处在于可以只储存有边存在的,对稀疏结构友好。总体而言,如果图是dense的,可以考虑矩阵表示,如果是稀疏的,最好使用稀疏邻接矩阵或邻接表 + + + +## 2 数据Data + + +### Data的内容 +pytorch Geometric Data使用邻接表去表示图,同时也表示了node特征x, 边属性edge_attr等, 需要注意的是, Data只表示一张图(single graph) + + +```py +Data(x=None, edge_index=None, edge_attr=None, y=None) +``` + +一个Graph本质是torch_geometric.data.Data的实例,它包括以下几个常见对象(属性,attributes): + +* data.x:节点的特征矩阵,形状为[num_nodes,num_node_features] +* data.edge_index:图的边索引,用COO稀疏矩阵格式保存,形状为[2,num_edgs],数据类型为torch.long; +* data.edge_attr:边的特征矩阵,形状为[num_edges,num_edge_features]; +* data.y:计算损失所需的目标数据,target,针对训练的目标可能有不同的形状,比如节点级别的形状为[num_nodes,*],或者图级别的形状为[1,*]; + +```py +import torch +from torch_geometric.data import Data + +edge_index = torch.tensor([[0, 1, 1, 2], + [1, 0, 2, 1]], dtype=torch.long) +x = torch.tensor([[-1], [0], [1]], dtype=torch.float) + +data = Data(x=x, edge_index=edge_index) +print(data) + +# 将数据集迁移到GPU上 +device=torch.device("cuda") +data=data.to(device) +``` + + +* Data的其他属性 +``` +data.num_nodes +data.num_edges +data.num_node_features +data.has_isolated_nodes() +data.has_self_loops() +data.is_directed() +``` + +## 3 数据集Dataset + +### Dataset实例 + +我们可以看到数据集中的第一个图包含 37 个节点,每个节点有 3 个特征。 有 168/2 = 84 条无向边,并且该图恰好分配给一个类。 此外,数据对象正好持有一个图级别目标。 +```py +from torch_geometric.datasets import TUDataset + +dataset = TUDataset(root='/home/ykl/ENZYMES', name='ENZYMES') + +print(len(dataset)) +print(dataset.num_classes) + + +print(dataset.num_node_labels) +print(dataset.num_node_features) +print(dataset.num_node_attributes) + +print(dataset.num_edge_labels) +print(dataset.num_edge_features) +print(dataset.num_edge_attributes) + + +print(dataset.num_features) + +``` + +* 打乱并划分数据集 + +```py +# 使用切片、长或布尔张量来分割数据集。 +划分测试集和训练集 + +torch.manual_seed(12345) +dataset = dataset.shuffle() + +train_dataset = dataset[:150] +test_dataset = dataset[150:] +``` +### Dataset中的Data对象 +Data对象为每个节点保存了一个标签,以及附加的节点级属性:train_mask,val_mask,test_mask,其中: + +* train_mask:表示针对哪些节点进行训练(140个节点); +* val_mask:表示针对哪些节点进行验证(500个节点); +* test_mask:表示针对哪些节点进行测试(1000个节点) + +``` +data=dataset[0] + +print(data.is_undirected()) # True +print(data.num_nodes) # 2708 +print(data.train_mask.sum().item()) # 140 +print(data.val_mask.sum().item()) # 500 +print(data.test_mask.sum().item()) # 1000 + +``` + + + +## 4 数据加载Dataloader +神经网络通常以批量方式进行训练。PyG 通过创建稀疏块对角邻接矩阵(由 定义edge_index)并在节点维度中连接特征和目标矩阵来实现小批量的并行化。这种组合允许在一批示例中使用不同数量的节点和边 + +$$ +\begin{split}\mathbf{A} = \begin{bmatrix} \mathbf{A}_1 & & \\ & \ddots & \\ & & \mathbf{A}_n \end{bmatrix}, \qquad \mathbf {X} = \begin{bmatrix} \mathbf{X}_1 \\ \vdots \\ \mathbf{X}_n \end{bmatrix}, \qquad \mathbf{Y} = \begin{bmatrix} \mathbf{Y }_1 \\ \vdots \\ \mathbf{Y}_n \end{bmatrix}\end{split} +$$ + +```py +from torch_geometric.datasets import TUDataset +from torch_geometric.data import DataLoader + +dataset=TUDataset(root='/home/ykl/ENZYMES',name='ENZYMES') +loader=DataLoader(dataset,batch_size=32,shuffle=True) + + +for batch in loader: + print(batch.num_graphs) # 32 +``` + + +## 5 数据转换 + + + + +### 自定义Dataset +InMemoryDataset 中有下列四个函数需要我们实现: + + +* raw_file_names() +返回一个包含所有未处理过的数据文件的文件名的列表。起始也可以返回一个空列表,然后在后面要说的 process() 函数里再定义。 + +* processed_file_names() +返回一个包含所有处理过的数据文件的文件名的列表。 + +* download() +如果在数据加载前需要先下载,则在这里定义下载过程,下载到 self.raw_dir 中定义的文件夹位置。如果不需要下载,返回 pass 即可。 + +* process() +这是最重要的一个函数,我们需要在这个函数里把数据处理成一个 Data 对象。 + +```py +import torch +from torch_geometric.data import InMemoryDataset +from tqdm import tqdm + +class YooChooseBinaryDataset(InMemoryDataset): + def __init__(self, root, transform=None, pre_transform=None): + super(YooChooseBinaryDataset, self).__init__(root, transform, pre_transform) + self.data, self.slices = torch.load(self.processed_paths[0]) + + @property + def raw_file_names(self): + return [] + @property + def processed_file_names(self): + return ['../input/yoochoose_click_binary_1M_sess.dataset'] + + def download(self): + pass + + def process(self): + + data_list = [] + + # process by session_id + grouped = df.groupby('session_id') + for session_id, group in tqdm(grouped): + sess_item_id = LabelEncoder().fit_transform(group.item_id) + group = group.reset_index(drop=True) + group['sess_item_id'] = sess_item_id + node_features = group.loc[group.session_id==session_id,['sess_item_id','item_id']].sort_values('sess_item_id').item_id.drop_duplicates().values + + node_features = torch.LongTensor(node_features).unsqueeze(1) + target_nodes = group.sess_item_id.values[1:] + source_nodes = group.sess_item_id.values[:-1] + + edge_index = torch.tensor([source_nodes, target_nodes], dtype=torch.long) + x = node_features + + y = torch.FloatTensor([group.label.values[0]]) + + data = Data(x=x, edge_index=edge_index, y=y) + data_list.append(data) + + data, slices = self.collate(data_list) + torch.save((data, slices), self.processed_paths[0]) +``` + diff --git a/pytorch/PyG/2 模型.md b/pytorch/PyG/2 模型.md new file mode 100644 index 00000000..07cc9546 --- /dev/null +++ b/pytorch/PyG/2 模型.md @@ -0,0 +1,95 @@ +# 消息传递网络 + + +## 1 原理 + +Message Passing 是图网络中学习 node embedding 的重要方法。 + +### 公式 +$$ +\mathbf{x}_i^{(k)} = \gamma^{(k)} \left( \mathbf{x}_i^{(k-1)}, \square_{j \in \mathcal{N} (i)} \, \phi^{(k)}\left(\mathbf{x}_i^{(k-1)}, \mathbf{x}_j^{(k-1)},\mathbf{ e}_{j,i}\right) \right), +$$ + +x 表示表格节点的 embedding,e 表示边的特征,ϕ 表示 message 函数,□ 表示聚合 aggregation 函数,γ 表示 update 函数。上标表示层的 index,比如说,当 k = 1 时,x 则表示所有输入网络的图结构的数据。 + + +### 函数 + +* propagate(edge_index, size=None, **kwargs) +这个函数最终会调用 message 和 update 函数。 +* message(**kwargs) +这个函数定义了对于每个节点对 (xi,xj),怎样生成信息(message)。 + +* update(aggr_out, **kwargs) +这个函数利用聚合好的信息(message)更新每个节点的 embedding。 + +### 传播消息 +``` +torch_geometric.nn.MessagePassing.propagate(edge_index, size=None, **kwargs) +``` +* 初始化时调用这个函数开始传播消息。输入边的索引和所有其他的用于构造消息和更新节点嵌入的数据。值得注意的是,这个propagate()方法不仅可以在形状为 [N, N] 的对称邻接矩阵中交换消息,还可以在一般的稀疏分配矩阵中交换消息,例如二分图,形状为[N, M]的话只需要 size=(N, M) 作为一个额外的参数就可以。如果设置为None,那么分配的矩阵会被假设为对称的。对于具有两组独立节点和索引的二分图,并且每组保存自己的信息,这种拆分可以通过在传递过程中将信息作为一个元组来标记。例如 x=(x_row, x_col),以此表明不同集合中的节点关系。 + +### 创建消息 + +``` +torch_geometric.nn.MessagePassing.message() +``` +* 以函数的方式构造消息,如果 flow="source_to_target",那么对每条边做此操作,即将消息从节点传播到节点;如果 flow="target_to_source",那么对每条边做此操作,即将消息从节点传播到节点。任何用于构造消息的输入都可以作为参数传递给 propagate() 函数。另外,通过将_i或者_j加在变量名后面可以将特征映射到相应的节点和,例如 x_i 和 x_j。 + + + +### 更新表示 +``` +torch_geometric.nn.MessagePassing.update() +``` + +* 以函数 的方式对每一个节点 进行更新节点嵌入的操作。将聚合操作后的输出结果作为第一个参数,任何需要在初始化时传递给 propagate() 函数的参数作为输入。 + +MessagePassing 类是实现各种图神经网络模型的基础。从一个图神经层到另一个图神经进行的消息传递操作可以分为三个计算层次: + +* 第一层计算函数操作,这属于创建消息的过程。输入为上一层的节点特征和边关系,可以指定消息的流向。(边关系以索引的形式进行查找)利用了图结构数据的边关系。 + +* 第二层计算函数操作,这属于邻域聚合的过程。经过第一层的操作,已经按边关系建立了节点间的消息,表示节点的(一阶)邻居节点,即限定了消息只在指定节点的邻域范围内传递。的 add,mean 和 max 都不会因为邻居节点的顺序排列问题而产生不同的结果。利用了图结构数据中的结构信息。 + +* 第三层计算函数操作,这属于更新特征的过程。经过邻域上的消息传递之后,将邻域上的信息聚合到目标节点,然后更新节点的特征,作为这一层的输出。 +### SAGE示例 + +```py +class SAGEConv(MessagePassing): + def __init__(self, in_channels, out_channels): + super(SAGEConv, self).__init__(aggr='max') + self.lin = torch.nn.Linear(in_channels, out_channels) + self.act = torch.nn.ReLU() + + def forward(self, x, edge_index): + # x has shape [N, in_channels] + # edge_index has shape [2, E] + + + edge_index, _ = remove_self_loops(edge_index) + edge_index, _ = add_self_loops(edge_index, num_nodes=x.size(0)) + + + return self.propagate(edge_index, size=(x.size(0), x.size(0)), x=x) + + def message(self, x_j): + # x_j has shape [E, in_channels] + + x_j = self.lin(x_j) + x_j = self.act(x_j) + + return x_j + + def update(self, aggr_out, x): + # aggr_out has shape [N, out_channels] + + + new_embedding = torch.cat([aggr_out, x], dim=1) + + new_embedding = self.update_lin(new_embedding) + new_embedding = self.update_act(new_embedding) + + return new_embedding +``` + + diff --git a/pytorch/PyG/3 训练.md b/pytorch/PyG/3 训练.md new file mode 100644 index 00000000..b2c1009c --- /dev/null +++ b/pytorch/PyG/3 训练.md @@ -0,0 +1,429 @@ +## 1 图分类示例 + +### 创建数据集 +``` + +import torch +from torch_geometric.datasets import TUDataset + +dataset = TUDataset('/home/ykl/TUDataset', name = 'MUTAG') + +print() +print(f'Dataset: {dataset}:') +print('====================') +print(f'Number of graphs: {len(dataset)}') +print(f'Number of features: {dataset.num_features}') +print(f'Number of classes: {dataset.num_classes}') + +data = dataset[0] # Get the first graph object. + +print() +print(data) +print('=============================================================') + +# Gather some statistics about the first graph. +print(f'Number of nodes: {data.num_nodes}') +print(f'Number of edges: {data.num_edges}') +print(f'Average node degree: {data.num_edges / data.num_nodes:.2f}') +print(f'Has isolated nodes: {data.has_isolated_nodes()}') +print(f'Has self-loops: {data.has_self_loops()}') +print(f'Is undirected: {data.is_undirected()}') +``` + +### 划分测试集和训练集 +```py +torch.manual_seed(12345) +dataset = dataset.shuffle() + +train_dataset = dataset[:150] +test_dataset = dataset[150:] + +print(f'Number of training graphs: {len(train_dataset)}') +print(f'Number of test graphs: {len(test_dataset)}') +``` + +### 加载数据集 +```py +from torch_geometric.loader import DataLoader + +train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) +test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False) + +for step, data in enumerate(train_loader): + print(f'Step {step + 1}:') + print('=======') + print(f'Number of graphs in the current batch: {data.num_graphs}') + print(data) + print() +``` + +### 创建模型 +```py +from torch.nn import Linear +import torch.nn.functional as F +from torch_geometric.nn import GCNConv +from torch_geometric.nn import global_mean_pool + +class GCN(torch.nn.Module): + def __init__(self, hidden_channels): + super(GCN, self).__init__() + torch.manual_seed(12345) + self.conv1 = GCNConv(dataset.num_node_features, hidden_channels) + self.conv2 = GCNConv(hidden_channels, hidden_channels) + self.conv3 = GCNConv(hidden_channels, hidden_channels) + self.lin = Linear(hidden_channels, dataset.num_classes) + + def forward(self, x, edge_index, batch): + # 1. 获得节点嵌入 + x = self.conv1(x, edge_index) + x = x.relu() + x = self.conv2(x, edge_index) + x = x.relu() + x = self.conv3(x, edge_index) + + # 2. Readout layer + x = global_mean_pool(x, batch) # [batch_size, hidden_channels] + + # 3. 分类器 + x = F.dropout(x, p=0.5, training=self.training) + x = self.lin(x) + + return x + +model = GCN(hidden_channels=64) +print(model) +``` +### 开始训练 +```py +model = GCN(hidden_channels=64) +optimizer = torch.optim.Adam(model.parameters(), lr=0.01) +criterion = torch.nn.CrossEntropyLoss() + +def train(): + model.train() + + for data in train_loader: + optimizer.zero_grad() + + out = model(data.x, data.edge_index, data.batch) + loss = criterion(out, data.y) + + loss.backward() + optimizer.step() + +def test(loader): + model.eval() + + correct = 0 + for data in loader: # 批遍历测试集数据集。 + out = model(data.x, data.edge_index, data.batch) # 一次前向传播 + pred = out.argmax(dim=1) # 使用概率最高的类别 + correct += int((pred == data.y).sum()) # 检查真实标签 + return correct / len(loader.dataset) + +for epoch in range(1, 121): + train() + train_acc = test(train_loader) + test_acc = test(test_loader) + print(f'Epoch: {epoch:03d}, Train Acc: {train_acc:.4f}, Test Acc: {test_acc:.4f}') +``` + + +## 2 图节点分类实例 + + + +1. 增加自连接到邻接矩阵,即邻接矩阵的对角线元素为1,得到[公式]; +2. 对节点的特征矩阵进行线性变换,将特征变换到维度D; +3. 使用函数​ [公式] 对节点特征进行规范化, 也就是乘以参数矩阵 [公式] 再乘以归一化的拉普拉斯矩阵; +4. 对邻居节点特征进行聚合操作,这里是求和; +5. 返回新的节点embedding + + +将卷积操作推广到不规则数据通常表示为邻域聚合(neighborhood aggregation)或消息传递(message passing)。在 PyTorch Geometric 中将各种图神经网络中的邻域聚合方法统一到一种消息传递网络的架构中。 + +```py +import torch +import torch.nn.functional as F +from torch_geometric.nn import GCNConv + +#数据集加载 +from torch_geometric.datasets import Planetoid +dataset = Planetoid(root='/home/ykl/Cora', name='Cora') + +#网络定义 +class Net(torch.nn.Module): + def __init__(self): + super(Net, self).__init__() + self.conv1 = GCNConv(dataset.num_node_features, 16) + self.conv2 = GCNConv(16, dataset.num_classes) + + def forward(self, data): + x, edge_index = data.x, data.edge_index + + x = self.conv1(x, edge_index) + x = F.relu(x) + x = F.dropout(x, training=self.training) + x = self.conv2(x, edge_index) + + return F.log_softmax(x, dim=1) + +device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') +model = Net().to(device) +data = dataset[0].to(device) +optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) + +#网络训练 +model.train() +for epoch in range(200): + optimizer.zero_grad() + out = model(data) + loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask]) + loss.backward() + optimizer.step() + +#测试 +model.eval() +_, pred = model(data).max(dim=1) +correct = float(pred[data.test_mask].eq(data.y[data.test_mask]).sum().item()) +acc = correct / data.test_mask.sum().item() +print('Accuracy: {:.4f}'.format(acc)) +``` + + + +## 3 自己创建数据集分类的实例 + +### 获取数据集 +RecSys Challenge 2015 是一个挑战赛,主要目的是创建一个 session-based recommender system。主要任务有两个: + +* 预测经过一系列的点击后,是否会产生购买行为。 +* 预测购买的商品。 + + +![](image/2022-02-22-15-04-25.png) + + +### 数据预处理 + + +下载好数据后,我们先进行一些预处理: +```py +from sklearn.preprocessing import LabelEncoder + +df = pd.read_csv('../input/yoochoose-click.dat', header=None) +df.columns=['session_id','timestamp','item_id','category'] + +buy_df = pd.read_csv('../input/yoochoose-buys.dat', header=None) +buy_df.columns=['session_id','timestamp','item_id','price','quantity'] + +item_encoder = LabelEncoder() +df['item_id'] = item_encoder.fit_transform(df.item_id) +df.head() +``` + +因为数据太多,我们随机进行取样以方便讲解: + +``` +#randomly sample a couple of them +sampled_session_id = np.random.choice(df.session_id.unique(), 1000000, replace=False) +df = df.loc[df.session_id.isin(sampled_session_id)] +df.nunique() +``` + +获取标签,即对于某个特定的 session,是否产生了购买行为,我们只需要检查文件 yoochoose-clicks.dat 中的 session_id 是否在文件 yoochoose-buys.dat 中出现即可: +```py +df['label'] = df.session_id.isin(buy_df.session_id) +df.head() +``` + + +### 创建 Dataset + + +这里我们将预处理过的数据创建成为 Dataset 对象。对于每个 session,里面的每个商品(item)看作一个节点,因此每个 session 里所有的商品组成一个图。 + +我们将数据集按照 session_id 进行分组,分组过程中 item_id 也要被重新编码,因为对于每个图,每个节点的 index 应该从 0 开始 + +```py +import torch +from torch_geometric.data import InMemoryDataset +from tqdm import tqdm + +class YooChooseBinaryDataset(InMemoryDataset): + def __init__(self, root, transform=None, pre_transform=None): + super(YooChooseBinaryDataset, self).__init__(root, transform, pre_transform) + self.data, self.slices = torch.load(self.processed_paths[0]) + + @property + def raw_file_names(self): + return [] + @property + def processed_file_names(self): + return ['../input/yoochoose_click_binary_1M_sess.dataset'] + + def download(self): + pass + + def process(self): + + data_list = [] + + # process by session_id + grouped = df.groupby('session_id') + for session_id, group in tqdm(grouped): + sess_item_id = LabelEncoder().fit_transform(group.item_id) + group = group.reset_index(drop=True) + group['sess_item_id'] = sess_item_id + node_features = group.loc[group.session_id==session_id,['sess_item_id','item_id']].sort_values('sess_item_id').item_id.drop_duplicates().values + + node_features = torch.LongTensor(node_features).unsqueeze(1) + target_nodes = group.sess_item_id.values[1:] + source_nodes = group.sess_item_id.values[:-1] + + edge_index = torch.tensor([source_nodes, target_nodes], dtype=torch.long) + x = node_features + + y = torch.FloatTensor([group.label.values[0]]) + + data = Data(x=x, edge_index=edge_index, y=y) + data_list.append(data) + + data, slices = self.collate(data_list) + torch.save((data, slices), self.processed_paths[0]) +``` + +然后我们对数据集进行随机排序,分成 training, validation 和 testing 三个子数据集: +```py +dataset = dataset.shuffle() +train_dataset = dataset[:800000] +val_dataset = dataset[800000:900000] +test_dataset = dataset[900000:] +len(train_dataset), len(val_dataset), len(test_dataset) +``` + +### 定义模型 + +```py +embed_dim = 128 +from torch_geometric.nn import TopKPooling +from torch_geometric.nn import global_mean_pool as gap, global_max_pool as gmp +import torch.nn.functional as F +class Net(torch.nn.Module): + def __init__(self): + super(Net, self).__init__() + + self.conv1 = SAGEConv(embed_dim, 128) + self.pool1 = TopKPooling(128, ratio=0.8) + self.conv2 = SAGEConv(128, 128) + self.pool2 = TopKPooling(128, ratio=0.8) + self.conv3 = SAGEConv(128, 128) + self.pool3 = TopKPooling(128, ratio=0.8) + self.item_embedding = torch.nn.Embedding(num_embeddings=df.item_id.max() +1, embedding_dim=embed_dim) + self.lin1 = torch.nn.Linear(256, 128) + self.lin2 = torch.nn.Linear(128, 64) + self.lin3 = torch.nn.Linear(64, 1) + self.bn1 = torch.nn.BatchNorm1d(128) + self.bn2 = torch.nn.BatchNorm1d(64) + self.act1 = torch.nn.ReLU() + self.act2 = torch.nn.ReLU() + + def forward(self, data): + x, edge_index, batch = data.x, data.edge_index, data.batch + x = self.item_embedding(x) + x = x.squeeze(1) + + x = F.relu(self.conv1(x, edge_index)) + + x, edge_index, _, batch, _ = self.pool1(x, edge_index, None, batch) + x1 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1) + + x = F.relu(self.conv2(x, edge_index)) + + x, edge_index, _, batch, _ = self.pool2(x, edge_index, None, batch) + x2 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1) + + x = F.relu(self.conv3(x, edge_index)) + + x, edge_index, _, batch, _ = self.pool3(x, edge_index, None, batch) + x3 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1) + + x = x1 + x2 + x3 + + x = self.lin1(x) + x = self.act1(x) + x = self.lin2(x) + x = self.act2(x) + x = F.dropout(x, p=0.5, training=self.training) + + x = torch.sigmoid(self.lin3(x)).squeeze(1) + + return x +``` + + +### 训练模型 + +训练 +训练过程中,我们使用 Adam 优化器,学习率 0.005,损失函数是 BCE: +```py +def train(): + model.train() + + loss_all = 0 + for data in train_loader: + data = data.to(device) + optimizer.zero_grad() + output = model(data) + label = data.y.to(device) + loss = crit(output, label) + loss.backward() + loss_all += data.num_graphs * loss.item() + optimizer.step() + return loss_all / len(train_dataset) + +device = torch.device('cuda') +model = Net().to(device) +optimizer = torch.optim.Adam(model.parameters(), lr=0.005) +crit = torch.nn.BCELoss() +train_loader = DataLoader(train_dataset, batch_size=batch_size) +for epoch in range(num_epochs): + train() +``` + + +### 验证模型 + +Validation +这个数据集非常的不平衡,因为大多数的 session 里没有购买行为。也就是说,如果一个模型将所有的结果都预测为 false,也能达到 90% 的准确率。因此,这里我们不使用 accuracy 作为评测标准,而是使用 Area Under Curve (AUC): +```py +def evaluate(loader): + model.eval() + + predictions = [] + labels = [] + + with torch.no_grad(): + for data in loader: + + data = data.to(device) + pred = model(data).detach().cpu().numpy() + + label = data.y.detach().cpu().numpy() + predictions.append(pred) + labels.append(label) +``` + + + +### 训练结果 + +``` +for epoch in range(1): + loss = train() + train_acc = evaluate(train_loader) + val_acc = evaluate(val_loader) + test_acc = evaluate(test_loader) + print('Epoch: {:03d}, Loss: {:.5f}, Train Auc: {:.5f}, Val Auc: {:.5f}, Test Auc: {:.5f}'. + format(epoch, loss, train_acc, val_acc, test_acc)) +``` \ No newline at end of file diff --git a/pytorch/PyG/image/2022-02-22-15-04-25.png b/pytorch/PyG/image/2022-02-22-15-04-25.png new file mode 100644 index 0000000000000000000000000000000000000000..a8b939bf19996a7427951c845e24da1cec566b67 GIT binary patch literal 57749 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 L4Y>*#^N{}qMzUj0 literal 0 HcmV?d00001 diff --git a/pytorch/官方教程/00 Pytorch 概述.md b/pytorch/官方教程/00 Pytorch 概述.md index cdd65f9b..ccbef5c6 100644 --- a/pytorch/官方教程/00 Pytorch 概述.md +++ b/pytorch/官方教程/00 Pytorch 概述.md @@ -10,16 +10,16 @@ 1. 获取数据集 2. 数据预处理 -3. 训练模型 - 1. 创建模型:torch.nn.Model.__init__定义具有一些可学习参数(或权重)的神经网络 - 2. 训练算法: - 1. 正向传播:torch.nn.Model.forward通过网络处理输入,进行正向传播 - 2. 计算损失:torch.nn.loss输出正确的距离有多远 - 3. 反向传播:torch.tensor.backward将梯度传播回网络参数 - 4. 更新权重:troch.optim通常使用简单的更新规则来更新网络的权重:weight = weight - learning_rate * gradient - 5. 迭代循环:重复以上步骤直到(精确度满足要求 或者 迭代次数到达上限) -4. 验证模型 -5. 使用模型 +3. 创建模型 + 1. 定义模型:torch.nn.Model.__init__定义具有一些可学习参数(或权重)的神经网络 + 2. 定义损失函数、训练算法、验证方法 +4. 训练模型 + 1. 正向传播:torch.nn.Model.forward通过网络处理输入,进行正向传播 + 2. 计算损失:torch.nn.loss输出正确的距离有多远 + 3. 反向传播:torch.tensor.backward将梯度传播回网络参数 + 4. 更新权重:troch.optim通常使用简单的更新规则来更新网络的权重:weight = weight - learning_rate * gradient + 5. 迭代循环:重复以上步骤直到(精确度满足要求 或者 迭代次数到达上限) +5. 验证模型 ## 术语 diff --git a/工作日志/2022年2月16日-今日计划.md b/工作日志/2022年2月16日-今日计划.md new file mode 100644 index 00000000..b69064c6 --- /dev/null +++ b/工作日志/2022年2月16日-今日计划.md @@ -0,0 +1,10 @@ +## 任务 + +1. 完成神经网络的二次设计。加入嵌入层。 +2. 完成图特征的提取和简化工作 + + +## 收获 +> 距离上次离开,正好二十天,浪费了大量时间吧。本来可以初七就返回学校的。本来可以晚点回家的。站在事后人的角度来说应该有很多时间。我现在很紧张很焦虑。需要通过一些手段,来让自己快速恢复到之前的状态。在完成这个月的计划之前,尽量不要休息。开始吧。工作的流程和事件安排,还是主要在毕业设计内部进行,这里只是用来复述一下之前的工作安排。记录其中的工作安排。进行双向完成的方案。tmd这个月只有28天,到月末都不到两周了,我是服了。所以这一周必须完成实验,下一周必须完成系统。 +> +> \ No newline at end of file