碎碎念
VGAE的论文解读和原理解读网上资料也比较多,这里就不多叙述了。
但是先挖一个坑,过段时间我一定会整理一波的!
我是刚深度学习入门的小白,现在主要研究图神经网络方面,对python编程能力较弱,希望可以整理一份详细的代码解读,可以帮助更多像我一样的新手迅速读懂代码。
原文链接
如果打不开GitHub链接可以试试下面的网盘。
夸克网盘:
链接:https://pan.quark.cn/s/a41c4eea2daf
提取码:NyLi
推荐的博文
VGAE(Variational graph auto-encoders)论文详解 - 嘉鱼的文章 - 知乎
https://zhuanlan.zhihu.com/p/78340397
代码解读
主要以cora数据集为示例
注意:注释的话大家先看代码,再看上面对代码的注释解读
项目主要有三个模块,数据读取,数据处理,数据训练。文件目录如下图所示
input_data.py是读出数据,preprocessing.py是数据预处理,model.py是网络模型,train.py是主函数。
data文件保存着一些数据集,这里我们主要以最简单的cora数据集为例,对下面的代码进行详解。
# .x 是训练集节点的特征 # .tx 是测试集节点的特征 # .allx 是有label和无label的训练集节点的特征向量(除了test集之外所有节点的特征集合) # .graph节点之间的边的关系
train.py
import torch
import torch.nn.functional as F
from torch.optim import Adam
from sklearn.metrics import roc_auc_score, average_precision_score
import scipy.sparse as sp
import numpy as np
import os
import time
from input_data import load_data
from preprocessing import *
import args
import model
# Train on CPU (hide GPU) due to memory constraints
# 在cpu上训练因为内存的限制
os.environ['CUDA_VISIBLE_DEVICES'] = ""
# adj是图对应的邻接矩阵,features是所有节点对应的特征
adj, features = load_data(args.dataset)
# Store original adjacency matrix (without diagonal entries) for later
# 存储原始邻接矩阵(不带对角线条目)以备后用
adj_orig = adj
# 将对角线上的所有元素清零,如果不理解后面有详细举例
# np.newaxis的作用就是在这一位置增加一个一维,这一位置指的是np.newaxis所在的位置
# adj.diagonal()[np.newaxis, :]取出adj中n个对角线元素,将其转为1*n的形式
# sp.dia_matri((arr, offset), shape)根据shape的大小,将arr中的元素作为对角线,根据offset进行偏移
# 这里就是提出adj的对角矩阵,相减从而将对角线上的元素清零
adj_orig = adj_orig - sp.dia_matrix((adj_orig.diagonal()[np.newaxis, :], [0]), shape=adj_orig.shape)
adj_orig.eliminate_zeros()
# adj_train是稀疏矩阵的表示形式
# 其他的都是以下形式,且是单边关系
# [[r1,c1],
# [r2,c2],
# ...
# [rn,cn]]
adj_train, train_edges, val_edges, val_edges_false, test_edges, test_edges_false = mask_test_edges(adj)
adj = adj_train
# Some preprocessing
# 对adj做归一化处理 D{-0.5}AD{-0.5},返回的时候做了形式的转换
# adj_norm[0]=coods
# adj_norm[1]=values
# adj_norm[2]=shape
adj_norm = preprocess_graph(adj)
# 节点的数量=adj的行数
num_nodes = adj.shape[0]
# feature转换形式,返回 coords, values, shape
features = sparse_to_tuple(features.tocoo())
# feature的数量=features的第三个元素即shape的第2个值
num_features = features[2][1]
# features_nonzero的数量=features的第二个元素即values的行数
features_nonzero = features[1].shape[0]
# Create Model
# 创建模型
# 注意,adj的每个元素非1即0。pos_weight是用于训练的邻接矩阵中负样本边(既不存在的边)和正样本边的倍数(即比值),这个数值在二分类交叉熵损失函数中用到,
# 如果正样本边所占的比例和负样本边所占比例失衡,比如正样本边很多,负样本边很少,那么在求loss的时候可以提供weight参数,将正样本边的weight设置小一点,负样本边的weight设置大一点,
# 此时能够很好的平衡两类在loss中的占比,任务效果可以得到进一步提升。参考:https://www.zhihu.com/question/383567632
pos_weight = float(adj.shape[0] * adj.shape[0] - adj.sum()) / adj.sum()
norm = adj.shape[0] * adj.shape[0] / float((adj.shape[0] * adj.shape[0] - adj.sum()) * 2)
# adj_label 是 训练集加上单位矩阵(补充对角线上的元素)
adj_label = adj_train + sp.eye(adj_train.shape[0])
adj_label = sparse_to_tuple(adj_label)
# 后面详解
# 这里就是将adj_norm[0],adj_norm[1],adj_norm[2]的值,将其变为稀疏矩阵的形式
adj_norm = torch.sparse.FloatTensor(torch.LongTensor(adj_norm[0].T),
torch.FloatTensor(adj_norm[1]),
torch.Size(adj_norm[2]))
adj_label = torch.sparse.FloatTensor(torch.LongTensor(adj_label[0].T),
torch.FloatTensor(adj_label[1]),
torch.Size(adj_label[2]))
features = torch.sparse.FloatTensor(torch.LongTensor(features[0].T),
torch.FloatTensor(features[1]),
torch.Size(features[2]))
# 将稀疏矩阵转为稠密矩阵,.view(-1)在降维到一维,参数-1表示该维自适应,如果只有-1表示降维到1维
weight_mask = adj_label.to_dense().view(-1) == 1
# 创建一个全为1的张量,大小和weight_mask相同
weight_tensor = torch.ones(weight_mask.size(0))
# 将值修改为pos_weight
weight_tensor[weight_mask] = pos_weight
# init model and optimizer
# 根据输入的参数args.model,在model文件中找到对应的model,将adj_norm做参数传入对应的model中
model = getattr(model, args.model)(adj_norm)
# getattr() 函数用于返回一个对象属性值。
# 优化器,Adam方式优化,传入模型的参数,和学习率
optimizer = Adam(model.parameters(), lr=args.learning_rate)
def get_scores(edges_pos, edges_neg, adj_rec):
'''
:param edges_pos: 正确的边
# [[r1,c1],
# [r2,c2],
# ...
# [rn,cn]]
:param edges_neg: 错误的边
:param adj_rec: 网络预测的结果
:return:
'''
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# Predict on test set of edges
preds = []
pos = []
for e in edges_pos:
# print(e)
# print(adj_rec[e[0], e[1]])
# 遍历正确的边,找到adj_rec中对应的位置,做sigmoid变化,将元素加入preds数组中
preds.append(sigmoid(adj_rec[e[0], e[1]].item()))
# 取出原始邻接矩阵对应位置的值(应该都是1)
pos.append(adj_orig[e[0], e[1]])
preds_neg = []
neg = []
# 同理,遍历错误的边
for e in edges_neg:
preds_neg.append(sigmoid(adj_rec[e[0], e[1]].data))
# 应该都是0
neg.append(adj_orig[e[0], e[1]])
# 将正确的结果和错误的结果横向拼接
preds_all = np.hstack([preds, preds_neg])
labels_all = np.hstack([np.ones(len(preds)), np.zeros(len(preds_neg))])
# 计算roc得分
roc_score = roc_auc_score(labels_all, preds_all)
# 计算ap得分
ap_score = average_precision_score(labels_all, preds_all)
return roc_score, ap_score
def get_acc(adj_rec, adj_label):
'''计算网络运算结果和实际标签的匹配度'''
# adj_label转换为稠密的矩阵,再转为一维
labels_all = adj_label.to_dense().view(-1).long()
# 将adj_rec中>0.5的值置为true,<0.5的值置为false
preds_all = (adj_rec > 0.5).view(-1).long()
# 这里注意preds_all中的true等价于1,false等价于0,所有可以进行比较
# 计算preds_all和labels_all相同的个数,计算预测的准确率
accuracy = (preds_all == labels_all).sum().float() / labels_all.size(0)
return accuracy
# train model
# 开始训练
for epoch in range(args.num_epoch):
t = time.time()
# 将特征传入模型,得到返回值,注意得到的A_pred每个元素不再是非1即0
A_pred = model(features)
# 优化器清零
optimizer.zero_grad()
# 计算loss
loss = log_lik = norm*F.binary_cross_entropy(A_pred.view(-1), adj_label.to_dense().view(-1), weight = weight_tensor)
if args.model == 'VGAE':
# kl_divergence就是正态分布的KL散度,即n个(0.5*(1+log(sigma^2)-mu^2-sigma^2))的和,n为图中节点的数量,也就是这里的A_pred.size(0)
# 2*model.logstd即为2*log(sigma),根据运算法则,log(sigma^2)=2*log(sigma);model.mean**2即为mu^2;torch.exp(model.logstd)**2即为sigma^2
# 1+log(sigma^2)-mu^2-sigma^2
# sum(1)表示矩阵每一行内元素求和
kl_divergence = 0.5/ A_pred.size(0) * (1 + 2*model.logstd - model.mean**2 - torch.exp(model.logstd)**2).sum(1).mean()
loss -= kl_divergence
loss.backward()
optimizer.step()
# 计算A_pred和adj_label匹配度
train_acc = get_acc(A_pred,adj_label)
# 计算roc和ap得分函数
val_roc, val_ap = get_scores(val_edges, val_edges_false, A_pred)
print("Epoch:", '%04d' % (epoch + 1), "train_loss=", "{:.5f}".format(loss.item()),
"train_acc=", "{:.5f}".format(train_acc), "val_roc=", "{:.5f}".format(val_roc),
"val_ap=", "{:.5f}".format(val_ap),
"time=", "{:.5f}".format(time.time() - t))
test_roc, test_ap = get_scores(test_edges, test_edges_false, A_pred)
print("End of training!", "test_roc=", "{:.5f}".format(test_roc),
"test_ap=", "{:.5f}".format(test_ap))
input_data.py
'''
****************NOTE*****************
CREDITS : Thomas Kipf
since datasets are the same as those in kipf's implementation,
Their preprocessing source was used as-is.
*************************************
'''
import numpy as np
import sys
import pickle as pkl
import networkx as nx
import scipy.sparse as sp
def parse_index_file(filename):
# 把.test.index文件中的数据(test集节点的索引)一行一行读出来,
# line.strip()删除line的头尾的空格,将结果存在index数组里面
index = []
for line in open(filename):
index.append(int(line.strip()))
return index
def load_data(dataset):
# load the data: x, tx, allx, graph
# .x 是训练集节点的特征
# .tx 是测试集节点的特征
# .allx 是有label和无label的训练集节点的特征向量(除了test集之外所有节点的特征集合)
# .graph节点之间的边的关系
# 里面是字典的形式{index:[index_of_neighbor_nodes]}
names = ['x', 'tx', 'allx', 'graph']
objects = []
for i in range(len(names)):
with open("data/ind.{}.{}".format(dataset, names[i]), 'rb') as f:
# python 版本问题 python版本>3执行这个条件
# 作用就是读文件中的每一行,把每一行添加到objects数组里面
if sys.version_info > (3, 0):
objects.append(pkl.load(f, encoding='latin1'))
else:
objects.append(pkl.load(f))
# 转换成元组
x, tx, allx, graph = tuple(objects)
# 读出.test.index文件中的元素,返回一个数组,里面都是测试集节点的索引(.test.index可以直接打开看)
test_idx_reorder = parse_index_file("data/ind.{}.test.index".format(dataset))
# 对索引按照从小到大的顺序重新排列, np.sort()排序作用
test_idx_range = np.sort(test_idx_reorder)
if dataset == 'citeseer':
# Fix citeseer dataset (there are some isolated nodes in the graph)
# Find isolated nodes, add them as zero-vecs into the right position
test_idx_range_full = range(min(test_idx_reorder), max(test_idx_reorder)+1)
tx_extended = sp.lil_matrix((len(test_idx_range_full), x.shape[1]))
tx_extended[test_idx_range-min(test_idx_range), :] = tx
tx = tx_extended
# sp.vstack垂直方向拼接(即增加行,上半部分是allx,下半部分是tx)
features = sp.vstack((allx, tx)).tolil()
# 将下半部分tx拼接进来的测试集特征按照test_idx_range的顺序重新排序
features[test_idx_reorder, :] = features[test_idx_range, :]
# 下面一行代码拆开理解
# nx.from_dict_of_lists(graph)将字典graph转为列表
# nx.adjacency_matrix()将列表转为邻接矩阵
adj = nx.adjacency_matrix(nx.from_dict_of_lists(graph))
# adj对应着图的邻接矩阵,features所有节点对应的特征
return adj, features
preprocessing.py
'''
****************NOTE*****************
CREDITS : Thomas Kipf
since datasets are the same as those in kipf's implementation,
Their preprocessing source was used as-is.
*************************************
'''
import numpy as np
import scipy.sparse as sp
def sparse_to_tuple(sparse_mx):
# 如果传入的数据不是coo的形式(一种稀疏矩阵的表示形式) ((row, col) data)
# 就把它变为这种形式
if not sp.isspmatrix_coo(sparse_mx):
sparse_mx = sparse_mx.tocoo()
# 垂直方向拼接,即竖直方向上增加行,将稀疏矩阵的行和列拼接起来
# 得到[[r1, r2,..., rn], [c1, c2,..., cn]]
# 然后再转置
coords = np.vstack((sparse_mx.row, sparse_mx.col)).transpose()
# 将稀疏矩阵中所有值(非零)取出来
values = sparse_mx.data
# 记录稀疏矩阵的形状
shape = sparse_mx.shape
return coords, values, shape
def preprocess_graph(adj):
# 先将adj转换成稀疏矩阵的形式
adj = sp.coo_matrix(adj)
# sp.eye(a)创建大小为a*a的单位矩阵,adj.shape[0]就是adj的行数
# 这一步就是添加对角线
adj_ = adj + sp.eye(adj.shape[0])
# adj_.sum(1)给数据每一行单独求和,adj_.sum(0)给数组每一列单独求和
rowsum = np.array(adj_.sum(1))
# 拆分下一行
# np.power(rowsum, -0.5)对rowsum中的每个元素做 -0.5次方的运算
# .flatten()将高维数组降为低维,按道理rowsum本来就是一维,降维也还是一维
# sp.diags(array)这里的意思是将array变为对角矩阵
degree_mat_inv_sqrt = sp.diags(np.power(rowsum, -0.5).flatten())
# 拆分下一行 联系上下文实际就是 D{-0.5}AD{-0.5}的过程,矩阵乘法
# adj_.dot(degree_mat_inv_sqrt) = A·D
# .transpose() = A·D的转置,因为A是对称矩阵,D是对角矩阵,也是对称的,所以A·D的转置=D·A
# 最后再点乘D
adj_normalized = adj_.dot(degree_mat_inv_sqrt).transpose().dot(degree_mat_inv_sqrt).tocoo()
# 转换一下矩阵的形式再返回
return sparse_to_tuple(adj_normalized)
def mask_test_edges(adj):
# Function to build test set with 10% positive links
# 构建一个测试集,只有10%的连接是正确的
# NOTE: Splits are randomized and results might slightly deviate from reported numbers in the paper.
# 注意:划分是随机的,结果可能会和论文中提到的不一样
# TODO: Clean up.
# 删除对角线(该步骤在train.py中有相同应用,已做解释)
adj = adj - sp.dia_matrix((adj.diagonal()[np.newaxis, :], [0]), shape=adj.shape)
# 消除矩阵中值为0的元素
adj.eliminate_zeros()
# Check that diag is zero:检测对角线上是否为0
assert np.diag(adj.todense()).sum() == 0
# 取上三角部分(理解为:图中的双箭头变单箭头)
adj_triu = sp.triu(adj)
# 转换形式
adj_tuple = sparse_to_tuple(adj_triu)
# 取出边(上三角部分的边)
edges = adj_tuple[0]
# 取出所有的边
edges_all = sparse_to_tuple(adj)[0]
# --------以下是在划分test集和val集
num_test = int(np.floor(edges.shape[0] / 10.))
num_val = int(np.floor(edges.shape[0] / 20.))
# 构建list列表(0,1,2,3,..., edges的行数-1)
all_edge_idx = list(range(edges.shape[0]))
# 随机排序
np.random.shuffle(all_edge_idx)
# 取出index从0到num_val的索引的值
val_edge_idx = all_edge_idx[:num_val]
# 取出index从num_val到(num_val + num_test)的索引的值
test_edge_idx = all_edge_idx[num_val:(num_val + num_test)]
# 根据test_edge_idx,取出edges对应索引位置的边
test_edges = edges[test_edge_idx]
# 根据val_edge_idx,取出edges对应索引位置的边
val_edges = edges[val_edge_idx]
# 删除test集和val集,剩下的就是train集
# np.hstack([test_edge_idx, val_edge_idx])横向拼接,即增加列
train_edges = np.delete(edges, np.hstack([test_edge_idx, val_edge_idx]), axis=0)
# --------
# --------创建一些错误的边
def ismember(a, b, tol=5):
'''判断a,b 是否相同'''
# b[:, None]给b新增加一个维度
# np.round(a - b[:, None], tol) 计算a-b的近似值,保留小数点后tol位
# all()函数用于判断整个数组中的元素的值是否全部满足条件(这里是np.round=0),如果满足条件返回True,否则返回False。
# all()相当于与运算,只要a,b中有一个元素不相同,np.all就为false
rows_close = np.all(np.round(a - b[:, None], tol) == 0, axis=-1)
# any()相当于或运算,只要有一个true就返回true
return np.any(rows_close)
test_edges_false = []
while len(test_edges_false) < len(test_edges):
# 随机生成一个数字,范围是(0, adj的行数)
idx_i = np.random.randint(0, adj.shape[0])
idx_j = np.random.randint(0, adj.shape[0])
if idx_i == idx_j:
continue
# 如果随机生成的边已经存在
if ismember([idx_i, idx_j], edges_all):
continue
if test_edges_false:
# 双向判断该错误边是否已经添加过
if ismember([idx_j, idx_i], np.array(test_edges_false)):
continue
if ismember([idx_i, idx_j], np.array(test_edges_false)):
continue
test_edges_false.append([idx_i, idx_j])
# 同样的逻辑,给val集添加错误边
val_edges_false = []
while len(val_edges_false) < len(val_edges):
idx_i = np.random.randint(0, adj.shape[0])
idx_j = np.random.randint(0, adj.shape[0])
if idx_i == idx_j:
continue
if ismember([idx_i, idx_j], train_edges):
continue
if ismember([idx_j, idx_i], train_edges):
continue
if ismember([idx_i, idx_j], val_edges):
continue
if ismember([idx_j, idx_i], val_edges):
continue
if val_edges_false:
if ismember([idx_j, idx_i], np.array(val_edges_false)):
continue
if ismember([idx_i, idx_j], np.array(val_edges_false)):
continue
val_edges_false.append([idx_i, idx_j])
# 检查test集的错误边,val集的错误边有没有和正确的边重合
assert ~ismember(test_edges_false, edges_all)
assert ~ismember(val_edges_false, edges_all)
# 判断test集和val集有没有和train集重合
assert ~ismember(val_edges, train_edges)
assert ~ismember(test_edges, train_edges)
# 判断test集和val集有没有重合
assert ~ismember(val_edges, test_edges)
# 创建一维数组,大小为训练集的行数,值全为1
data = np.ones(train_edges.shape[0])
# Re-build adj matrix
# 重构adj矩阵为稀疏矩阵形式(row, col) data的形式
# 注意这里的train是二维,train_edges[:, 0]实际上就是非零值对应的所有的行,train_edges[:, 1]实际就是非零值对应的所有列
# [[r1,c1],
# [r2,c2],
# ...
# [rn,cn]]
adj_train = sp.csr_matrix((data, (train_edges[:, 0], train_edges[:, 1])), shape=adj.shape)
# 原矩阵加上对应的转置,即构建图中的双边关系
adj_train = adj_train + adj_train.T
# NOTE: these edge lists only contain single direction of edge!
# 注意这些边的集合仅仅包含了单边的关系
return adj_train, train_edges, val_edges, val_edges_false, test_edges, test_edges_false
model.py
import torch
import torch.nn as nn
import torch.nn.functional as F
import os
import numpy as np
import args
class VGAE(nn.Module):
def __init__(self, adj):
super(VGAE,self).__init__()
self.base_gcn = GraphConvSparse(args.input_dim, args.hidden1_dim, adj)
# lambda是匿名函数,冒号左边是参数,多个参数用逗号隔开,右边是表达式
self.gcn_mean = GraphConvSparse(args.hidden1_dim, args.hidden2_dim, adj, activation=lambda x:x)
self.gcn_logstddev = GraphConvSparse(args.hidden1_dim, args.hidden2_dim, adj, activation=lambda x:x)
def encode(self, X):
hidden = self.base_gcn(X)
self.mean = self.gcn_mean(hidden)
self.logstd = self.gcn_logstddev(hidden)
# torch.randn(a, b)随机生成大小为a,b的张量,值为标准正态分布中抽取的随机值
gaussian_noise = torch.randn(X.size(0), args.hidden2_dim)
sampled_z = gaussian_noise*torch.exp(self.logstd) + self.mean
return sampled_z
def forward(self, X):
# X最开始是传入的特征
Z = self.encode(X)
A_pred = dot_product_decode(Z)
return A_pred
class GraphConvSparse(nn.Module):
def __init__(self, input_dim, output_dim, adj, activation = F.relu, **kwargs):
'''
:param input_dim: 输入维度
:param output_dim: 输出维度
:param adj: 图的邻接矩阵
:param activation: 激活函数。默认为relu
:param kwargs: 其他的参数
'''
super(GraphConvSparse, self).__init__(**kwargs)
self.weight = glorot_init(input_dim, output_dim)
self.adj = adj
self.activation = activation
def forward(self, inputs):
x = inputs
# torch.mm(a, b)矩阵的乘法运算
x = torch.mm(x,self.weight)
x = torch.mm(self.adj, x)
outputs = self.activation(x)
return outputs
def dot_product_decode(Z):
# torch.matmul(a, b),a,b若是2维则是普通的矩阵乘法
A_pred = torch.sigmoid(torch.matmul(Z,Z.t()))
return A_pred
def glorot_init(input_dim, output_dim):
'''随机初始化权重'''
# 知道作用和计算过程即可,为何要这样计算不用深究
init_range = np.sqrt(6.0/(input_dim + output_dim))
# torch.rand(a, b)随机生成a*b大小的张量,值为均匀分布中随机抽取的数
initial = torch.rand(input_dim, output_dim)*2*init_range - init_range
return nn.Parameter(initial)
class GAE(nn.Module):
def __init__(self,adj):
super(GAE,self).__init__()
self.base_gcn = GraphConvSparse(args.input_dim, args.hidden1_dim, adj)
self.gcn_mean = GraphConvSparse(args.hidden1_dim, args.hidden2_dim, adj, activation=lambda x:x)
def encode(self, X):
hidden = self.base_gcn(X)
z = self.mean = self.gcn_mean(hidden)
return z
def forward(self, X):
Z = self.encode(X)
A_pred = dot_product_decode(Z)
return A_pred
# class GraphConv(nn.Module):
# def __init__(self, input_dim, hidden_dim, output_dim):
# super(VGAE,self).__init__()
# self.base_gcn = GraphConvSparse(args.input_dim, args.hidden1_dim, adj)
# self.gcn_mean = GraphConvSparse(args.hidden1_dim, args.hidden2_dim, adj, activation=lambda x:x)
# self.gcn_logstddev = GraphConvSparse(args.hidden1_dim, args.hidden2_dim, adj, activation=lambda x:x)
# def forward(self, X, A):
# out = A*X*self.w0
# out = F.relu(out)
# out = A*X*self.w0
# return out
复杂语法详解
sp.diags()
我参考的这篇博客http://t.csdnimg.cn/JOOAr
sp.diags(diagonals, offset, shape)
diagonals是一个多维向量(不一定规则,例如一个二维向量,但每一行的列数并不相同)
你看这个单词diagonals它是复数形式,可以理解为有多条对角线
offset 代表偏移,若diagonals为2维,那offset元素个数=diagonals的行数
理解一下:每一条对角线都有其对应的偏移
默认为0
值<0:沿着正对角线向下偏移
值>0:沿着正对角线向上偏移
shape 生成的向量的大小,形状
shape可以省略,自适应
代码演示
diagonals = [[1,2,3,4],[5,6,7],[8,9]]
offset = [0,1,-2]
res = sp.diags(diagonals, offset, (4, 4))
print(res.toarray())
'''
[[1. 5. 0. 0.]
[0. 2. 6. 0.]
[8. 0. 3. 7.]
[0. 9. 0. 4.]]
'''
# 整点奇形怪状的
diagonals = [[1,2,3,4],[5,6,7,8,9],[10,11,12]]
offset = [0,1,-2]
res = sp.diags(diagonals, offset, (4, 5))
print(res.toarray())
'''
[[ 1. 5. 0. 0. 0.]
[ 0. 2. 6. 0. 0.]
[10. 0. 3. 7. 0.]
[ 0. 11. 0. 4. 8.]]
'''
np.vstack() np.hstack()
vstack((a,b))将a和b竖直方向拼接
hstack((a,b))将a和b水平方向拼接
i = [1,2,3,4]
j = [5,6,7,8]
ivj = np.vstack((i,j))
print(ivj)
ihj = np.hstack((i,j))
print(ihj)
'''
[[1 2 3 4]
[5 6 7 8]]
[1 2 3 4 5 6 7 8]
'''
sp.dia_matrix()
sp.dia_matrix((arr, offset), shape)根据shape的大小,将arr中的元素作为对角线,根据offset进行偏移
该函数的理解和sp.diags()是完全一样的
但该函数适用范围比sp.diags()窄
sp.diags()和sp.dia_matrix()的区别见下博客链接
torch.sparse.FloatTensor()
该函数的主要作用就是将矩阵转换为稀疏矩阵的形式
pos = torch.tensor([[0,0,0,1,1,2],[0,1,2,0,2,1]])
data = torch.tensor([1,1,1,1,1,1])
res = torch.sparse.FloatTensor(pos, data, (3,3)).to_dense()
print(res)
'''
tensor([[1, 1, 1],
[1, 0, 1],
[0, 1, 0]])
'''
'''
如上,pos这个二维张量,两行分别代表矩阵中所有非零元素的行的位置,和列的位置
将pos转置一下,会更容易理解
[[0,0]
[0,1]
[0,2]
[1,0]
[1,2]
[2,1]
]
data:记录着非零元素的值,这里我们都设置为1
shape:代表原矩阵的形状,一定要和pos记录的位置对应起来
运行该函数时,我还用了.to_dense()将它转换成稠密矩阵的形式(原矩阵)输出,否则输出的就是稀疏矩阵
'''
更详细的解释参考链接http://t.csdnimg.cn/jW4eb
np.newaxis [:, np.newaxis] 和 None [:, None]
np.newaxis的作用就是在它所在的位置增加一个一维,这一位置指的是np.newaxis所在的位置。
np.newaxis的作用和None很类似
i = np.array([1,2,3])
j0 = i[:, np.newaxis]
j1 = i[:, None]
j2 = i[np.newaxis, :]
j3 = i[None, :]
print(i.shape)
print('---------j0----------')
print(j0)
print(j0.shape)
print('---------j1----------')
print(j1)
print(j1.shape)
print('---------j2----------')
print(j2)
print(j2.shape)
print('---------j3----------')
print(j3)
print(j3.shape)
'''
(3,)
---------j0----------
[[1]
[2]
[3]]
(3, 1)
---------j1----------
[[1]
[2]
[3]]
(3, 1)
---------j2----------
[[1 2 3]]
(1, 3)
---------j3----------
[[1 2 3]]
(1, 3)
'''
numpy.all() 与 numpy.any()
roc和ap
PR曲线、ROC曲线、AUC、AP简单梳理 - 宁梧的文章 - 知乎
https://zhuanlan.zhihu.com/p/404597642