[Tensorflow实战Google深度学习框架]笔记4
本系列为Tensorflow实战Google深度学习框架知识笔记,仅为博主看书过程中觉得较为重要的知识点,简单摘要下来,内容较为零散,请见谅。
2017-11-06
[第五章] MNIST数字识别问题
1. MNIST数据处理
为了方便使用,Tensorflow提供了一个类来处理MNIST数据,这个类会自动下载并转化MNIST数据的格式,将数据从原始的数据包中解析成训练和测试神经网络时使用的格式。
2. 神经网络模型训练及不同模型结果对比
为了评测神经网络模型在不同参数下的效果,一般会从训练数据中抽取一部分作为验证数据。使用验证数据就可以评判不同参数取值下模型的表现。除了使用验证数据集,还可以采用交叉验证(cross validation)的方式来验证模型效果,但因为神经网络训练实践本身就比较长,采用cross validation会花费大量时间。所以在海量数据的情况下,一般会更多地采用验证数据集的形式来评测模型的效果。
为了说明验证数据在一定程度上可以作为模型效果的评判标准,我们将对比在不同迭代轮数的情况下,模型在验证数据和测试数据上的正确率。为了同时得到同一个模型在验证数据和测试数据上的正确率,可以在每1000轮的输出中加入在测试数据集上的正确率。
在神经网络结构的设计上,需要使用激活函数和多层隐藏层。在神经网络优化时,可以使用指数衰减的学习率,加入正则化的损失函数以及滑动平均模型。
3. 变量管理
Tensorflow提供了通过变量名称来创建或者获取一个变量的机制,通过这个机制,在不同的函数中可以直接通过变量的名字来使用变量,而不需要将变量通过参数的形式到处传递。Tensorflow中通过变量名称获取变量的机制主要时通过tf.get_variable和tf.variable_scope函数实现的。
除了tf.Variable函数,Tensorflow还提供了tf.get_variable函数来创建或者获取变量。当tf.get_variable用于创建变量时,它和tf.Variable的功能是基本等价的。
如:#下面这两个定义是等价的。
v = tf.get_variable("v",shape=[1],initializer=tf.constant_initilizer(1.0))
v = tf.Variable(tf.constant(1.0,shape=[1]),name="v")
Tensorflow提供了7种不同的初始化函数,如下:
初始化函数 功能 主要参数
tf.constant_initializer--->将变量初始化为给定常量----> 常量的取值
tf.random_normal_initializer--->将变量初始化为满足正太分布的随机值--->正太分布的均值和标准差
tf.truncated_normal_initializer--->将变量初始化为满足正太分布的随机值,但若随机出来的值偏离平均值超过两个标准差,那么这个数将会被重新随机---->正太分布的均值和标准差
tf.random_uniform_initializer--->将变量初始化为满足平均分布的随机值---->最大,最小值
tf.uniform_unit_scaling_initializer--->将变量初始化为满足平均分布但不影响输出数量级的随机值---->factor(产生随机值时乘以的系数)
tf.zeros_initializer--->将变量设置为全为0--->变量维度
tf.ones_initializer--->将变量设置为全为1--->变量维度
在上面的样例定义程序中,tf.get_variable首先会试图去创建一个名字为v的参数,如果创建失败(比如已经有同名的参数),那么这个程序就会报错。这是为了避免无意识的变量复用造成的错误。比如在定义神经网络参数时,第一层网络的权重已经叫weights了,那么在创建第二层神经网络时,如果参数名仍然叫weights,就会触发变量重用的错误。否则两层神经网络共用一个权重会出现一些比较难以发现的错误。如果需要通过tf.get_variable获取一个已经创建的变量,需要通过tf.variable_scope函数来生成一个上下文管理器,并明确指定在这个上下文管理器中,tf.get_variable将直接获取已经生成的变量。下面给出一段代码说明如何通过tf.variable_scope函数来控制tf.get_variable函数获取已经创建过的变量。
#在名字为foo的命名空间内创建名字为v的变量
with tf.variable_scope("foo"):
v = tf.get_variable("v",[1],initializer=tf.constant_initializer(1.0))
#因为在命名空间foo已经存在名字为v的变量,所有下面的代码将会报错:
with tf.variable_scope("foo"):
v = tf.get_variable("v",[1])
#在生成上下文管理器时,将参数reuse设置为True。这样tf.get_variable函数将直接获取已经生成的变量
with tf.variable_scope("foo",reuse=True):
v1 = tf.get_variable("v",[1])
print v == v1 #输出为True,代表v,v1是相同的Tensorflow中的变量
#将参数reuse设置为True时,tf.variable_scope将只能获取已经创建的变量,因为在命名空间bar中还没有创建变量v,所以下面的代码将会报错:
with tf.variable_scope("bar",reuse=True):
v = tf.get_variable("v",[1])
同样,如果tf.variable_scope函数使用参数reuse=None或者reuse=False创建上下文管理器,tf.get_variable操作将创建新的变量,如果同名的变量已经存在,则tf.get_variable函数将报错。另外,Tensorflow中tf.variable_scope函数是可以嵌套的。
使用变量管理后,就不再需要将所有变量都作为参数传递到不同的函数中了,当神经网络结构更加复杂,参数更多时,使用这种变量管理的方式将大大提高程序的可读性。
4. Tensorflow模型持久化
为了让训练结果可以复用,需要将训练得到的神经网络模型持久化(保存下来方便下次使用)。
Tensorflow提供了一个非常简单的API来保存和还原一个神经网络模型,这个API就是tf.train.Saver类。
saver = tf.train.Saver()
saver.save(sess,"/path/to/model/model.ckpt")
Tensorflow模型一般会存在后缀为.ckpt文件中,虽然上面的程序只指定了一个文件路径,但是在这个文件目录下会出现三个文件,这是因为Tensorflow会将计算图的结构和涂上的参数取值分来保存。第一个文件为model.ckpt.meta,它保存了Tensorflow计算图的结构,第二个文件为model.ckpt,这个文件保存了Tensorflow程序中每一个变量的取值,最后一个文件为checkpoint文件,这个文件保存了一个目录下所有的模型文件列表。
加载模型的代码:
saver = tf.train.Saver()
saver.restore(sess,"/path/to/model/model.ckpt")
上面给出的程序中,默认保存和加载了Tensorflow计算图上定义的全部变量。但有时候可能只需要保存或者加载部分变量,比如,可能有一个之前训练好的五层神经网络模型,但现在想尝试一个六层的神经网络,那么可以将前面五层神经网络中的参数直接加载到新的模型,而仅仅将最后一层神经网络重新训练。为了保存或者加载部分变量,在声明tf.train.Saver类时可以提供一个列表来指定需要保存或者加载的变量。比如在加载模型的代码中使用saver=tf.train.Saver([v1])命令来构建tf.train.Saver类,那么只有变量v1会被加载进来,如果运行修改后之家在v1的代码会得到变量未初始化的错误:
tensorflow.python.framework.errors.FailedPreconditionError:Attempting to use uninitialized value v2
因为v2没有被加载,所以v2在运行初始化之前是没有值的。除了可以选取需要被加载的变量,tf.train.Saver类也支持在保存或者加载时给变量重命名。
下面给出一个简单的程序来说明重命名时如何被调用的
#这里声明的变量名称和已经保存的模型中变量的名称不同
v1 = tf.Variable(tf.constant(1.0,shape=[1]),name="other-v1")
v2 = tf.Variable(tf.constant(2.0,shape=[1]),name="other-v2")
#如果直接使用tf.train.Saver类来加载模型会报变量找不到的错误。
#使用一个字典(dictionary)来重命名变量就可以加载原来的模型了。这个字典指定了原来名称为v1的变量现在加载到变量v1中(名称为other-v1),
#名称为v2的变量加载到变量v2中(名称为other-v2)
saver = tf.train.Saver({"v1":v1, "v2":v2})
在这个程序中,对变量v1和v2的名称进行了修改,如果直接通过tf.train.Saver默认的构造函数来加载保存的模型,那么程序会报变量找不到的错误,因为保存时候变量的名称和加载时变量的名称不一致。为了解决这个问题,Tensorflow可以通过字典(dictionary)将模型保存时的变量名和需要加载的变量联系起来。这样做主要目的之一时方便使用变量的滑动平均值,在Tensorflow中,每一个变量的滑动平均值是通过影子变量维护的,所以要获取变量的滑动平均值实际上就是获取这个影子变量的取值。如果在加载模型时直接将影子变量映射到变量自身,那么在使用训练好的模型时就不需要再调用函数来获取变量的滑动平均值了。下面的代码给出了一个保存滑动平均模型的样例:
import tensorflow as tf
v = tf.Variable(0,dtype=tf.float32,name="v")
#在没有申明滑动平均模型时只有一个变量v,所以下面的语句只会输出"v:0"
for variables in tf.all_variables():
print(variables.name)
ema = tf.train.ExponentialMovingAverage(0.99)
maintain_average_op = ema.apply(tf.all_variables())
#在申明滑动平均模型之后,Tensorflow会自动生成一个影子变量v/ExponentialMoving Average
#于是下面的语句会输出"v:0"和“v/ExponentialMoving Average:0”
for variables in tf.all_variables():
print(variables.name)
saver = tf.train.Saver()
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
sess.run(tf.assign(v,10))
sess.run(maintain_average_op)
#保存时Tensorflow会将v:0和v/ExponentialMoving Average:0两个变量都保存下来
saver.save(sess,"path/to/model/model.ckpt")
print(sess.run([v,ema.average(v)])) #输出[10.0,0.09999]
使用tf.train.Saver会保存运行Tensorflow程序所需要的全部信息,然后有时并不需要某些信息。比如在测试或者离线预测时,只需要知道如何从神经网络的输入层经过前向传播计算得到输出层即可,而不需要类似于变量初始化,模型保存等辅助节点的信息。而且,将变量取值和计算图结构分成不同的文件存储有时候也不方便,于是Tensorflow提供了convert_variables_to_constants函数,通过这个函数可以将计算图中的变量及其取值通过常量的方式保存,这样整个Tensorflow计算图可以统一存放在一个文件中。如下:
import tensorflow as tf
from tensorflow.python.framework import graph_util v1 = tf.Variable(tf.constant(1.0,shape=[1]),name="v1")
v2 = tf.Variable(tf.constant(2.0,shape=[1]),name="v2")
result = v1 + v2 init_op = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init_op)
#导出当前计算图的GraphDef部分,只需要这部分就可以完成从输入层到输出层的计算过程
graph_def = tf.get_default_graph().as_graph_def()
#将图中的变量及其取值转化为常量,同时将图中不必要的节点去掉
output_graph_def = graph_util.convert_variables_to_constants(sess,graph_def,['add'])
with tf.gfile.GFile("/path/to/model/combined_model.pb","wb") as f:f.write(output_graph_def.SerializeToString())
通过下面的程序可以直接计算定义的加法运算的结果:
import tensorflow as tf
from tensorflow.python.platform import gfile
with tf.Session() as sess:
model_filename = "/path/to/model/combined_model.pb"
#读取保存的模型文件,并将文件解析成对应的GraphDef Protocol Buffer
with gfile.FastGFile(model_filename,'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
#将graph_def中保存的图加载到当前的图中.return_elements=["add:0"]给出了返回的张量的名称。
#在保存能的时候给出的时计算节点的名称,所以为"add",在加载的时候给出的是张量的名称,所以时add:0
result = tf.import_graph_def(graph_def,return_elements=["add:0"])
print(sess.run(result))
Tensorflow是一个通过图的形式来表达计算的编程系统,Tensorflow程序中的所有计算都会表达为计算图上的节点。Tensorflow通过元图(MetGraph)来记录计算图中节点的信息以及运行计算图中节点所需要的元数据。Tensorflow中元图是由MetaGraphDef Protocol BUffer定义的,MetaGraphDef中的内容就构成了Tensorflow持久化时的第一个文件。关于MetaGraphDef类型的定义以及模型持久化的原理和数据的格式请另查阅相关资料。[page 116 129/297]
5. Tensorflow最佳实践样例程序
本节中将介绍一个Tensorflow训练神经网络模型的最佳实践,将训练和测试分成两个独立的程序,这可以使得每一个组件更加灵活。比如训练神经网络的程序可以持续输出训练好的模型,而测试程序可以每隔一段实践检验最新模型的正确率,如果模型效果更好,则将这个模型提供给产品使用。除了将不同的功能模块分开,本节还将前向传播的过程抽象成一个单独的库函数。因为神经网络的前向传播过程在训练和测试的过程中都会用到,所以通过库函数的方式使用起来既方便又可以保证训练和测试过程中使用的前向传播方法是一致的。
下面将提供重构之后的程序来解决MNIST问题:第一个是mnist_inference.py,它定义了前向传播的过程以及神经网络中的参数,第二个时minst_train.py,它定义了神经网络的训练过程,第三个时mnist_eval.py,它定义了测试过程。下面给出具体代码:
mnist_inference.py:
#-*- coding:utf-8 -*-
import tensorflow as tf
#定义神经网络结构相关的参数
INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500
#通过tf.get_variable函数来获取变量
def get_weight_variable(shape,regularizer):
weights = tf.get_variable("weights",shape,initializer=tf.truncated_normal_initializer(stddev=0.1))
#当给出了正则化生成函数时,将当前变量的正则化损失加入名字为losses的集合
#这是自定义的集合,不再Tensorflow自动管理的集合列表中
if regularizer != None:
tf.add_to_collection('losses',regularizer(weights))
return weights
#定义神经网络的前向传播过程
def inference(input_tensor,regularizer):
#声明第一层神经网络的变量并完成前向传播的过程
with tf.variable_scope('layer1'):
weights = get_weight_variable([INPUT_NODE,LAYER1_NODE],regularizer)
biases = tf.get_variable("biases",[LAYER1_NODE],initializer=tf.constant_initializer(0.0))
layer1 = tf.nn.relu(tf.matmul(input_tensor,weights)+biases)
#类似的声明第二层神经网络的变量并完成前向传播过程
with tf.variable_scope('layer2'):
weights = get_weight_variable([LAYER1_NODE, OUTPUT_NODE], regularizer)
biases = tf.get_variable("biases", [OUTPUT_NODE], initializer=tf.constant_initializer(0.0))
layer2 = tf.nn.relu(tf.matmul(layer1, weights) + biases)
#返回最后前向传播的结果
return layer2
mnist_train.py:
#-*- coding:utf-8 -*-
import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
#加载mnist_inference.py中定义的常量和前向传播的函数
import mnist_inference
#配置神经网络的参数
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARAZTION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99
#模型保存的路径和文件名
MODEL_SAVE_PATH = "/path/to/model/"
MODEL_NAME = "model.ckpt"
def train(mnist):
#定义输入输出placeholder
x = tf.placeholder(tf.float32,[None,mnist_inference.INPUT_NODE],name='x-input')
y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')
regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)
#直接使用mnist_inference.py中定义的前向传播过程
y = mnist_inference.inference(x,regularizer)
global_step = tf.Variable(0,trainable=False)
#定义损失函数,学习率,滑动平均操作以及训练过程
variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)
variable_averages_op = variable_averages.apply(tf.trainable_variables())
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(y,tf.arg_max(y_,1))
cross_entropy_mean = tf.reduce_mean(cross_entropy)
loss = cross_entropy_mean+tf.add_n(tf.get_collection_ref('losses'))
learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE,global_step,mnist.train.num_examples/BATCH_SIZE,LEARNING_RATE_DECAY)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)
with tf.control_dependencies([train_step,variable_averages_op]):
train_op = tf.no_op(name='train')
#初始化Tensorflow持久化类
saver = tf.train.Saver()
with tf.Session() as sess:
tf.global_variables_initializer().run()
#在训练过程中不再测试模型在验证数据上的表现,验证和测试的过程将会有一个独立的程序来完成
for i in range(TRAINING_STEPS):
xs,ys = mnist.train.next_batch(BATCH_SIZE)
_,loss_value,step = sess.run([train_op,global_step],feed_dict={x:xs,y_:ys})
#每1000轮保存一次模型
if i % 1000 == 0:
#输出当前的训练情况
print("After %d training steps, loss on training batch is %g."%(step,loss_value))
#保存当前的模型
saver.save(sess,os.path.join(MODEL_SAVE_PATH,MODEL_NAME),global_step=global_step)
def main(argv=None):
mnist = input_data.read_data_sets("MNIST_DATA",one_hot=True)
train(mnist)
if __name__ == '__main__':
tf.app.run()
mnist_eval.py:
#-*- coding:utf-8 -*-
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
#加载mnist_inference.py和mnist_train.py中定义的常量和函数
import mnist_inference
import mnist_train
#每10秒加载一次最新的模型,并在测试数据上测试最新模型的正确率
EVAL_INTERVAL_SECS = 10
def evaluate(mnist):
with tf.Graph().as_default() as g:
x = tf.placeholder(tf.float32,[None,mnist_inference.INPUT_NODE],name='x-input')
y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')
validate_feed = {x:mnist.validation.images,y_:mnist.validation.labels}
#直接通过调用封装好的函数来计算前向传播的结果,因为测试时不关注正则化损失的值,所以这里用于计算正则化损失的函数被设置为None
y = mnist_inference.inference(x,None)
#使用前向传播的结果计算正确率,如果需要对未知的样例进行分类,那么使用tf.argmax(y,1)就可以得到样例的预测类别了
correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
#通过变量重命名的方式来加载模型,这样就可以完全共用mnist_inference.py中定义的前向传播过程
variable_averages = tf.train.ExponentialMovingAverage(mnist_train.MOVING_AVERAGE_DECAY)
variable_to_restore = variable_averages.variables_to_restore()
saver = tf.train.Saver(variable_to_restore)
#每隔EVAL_INTERVAL_SECS秒调用一次计算正确率的过程以检测训练过程中正确率的变化
while True:
with tf.Session() as sess:
#tf.train.get_checkpoint_state函数会通过checkpoint文件自动找到目录中最新模型的文件名
ckpt = tf.train.get_checkpoint_state(mnist_train.MODEL_SAVE_PATH)
if ckpt and ckpt.model_checkpoint_path:
#加载模型
saver.restore(sess,ckpt.model_checkpoint_path)
#通过文件名得到模型保存时迭代的轮数
global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
accuracy_score = sess.run(accuracy,feed_dict=validate_feed)
print("After %s training steps, validation accuracy = %g." %(global_step,accuracy_score))
else:
print('No checkpoint file found.')
return
time.sleep(EVAL_INTERVAL_SECS)
def main(argv=None):
mnist = input_data.read_data_sets("MNIST_DATA",one_hot=True)
evaluate(mnist)
if __name__ == '__main__':
tf.app.run()
[Tensorflow实战Google深度学习框架]笔记4的更多相关文章
- TensorFlow+实战Google深度学习框架学习笔记(5)----神经网络训练步骤
一.TensorFlow实战Google深度学习框架学习 1.步骤: 1.定义神经网络的结构和前向传播的输出结果. 2.定义损失函数以及选择反向传播优化的算法. 3.生成会话(session)并且在训 ...
- 1 如何使用pb文件保存和恢复模型进行迁移学习(学习Tensorflow 实战google深度学习框架)
学习过程是Tensorflow 实战google深度学习框架一书的第六章的迁移学习环节. 具体见我提出的问题:https://www.tensorflowers.cn/t/5314 参考https:/ ...
- 学习《TensorFlow实战Google深度学习框架 (第2版) 》中文PDF和代码
TensorFlow是谷歌2015年开源的主流深度学习框架,目前已得到广泛应用.<TensorFlow:实战Google深度学习框架(第2版)>为TensorFlow入门参考书,帮助快速. ...
- TensorFlow实战Google深度学习框架-人工智能教程-自学人工智能的第二天-深度学习
自学人工智能的第一天 "TensorFlow 是谷歌 2015 年开源的主流深度学习框架,目前已得到广泛应用.本书为 TensorFlow 入门参考书,旨在帮助读者以快速.有效的方式上手 T ...
- TensorFlow实战Google深度学习框架10-12章学习笔记
目录 第10章 TensorFlow高层封装 第11章 TensorBoard可视化 第12章 TensorFlow计算加速 第10章 TensorFlow高层封装 目前比较流行的TensorFlow ...
- TensorFlow实战Google深度学习框架5-7章学习笔记
目录 第5章 MNIST数字识别问题 第6章 图像识别与卷积神经网络 第7章 图像数据处理 第5章 MNIST数字识别问题 MNIST是一个非常有名的手写体数字识别数据集,在很多资料中,这个数据集都会 ...
- TensorFlow实战Google深度学习框架1-4章学习笔记
目录 第1章 深度学习简介 第2章 TensorFlow环境搭建 第3章 TensorFlow入门 第4章 深层神经网络 第1章 深度学习简介 对于许多机器学习问题来说,特征提取不是一件简单的事情 ...
- Tensorflow实战Google深度学习框架-总结-1
第一章:深度学习简介 1⃣️应用有 1.计算机视觉 2.语音识别 3.自然语言处理 4.人机博弈 2⃣️深度学习,机器学习,AI 的关系
- TensorFlow+实战Google深度学习框架学习笔记(10)-----神经网络几种优化方法
神经网络的优化方法: 1.学习率的设置(指数衰减) 2.过拟合问题(Dropout) 3.滑动平均模型(参数更新,使模型在测试数据上更鲁棒) 4.批标准化(解决网络层数加深而产生的问题---如梯度弥散 ...
随机推荐
- iterator与const_iterator
iterator与const_iterator 所有的标准库容器都定义了相应的迭代器类型.迭代器对所有的容器都适用,现代 C++ 程序更倾向于使用迭代器而不是下标操作访问容器元素. 1.iterato ...
- Python 第五阶段 学习记录之--- Web框架
什么是web服务器的原理是什么 Web框架本质 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env pyt ...
- iOS项目之交换方法(runtime)
在项目中,经常会遇到系统自带的方法满足不了自己的需求,往往我们解决这种情况的时候,都是在分类中添加一个方法.然而很多时候,项目已经开发很长时间了,如果一个一个的去替换系统的方法,太浪费宝贵的时间,所以 ...
- Dockerfile的HEALTHCHECK指令
容器实例的状态虽然是up,但不能保证里面的进程一定是监控的.我门可以借助HEALTHCHECK指令来做监控状态检查 HEALTHCHECK指令有两种形式: HEALTHCHECK [OPTIONS] ...
- vue_elementUI_ tree树形控件 获取选中的父节点ID
el-tree 的 this.$refs.tree.getCheckedKeys() 只可以获取选中的id 无法获取选中的父节点ID想要获取选中父节点的id;需要如下操作1. 找到工程下的node_m ...
- 一、restful规范 二、CBV(View)源代码执行流程 三、drf框架安装和简单使用
一.restful规范 ''' 它是一个规范,面向资源架构 十条规范 1.API与用户的通讯协议,总是使用HTTPs协议,确保了网络传输的安全性 2.域名 --https://api.example. ...
- dijkstral改编
题意:给你包含n个点的连通图,每个点都有一个权值.给定起点和终点.问你起点到终点的最短路条数,并且输出路径最短且权值之和最大的一条路径. 思路:1.如何根据父节点更新子节点.x,y是父子节点.如果从起 ...
- 最新版的Chrome如何始终开启flash而不是先询问?
链接:https://www.zhihu.com/question/266170237/answer/342137190 设置Chrome启用Flash,修改配置之前先看Chrome的版本,不同版 ...
- Learning-Python【34】:进程之生产者消费者模型
一.什么是生产者消费者模型 生产者指的是生产数据的任务,消费者指的是处理数据的任务,在并发编程中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据.同样 ...
- LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法
本篇继续LINQ Operators的介绍,包括元素运算符/Element Operators.集合方法/Aggregation.量词/Quantifiers Methods.元素运算符从一个sequ ...