代码解读VGAE(Variational graph auto-encoders)

碎碎念

VGAE的论文解读和原理解读网上资料也比较多,这里就不多叙述了。

但是先挖一个坑,过段时间我一定会整理一波的!

我是刚深度学习入门的小白,现在主要研究图神经网络方面,对python编程能力较弱,希望可以整理一份详细的代码解读,可以帮助更多像我一样的新手迅速读懂代码。

原文链接

代码地址: GitHub - DaehanKim/vgae_pytorch: This repository implements variational graph auto encoder by Thomas Kipf.

如果打不开GitHub链接可以试试下面的网盘。

夸克网盘:
链接:https://pan.quark.cn/s/a41c4eea2daf
提取码:NyLi

推荐的博文

http://t.csdnimg.cn/NP1tJ

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()的区别见下博客链接

http://t.csdnimg.cn/mw9Xg

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()

http://t.csdnimg.cn/3EAw1

roc和ap

PR曲线、ROC曲线、AUC、AP简单梳理 - 宁梧的文章 - 知乎
https://zhuanlan.zhihu.com/p/404597642

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值