timertask run函数未执行_图执行模式下的 TensorFlow 2

文 /  李锡涵,Google Developers Expert

本文节选自《简单粗暴 TensorFlow 2.0》

b9e66442b62f865bb84db1de50fc8396.png

尽管 TensorFlow 2 建议以即时执行模式(Eager Execution)作为主要执行模式,然而,图执行模式(Graph Execution)作为 TensorFlow 2 之前的主要执行模式,依旧对于我们理解 TensorFlow 具有重要意义。尤其是当我们需要使用 tf.function 时,对图执行模式的理解更是不可或缺。

图执行模式在 TensorFlow 1.X 和 2.X 版本中的 API 不同:
  • 在 TensorFlow 1.X 中,图执行模式主要通过 “直接构建计算图 + tf.Session” 进行操作;
  • 在 TensorFlow 2 中,图执行模式主要通过 tf.function 进行操作。

在本章,我们将在 tf.function:图执行模式 一节的基础上,进一步对图执行模式的这两种 API 进行对比说明,以帮助已熟悉 TensorFlow 1.X 的用户过渡到 TensorFlow 2。

提示
TensorFlow 2 依然支持 TensorFlow 1.X 的 API。为了在 TensorFlow 2 中使用 TensorFlow 1.X 的 API ,我们可以使用 import tensorflow.compat.v1 as tf 导入 TensorFlow,并通过 tf.disable_eager_execution() 禁用默认的即时执行模式。

TensorFlow 1+1

TensorFlow 的图执行模式是一个符号式的(基于计算图的)计算框架。简而言之,如果你需要进行一系列计算,则需要依次进行如下两步:
  • 建立一个 “计算图”,这个图描述了如何将输入数据通过一系列计算而得到输出;
  • 建立一个会话,并在会话中与计算图进行交互,即向计算图传入计算所需的数据,并从计算图中获取结果。

使用计算图进行基本运算 

这里以计算 1+1 作为 Hello World 的示例。以下代码通过 TensorFlow 1.X 的图执行模式 API 计算 1+1:

import tensorflow.compat.v1 as tf
tf.disable_eager_execution()

# 以下三行定义了一个简单的“计算图”
a = tf.constant(1) # 定义一个常量张量(Tensor)
b = tf.constant(1)
c = a + b # 等价于 c = tf.add(a, b),c是张量a和张量b通过 tf.add 这一操作(Operation)所形成的新张量
# 到此为止,计算图定义完毕,然而程序还没有进行任何实质计算。
# 如果此时直接输出张量 c 的值,是无法获得 c = 2 的结果的

sess = tf.Session() # 实例化一个会话(Session)
c_ = sess.run(c) # 通过会话的 run() 方法对计算图里的节点(张量)进行实际的计算
print(c_)
输出:
2
而在 TensorFlow 2 中,我们将计算图的建立步骤封装在一个函数中,并使用 @tf.function修饰符对函数进行修饰。当需要运行此计算图时,只需调用修饰后的函数即可。由此,我们可以将以上代码改写如下:
import tensorflow as tf# 以下被 @tf.function 修饰的函数定义了一个计算图@tf.functiondef graph():
a = tf.constant(1)
b = tf.constant(1)
c = a + breturn c# 到此为止,计算图定义完毕。由于 graph() 是一个函数,在其被调用之前,程序是不会进行任何实质计算的。# 只有调用函数,才能通过函数返回值,获得 c = 2 的结果
c_ = graph()
print(c_.numpy())

小结

  • 在 TensorFlow 1.X 的 API 中,我们直接在主程序中建立计算图。而在 TensorFlow 2 中,计算图的建立需要被封装在一个被 @tf.function 修饰的函数中;

  • 在 TensorFlow 1.X 的 API 中,我们通过实例化一个 tf.Session ,并使用其 run 方法执行计算图的实际运算。而在 TensorFlow 2 中,我们通过直接调用被 @tf.function 修饰的函数来执行实际运算。

计算图中的占位符与数据输入 

上面这个程序只能计算 1+1,以下代码通过 TensorFlow 1.X 的图执行模式 API 中的 tf.placeholder() (占位符张量)和 sess.run()feed_dict 参数,展示了如何使用 TensorFlow 计算任意两个数的和:

import tensorflow.compat.v1 as tf
tf.disable_eager_execution()

a = tf.placeholder(dtype=tf.int32) # 定义一个占位符Tensor
b = tf.placeholder(dtype=tf.int32)
c = a + b

a_ = int(input("a = ")) # 从终端读入一个整数并放入变量a_
b_ = int(input("b = "))

sess = tf.Session()
c_ = sess.run(c, feed_dict={a: a_, b: b_}) # feed_dict参数传入为了计算c所需要的张量的值
print("a + b = %d" % c_)

运行程序:

>>> a = 2
>>> b = 3
a + b = 5

而在 TensorFlow 2 中,我们可以通过为函数指定参数来实现与占位符张量相同的功能。为了在计算图运行时送入占位符数据,只需在调用被修饰后的函数时,将数据作为参数传入即可。由此,我们可以将以上代码改写如下:

import tensorflow as tf

@tf.function
def graph(a, b):
c = a + b
return c

a_ = int(input("a = "))
b_ = int(input("b = "))
c_ = graph(a_, b_)
print("a + b = %d" % c_)

小结在 TensorFlow 1.X 的 API 中,我们使用 tf.placeholder() 在计算图中声明占位符张量,并通过 sess.run()feed_dict 参数向计算图中的占位符传入实际数据。而在 TensorFlow 2 中,我们使用 tf.function 的函数参数作为占位符张量,通过向被 @tf.function 修饰的函数传递参数,来为计算图中的占位符张量提供实际数据。

计算图中的变量 

变量的声明 

(Variable)是一种特殊类型的张量,使用 tf.get_variable()建立,与编程语言中的变量很相似。使用变量前需要先初始化,变量内存储的值可以在计算图的计算过程中被修改。以下示例代码展示了如何建立一个变量,将其值初始化为 0,并逐次累加 1。
import tensorflow.compat.v1 as tf
tf.disable_eager_execution()

a = tf.get_variable(name='a', shape=[])
initializer = tf.assign(a, 0.0) # tf.assign(x, y)返回一个“将张量y的值赋给变量x”的操作
plus_one_op = tf.assign(a, a + 1.0)

sess = tf.Session()
sess.run(initializer)
for i in range(5):
sess.run(plus_one_op) # 对变量a执行加一操作
print(sess.run(a)) # 输出此时变量a在当前会话的计算图中的值
输出:
1.0
2.0
3.0
4.0
5.0

提示为了初始化变量,也可以在声明变量时指定初始化器(initializer),并通过 tf.global_variables_initializer() 一次性初始化所有变量,在实际工程中更常用:

import tensorflow.compat.v1 as tf
tf.disable_eager_execution()

a = tf.get_variable(name='a', shape=[],
initializer=tf.zeros_initializer) # 指定初始化器为全0初始化
plus_one_op = tf.assign(a, a + 1.0)

sess = tf.Session()
sess.run(tf.global_variables_initializer()) # 初始化所有变量
for i in range(5):
sess.run(plus_one_op)
print(sess.run(a)
在 TensorFlow 2 中,我们通过实例化tf.Variable类来声明变量。由此,我们可以将以上代码改写如下:
import tensorflow as tf

a = tf.Variable(0.0)

@tf.function
def plus_one_op():
a.assign(a + 1.0)
return a

for i in range(5):
plus_one_op()
print(a.numpy())

小结
在 TensorFlow 1.X 的 API 中,我们使用 tf.get_variable() 在计算图中声明变量节点。而在 TensorFlow 2 中,我们直接通过 tf.Variable 实例化变量对象,并在计算图中使用这一变量对象。

变量的作用域与重用 在 TensorFlow 1.X 中,我们建立模型时经常需要指定变量的作用域,以及复用变量。此时,TensorFlow 1.X 的图执行模式 API 为我们提供了 tf.variable_scope()reuse 参数来实现变量作用域和复用变量的功能。以下的例子使用了 TensorFlow 1.X 的图执行模式 API 建立了一个三层的全连接神经网络,其中第三层复用了第二层的变量。
import tensorflow.compat.v1 as tf
import numpy as np
tf.disable_eager_execution()

def dense(inputs, num_units):
weight = tf.get_variable(name='weight', shape=[inputs.shape[1], num_units])
bias = tf.get_variable(name='bias', shape=[num_units])
return tf.nn.relu(tf.matmul(inputs, weight) + bias)

def model(inputs):
with tf.variable_scope('dense1'): # 限定变量的作用域为 dense1
x = dense(inputs, 10) # 声明了 dense1/weight 和 dense1/bias 两个变量
with tf.variable_scope('dense2'): # 限定变量的作用域为 dense2
x = dense(x, 10) # 声明了 dense2/weight 和 dense2/bias 两个变量
with tf.variable_scope('dense2', reuse=True): # 第三层复用第二层的变量
x = dense(x, 10)
return x

inputs = tf.placeholder(shape=[10, 32], dtype=tf.float32)
outputs = model(inputs)
print(tf.global_variables()) # 输出当前计算图中的所有变量节点
sess = tf.Session()
sess.run(tf.global_variables_initializer())
outputs_ = sess.run(outputs, feed_dict={inputs: np.random.rand(10, 32)})
print(outputs_)
在上例中,计算图的所有变量节点为:
['dense1/weight:0' shape=(32, 10) dtype=float32>,'dense1/bias:0' shape=(10,) dtype=float32>,'dense2/weight:0' shape=(10, 10) dtype=float32>,'dense2/bias:0' shape=(10,) dtype=float32>]

可见,tf.variable_scope() 为在其上下文中的,以 tf.get_variable 建立的变量的名称添加了 “前缀” 或 “作用域”,使得变量在计算图中的层次结构更为清晰,不同 “作用域” 下的同名变量各司其职,不会冲突。同时,虽然我们在上例中调用了 3 次 dense 函数,即调用了 6 次 tf.get_variable 函数,但实际建立的变量节点只有 4 个。这即是 tf.variable_scope()reuse 参数所起到的作用。当 reuse=True 时, tf.get_variable 遇到重名变量时将会自动获取先前建立的同名变量,而不会新建变量,从而达到了变量重用的目的。

而在 TensorFlow 2 的图执行模式 API 中,不再鼓励使用 tf.variable_scope() ,而应当使用 tf.keras.layers.Layertf.keras.Model 来封装代码和指定作用域,具体可参考 本手册第三章。上面的例子与下面基于 tf.kerastf.function 的代码等价。
import tensorflow as tf
import numpy as np

class Dense(tf.keras.layers.Layer):
def __init__(self, num_units, **kwargs):
super().__init__(**kwargs)
self.num_units = num_units

def build(self, input_shape):
self.weight = self.add_variable(name='weight', shape=[input_shape[-1], self.num_units])
self.bias = self.add_variable(name='bias', shape=[self.num_units])

def call(self, inputs):
y_pred = tf.matmul(inputs, self.weight) + self.bias
return y_pred

class Model(tf.keras.Model):
def __init__(self):
super().__init__()
self.dense1 = Dense(num_units=10, name='dense1')
self.dense2 = Dense(num_units=10, name='dense2')

@tf.function
def call(self, inputs):
x = self.dense1(inputs)
x = self.dense2(inputs)
x = self.dense2(inputs)
return x

model = Model()
print(model(np.random.rand(10, 32)))

我们可以注意到,在 TensorFlow 2 中,变量的作用域以及复用变量的问题自然地淡化了。基于 Python 类的模型建立方式自然地为变量指定了作用域,而变量的重用也可以通过简单地多次调用同一个层来实现。

为了详细了解上面的代码对变量作用域的处理方式,我们使用 get_concrete_function导出计算图,并输出计算图中的所有变量节点:
graph = model.call.get_concrete_function(np.random.rand(10, 32))
print(graph.variables)
输出如下:
('dense1/weight:0' shape=(32, 10) dtype=float32, numpy=...>,'dense1/bias:0' shape=(10,) dtype=float32, numpy=...>,'dense2/weight:0' shape=(32, 10) dtype=float32, numpy=...>,'dense2/bias:0' shape=(10,) dtype=float32, numpy=...)
可见,TensorFlow 2 的图执行模式在变量的作用域上与 TensorFlow 1.X 实际保持了一致。我们通过name参数为每个层指定的名称将成为层内变量的作用域。

小结
在 TensorFlow 1.X 的 API 中,使用 tf.variable_scope()reuse 参数来实现变量作用域和复用变量的功能。在 TensorFlow 2 中,使用 tf.keras.layers.Layertf.keras.Model 来封装代码和指定作用域,从而使变量的作用域以及复用变量的问题自然淡化。两者的实质是一样的。

自动求导机制与优化器 

在本节中,我们对 TensorFlow 1.X 和 TensorFlow 2 在图执行模式下的自动求导机制进行较深入的比较说明。

自动求导机制 我们首先回顾 TensorFlow 1.X 中的自动求导机制。在 TensorFlow 1.X 的图执行模式 API 中,可以使用 tf.gradients(y, x) 计算计算图中的张量节点 y 相对于变量 x 的导数。以下示例展示了在 TensorFlow 1.X 的图执行模式 API 中计算e23e8c8b55289a0c1d3b40eb1005da08.png1655f326c8e18aee5a5d04b875361f3a.png时的导数。
x = tf.get_variable('x', dtype=tf.float32, shape=[], initializer=tf.constant_initializer(3.))
y = tf.square(x) # y = x ^ 2
y_grad = tf.gradients(y, x)

以上代码中,计算图中的节点 y_grad 即为 y 相对于 x 的导数。

而在 TensorFlow 2 的图执行模式 API 中,我们使用 tf.GradientTape 这一上下文管理器封装需要求导的计算步骤,并使用其 gradient 方法求导,代码示例如下:

x = tf.Variable(3.)
@tf.function
def grad():
with tf.GradientTape() as tape:
y = tf.square(x)
y_grad = tape.gradient(y, x)
return y_grad

小结
在 TensorFlow 1.X 中,我们使用 tf.gradients() 求导。而在 TensorFlow 2 中,我们使用使用 tf.GradientTape 这一上下文管理器封装需要求导的计算步骤,并使用其 gradient 方法求导。

优化器

由于机器学习中的求导往往伴随着优化,所以 TensorFlow 中更常用的是优化器(Optimizer)。在 TensorFlow 1.X 的图执行模式 API 中,我们往往使用tf.train中的各种优化器,将求导和调整变量值的步骤合二为一。例如,以下代码片段在计算图构建过程中,使用 tf.train.GradientDescentOptimizer这一梯度下降优化器优化损失函数 loss

y_pred = model(data_placeholder)    # 模型构建
loss = ... # 计算模型的损失函数 loss
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
train_one_step = optimizer.minimize(loss)
# 上面一步也可拆分为
# grad = optimizer.compute_gradients(loss)
# train_one_step = optimizer.apply_gradients(grad)

以上代码中, train_one_step 即为一个将求导和变量值更新合二为一的计算图节点(操作),也就是训练过程中的 “一步”。特别需要注意的是,对于优化器的 minimize 方法而言,只需要指定待优化的损失函数张量节点 loss 即可,求导的变量可以自动从计算图中获得(即 tf.trainable_variables )。在计算图构建完成后,只需启动会话,使用 sess.run 方法运行 train_one_step 这一计算图节点,并通过 feed_dict 参数送入训练数据,即可完成一步训练。代码片段如下:

for data in dataset:
data_dict = ... # 将训练所需数据放入字典 data 内
sess.run(train_one_step, feed_dict=data_dict)

而在 TensorFlow 2 的 API 中,无论是图执行模式还是即时执行模式,均先使用 tf.GradientTape 进行求导操作,然后再使用优化器的 apply_gradients 方法应用已求得的导数,进行变量值的更新。也就是说,和 TensorFlow 1.X 中优化器的 compute_gradients + apply_gradients 十分类似。同时,在 TensorFlow 2 中,无论是求导还是使用导数更新变量值,都需要显式地指定变量。计算图的构建代码结构如下:

optimizer = tf.keras.optimizer.SGD(learning_rate=...)

@tf.function
def train_one_step(data):
with tf.GradientTape() as tape:
y_pred = model(data) # 模型构建
loss = ... # 计算模型的损失函数 loss
grad = tape.gradient(loss, model.variables)
optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))
在计算图构建完成后,我们直接调用 train_one_step函数并送入训练数据即可:
for data in dataset:
train_one_step(data)

小结
在 TensorFlow 1.X 中,我们多使用优化器的 minimize 方法,将求导和变量值更新合二为一。而在 TensorFlow 2 中,我们需要先使用 tf.GradientTape 进行求导操作,然后再使用优化器的 apply_gradients 方法应用已求得的导数,进行变量值的更新。而且在这两步中,都需要显式指定待求导和待更新的变量。

自动求导机制的计算图对比 *

在本节,为了帮助读者更深刻地理解 TensorFlow 的自动求导机制,我们以前节的 “计算 e23e8c8b55289a0c1d3b40eb1005da08.png 在 1655f326c8e18aee5a5d04b875361f3a.png时的导数” 为例,展示 TensorFlow 1.X 和 TensorFlow 2 在图执行模式下,为这一求导过程所建立的计算图,并进行详细讲解。

在 TensorFlow 1.X 的图执行模式 API 中,将生成的计算图使用 TensorBoard 进行展示:

560bd77c6f01160369200b3eba7115b5.png在计算图中,灰色的块为节点的命名空间(Namespace,后文简称 “块”),椭圆形代表操作节点(OpNode),圆形代表常量,灰色的箭头代表数据流。为了弄清计算图节点 xyy_grad 与计算图中节点的对应关系,我们将这些变量节点输出,可见:
  • x
  • y : Tensor("Square:0", shape=(), dtype=float32)
  • y_grad : []

在 TensorBoard 中,我们也可以通过点击节点获得节点名称。通过比较我们可以得知,变量 x 对应计算图最下方的 x,节点 y 对应计算图 “Square” 块的 “ (Square) ”,节点 y_grad 对应计算图上方 “Square_grad” 的 Mul_1 节点。同时我们还可以通过点击节点发现,“Square_grad” 块里的 const 节点值为 2,“gradients” 块里的 grad_ys_0 值为 1, Shape 值为空,以及 “x” 块的 const 节点值为 3。

接下来,我们开始具体分析这个计算图的结构。我们可以注意到,这个计算图的结构是比较清晰的,“x” 块负责变量的读取和初始化,“Square” 块负责求平方 y = x ^ 2 ,而 “gradients” 块则负责对 “Square” 块的操作求导,即计算 y_grad = 2 * x。由此我们可以看出, tf.gradients 是一个相对比较 “庞大” 的操作,并非如一般的操作一样往计算图中添加了一个或几个节点,而是建立了一个庞大的子图,以应用链式法则求计算图中特定节点的导数。

在 TensorFlow 2 的图执行模式 API 中,将生成的计算图使用 TensorBoard 进行展示:

96f65e68d9f4fa29fd0274f17a0751f3.png我们可以注意到,除了求导过程没有封装在 “gradients” 块内,以及变量的处理简化以外,其他的区别并不大。由此,我们可以看出,在图执行模式下, tf.GradientTape这一上下文管理器的 gradient 方法和 TensorFlow 1.X 的 tf.gradients 是基本等价的。

小结
TensorFlow 1.X 中的 tf.gradients 和 TensorFlow 2 图执行模式下的 tf.GradientTape 上下文管理器尽管在 API 层面的调用方法略有不同,但最终生成的计算图是基本一致的。

a1e8463b8ab04756a01c89f4209a6056.png

dbedb26e4179aac206a9de6fc0eb43fa.png

“哪吒头”—玩转小潮流

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/276368.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何从 0 到 1 打造团队 PC/H5 构建工具

关注若川视野, 回复"pdf" 领取资料,回复"加群",可加群长期交流学习一、前言 大家好,我叫鳗鱼,这次分享的主题是如何从 0 到 1 打造适合自己的构建部署方案。image.png先例行的自我介绍,大概 14 年…

testng接口自动化测试_Java+Maven+TestNG接口(API)自动化测试教程(10) 使用 Jenkins 构建自动化测试持续集成...

现在代码可以运行了,但是每次运行都需要我们手工去执行,并且测试报告也只能在执行测试的电脑上才能看到,我们希望能够定时自动执行测试,并且能够做到自动发送测试报告到相关人员的电子邮箱中。Jenkins 正好可以很好的完成以上诉求…

论公众号内卷

关注若川视野, 回复"pdf" 领取资料,回复"加群",可加群长期交流学习曾几何时公众号文章的标题单纯且没有套路七年前的我就是这样仅仅把公众号当做一个写文章的博客平台甚至是像有道云一样的在线笔记平台当时的标题是这样子滴《hashma…

程序异常异常代码: 0xc0000005_Java基础:看完这篇你还怕碰到异常吗?

前言在日常的开发以及平时的学习练习中,异常相信对于大家来讲并不陌生,但是对于异常的具体使用、底层实现以及分类等等可能并不是很了解。今天我就抽出了一点时间系统的整理了异常的各个知识点,希望能够帮助到大家对于Java 异常的理解与学习。…

写给初中级前端工程师的进阶指南

学习一门新技术的时候,最大的苦恼之一,大概就是不知道从何入手。典型的情况是,你大概知道学会以后,这门技术可以帮你解决什么问题。但是,怎么才能学会、循序渐进的学习路线是什么、学习范围有多大的深度和广度、涉及到…

千层套路 - Vue 3.0 初始化源码探秘

关注若川视野, 回复"pdf" 领取资料,回复"1",可加群长期交流学习刘崇桢,微医云服务团队前端工程师,左手抱娃、右手持家的非典型码农。9 月初 Vue.js 3.0 正式发布,代号 "One Piece"。大秘…

2020年大前端技术趋势解读

导Lead语如今的前端早已不再拘泥于满足页面展示,而是开始延展到通过全栈来闭环产品。这表明前端已经有能力透过业务深入产业,继而影响商业结果。这种表象的改变背后是本质的转变,从更为宏观的角度来说,前端正在通过持续的技术革新…

lc滤波器是利用电感的感抗_你对LC谐振电路你都了解吗

根据在电路中电感器L和电容C的连接方式不同,可以有两种LC谐振电路,LC并联谐振电路和LC串联谐振电路。LC并联、串联谐振电路在应用中的变化较多,是电路中分析的一个难点,只有掌握LC并联、串联电路的阻抗特性等基本概念,…

给小程序再减重 30% 的秘密​(京喜小程序首页瘦身实践)

前言—在 web 开发场景,减少代码体积虽然是性能优化的一个方向,还没到锱铢必较的程度。但是在小程序场景,由于代码包上传阶段限制了主包 2M 和总包 16M(近期微信官方正在内测将总包上限调整至 20M )的尺寸,…

本周ASP.NET英文技术文章推荐[10/21 – 10/27]

这一篇是《本周ASP.NET英文技术文章推荐》系列的第一篇,在这个系列中,我将介绍5-10篇比较有价值的、本周发布的、与ASP.NET相关的英文技术文章,帮助各位朋友从良莠不齐的大量文章中挑出一些我认为非常有价值阅读的,在进行一段简要…

3 年前端面经和他在创业公司的成长历程

在掘金上当了几年的伸手党,最近也准备输出一些自己的东西。关于我首先介绍一下我自己,17 年毕业于一所 211 学校,但是由于大学四年驰骋在召唤师峡谷,毕业时也没有找到一份大厂的工作,随便找了一家创业公司签了三方就去…

Spring.NET学习笔记9——打造简易的依赖注入框架(练习篇) Level 100

我们在第三篇中学习里一个简易的IoC框架。今天我们接着上次的程序,实现带参数构造函数对象的实例和属性的注入 。  我们知道可以通过反射获取类的构造函数及参数(GetConstructors方法);可以获取属性和属性的类型(GetProperties方法)。通过Activator的C…

android 单元测试

首先AndroidManifest.xml View Code <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android"package"com.travelsky.test" android:versionCode"1"androi…

申万一级行业日指数_基金收评 | 指数震荡走弱,军工股成两市主线!后期行情如何?...

收评君复盘日记(2020年9月21日)三大指数集体收跌&#xff0c;北向资金全天大幅净流出近65亿元&#xff0c;军工板块表现强势。盘面回顾9月21日&#xff0c;两市全天高开低走&#xff0c;早盘指数弱势震荡&#xff0c;三大指数盘中一度翻红&#xff0c;但随后震荡走弱&#xff0…

若川的2016年度总结,毕业工作

可以点击上方的标签若川的故事、年度总结&#xff0c;查看往期文章有读者反馈说看我年度总结系列比我源码系列更有启发。所以打算把2016-2018的年度总结发布到公众号声明原创&#xff0c;希望对大家有所启发。&#xff08;虽然我的每一年都过得非常普通...&#xff09;以下是正…

jQuery之Ajax

转载链接&#xff1a;http://cargoj.iteye.com/blog/1008047 1 . jQuery帮助之Ajax请求&#xff08;一&#xff09;jQuery.ajax(options) 2 . jQuery帮助之Ajax请求&#xff08;二&#xff09;jQuery.get(url,[data],[callback] 3 . jQuery帮助之Ajax请求&#xff08;三&am…

面试官问:能否模拟实现JS的new操作符(高频考点)

可以点击上方的话题JS基础系列&#xff0c;查看往期文章这篇文章写于2018年11月05日&#xff0c;new模拟实现&#xff0c;Object.create是面试高频考点&#xff0c;之前发布在掘金有近2万人阅读&#xff0c;现在发布到公众号声明原创。1. 前言这是面试官问系列的第一篇&#xf…

跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用

一、引言 上一篇博文分享了消息队列&#xff08;MSMQ&#xff09;技术来实现分布式应用&#xff0c;在这篇博文继续分享下.NET平台下另一种分布式技术——.NET Remoting。 二、.NET Remoting 介绍 2.1 .NET Remoting简介 .NET REmoting与MSMQ不同&#xff0c;它不支持离线可得&…

二叉树的建立与遍历_51、二叉树遍历-重建二叉树JZ4

题目描述输入某二叉树的前序遍历和中序遍历的结果&#xff0c;请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}&#xff0c;则重建二叉树并返回。思路回顾三种经典的遍历&…

推荐关注这7个高质量的前端公众号

拓宽眼界&#xff0c;增加深度&#xff0c;在阅读的世界里&#xff0c;我们往往能找到不一样的态度&#xff0c;提升朋友圈质量&#xff0c;从关注这几个公众号开始。轻扫一下二维码就行了&#xff0c;你可以试试&#xff0c;肯定会有意外收获。大迁世界 简介&#xff1a;前端小…