原创文章~转载请注明出处哦。其他部分内容参见以下链接~

GraphSAGE 代码解析(二) - layers.py

GraphSAGE 代码解析(三) - aggregators.py

GraphSAGE 代码解析(四) - models.py

GraphSAGE代码详解

example_data:

1. toy-ppi-G.json 图的信息

{
directed: false
graph : {
      {name: disjoint_union(,) }
           nodes: [
           { 
test: false
            id: 0
            features: [ ... ]
            val: false
           lable: [ ... ]
             }
             {...}
           ...
             ]             links: [
             { 
test_removed: false
             train_removed: false
             target: 800 # 指向的节点id(默认从小节点指向大节点)
             source: 0 # 从0节点按顺序展示
           }
           {...}
          ...
           ]
      }
}

2. toy-ppi-class_map.json

3. toy-ppi-feats.npy 预训练好得到的features

4. toy-ppi-id_map.json 节点编号与序号的一一对应;数据格式为:{"0": 0, "1": 1,..., "14754": 14754}

5. toy-ppi-walks.txt

从一点出发随机游走到邻居节点的情况,对于每个点取198次(即可能有重复情况)

例如:0    708 表示从0点走到708点。

1. __init__.py

 from __future__ import print_function
#即使在python2.X,使用print就得像python3.X那样加括号使用。 from __future__ import division
# 导入python未来支持的语言特征division(精确除法),
# 当我们没有在程序中导入该特征时,"/"操作符执行的是截断除法(Truncating Division);
# 当我们导入精确除法之后,"/"执行的是精确除法, "//"执行截断除除法

2. unsupervised_train.py

 if __name__ == '__main__':
  tf.app.run()
# https://blog.csdn.net/fxjzzyo/article/details/80466321
# tf.app.run()的作用:通过处理flag解析,然后执行main函数
# 如果你的代码中的入口函数不叫main(),而是一个其他名字的函数,如test(),则你应该这样写入口tf.app.run(test())
# 如果你的代码中的入口函数叫main(),则你就可以把入口写成tf.app.run()
 def main(argv=None):
  print("Loading training data..")
  train_data = load_data(FLAGS.train_prefix, load_walks=True)
  # load_data函数在graphsage.utils中定义   print("Done loading training data..")
  train(train_data)
  # train函数在该文件中定义def train(train_data, test_data=None)

3. utils.py - func: load_data

(1) 读入id_map, class_map

 if isinstance(G.nodes()[0], int):
def conversion(n): return int(n)
else:
def conversion(n): return n

a. isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。

isinstance(object, classinfo)

参数
object -- 实例对象。
classinfo -- 可以是直接或间接类名、基本类型或者由它们组成的元组。

返回值
如果对象的类型与参数二的类型(classinfo)相同则返回 True,否则返回 False。

>>>a = 2
>>> isinstance (a,int)
True
>>> isinstance (a,str)
False
>>> isinstance (a,(str,int,list)) # 是元组中的一个返回 True
True

type() 与 isinstance() 区别:

type() 不会认为子类是一种父类类型,不考虑继承关系。
isinstance() 会认为子类是一种父类类型,考虑继承关系。
如果要判断两个类型是否相同推荐使用 isinstance()。

 class A:
pass class B(A):
pass isinstance(A(), A) # returns True
type(A()) == A # returns True
isinstance(B(), A) # returns True
type(B()) == A # returns False

b. G.nodes()

返回的是图中节点n与节点属性nodedata。https://networkx.github.io/documentation/stable/reference/classes/generated/networkx.Graph.nodes.html

例子:

>>> G = nx.path_graph(3)
>>> list(G.nodes)
[0, 1, 2]
>>> list(G)
[0, 1, 2]

获取nodedata:

>>> G.add_node(1, time='5pm')
>>> G.nodes[0]['foo'] = 'bar'
>>> list(G.nodes(data=True))
[(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
>>> list(G.nodes.data())
[(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})] >>> list(G.nodes(data='foo'))
[(0, 'bar'), (1, None), (2, None)] >>> list(G.nodes(data='time'))
[(0, None), (1, '5pm'), (2, None)] >>> list(G.nodes(data='time', default='Not Available'))
[(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]

If some of your nodes have an attribute and the rest are assumed to have a default attribute value you can create a dictionary from node/attribute pairs using the default keyword argument to guarantee the value is never None:

>>> G = nx.Graph()
>>> G.add_node(0)
>>> G.add_node(1, weight=2)
>>> G.add_node(2, weight=3)
>>> dict(G.nodes(data='weight', default=1))
{0: 1, 1: 2, 2: 3}

----------------------------

在utils.py中,判断G.nodes()[0] 是否为int型(即不带nodedata)。

若为int型,则将n转为int型;否则直接返回n.

b. conversion() 函数

 id_map = json.load(open(prefix + "-id_map.json"))
id_map = {conversion(k): int(v) for k, v in id_map.items()}

前面定义的conversion()函数在id_map这里用到了,把外存中的文件内容读到内存中,用dict类型的id_map存储。

id_map.json文件中数据格式为:{"0": 0, "1": 1,..., "14754": 14754},也即id_map的迭代中k为str类型,v为int型。数据文件中G.nodes()[0] 显然是带nodedata的,也就算一般采用 def conversion(n): return n,返回的n为类型的(就是前面形参k的类型);

但是为什么当G.nodes()[0] 不带nodedata时,要返回int(n)?

c. class_map:  {"0": [.0,1,..], "1": [.0,1,..]...} ?含义?

list(class_map.values()): [ [...], [...], ... ,[...] ]
list(class_map.values())[0]: 表示取第一个[...] =>含义? 
if isinstance(list(class_map.values())[0], list):
def lab_conversion(n): return n
else:
def lab_conversion(n): return int(n)

(2) Remove node

 # Remove all nodes that do not have val/test annotations
# (necessary because of networkx weirdness with the Reddit data)
broken_count = 0
for node in G.nodes():
if not 'val' in G.node[node] or not 'test' in G.node[node]:
G.remove_node(node)
broken_count += 1

这里删除的节点是不具有'val','test'属性 的节点,而不是'val','test' 属性值为None的节点。

区分开 if not 'val' in G.node[node] 和 if not G.node[n]['val']的不同意义。

broken_count  记录删去的没有val 或者 test的属性的节点的数目。

e. G.edges()

 for edge in G.edges():
if (G.node[edge[0]]['val'] or G.node[edge[1]]['val'] or
G.node[edge[0]]['test'] or G.node[edge[1]]['test']):
G[edge[0]][edge[1]]['train_removed'] = True
else:
G[edge[0]][edge[1]]['train_removed'] = False

G.edges() 得到edge_list, [( , ), ( , ), ... ( , )].list中每一个元素是所表示边的两个节点信息。若设置data = True,则会显示边的权重等属性信息。

>>> G = nx.Graph()   # or DiGraph, MultiGraph, MultiDiGraph, etc
>>> G.add_path([0,1,2])
>>> G.add_edge(2,3,weight=5)
>>> G.edges()
[(0, 1), (1, 2), (2, 3)]
>>> G.edges(data=True) # default edge data is {} (empty dictionary)
[(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})]
>>> list(G.edges_iter(data='weight', default=1))
[(0, 1, 1), (1, 2, 1), (2, 3, 5)]
>>> G.edges([0,3])
[(0, 1), (3, 2)]
>>> G.edges(0)
[(0, 1)]

代码中edge对edges迭代,每次去list中的一个元组,而edge[0], edge[1]则分别表示两个顶点。

若两个顶点中至少有一个的val/test不为空,则将该边的'train_removed'设为True,否则为False.

该操作为保证'train_removed'不为空。

(3) 获取训练数据features并标准化

 if normalize and not feats is None:
from sklearn.preprocessing import StandardScaler
train_ids = np.array([id_map[n] for n in G.nodes(
) if not G.node[n]['val'] and not G.node[n]['test']])
train_feats = feats[train_ids]
scaler = StandardScaler()
scaler.fit(train_feats)
feats = scaler.transform(feats)

这里if not feats is None 等价于 if feats is not None.

将val,test均为None的node选为训练数据,通过id_map获取其在feature表中的索引值,添加到train_ids数组中。根据索引train_ids,train_fests获取这些nodes的features.

StandardScaler的用法:

http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html

Methods:

fit(X[, y]) : Compute the mean and std to be used for later scaling.

transform(X[, y, copy]) : Perform standardization by centering and scaling

fit_transform(X[, y]) : Fit to data, then transform it.

例子:

>>> from sklearn.preprocessing import StandardScaler
>>> data = [[0, 0], [0, 0], [1, 1], [1, 1]]
>>> scaler = StandardScaler()
>>> print(scaler.fit(data))
StandardScaler(copy=True, with_mean=True, with_std=True)
>>> print(scaler.mean_)
[0.5 0.5]
>>> print(scaler.transform(data))
[[-1. -1.]
[-1. -1.]
[ 1. 1.]
[ 1. 1.]]
>>> print(scaler.transform([[2, 2]]))
[[3. 3.]] # 计算得
# 均值[0.5, 0.5],
# 方差:1/4 * [(0 - 0.5)^2 * 2 + (1 - 0.5)^2 * 2] = 1/4 = 0.25
# 标准差:0.5
# 对于[2,2] transform 标准化之后: (2 - 0.5) / 0.5 = 3

(4) Load walks

在unsupervised_train.py的main函数中:

 train_data = load_data(FLAGS.train_prefix, load_walks=True)

load_walks = True,需要执行utils.py中的load_walks操作。

 if load_walks:  # false by default
with open(prefix + "-walks.txt") as fp:
for line in fp:
walks.append(map(conversion, line.split()))

map() 的用法:http://www.runoob.com/python/python-func-map.html

map(function, iterable, ...)

map() 会根据提供的函数对指定序列做映射。

第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。

例子:

>>>def square(x) :            # 计算平方数
... return x ** 2
...
>>> map(square, [1,2,3,4,5]) # 计算列表各个元素的平方
[1, 4, 9, 16, 25]
>>> map(lambda x: x ** 2, [1, 2, 3, 4, 5]) # 使用 lambda 匿名函数
[1, 4, 9, 16, 25] # 提供了两个列表,对相同位置的列表数据进行相加
>>> map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
[3, 7, 11, 15, 19]

walks初始化为[], 之后append的是游走的节点对的对象。

例子:walks.txt:


    276
 def conversion(n): return n
walks = []
with open("walks.txt") as fp:
for line in fp:
print(line.split())
walks.append(map(conversion, line.split()))
print(walks)
print(len(walks))

输出:

['', '']
['', '']
['', '']
[<map object at 0x7f5bc0d68da0>, <map object at 0x7f5bc0d68e48>, <map object at 0x7f5bc0d68f28>]
3

(5) 函数返回值

 return G, feats, id_map, walks, class_map

------------------------------------------------------------------------------------

4. unsupervised_train.py - func: train(train_data)

 def train(train_data, test_data=None):

这里的train_data是上文所述的load_data函数的返回值。

变量含义:

G = train_data[0]    # 图
features = train_data[1] # 训练数据的features
id_map = train_data[2] # "n" : n
context_pairs = train_data[3] if FLAGS.random_context else None #random walk的点对
 if not features is None:
# pad with dummy zero vector
features = np.vstack([features, np.zeros((features.shape[1],))])

这里vstack为features添加列一行0向量,用于WX + b中与b相加。

 placeholders = construct_placeholders()
# def construct_placeholders()定义的placeholders包含:
# batch1, batch2, neg_samples, dropout, batch_size

minibatch是EdgeMinibatchIterator的一个实例,转至minibatch.py看class EdgeMinibatchIterator(object)的定义。

5. minibatch.py - class EdgeMinibatchIterator

https://www.cnblogs.com/shiyublog/p/9902423.html

6. unsupervised_train.py - func train

继续回来看unsupervised_trian.py 中的train函数

变量:

 adj_info_ph = tf.placeholder(tf.int32, shape=minibatch.adj.shape)
adj_info = tf.Variable(adj_info_ph, trainable=False, name="adj_info")

adj_info记录邻居信息,是一个矩阵,矩阵每一行对应每一个节点的邻居节点编号数组。

(1)选择模型

接下来根据输入参数判断选择6种模型(graphsage_mean,gcn,graphsage_seq,graphsage_maxpool,graphsage_meanpool,n2v)中的哪一种。

以graphsage开头的几种是graphsage的几种变体,由于aggregator不同而不同。可以通过设定SampleAndAggregate()中的aggregator_type进行选择。默认为mean.

其中gcn与graphsage的参数不同在于:

gcn的aggregator中进行列concat的操作,因此其维数是graphsage的二倍。

a. graphsage_maxpool

 sampler = UniformNeighborSampler(adj_info)

首先看UniformNeighborSampler,该类用于sample节点的邻居,在neigh_samplers.py中。

neigh_samplers.py

 class UniformNeighborSampler(Layer):
"""
Uniformly samples neighbors.
Assumes that adj lists are padded with random re-sampling
"""
def __init__(self, adj_info, **kwargs):
super(UniformNeighborSampler, self).__init__(**kwargs)
self.adj_info = adj_info def _call(self, inputs):
ids, num_samples = inputs
adj_lists = tf.nn.embedding_lookup(self.adj_info, ids)
adj_lists = tf.transpose(tf.random_shuffle(tf.transpose(adj_lists)))
adj_lists = tf.slice(adj_lists, [0,0], [-1, num_samples])
return adj_lists

1.  tf.nn.embedding_lookup 用于根据ids在adj_info中找到各个对应位的向量。

2. adj_lists = tf.transpose(tf.random_shuffle(tf.transpose(adj_lists)))

adj_lists = tf.slice(adj_lists, [0,0], [-1, num_samples]) 的过程见下:

id0 id1 id2...   --transpose--> id0 [...]  --shuffle--> id1 [...]  --transpose--> id1 id2 id0 --slice--> id1 id2

[]    []    []                                id1 [...]                     id2 [...]                         []     []    []                  []    []

id2 [...]                     id0 [...]

均匀:shuffle打乱0维的顺序,即打乱行顺序,以此使下面采样可以“均匀”。为了使用shuffle函数,需要在shuffle前后transpose一下。

采样:slice之后,相当于随机挑选了num_samples个样本,并保留了这些样本的全部属性特征。

3. 最后的adj_lists即为均匀采样后的表示邻居信息的矩阵。

---------------------------------------------------

回到unsupervised_train.py 的train()函数.

 sampler = UniformNeighborSampler(adj_info)

sampler获取均匀采样后的邻居节点信息。

---------------------------------------------------

 layer_infos = [SAGEInfo("node", sampler, FLAGS.samples_1, FLAGS.dim_1),
SAGEInfo("node", sampler, FLAGS.samples_2, FLAGS.dim_2)]

其中SAGEInfo在models.py中。

models.py 

https://www.cnblogs.com/shiyublog/p/9879875.html

 # SAGEInfo is a namedtuple that specifies the parameters
# of the recursive GraphSAGE layers
SAGEInfo = namedtuple("SAGEInfo",
['layer_name', # name of the layer (to get feature embedding etc.)
'neigh_sampler', # callable neigh_sampler constructor
'num_samples',
'output_dim' # the output (i.e., hidden) dimension
])

namedtuple 命名元组,可以给tuple命名,用法见下:

https://www.cnblogs.com/chenlin163/p/7259061.html

 import collections

 MyTupleClass = collections.namedtuple('MyTupleClass',['name', 'age', 'job'])
obj = MyTupleClass("Tomsom",12,'Cooker')
print(obj.name)
print(obj.age)
print(obj.job) # Output:
# Tomsom
#
# Cooker
############################# Person=collections.namedtuple('Person','name age gender')
# 以空格分开,表示这个namedtuple有三个元素 print( 'Type of Person:',type(Person))
Bob=Person(name='Bob',age=30,gender='male')
print( 'Representation:',Bob)
Jane=Person(name='Jane',age=29,gender='female')
print( 'Field by Name:',Jane.name)
for people in [Bob,Jane]:
print ("%s is %d years old %s" % people) # Output:
# Type of Person: <class 'type'>
# Representation: Person(name='Bob', age=30, gender='male')
# Field by Name: Jane
# Bob is 30 years old male
# Jane is 29 years old female
############################# # 在使用namedtyuple的时候要注意其中的名称不能使用Python的关键字,如class def等
# 不能有重复的元素名称,比如:不能有两个’age age’。如果出现这些情况,程序会报错。
# 但是,在实际使用的时候可能无法避免这种情况,
# 比如:可能我们的元素名称是从数据库里读出来的记录,这样很难保证一定不会出现Python关键字。
# 这种情况下的解决办法是将namedtuple的重命名模式打开,
# 这样如果遇到Python关键字或者有重复元素名 时,自动进行重命名。 with_class=collections.namedtuple('Person','name age class gender',rename=True)
print with_class._fields
two_ages=collections.namedtuple('Person','name age gender age',rename=True)
print two_ages._fields # Output:
# ('name', 'age', '_2', 'gender')
# ('name', 'age', 'gender', '_3') # 使用rename=True的方式打开重命名选项。
# 可以看到第一个集合中的class被重命名为 ‘_2' ;
# 第二个集合中重复的age被重命名为 ‘_3'
# namedtuple在重命名的时候使用了下划线 _ 加元素所在索引数的方式进行重命名
############################## # 附两段官方文档代码实例:
# 1) namedtuple基本用法
>>> # Basic example
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22) # instantiate with positional or keyword arguments
>>> p[0] + p[1] # indexable like the plain tuple (11, 22)
33
>>> x, y = p # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessible by name
33
>>> p # readable __repr__ with a name=value style
Point(x=11, y=22) # 2) namedtuple结合csv和sqlite用法
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')
import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
print(emp.name, emp.title) import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
print(emp.name, emp.title)

对于FLAGS.dim_1FLAGS.dim_2,定义为:

 flags.DEFINE_integer(
'dim_1', 128, 'Size of output dim (final is 2x this, if using concat)')
flags.DEFINE_integer(
'dim_2', 128, 'Size of output dim (final is 2x this, if using concat)')

若GCN,因为有concat操作,故使用2x.

对于FLAGS.samples_1FLAGS.samples_2,定义为:

1 flags.DEFINE_integer('samples_1', 25, 'number of samples in layer 1')
2 flags.DEFINE_integer('samples_2', 10, 'number of users samples in layer 2')

对应论文中的K = 1 ,第一层S1 = 25; K = 2 ,第二层S2 = 10。

----------------------------------------------------------

 model = SampleAndAggregate(placeholders,
features,
adj_info,
minibatch.deg,
layer_infos=layer_infos,
aggregator_type="maxpool",
model_size=FLAGS.model_size,
identity_dim=FLAGS.identity_dim,
logging=True)

SampleAndAggregate在models.py中。

class SampleAndAggregate(GeneralizedModel)主要包含的函数有:

1. def __init__(self, placeholders, features, adj, degrees, layer_infos, concat=True, aggregator_type="mean",  model_size="small", identity_dim=0, **kwargs)

2. def sample(self, inputs, layer_infos, batch_size=None)

3. def aggregate(self, samples, input_features, dims, num_samples, support_sizes, batch_size=None,
aggregators=None, name=None, concat=False, model_size="small")

4. def _build(self)

5. def build(self)

6. def _loss(self)

7. def _accuracy(self)

---------------------------------------------------------------

(2) Session

Config

 config = tf.ConfigProto(log_device_placement=FLAGS.log_device_placement)
# 参数初始化为False:
# tf.app.flags.DEFINE_boolean('log_device_placement', False,
# """Whether to log device placement.""") config.gpu_options.allow_growth = True
# 控制GPU资源使用率
# 使用allow_growth option,刚一开始分配少量的GPU容量,然后按需慢慢的增加,
# 由于不会释放内存,所以会导致碎片 #config.gpu_options.per_process_gpu_memory_fraction = GPU_MEM_FRACTION
# 设置每个GPU应该拿出多少容量给进程使用,
# per_process_gpu_memory_fraction =0.4代表 40% config.allow_soft_placement = True
# 自动选择运行设备
# 在tf中,通过命令 "with tf.device('/cpu:0'):",允许手动设置操作运行的设备。
# 如果手动设置的设备不存在或者不可用,就会导致tf程序等待或异常,
# 为了防止这种情况,可以设置tf.ConfigProto()中参数allow_soft_placement=True,
# 允许tf自动选择一个存在并且可用的设备来运行操作。

Initialize session

 # Initialize session
sess = tf.Session(config=config)
merged = tf.summary.merge_all()
# tf.summary()能够保存训练过程以及参数分布图并在tensorboard显示。
# merge_all 可以将所有summary全部保存到磁盘,以便tensorboard显示。
# 如果没有特殊要求,一般用这一句就可一显示训练时的各种信息了 summary_writer = tf.summary.FileWriter(log_dir(), sess.graph)
# 指定一个文件用来保存图。
# 格式:tf.summary.FileWritter(path,sess.graph)
# 可以调用其add_summary()方法将训练过程数据保存在filewriter指定的文件中

Init variables

 sess.run(tf.global_variables_initializer(),
feed_dict={adj_info_ph: minibatch.adj})

---------------------------------------------------------

(4) Train model

 feed_dict = minibatch.next_minibatch_feed_dict()

next_minibatch_feed_dict() 在minibatch.py的class EdgeMinibatchIterator(object)中定义。

 def next_minibatch_feed_dict(self):
start_idx = self.batch_num * self.batch_size
self.batch_num += 1
end_idx = min(start_idx + self.batch_size, len(self.train_edges))
batch_edges = self.train_edges[start_idx: end_idx]
return self.batch_feed_dict(batch_edges)

函数中获取下个edgeminibatch的起始与终止序号,将batch后的边的信息传给batch_feed_dict(self, batch_edges)函数,更新placeholders中的batch1, batch2, batch_size信息。

 def batch_feed_dict(self, batch_edges):
batch1 = []
batch2 = []
for node1, node2 in batch_edges:
batch1.append(self.id2idx[node1])
batch2.append(self.id2idx[node2]) feed_dict = dict()
feed_dict.update({self.placeholders['batch_size']: len(batch_edges)})
feed_dict.update({self.placeholders['batch1']: batch1})
feed_dict.update({self.placeholders['batch2']: batch2}) return feed_dict

也即next_minibatch_feed_dict()返回的是下一个edge minibatch的placeholders信息。

=======================================

             

感谢您的打赏!

(梦想还是要有的,万一您喜欢我的文章呢)

GraphSAGE 代码解析(一) - unsupervised_train.py的更多相关文章

  1. GraphSAGE 代码解析(四) - models.py

    原创文章-转载请注明出处哦.其他部分内容参见以下链接- GraphSAGE 代码解析(一) - unsupervised_train.py GraphSAGE 代码解析(二) - layers.py ...

  2. GraphSAGE 代码解析(三) - aggregators.py

    原创文章-转载请注明出处哦.其他部分内容参见以下链接- GraphSAGE 代码解析(一) - unsupervised_train.py GraphSAGE 代码解析(二) - layers.py ...

  3. GraphSAGE 代码解析(二) - layers.py

    原创文章-转载请注明出处哦.其他部分内容参见以下链接- GraphSAGE 代码解析(一) - unsupervised_train.py GraphSAGE 代码解析(三) - aggregator ...

  4. GraphSAGE 代码解析 - minibatch.py

    class EdgeMinibatchIterator """ This minibatch iterator iterates over batches of samp ...

  5. 用 TensorFlow 实现 k-means 聚类代码解析

    k-means 是聚类中比较简单的一种.用这个例子说一下感受一下 TensorFlow 的强大功能和语法. 一. TensorFlow 的安装 按照官网上的步骤一步一步来即可,我使用的是 virtua ...

  6. OpenStack之虚机热迁移代码解析

    OpenStack之虚机热迁移代码解析 话说虚机迁移分为冷迁移以及热迁移,所谓热迁移用度娘的话说即是:热迁移(Live Migration,又叫动态迁移.实时迁移),即虚机保存/恢复(Save/Res ...

  7. pointnet.pytorch代码解析

    pointnet.pytorch代码解析 代码运行 Training cd utils python train_classification.py --dataset <dataset pat ...

  8. VBA常用代码解析

    031 删除工作表中的空行 如果需要删除工作表中所有的空行,可以使用下面的代码. Sub DelBlankRow() DimrRow As Long DimLRow As Long Dimi As L ...

  9. [nRF51822] 12、基础实验代码解析大全 · 实验19 - PWM

    一.PWM概述: PWM(Pulse Width Modulation):脉冲宽度调制技术,通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形. PWM 的几个基本概念: 1) 占空比:占空比是指 ...

随机推荐

  1. tomcat解决端口号占用问题

    1.第一种方法 更改tomcat自己的端口号: conf 目录下 找到 server.xml,把默认的8080端口改个别的试试,tomcat 一般端口号改的要大于 6000,. 2.第二种方法 关闭端 ...

  2. 学习WebSocket笔记

    由于HTTP协议是无状态的,服务器只会响应来自客户端的请求,但是它与客户端之间不具备持续连接. 当用户在浏览器上进行操作时,可以请求服务器上的api:但是反过来不可以:服务端发生了一件事,无法将这个事 ...

  3. 前端 new和instanceof JavaScript

    new和instanceof的内部机制 new 代码例子 var Func=function(){ }; var func=new Func (); new共经过4个阶段 1.创建一个空对象 var ...

  4. SpringMVC 导入导出Excel文件

    /**  * 下载Excel模板 创建一个新的文件用于下载,创建的文件放在缓存中  *   * @param request  * @param response  */ /*  * @Request ...

  5. Oracle oerr工具介绍

    (1)什么是oerr oerr是Oracle提供的在UNIX/Linux上查看Oracle错误的小工具,使用起来非常方便. (2)如何使用 oerr工具位于ORACLE_HOME下面,可以使用whic ...

  6. oracle快速添加用户及授权

    --Oracle使用的是用户管理模式--意味着,Oracle的数据使用用户来分割 --以后开发,我们需要每个项目都需要使用一个用户 --所以:一个数据文件是可以放多个用户的数据的.但是我们开发从数据的 ...

  7. Spring知识点小结(一)

    一.Spring的简介 1.spring是一个full-stack轻量级开源框架    2.spring的两大核心        IoC: inverse of control  控制反转:反转是对象 ...

  8. c# LRU实现的缓存类

    在网上找到网友中的方法,将其修改整理后,实现了缓存量控制以及时间控制,如果开启缓存时间控制,会降低效率. 定义枚举,移除时使用 public enum RemoveType    {        [ ...

  9. c# 开发可替换的通用序列化库

    开篇继续吹牛.... 其实没有什么可吹的了,哈哈哈哈哈 主要是写一个通用库,既可以直接用,又方便替换,我的序列化都是采用第三方的哈. 我不上完整代码了,只是给大家讲讲过程. 1.写一个序列化的类,我是 ...

  10. JavaScript实现快速排序(Quicksort)

    目前,最常见的排序算法大概有七八种,其中"快速排序"(Quicksort)使用得最广泛,速度也较快.它是图灵奖得主 东尼·霍尔(C. A. R. Hoare)于1960时提出来的. ...