深度学习系列 Part (2)
1. 神经网络原理
神经网络模型,是上一章节提到的典型的监督学习问题,即我们有一组输入以及对应的目标输出,求最优模型。通过最优模型,当我们有新的输入时,可以得到一个近似真实的预测输出。
我们先看一下如何实现这样一个简单的神经网络:
输入 x = [1,2,3],
目标输出 y = [-0.85, 0.72]
中间使用一个包含四个单元的隐藏层。
结构如图:
求所需参数 w10w10 w20w20 b10b10 b20b20, 使得给定输入 x 下得到的输出 ,和目标输出 y^y^ 之间的平均均方误差 (Mean Square Errors, MSE) 最小化 。
我们首先需要思考,有几个参数?由于是两层神经网络,结构如下图(图片来源http://stackoverflow.com/questions/22054877/backpropagation-training-stuck) 其中输入层为 3,中间层为 4,输出层是 2:
因此,其中总共包含 (3x4+4) + (4*2+2) = 26 个参数需要训练。我们可以如图初始化参数。参数可以随机初始化,也可以随便指定:
Python 3重置复制
Python示例代码
1
import numpy as np
2
w1_0 = np.array([[ 0.1, 0.2, 0.3, 0.4],
3
[ 0.5, 0.6, 0.7, 0.8],
4
[ 0.9, 1.0, 1.1, 1.2]])
5
w2_0 = np.array([[ 1.3, 1.4],
6
[ 1.5, 1.6],
7
[ 1.7, 1.8],
8
[ 1.9, 2.0]])
9
10
b1_0 = np.array( [-2.0, -6.0, -1.0, -7.0])
11
b2_0 = np.array( [-2.5, -5.0])
运行
我们进行一次正向传播:
Python 3重置复制
Python示例代码
1
x = [1,2,3]
2
y = [-0.85, 0.72]
3
4
o1 = np.dot(x, w1_0 ) + b1_0
5
os1 = np.power(1+np.exp(o1*-1), -1)
6
o2 = np.dot(os1, w2_0) + b2_0
7
os2 = np.tanh(o2)
运行
再进行一次反向传播:
Python 3重置复制
Python示例代码
1
alpha = 0.1
2
grad_os2 = (y - os2) * (1-np.power(os2, 2))
3
grad_os1 = np.dot(w2_0, grad_os2.T).T * (1-os1)*os1
4
grad_w2 = ...
5
grad_b2 = ...
6
...
7
...
8
w2_0 = w2_0 + alpha * grad_w2
9
b2_0 = b2_0 + alpha * grad_b2
10
...
11
...
运行
如此反复多次,直到最终误差收敛。进行反向传播时,需要将所有参数的求导结果都写上去,然后根据求导结果更新参数。我这里就没有写全,因为一层一层推导实在是太过麻烦。更重要的是,当我们需要训练新的神经网络结构时,这些都需要重新推导一次,费时费力。
然而仔细想一想,这个推导的过程也并非无规律可循。即上一级的神经网络梯度输出,会被用作下一级计算梯度的输入,同时下一级计算梯度的输出,会被作为上一级神经网络的输入。于是我们就思考能否将这一过程抽象化,做成一个可以自动求导的框架?OK,以 Tensorflow 为代表的一系列深度学习框架,正是根据这一思路诞生的。
2.深度学习框架
近几年最火的深度学习框架是什么?毫无疑问,Tensorflow 高票当选。
但实际上,这些深度学习框架都具有一些普遍特征。Gokula Krishnan Santhanam认为,大部分深度学习框架都包含以下五个核心组件:
张量(Tensor)
基于张量的各种操作
计算图(Computation Graph)
自动微分(Automatic Differentiation)工具
BLAS、cuBLAS、cuDNN等拓展包
其中,张量 Tensor 可以理解为任意维度的数组——比如一维数组被称作向量(Vector),二维的被称作矩阵(Matrix),这些都属于张量。有了张量,就有对应的基本操作,如取某行某列的值,张量乘以常数等。运用拓展包其实就相当于使用底层计算软件加速运算。
我们今天重点介绍的,就是计算图模型,以及自动微分两部分。首先介绍以 Torch 框架为例,谈谈如何实现自动求导,然后再用最简单的方法,实现这两部分。
2.1. 深度学习框架如何实现自动求导
诸如 Tensorflow 这样的深度学习框架的入门,网上有大量的 几行代码、几分钟入门这样的资料,可以快速实现手写数字识别等简单任务。但如果想深入了解 Tensorflow 的背后原理,可能就不是这么容易的事情了。这里我们简单的谈一谈这一部分。
我们知道,当我们拿到数据、训练神经网络时,网络中的所有参数都是 变量。训练模型的过程,就是如何得到一组最佳变量,使预测最准确的过程。这个过程实际上就是,输入数据经过 正向传播,变成预测,然后预测与实际情况的误差 反向传播 误差回来,更新变量。如此反复多次,得到最优的参数。这里就会遇到一个问题,神经网络这么多层,如何保证正向、反向传播都可以正确运行?
值得思考的是,这两种传播方式,都具有 管道传播 的特征。正向传播一层一层算就可以了,上一层网络的结果作为下一层的输入。而反向传播过程可以利用 链式求导法则,从后往前,不断将误差分摊到每一个参数的头上。
图片来源:Colah博客
进过抽象化后,我们发现,深度学习框架中的 每一个模块都需要两个函数,一个连接正向,一个连接反向。这里的正向和反向,如同武侠小说中的 任督二脉。而训练模型的过程,数据通过正向传播生成预测结果,进而将误差反向传回更新参数,就如同让真气通过任督二脉在体内游走,随着训练误差逐渐缩小收敛,深度神经网络也将打通任督二脉。
接下来,我们将首先审视一下 Torch 框架的源码如何实现这两部分内容,其次我们通过 Python 直接编写一个最简单的深度学习框架。
举 Torch 的 nn 项目的例子是因为Torch 的代码文件结构比较简单,Tensorflow 的规律和Torch比较近似,但文件结构相对更加复杂,有兴趣的可以仔细读读相关文章。
Torch nn 模块Github 源码 这个目录下的几乎所有 .lua 文件,都有这两个函数:
# lua
function xxx:updateOutput(input)
input.THNN.xxx_updateOutput(
input:cdata(),
self.output:cdata()
)
return self.output
end
function xxx:updateGradInput(input, gradOutput)
input.THNN.xxx_updateGradInput(
input:cdata(),
gradOutput:cdata(),
self.gradInput:cdata(),
self.output:cdata()
)
return self.gradInput
end
这里其实是相当于留了两个方法的定义,没有写具体功能。具体功能的代码,在 ./lib/THNN/generic 目录 中用 C 实现实现,具体以 Sigmoid 函数举例。
我们知道 Sigmoid 函数的形式是: 代码实现起来是这样:
# lua
void THNN_(Sigmoid_updateOutput)( THNNState
*state, THTensor
*input, THTensor
*output)
{
THTensor_(resizeAs)(output, input);
TH_TENSOR_APPLY2(real, output, real, input,
*output_data = 1./(1.+ exp(- *input_data));
);
}
Sigmoid 函数求导变成:所以这里在实现的时候就是:
// c
void THNN_(Sigmoid_updateGradInput)(
THNNState *state,
THTensor *input,
THTensor *gradOutput,
THTensor *gradInput,
THTensor *output)
{
THNN_CHECK_NELEMENT(input, gradOutput);
THTensor_(resizeAs)(gradInput, output);
TH_TENSOR_APPLY3(real, gradInput, real, gradOutput, real, output,
real z = * output_data;
*gradInput_data = *gradOutput_data * (1. - z) * z;
);
}
大家应该注意到了一点, updateOutput 函数, output_data 在等号左边, input_data 在等号右边。 而 updateGradInput 函数, gradInput_data 在等号左边, gradOutput_data 在等号右边。 这里,output = f(input) 对应的是 正向传播 input = f(output) 对应的是 反向传播。
2.2 用 Python 直接编写一个最简单的深度学习框架
这部分内容属于“造轮子”,并且借用了优达学城的一个小型项目 MiniFlow。
数据结构部分
首先,我们实现一个父类 Node,然后基于这个父类,依次实现 Input Linear Sigmoid 等模块。这里运用了简单的 Python Class 继承。这些模块中,需要将 forward 和 backward 两个方法针对每个模块分别重写。
代码如下:
Python 3重置复制
Python示例代码
1
class Node(object):
2
"""
3
Base class for nodes in the network.
4
5
Arguments:
6
7
`inbound_nodes`: A list of nodes with edges into this node.
8
"""
9
def __init__(self, inbound_nodes=[]):
10
"""
11
Node's constructor (runs when the object is instantiated). Sets
12
properties that all nodes need.
13
"""
14
# A list of nodes with edges into this node.
15
self.inbound_nodes = inbound_nodes
16
# The eventual value of this node. Set by running
17
# the forward() method.
18
self.value = None
19
# A list of nodes that this node outputs to.
20
self.outbound_nodes = []
21
# New property! Keys are the inputs to this node and
22
# their values are the partials of this node with
23
# respect to that input.
24
self.gradients = {}
25
26
# Sets this node as an outbound node for all of
27
# this node's inputs.
28
for node in inbound_nodes:
29
node.outbound_nodes.append(self)
30
31
def forward(self):
32
"""
33
Every node that uses this class as a base class will
34
need to define its own `forward` method.
35
"""
36
raise NotImplementedError
37
38
def backward(self):
39
"""
40
Every node that uses this class as a base class will
41
need to define its own `backward` method.
42
"""
43
raise NotImplementedError
44
45
46
class Input(Node):
47
"""
48
A generic input into the network.
49
"""
50
def __init__(self):
51
Node.__init__(self)
52
53
def forward(self):
54
pass
55
56
def backward(self):
57
self.gradients = {self: 0}
58
for n in self.outbound_nodes:
59
self.gradients[self] += n.gradients[self]
60
61
class Linear(Node):
62
"""
63
Represents a node that performs a linear transform.
64
"""
65
def __init__(self, X, W, b):
66
Node.__init__(self, [X, W, b])
67
68
def forward(self):
69
"""
70
Performs the math behind a linear transform.
71
"""
72
X = self.inbound_nodes[0].value
73
W = self.inbound_nodes[1].value
74
b = self.inbound_nodes[2].value
75
self.value = np.dot(X, W) + b
76
77
def backward(self):
78
"""
79
Calculates the gradient based on the output values.
80
"""
81
self.gradients = {n: np.zeros_like(n.value) for n in self.inbound_nodes}
82
for n in self.outbound_nodes:
83
grad_cost = n.gradients[self]
84
self.gradients[self.inbound_nodes[0]] += np.dot(grad_cost, self.inbound_nodes[1].value.T)
85
self.gradients[self.inbound_nodes[1]] += np.dot(self.inbound_nodes[0].value.T, grad_cost)
86
self.gradients[self.inbound_nodes[2]] += np.sum(grad_cost, axis=0, keepdims=False)
87
88
89
class Sigmoid(Node):
90
"""
91
Represents a node that performs the sigmoid activation function.
92
"""
93
def __init__(self, node):
94
Node.__init__(self, [node])
95
96
def _sigmoid(self, x):
97
"""
98
This method is separate from `forward` because it
99
will be used with `backward` as well.
100
101
`x`: A numpy array-like object.
102
"""
103
return 1. / (1. + np.exp(-x))
104
105
def forward(self):
106
"""
107
Perform the sigmoid function and set the value.
108
"""
109
input_value = self.inbound_nodes[0].value
110
self.value = self._sigmoid(input_value)
111
112
def backward(self):
113
"""
114
Calculates the gradient using the derivative of
115
the sigmoid function.
116
"""
117
self.gradients = {n: np.zeros_like(n.value) for n in self.inbound_nodes}
118
for n in self.outbound_nodes:
119
grad_cost = n.gradients[self]
120
sigmoid = self.value
121
self.gradients[self.inbound_nodes[0]] += sigmoid * (1 - sigmoid) * grad_cost
122
123
class Tanh(Node):
124
def __init__(self, node):
125
"""
126
The tanh cost function.
127
Should be used as the last node for a network.
128
"""
129
Node.__init__(self, [node])
130
131
def forward(self):
132
"""
133
Calculates the tanh.
134
"""
135
input_value = self.inbound_nodes[0].value
136
self.value = np.tanh(input_value)
137
138
def backward(self):
139
"""
140
Calculates the gradient of the cost.
141
"""
142
self.gradients = {n: np.zeros_like(n.value) for n in self.inbound_nodes}
143
for n in self.outbound_nodes:
144
grad_cost = n.gradients[self]
145
tanh = self.value
146
self.gradients[self.inbound_nodes[0]] += (1 + tanh) * (1 - tanh) * grad_cost.T
147
148
149
150
class MSE(Node):
151
def __init__(self, y, a):
152
"""
153
The mean squared error cost function.
154
Should be used as the last node for a network.
155
"""
156
Node.__init__(self, [y, a])
157
158
def forward(self):
159
"""
160
Calculates the mean squared error.
161
"""
162
y = self.inbound_nodes[0].value.reshape(-1, 1)
163
a = self.inbound_nodes[1].value.reshape(-1, 1)
164
165
self.m = self.inbound_nodes[0].value.shape[0]
166
self.diff = y - a
167
self.value = np.mean(self.diff**2)
168
169
def backward(self):
170
"""
171
Calculates the gradient of the cost.
172
"""
173
self.gradients[self.inbound_nodes[0]] = (2 / self.m) * self.diff
174
self.gradients[self.inbound_nodes[1]] = (-2 / self.m) * self.diff
运行
调度算法与优化部分
优化部分则会在以后的系列中单独详细说明。这里主要将简单讲一下图计算的算法调度。就是实际上Tensorflow的各个模块会生成一个有向无环图,如下图(来源http://www.geeksforgeeks.org/topological-sorting-indegree-based-solution/):
在计算过程中,几个模块存在着相互依赖关系,比如要计算模块1,就必须完成模块3和模块4,而要完成模块3,就需要在之前顺次完成模块5、2;因此这里可以使用 Kahn 算法作为调度算法(下面的 topological_sort 函数),从计算图中,推导出类似 5->2->3->4->1 的计算顺序。
Python 3重置复制
Python示例代码
1
def topological_sort(feed_dict):
2
"""
3
Sort the nodes in topological order using Kahn's Algorithm.
4
5
`feed_dict`: A dictionary where the key is a `Input` Node and the value is the respective value feed to that Node.
6
7
Returns a list of sorted nodes.
8
"""
9
input_nodes = [n for n in feed_dict.keys()]
10
G = {}
11
nodes = [n for n in input_nodes]
12
while len(nodes) > 0:
13
n = nodes.pop(0)
14
if n not in G:
15
G[n] = {'in': set(), 'out': set()}
16
for m in n.outbound_nodes:
17
if m not in G:
18
G[m] = {'in': set(), 'out': set()}
19
G[n]['out'].add(m)
20
G[m]['in'].add(n)
21
nodes.append(m)
22
23
L = []
24
S = set(input_nodes)
25
while len(S) > 0:
26
n = S.pop()
27
if isinstance(n, Input):
28
n.value = feed_dict[n]
29
30
L.append(n)
31
for m in n.outbound_nodes:
32
G[n]['out'].remove(m)
33
G[m]['in'].remove(n)
34
if len(G[m]['in']) == 0:
35
S.add(m)
36
return L
37
38
39
def forward_and_backward(graph):
40
"""
41
Performs a forward pass and a backward pass through a list of sorted Nodes.
42
43
Arguments:
44
45
`graph`: The result of calling `topological_sort`.
46
"""
47
for n in graph:
48
n.forward()
49
50
for n in graph[::-1]:
51
n.backward()
52
53
54
def sgd_update(trainables, learning_rate=1e-2):
55
"""
56
Updates the value of each trainable with SGD.
57
58
Arguments:
59
60
`trainables`: A list of `Input` Nodes representing weights/biases.
61
`learning_rate`: The learning rate.
62
"""
63
for t in trainables:
64
t.value = t.value - learning_rate * t.gradients[t]
运行
使用模型
Python 3重置复制
Python示例代码
1
import numpy as np
2
from sklearn.utils import resample
3
np.random.seed(0)
4
5
w1_0 = np.array([[ 0.1, 0.2, 0.3, 0.4],
6
[ 0.5, 0.6, 0.7, 0.8],
7
[ 0.9, 1.0, 1.1, 1.2]])
8
w2_0 = np.array([[ 1.3, 1.4],
9
[ 1.5, 1.6],
10
[ 1.7, 1.8],
11
[ 1.9, 2.0]])
12
b1_0 = np.array( [-2.0, -6.0, -1.0, -7.0])
13
b2_0 = np.array( [-2.5, -5.0])
14
15
X_ = np.array([[1.0, 2.0, 3.0]])
16
y_ = np.array([[-0.85, 0.75]])
17
n_features = X_.shape[1]
18
19
W1_ = w1_0
20
b1_ = b1_0
21
W2_ = w2_0
22
b2_ = b2_0
23
24
X, y = Input(), Input()
25
W1, b1 = Input(), Input()
26
W2, b2 = Input(), Input()
27
28
l1 = Linear(X, W1, b1)
29
s1 = Sigmoid(l1)
30
l2 = Linear(s1, W2, b2)
31
t1 = Tanh(l2)
32
cost = MSE(y, t1)
33
34
feed_dict = {
35
X: X_, y: y_,
36
W1: W1_, b1: b1_,
37
W2: W2_, b2: b2_
38
}
39
40
epochs = 10
41
m = X_.shape[0]
42
batch_size = 1
43
steps_per_epoch = m // batch_size
44
45
graph = topological_sort(feed_dict)
46
trainables = [W1, b1, W2, b2]
47
48
l_Mat_W1 = [w1_0]
49
l_Mat_W2 = [w2_0]
50
l_Mat_out = []
51
52
l_val = []
53
for i in range(epochs):
54
loss = 0
55
for j in range(steps_per_epoch):
56
X_batch, y_batch = resample(X_, y_, n_samples=batch_size)
57
X.value = X_batch
58
y.value = y_batch
59
forward_and_backward(graph)
60
sgd_update(trainables, 0.1)
61
loss += graph[-1].value
62
63
mat_W1 = []
64
mat_W2 = []
65
for i in graph:
66
try:
67
if (i.value.shape[0] == 3) and (i.value.shape[1] == 4):
68
mat_W1 = i.value
69
if (i.value.shape[0] == 4) and (i.value.shape[1] == 2):
70
mat_W2 = i.value
71
except:
72
pass
73
74
l_Mat_W1.append(mat_W1)
75
l_Mat_W2.append(mat_W2)
76
l_Mat_out.append(graph[9].value)
运行
来观察一下。当然还有更高级的可视化方法:可视化的神经网络
Python 3重置复制
Python示例代码
1
import matplotlib.pyplot as plt
2
%matplotlib inline
3
4
fig = plt.figure( figsize=(14,10))
5
ax0 = fig.add_subplot(131)
6
#aax0 = fig.add_axes([0, 0, 0.3, 0.1])
7
c0 = ax0.imshow(np.array(l_Mat_out).reshape([-1,2]).T, interpolation='nearest',aspect='auto', cmap="Reds", vmax=1, vmin=-1)
8
ax0.set_title("Output")
9
10
cbar = fig.colorbar(c0, ticks=[-1, 0, 1])
11
12
13
14
ax1 = fig.add_subplot(132)
15
c1 = ax1.imshow(np.array(l_Mat_W1).reshape(len(l_Mat_W1), 12).T, interpolation='nearest',aspect='auto', cmap="Reds")
16
ax1.set_title("w1")
17
cbar = fig.colorbar(c1, ticks=[np.min(np.array(l_Mat_W1)), np.max(np.array(l_Mat_W1))])
18
19
ax2 = fig.add_subplot(133)
20
c2 = ax2.imshow(np.array(l_Mat_W2).reshape(len(l_Mat_W2), 8).T, interpolation='nearest',aspect='auto', cmap="Reds")
21
ax2.set_title("w2")
22
cbar = fig.colorbar(c2, ticks=[np.min(np.array(l_Mat_W2)), np.max(np.array(l_Mat_W2))])
23
24
ax0.set_yticks([0,1])
25
ax0.set_yticklabels(["out0", "out1"])
26
27
ax1.set_xlabel("epochs")
28
#for i in range(len(l_Mat_W1)):
运行
我们注意到,随着训练轮数 Epoch 不断增多, Output 值从最初的 [0.72, -0.88] 不断接近 y = [-0.85, 0.72], 其背后的原因,是模型参数不断的从初始化的值变化、更新,如图中的 w1 w2 两个矩阵。
好了,最简单的轮子已经造好了。 我们的轮子,实现了 Input Linear Sigmoid Tanh 以及 MSE 这几个模块。 接下来的内容,我们将基于现在最火的轮子 Tensorflow,详细介绍一下更多的模块。
最后,本篇只是造了个最基本的轮子,我们集智的知乎专栏上,有一个系列文章,正在介绍如何在Matlab上手写深度学习框架,欢迎大家围观。
深度学习系列 Part (2)的更多相关文章
- 【深度学习系列3】 Mariana CNN并行框架与图像识别
[深度学习系列3] Mariana CNN并行框架与图像识别 本文是腾讯深度学习系列文章的第三篇,聚焦于腾讯深度学习平台Mariana中深度卷积神经网络Deep CNNs的多GPU模型并行和数据并行框 ...
- 【深度学习系列2】Mariana DNN多GPU数据并行框架
[深度学习系列2]Mariana DNN多GPU数据并行框架 本文是腾讯深度学习系列文章的第二篇,聚焦于腾讯深度学习平台Mariana中深度神经网络DNN的多GPU数据并行框架. 深度神经网络( ...
- 深度学习系列 Part(3)
这是<GPU学习深度学习>系列文章的第三篇,主要是接着上一讲提到的如何自己构建深度神经网络框架中的功能模块,进一步详细介绍 Tensorflow 中 Keras 工具包提供的几种深度神经网 ...
- 【深度学习系列】关于PaddlePaddle的一些避“坑”技巧
最近除了工作以外,业余在参加Paddle的AI比赛,在用Paddle训练的过程中遇到了一些问题,并找到了解决方法,跟大家分享一下: PaddlePaddle的Anaconda的兼容问题 之前我是在服务 ...
- 【深度学习系列】PaddlePaddle垃圾邮件处理实战(二)
PaddlePaddle垃圾邮件处理实战(二) 前文回顾 在上篇文章中我们讲了如何用支持向量机对垃圾邮件进行分类,auc为73.3%,本篇讲继续讲如何用PaddlePaddle实现邮件分类,将深度 ...
- 基于TensorFlow的深度学习系列教程 2——常量Constant
前面介绍过了Tensorflow的基本概念,比如如何使用tensorboard查看计算图.本篇则着重介绍和整理下Constant相关的内容. 基于TensorFlow的深度学习系列教程 1--Hell ...
- 使用腾讯云 GPU 学习深度学习系列之二:Tensorflow 简明原理【转】
转自:https://www.qcloud.com/community/article/598765?fromSource=gwzcw.117333.117333.117333 这是<使用腾讯云 ...
- 【深度学习系列】PaddlePaddle之手写数字识别
上周在搜索关于深度学习分布式运行方式的资料时,无意间搜到了paddlepaddle,发现这个框架的分布式训练方案做的还挺不错的,想跟大家分享一下.不过呢,这块内容太复杂了,所以就简单的介绍一下padd ...
- 【深度学习系列】一起来参加百度 PaddlePaddle AI 大赛吧!
写这个系列写了两个月了,对paddlepaddle的使用和越来越熟悉,不过一直没找到合适的应用场景.最近百度搞了个AI大赛,据说有四个赛题,现在是第一个----综艺节目精彩片段预测 ,大家可以去检测一 ...
随机推荐
- c# 第一节课 一些简单的应用
注册要钱 我没钱
- HDU2688-Rotate
Recently yifenfei face such a problem that give you millions of positive integers,tell how many pair ...
- Apache Spark 2.2.0 中文文档 - Spark SQL, DataFrames and Datasets Guide | ApacheCN
Spark SQL, DataFrames and Datasets Guide Overview SQL Datasets and DataFrames 开始入门 起始点: SparkSession ...
- React的组件用法
React.createClass() 中文翻译 https://discountry.github.io/react/3.4K ( https://doc.react-china.org868 ) ...
- MySql join on 和 where
原文:http://www.cnblogs.com/Jessy/p/3525419.html left join :左连接,返回左表中所有的记录以及右表中连接字段相等的记录. right join : ...
- 【充分利用你的Azure】将Azure用作云计算平台(1)
本文将围绕几个步骤来讲. 因为本人是MSP,微软送了150刀的额度给我随便使用.这篇文章是要讲将Azure用作云计算平台,对于我来说,我是做机器学习的,那么Azure就要有机器学习的平台. 本文的目的 ...
- web版的tty
1.wetty Wetty是使用Node.js和websockets开发的一个开源`Web-based SSH` 2.环境配置 2.1.配置epel源 [epel] name=epel baseu ...
- 玩转 sublime3 第二弹 ES6环境
安装node: node作为JS的运行环境必须安装 文件下载:https://nodejs.org/dist/v6.11.4/node-v6.11.4-x64.msi 备注:可以去官网 https:/ ...
- python中strip函数的用法
python中往往使用剥除函数strip()来对用户的输入进行清理.strip函数的最一般形式为: str.strip('序列') 其中,序列是一段字符串,该函数表示从头或者从尾部开始进行扫描,如果扫 ...
- 管中窥豹——从OVS看SDN
网络虚拟化是当前云计算最重要的特点之一,打通租户网络之间互通以及访问控制策略,最重要的是满足租户之间的网络隔离,这才是云计算网络的特点.而SDN的产生则是在网络虚拟化中,将控制面和业务面分离,控制面只 ...