代码实现tan graph model for classification_自定义 Estimator 实现(以BERT为例)

69e666a348716a7ca8576e0caa551e25.png

本文将主要介绍tensorflow 的Estimator 这个高级API,它的主要作用就是提出一个高级范式(paradigm),将模型的训练,验证,预测,以及保存规范起来,免去了tensorflow的Session.run 的操作,并且很好地结合了 tf.data.Dataset 作为数据处理的包装,使得整个思路变得非常清晰。

Estimator 并没有改变网络结构的定义,而是将训练,验证,测试的管理抽象起来,并且简单化,形成一个范式,用户可以通过实现对应的部分,从而使得模型的管理便捷起来。

BERT的源码实现采用的是TPU的estimator的方式,但是大家日常训练模型的时候,很多都无法使用得Â上TPU,虽然 TPUEstimator 会自动适配到CPU 或者 GPU 的普通 Estimator, 但是我在使用tensorflow==1.14.0的时候,会发现在训练过程中的logging无法获得训练过程中的loss变化等,只有下图的log信息。see github issue

具体改动后的代码详见:https://github.com/dongxiaohuang/TextClassifier_Transformer/blob/master/run_classifier_serving_gpu.py

447a473d98b111c09b1582381d12a1c5.png

这些信息对于debug来说是远远不够的,当然可以通过增加log hook的方式定时打印需要的log,但是即使这样日志还是多条才看能到一次loss。于是我就将 TPUEstimator 收到改成普通的 Estimator 实现,并且成功地获得正常的日志输出。

e6f8a03a77ddd3c3fe71f7763832e59d.png

本文将会介绍总体介绍 Estimator, 然后介绍各个component,并配以bert实现进行讲解。有问题麻烦指出,谢谢。

Estimator 组成

首先我们创建一个estimator,它对外暴露了4个接口,分别是: train(), evaluate(), predict(), export_savedmodel(),对应的是模型的训练,验证,预测,以及导出。estimator 通过用户实现 model_fn 构建模型,并且通过 model_fn 的实现不同的mode(ModeKeys.TRAIN,ModeKeys.EVAL,ModeKeys.PREDICT),返回不同的EstimatorSpec。

对于 train, evaluate, predict 都需要传入相应的 input_fn, 代表训练集,验证集,以及测试集。input_fn 将返回features,以及lables。具体的 input_fn 的将会单独介绍,我们只需要知道它构造 tf.data.Dataset, 作为estimator不同模式的数据源就行了。

def input_fn(dataset):... #manipulate dataset, extracting teh feature dict and the labelreturn feat_dict, label

我们可以通过不同hooks,在estimator执行的不同阶段,执行自己的某些操作,例如增加log,或者是early stopping等。

9c23fc279c50426e7854f6aa32aa9f13.png

使用 estimator 构建模型

使用 estimator 构建模型,你需要实现 model_fn(features, labels, mode, params) -> tf.estimator.EstimatorSpec , 该函数有严格的函数签名,并且将会hold 模型图定义。

  • features:input_fn 返回的第一个参数,即输入数据的特征Tensor
  • labels: input_fn 返回的第二个参数,即输入数据的label
  • mode: string,根据这个mode (PREDICT, EVAL or TRAIN) 去决定我们要运行 model_fn 中哪一部分的graph要被建立。我们会在 model_fn 中实现三种模式的不同的graph,然后通过estimator的不同的方法(train/evaluate/predict)被调用,执行不同部分,而build 不同的graph。
  • params:一个参数的字典,与 estimator 的参数之一 params 对应,例如我们有一个params 的字典
  params = {'buffer': 15000,'lstm_size': 100,'words': str(Path(DATADIR, 'vocab.words.txt')),'chars': str(Path(DATADIR, 'vocab.chars.txt')),'tags': str(Path(DATADIR, 'vocab.tags.txt')),'glove': str(Path(DATADIR, 'glove.npz'))
}

我们要使用buffer,可以直接通过 params['buffer'] 使用。当然我们不使用这一参数也可以,我们可以通过构建一个 model_fn_builder() 将参数传入这个builder中,最后返回 model_fn(),BERT中也是这么实现的,待会会具体介绍。

模型的 model_fn 的主要实现框架如下,通过features的输入传入模型,得到输出,然后根据不同mode,实现特定输出。Estimator是通过 model_fn 进行配置的,该函数构建了tensorflow的图,并且返回了足够的信息使得模型可以进行训练,验证,测试及导出。使用自定义的estimator只需要实现这个函数。大概的实现框架如下图

def model_fn(features, labels, mode, params):# Define the inference graphgraph_outputs = some_tensorflow_applied_to(features)if mode == tf.estimator.ModeKeys.PREDICT:# Extract the predictionspredictions = some_dict_from(graph_outputs)return tf.estimator.EstimatorSpec(mode, predictions=predictions)else:# Compute loss, metrics, tensorboard summariesloss = compute_loss_from(graph_outputs, labels)metrics = compute_metrics_from(graph_outputs, labels)if mode == tf.estimator.ModeKeys.EVAL:return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics)elif mode == tf.estimator.ModeKeys.TRAIN:# Get train operatortrain_op = compute_train_op_from(graph_outputs, labels)return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)else:raise NotImplementedError('Unknown mode {}'.format(mode))

ae4064b83084fc3a5fe6fadd56389044.png

定义inference 图

首先我们将根据我们的特征输入,构建我们的graph。下面的代码主要是通过数据的features传入 BERT模型, 然后获得模型的loss, probabilities 等结果,然后通过预训练模型进行初始化,具体原理参考BERT详解。

# 正常图构造
input_ids = features["input_ids"]
input_mask = features["input_mask"]
segment_ids = features["segment_ids"]
label_ids = features["label_ids"]
is_real_example = None
if "is_real_example" in features:is_real_example = tf.cast(features["is_real_example"], dtype=tf.float32)
else:is_real_example = tf.ones(tf.shape(label_ids), dtype=tf.float32)(total_loss, per_example_loss, probabilities, predictions) = create_model(albert_config, is_training, input_ids, input_mask,segment_ids, label_ids, num_labels, use_one_hot_embeddings)
# 模型参数通过预训练模型进行初始化,fine-tuning
tvars = tf.trainable_variables()
initialized_variable_names = {}
if init_checkpoint:(assignment_map, initialized_variable_names) = modeling.get_assignment_map_from_checkpoint(tvars, init_checkpoint)tf.train.init_from_checkpoint(init_checkpoint, assignment_map)

Train Mode

在训练模式的时候,EstimatorSpec需要返回三个参数

  • mode(所有模式都必须有的)
  • loss:训练的loss
  • train_op:训练的operation,一般是通过 optimizer.minimise(loss)
if mode == tf.estimator.ModeKeys.TRAIN:train_op = optimization.create_optimizer(total_loss, learning_rate, num_train_steps, num_warmup_steps, False)output_spec = tf.estimator.EstimatorSpec(mode=mode,loss=total_loss,train_op=train_op)

Eval Mode

在 tf.estimator.ModeKeys.EVAL 模式下,我们需要返回

  • - mode
  • - loss
  • - eval_metric_ops: 评测的Metric operation 指标。这是一个字典(metrics dictionary)的形式,key是评测指标的名称(string),value是tuple的形式,tuple由以下两个tensor 组成:
  • 第一个元素是 value_op,它指的是真实最终的metrics value。
  • 第二个元素是 update_op,它指的是用来更新metrics value的tensor,为什么这样设计是因为,我们通常在做验证的时候,数据不能一下子fit进memory 或者其它原因,我们不能通过一次性在验证集上进行验证,我们是通过mini batch的方式进行评测的,而batch的方式就导致我们需要每验证一个batch就更新我们的metrics value,而不是单独地使用某一个batch的metrics value作为结果。

其中 Metrics 是layers的一个特殊类,通常用于做evaluation:它的输入是真是值 labels,以及预测值 predictions,可选项权重weights;输出是metric 例如说是 log-likelihood, accuracy,或者MSE等。该输出由两个Tensor组成,update op:这每一个 minibatch 都会计算; 然后是 value op:这个计算最后的metric value。所以我们可以借助metrics这个类,来计算我们需要的验证指标,例如列了常见了几种metric的计算方式,其中 tf_metrics以及ref 里面实现了accuracy以及recall,f1等评测指标。

'accuracy': tf.metrics.accuracy(labels=label_ids, predictions=predictions, weights=is_real_example)
'loss': tf.metrics.mean(values=per_example_loss, weights=is_real_example)
'Recall@5': metrics.streaming_sparse_recall_at_k(predictions, tf.cast(labels["class_idx"], tf.int64), 5)
'precision': tf_metrics.precision(tags, pred_ids, num_tags, indices, weights),
'recall': tf_metrics.recall(tags, pred_ids, num_tags, indices, weights),
'f1': tf_metrics.f1(tags, pred_ids, num_tags, indices, weights),

其中tf_metrics 可以通过以下指令安装: pip install git+https://github.com/guillaumegenthial/tf_metrics.git

其中BERT的实现如下:

elif mode == tf.estimator.ModeKeys.EVAL:def metric_fn(per_example_loss, label_ids, predictions, is_real_example):accuracy = tf.metrics.accuracy(labels=label_ids, predictions=predictions, weights=is_real_example)loss = tf.metrics.mean(values=per_example_loss, weights=is_real_example)for metric_name, op in metrics.items(): # tensorboardtf.summary.scalar(metric_name, op[1])return {"eval_accuracy": accuracy,"eval_loss": loss,}eval_metric_ops = metric_fn(per_example_loss, label_ids, predictions, is_real_example)output_spec = tf.estimator.EstimatorSpec(mode=mode,loss=total_loss,eval_metric_ops=eval_metric_ops

Prediction Mode

prediction mode是最简单的模式,我们需要返回:

  • mode
  • predictions:这是一个字典,字典的key是我们要返回的tensor的名字,字典的value是我们要返回的tensor,例如说, 我们需要返回两个tensor,分别是 probabilities, predictions, 我们将他们直接构建一个字典,传入predictions这个参数中。
_,top_3 =  tf.nn.top_k(probabilities,k=3)
output_spec = tf.estimator.EstimatorSpec(mode=mode,predictions={"probabilities": probabilities,"predictions": predictions,"top_3":top_3})

BERT的model_fn 实现

bert的 model_fn 实现,并没有使用到estimator传入的params,而是通过构建一个 model_fn_builder, 将各种参数传入其中,最后返回需要的 model_fn(features, labels, mode, params) 函数。

def model_fn_builder(albert_config, num_labels, init_checkpoint, learning_rate,num_train_steps, num_warmup_steps, use_one_hot_embeddings):def model_fn(features, labels, mode, params):  # pylint: disable=unused-argument"""The `model_fn` for TPUEstimator."""tf.logging.info("*** Features ***")for name in sorted(features.keys()):tf.logging.info("  name = %s, shape = %s" % (name, features[name].shape))input_ids = features["input_ids"]input_mask = features["input_mask"]segment_ids = features["segment_ids"]label_ids = features["label_ids"]is_real_example = Noneif "is_real_example" in features:is_real_example = tf.cast(features["is_real_example"], dtype=tf.float32)else:is_real_example = tf.ones(tf.shape(label_ids), dtype=tf.float32)is_training = (mode == tf.estimator.ModeKeys.TRAIN)(total_loss, per_example_loss, probabilities, predictions) = create_model(albert_config, is_training, input_ids, input_mask,segment_ids, label_ids, num_labels, use_one_hot_embeddings)tvars = tf.trainable_variables()initialized_variable_names = {}if init_checkpoint:(assignment_map, initialized_variable_names) = modeling.get_assignment_map_from_checkpoint(tvars, init_checkpoint)tf.train.init_from_checkpoint(init_checkpoint, assignment_map)tf.logging.info("**** Trainable Variables ****")for var in tvars:init_string = ""if var.name in initialized_variable_names:init_string = ", *INIT_FROM_CKPT*"tf.logging.info("  name = %s, shape = %s%s", var.name, var.shape,init_string)output_spec = Noneif mode == tf.estimator.ModeKeys.TRAIN:train_op = optimization.create_optimizer(total_loss, learning_rate, num_train_steps, num_warmup_steps, False)output_spec = tf.estimator.EstimatorSpec(mode=mode,loss=total_loss,train_op=train_op)elif mode == tf.estimator.ModeKeys.EVAL:def metric_fn(per_example_loss, label_ids, predictions, is_real_example):accuracy = tf.metrics.accuracy(labels=label_ids, predictions=predictions, weights=is_real_example)loss = tf.metrics.mean(values=per_example_loss, weights=is_real_example)return {"eval_accuracy": accuracy,"eval_loss": loss,}eval_metric_ops = metric_fn(per_example_loss, label_ids, predictions, is_real_example)output_spec = tf.estimator.EstimatorSpec(mode=mode,loss=total_loss,eval_metric_ops=eval_metric_ops)else:output_spec = tf.estimator.EstimatorSpec(mode=mode,predictions={"probabilities": probabilities,"predictions": predictions})return output_specreturn model_fn

初始化Estimator

在定义完estimator之后,就可以初始化estimator了,estimator有四个参数:

  • model_fn:我们上面定义的模型model_fn
  • model_dir:模型保存的路径,如果没有设置,会使用config中的 model_dir 参数的值,如果两个都设置了,必须一致,如果两个都没有设置,那么将会保存在一个temp的路径下。
  • config:estimator的config,下面会具体介绍
  • params=None:params,字典形式,将会传给model_fn的params参数
  • warm_start_from:string,checkpoint or SavedModel的文件路径用来warm_start

RunConfig

Runconfig主要用来设置模型保存的路径model_dir, 以及模型保存的频率 save_checkpoints_steps or save_checkpoints_secs (默认是600 seconds 保存一次,如果两个参数都设置未None,则不保存),以及Session的设置 session_config,例如说XLA是否开启等等。 或者说分布式策略设置train_distributeor eval_distribute 等等。

config = tf.compat.v1.ConfigProto()if FLAGS.horovod:tf.compat.v1.logging.info("Multi-GPU training with TF Horovod")tf.compat.v1.logging.info("hvd.size() = %d hvd.rank() = %d", hvd.size(), hvd.rank())global_batch_size = FLAGS.train_batch_size * FLAGS.num_accumulation_steps * hvd.size()master_process = (hvd.rank() == 0)hvd_rank = hvd.rank()config.gpu_options.visible_device_list = str(hvd.local_rank())if FLAGS.use_xla:config.graph_options.optimizer_options.global_jit_level = tf.compat.v1.OptimizerOptions.ON_1run_config = tf.estimator.RunConfig(model_dir=FLAGS.output_dir if master_process else None,session_config=config,save_checkpoints_steps=FLAGS.save_checkpoints_steps if master_process else None,keep_checkpoint_max=1)

实例化代码

estimator = tf.estimator.Estimator(model_fn=model_fn,config=run_config,params=None)

Estimator使用

estimator初始化完了之后,我们就很容易通过调用它的train 进行训练, 调用 evaluate进行验证, 调用predict 进行预测,同时调用export_saved_model导出SavedModel。通常我们还可以使用tf.estimator.train_and_evaluate()进行模型的训练及验证(推荐)。

训练

训练的脚本很简单,我们传入训练的input_fn(暂不cover在这个tutorial),以及hooks(下面会介绍),以及saving_listeners(每次checkpoint保存之后执行)。 train( input_fn, hooks=None, steps=None, max_steps=None, saving_listeners=None ) 具体的bert训练的脚本就是如下:

num_train_steps = int(len(train_examples) / FLAGS.train_batch_size * FLAGS.num_train_epochs)
estimator.train(input_fn=train_input_fn,max_steps=num_train_steps,hooks=[early_stopping_hook])

验证

evaluate(input_fn, steps=None, hooks=None, checkpoint_path=None, name=None
)

evaluate 函数将传入验证集的input_fn,验证的步数steps, hooks,以及模型的 checkpoint_path 注意如果:

  • steps:验证步数,如果未None,则运行到input_fn raises an end-of-input exception.
  • checkpoint_path:需要验证的模型的路径,如果 None, 会使用 model_dir 中最新的checkpoint。如果 model_dir 不存在或者没有checkpoints,会使用新初始化的参数下去验证,在我们bert例子中,就是用预训练的模型而不是fine-tuning的模型下去验证。

函数返回值是一个字典,即我们定义在 tf.estimator.ModeKeys.EVAL 模式下的字典结果。

result = estimator.evaluate(input_fn=eval_input_fn, steps=None, checkpoint_path=None)
for key in sorted(result.keys()):tf.logging.info("  %s = %s", key, str(result[key]))

预测

predict共有五个参数

predict(input_fn, predict_keys=None, hooks=None, checkpoint_path=None,yield_single_examples=True
)
  • input_fn:测试集的传入input_fn
  • predict_keys:list,如果未None,则返回所有的tf.estimator.EstimatorSpec里的 predictions 的所有key的值,否则,则只返回 predict_keys 里的key的值,过滤掉其他的keys。
  • checkpoint_path:这个作用和evaluate的作用一致,如果没指定,则使用 model_dir 里最新的checkpoint,还是没有的话,则使用新初始化的参数模型进行预测。
  • yield_single_examples:True 返回的结果以单条呈现,否则则按照feed进predictor的batch返回。

返回的是一个generator,yield 的是values of predictions tensors,其为字典形式,根据EstimatorSpec中predictions来的。我们要取用某个值时,跟平常字典使用类似。 例如我们要取得我们bert中的 probabilities 的值,我们可以采用以下的方式:

result = estimator.predict(input_fn=predict_input_fn)
for (i, (example, prediction)) in enumerate(zip(predict_examples, result)):probabilities = prediction["probabilities"]

导出

tensorflow具有三种模型保存的方式

  • checkpoint
  • SavedModel
  • frozen graph

之后会专门出一篇介绍, 并介绍如何使用SavedModel进行single example和batch预测。我们这边主要讲一下 export_saved_model API,

export_saved_model(export_dir_base, serving_input_receiver_fn, assets_extra=None, as_text=False,checkpoint_path=None, experimental_mode=ModeKeys.PREDICT
)
  • export_dir_base: 导出的路径,导出SavedModel
  • serving_input_receiver_fn: 一个无参函数,返回 tf.estimator.export.ServingInputReceiver or tf.estimator.export.TensorServingInputReceiver.具体的作用就是定义我们模型的输入placeholders。
  • assets_extra: 一个字典说明如何在SavedModel 路径下产生assets.extra 文件夹,None 代表不产生。
  • as_text: 是否将 SaveModel 保存为 Text 格式
  • checkpoint_path: 要被导出的checkpoint的路径,如果是None,则使用 model_dir 里最新的checkpoint
  • experimental_mode: tf.estimator.ModeKeys 值,代表哪一个Mode要被exported。正常来说默认是 ModeKeys.PREDICT

具体可以参考:

def serving_input_fn():label_ids = tf.placeholder(tf.int32, [None], name='label_ids')input_ids = tf.placeholder(tf.int32, [None, FLAGS.max_seq_length], name='input_ids')input_mask = tf.placeholder(tf.int32, [None, FLAGS.max_seq_length], name='input_mask')segment_ids = tf.placeholder(tf.int32, [None, FLAGS.max_seq_length], name='segment_ids')input_fn = tf.estimator.export.build_raw_serving_input_receiver_fn({'label_ids': label_ids,'input_ids': input_ids,'input_mask': input_mask,'segment_ids': segment_ids,})()return input_fnif FLAGS.do_export:estimator._export_to_tpu = Falseestimator.export_saved_model(FLAGS.output_dir, serving_input_fn)

train_and_evaluate

这个工具函数提供了一个estimator的train,evaluate,和export(可选)的接口。

所有训练相关的配置都在 train_spec 中配置(包括training input_fn 和 training max steps, etc),所有的验证相关的配置都在 eval_spec 中(evaluation input_fn 和 steps, etc),

这个函数函数的好处在于他将train 和 evaluate结合起来,然后在训练和验证之间的转换的时候,不需要销毁和重建graph,而之前的 estimator.train 和 estimator.evaluate 是异步的。使用这个函数之后,就能够很高效地实现训练和验证集合。并且它支持分布式训练等。 此外它支持导出最优模型的配置 BestExporter。

具体参考下面BERT的使用:

if FLAGS.do_train:train_spec = tf.estimator.TrainSpec(input_fn=train_input_fn,max_steps=num_train_steps,hooks=[early_stopping_hook])exporter = tf.estimator.BestExporter(serving_input_receiver_fn= serving_input_fn,exports_to_keep=2)eval_spec = tf.estimator.EvalSpec(input_fn=eval_input_fn,steps=None, # steps=None, evaluate on the entire eval datasetexporters=exporter)tf.estimator.train_and_evaluate(estimator, train_spec, eval_spec)

注意:EvalSpec中的 steps需要显性设置未None, 否则默认是100。并不是在整个验证集上面进行验证。

hooks

我们可以为训练 estimator.train() 和测试 estimator.evaluate() 增加hooks。hooks 顾名思义,就是钩子,指的是如果一个事件发生了,先被特定hook捕获,然后执行hook的操作,然后在传入下个pipeline。也即这项技术就是提供了一个入口,能够针对不同的消息或者API在执行前,先执行你的操作,你的操作也称为「钩子函数。

常见的操作有

  • early_stopping_hooks

我们可以通过tf.estimator.experimental.stop_if_no_decrease_hook 或者 tf.contrib.estimator.stop_if_no_increase_hook 实现early stopping hook。比如说eval_loss 多少个steps都不下降,那么我们启动early stopping,或者说 f1 在多少个steps都不上升,那么我们进行early stopping。因为 hook somehow 假设模型 model’s eval_dir 已经存在,所以我们需要在其未存在的时候提前创建路径。

具体参考下面的代码:

Path(estimator.eval_dir()).mkdir(parents=True, exist_ok=True)
early_stopping_hook = tf.estimator.experimental.stop_if_no_decrease_hook(estimator=estimator,metric_name='eval_loss',max_steps_without_decrease=3*FLAGS.save_checkpoints_steps,eval_dir=None,min_steps=0,run_every_secs=None,run_every_steps=FLAGS.save_checkpoints_steps)

或者是

Path(estimator.eval_dir()).mkdir(parents=True, exist_ok=True)
hook = tf.contrib.estimator.stop_if_no_increase_hook(estimator, 'f1', 500, min_steps=8000, run_every_secs=120)
  • LoggingHooks
logging_hook = tf.train.LoggingTensorHook({"loss": total_loss}, every_n_iter=10)

然后将我们这个hook加到

estimator.train(input_fn=train_input_fn,max_steps=num_train_steps,hooks=[early_stopping_hook, logging_hook])

或者是

train_spec = tf.estimator.TrainSpec(input_fn=train_input_fn,max_steps=num_train_steps,hooks=[early_stopping_hook, logging_hook])

ref

  • https://towardsdatascience.com/first-contact-with-tensorflow-estimator-69a5e072998d
  • https://arxiv.org/pdf/1708.02637.pdf
  • https://towardsdatascience.com/an-advanced-example-of-tensorflow-estimators-part-1-3-c9ffba3bff03
  • https://guillaumegenthial.github.io/serving-tensorflow-estimator.html
  • https://github.com/tensorflow/docs/blob/master/site/en/guide/estimator.ipynb
  • https://b23.tv/av80286594/p1
  • https://guillaumegenthial.github.io/introduction-tensorflow-estimator.html
  • https://github.com/NVIDIA/DeepLearningExamples/tree/master/TensorFlow/LanguageModeling/BERT

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

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

相关文章

Linux查看文件内容

cat 一次性将文件内容全部输出到控制台 more 可以翻页查看 空格:下翻一页 b:上翻一页 q:退出 less 可以翻页查看 空格:下翻一页 b:上翻一页 q:退出 向上键:上翻一行 向下键&#xff1…

刷新页面微信二维码图片随机换,点击按钮自动复制对应微信号

<div style"text-align: center;"> <p style"font-size: 18px;color: rgb(255, 79, 121);">添加微信号</p> <span style"font-size: 18px;margin-left: 10%;" id"cod">jyl88jimei</span><br /> &…

SecureCRT向多个tab窗口发命令

可以利用SecureCRT的 Chat Windows选项Send chat to all tabs来同时在服务器上执行相同的命令&#xff0c;具体步骤如下&#xff1a; 一、首先在SecureCRT里同时打开多个服务器session 二、选择菜单栏View -->Chat Windows 对号&#xff0c;此时所有服务器连接下方应该有个…

英雄联盟怎么解除小窗口_英雄联盟手游怎么加好友_英雄联盟手游怎么加好友一起玩_资讯...

英雄联盟手游是腾讯联合英雄联盟开发商拳头开发的英雄联盟手游。不仅能够高度还原端游的经典操作和竞技体验&#xff0c;也具有非常多创新的元素&#xff0c;对于英雄联盟的全球生态布局具有重要意义。英雄联盟手游游戏中有非常多的英雄可以供玩家选择&#xff0c;并且拥有排位…

jfinal mysql 配置文件_JFinal 如何将操作日志存入到数据库中

展开全部操作日志, 也分粗细颗粒.比如常见的 配置JFinal的Handler, 配置LogHandler的处理器&#xff0c;Handler可以接管所有web请求, 这里可以做粗颗粒的处理, 对每一个请62616964757a686964616fe59b9ee7ad9431333365653839求做入库处理, 如果访问量大时, 入库操作做列队处理就…

快速入门python_一天快速入门 Python

Python 是由Guido Van Rossum在 90 年代早期设计&#xff0c;现在是最常用的编程语言之一。特别是人工智能的火热&#xff0c;再加之它的语法简洁且优美&#xff0c;实乃初学者入门AI必备的编程语言。作者 | yuquanle责编 | 屠敏Python基本语法标识符第一个字符必须是英文字母或…

Sonar与jenkins集成

2019独角兽企业重金招聘Python工程师标准>>> 参考文档&#xff1a;http://blog.csdn.net/kefengwang/article/details/54377055 一.下载&#xff1a;wget https://fossies.org/linux/misc/sonarqube-7.0.zip 二.配置sonar.properties ## sudo vim /opt/sonarqube-6.…

python变量和常量_5、python的变量和常量

今天看看python的变量和常量&#xff0c;这是python中最基本的两个概念。首先先说一下解释器执行Python的过程&#xff1a; python3 C:\test.py1. 启动python解释器(内存中)2. 将C:\test.py内容从硬盘读入内存(这一步与文本编辑器是一样的)3. 执行读入内存的代码如果想要永久…

eplan连接定义点不显示_EPLAN电气图实例--控制柜(控制面板)

EPLAN电气图实例--控制柜(控制面板)上期回顾(上期主要画了硬件的布局图)&#xff1a;这期主要画一个控制面板控制柜布局1.0 上期主要做了一个长方形的结构板&#xff0c;里面插入了一个结构盒&#xff0c;然后放置一个HMI的宏(这里是KTP1000&#xff0c;在官网随便找下就行了)&…

virtualbox安装android6.0并设置分辨率为1920x1080x32

下载安装&#xff1a;https://www.cnblogs.com/wynn0123/p/6288344.html 这里我做的是下载android6.0-64bit&#xff0c;然后文件系统只支持ext4 安装完成之后我的虚拟机名称是Android6.0 设置分辨率为1920x1080x32&#xff1a;https://my.oschina.net/xldc/blog/290155 首先&a…

python中装饰器修复技术_python3之装饰器修复技术@wraps

普通函数def f():"""这是一个用来测试装饰器修复技术的函数"""print("哈哈哈")if __name__ __main__:print("执行的函数名:", f.__name__)print("函数的注释:", f.__doc__)# 打印结果执行的函数名: f函数的注释:…

markdown 语法_markdown特殊语法之上下标

markdown特殊语法之上下标​markdown的基本语法很简单&#xff0c;百度一下就可以了&#xff0c;有空的话我再转载一些过来。我想的是平常其实需要用到的一些输入技巧&#xff0c;特殊用法或者扩展语法&#xff0c;还有一些难点倒是要记录学习一下。在写作的时候&#xff0c;大…

oracle安装向导卡住了_JDK 8 的安装与配置

一、安装环节1. 打开网页https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html2.找到对象的版本 尽量从官网下载(官网可能会比较慢&#xff0c;也可以通过浏览器输入jdk版本号进行下载)官网下载需要注册一个账号3.双击下载的 exe,如 jdk-8u131-windows…

Error contacting service. It is probably not running.

安装完zookeeper集群后&#xff0c; [rootzk1 data]# zkServer.sh start JMX enabled by default Using config: /application/zookeeper-3.3.6/bin/../conf/zoo.cfg Starting zookeeper ... STARTED [rootzk1 data]# zkServer.sh status JMX enabled by default Using config…

JavaScript 函数节流 throttle 和防抖 debounce

今天和别人聊到JavaScript函数的节流和防抖&#xff0c;发现自己对这两个的区别很是模糊&#xff0c;遂小小实践一下&#xff0c;在此记录&#xff0c;希望对需要的人有所帮助。 节流 - 频繁操作&#xff0c;间隔一定时间去做一件事 举例说明&#xff1a;假定时间间隔为 500ms&…

python 第三方绘图库_D3py首页、文档和下载 - 基于 D3 的 Python 绘图库 - OSCHINA - 中文开源技术交流社区...

D3py 是一个基于 D3 的 Python 绘图库&#xff0c;可以像 D3 那样画出可交互的漂亮图形。D3py 的目的是把来自命令行或者脚本的数据画到浏览器窗口&#xff0c;d3py 通过构建两个优秀的包来实现这一点。 第一个是 d3.js(Mike Bostock)&#xff0c;它是一个用于创建数据驱动文档…

web 前端 如何分享到instagram_如何找到靠谱的Web培训机构?web前端培训机构哪个好?...

现如今Web前端开发应用越来越广泛&#xff0c;Web前端工程师人才需求逐年递增&#xff0c;薪资待遇也是水涨船高&#xff0c;也因此吸引了越来越多的人想要迈入Web前端行业&#xff0c;参加Web前端培训是很多人选择学习前端开发技能的途径&#xff0c;那么Web前端培训机构哪个好…

java main 命令行_java Main 命令行

CLI 即Command Line Interface&#xff0c;也就是"命令行接口"&#xff0c;它为Java 程序访问和解析命令行参数提供了一种统一的接口。apache Commons CLI为用户提供了一个解释命令行的API.它在解释命令行时主要有三个状态&#xff0c;即&#xff1a;定义、解释和询问…

zookeeper命令行操作

进入命令行 运行 zkCli.sh –server <ip>进入命令行工具。 [rootzk1 bin]# zkCli.sh -server <不加ip地址&#xff0c;连接本地zookeeper> Error: no argument found for option -server Connecting to localhost:2181 [zk: localhost:2181(CONNECTED) 0] …

Spring Data JPA 实例查询

转自&#xff1a;https://www.cnblogs.com/rulian/p/6533109.html 一、相关接口方法 在继承JpaRepository接口后&#xff0c;自动拥有了按“实例”进行查询的诸多方法。这些方法主要在两个接口中定义&#xff0c;一是QueryByExampleExecutor&#xff0c;一个是JpaRepository&am…