【Tensorflow object detection API + 微软NNI】图像分类问题完成自动调参,进一步提升模型准确率!

1. 背景&目标

利用Tensorflow object detection API开发并训练图像分类模型(例如,Mobilenetv2等),自己直接手动调参,对于模型的准确率提不到极致,利用微软NNI自动调参工具进行调参,进一步提升准确率。

2. 方法

  1. 关于Tensorflow object detection API开发并训练图像分类模型详见这篇博客:【tensorflow-slim】使用tensroflow-slim训练自己的图像分类数据集+冻成pb文件+预测(本文针对场景分类,手把手详细教学!)
  2. 关于微软NNI工具的使用参考官方网站即可:Neural Network Intelligence
  3. 具体代码实现,直接将下列代码放在一个.py文件中,文件名为nni_train_eval_image_classifier.py,放在Tensorflow object detection API官方代码仓的models-master/research/slim目录下,再依照NNI工具官方使用方式使用即可。
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Generic training script that trains a model using a given dataset."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os
import nni
import argparse
import logging
import time

import tensorflow as tf
from tensorflow.contrib import quantize as contrib_quantize
from tensorflow.contrib import slim as contrib_slim
from tensorflow.core.protobuf import config_pb2
from tensorflow.python.client import timeline
from tensorflow.python.lib.io import file_io

from datasets import dataset_factory
from deployment import model_deploy
from nets import nets_factory
from preprocessing import preprocessing_factory

slim = contrib_slim
logger = logging.getLogger('mobilenetv2_AutoML')

def _configure_learning_rate(num_samples_per_epoch, global_step):
  """Configures the learning rate.

  Args:
    num_samples_per_epoch: The number of samples in each epoch of training.
    global_step: The global_step tensor.

  Returns:
    A `Tensor` representing the learning rate.

  Raises:
    ValueError: if
  """
  # Note: when num_clones is > 1, this will actually have each clone to go
  # over each epoch FLAGS.num_epochs_per_decay times. This is different
  # behavior from sync replicas and is expected to produce different results.
  steps_per_epoch = num_samples_per_epoch / params['batch_size']

  decay_steps = int(steps_per_epoch * params['num_epochs_per_decay'])

  if params['learning_rate_decay_type'] == 'exponential':
    learning_rate = tf.train.exponential_decay(
        params['learning_rate'],
        global_step,
        decay_steps,
        params['learning_rate_decay_factor'],
        staircase=True,
        name='exponential_decay_learning_rate')
  else:
    raise ValueError('learning_rate_decay_type [%s] was not recognized' %
                     params['learning_rate_decay_type'])
  return learning_rate


def _configure_optimizer(learning_rate):
  """Configures the optimizer used for training.

  Args:
    learning_rate: A scalar or `Tensor` learning rate.

  Returns:
    An instance of an optimizer.

  Raises:
    ValueError: if FLAGS.optimizer is not recognized.
  """
  if params['optimizer'] == 'adadelta':
    optimizer = tf.train.AdadeltaOptimizer(
        learning_rate,
        rho=0.95,
        epsilon=1.0)
  elif params['optimizer'] == 'adagrad':
    optimizer = tf.train.AdagradOptimizer(
        learning_rate,
        initial_accumulator_value=0.1)
  elif params['optimizer'] == 'adam':
    optimizer = tf.train.AdamOptimizer(
        learning_rate,
        beta1=0.9,
        beta2=0.999,
        epsilon=1.0)
  elif params['optimizer'] == 'rmsprop':
    optimizer = tf.train.RMSPropOptimizer(
        learning_rate,
        decay=0.9,
        momentum=0.9,
        epsilon=1.0)
  elif params['optimizer'] == 'sgd':
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
  else:
    raise ValueError('Optimizer [%s] was not recognized' % params['optimizer'])
  return optimizer


def _get_init_fn():
  """Returns a function run by the chief worker to warm-start the training.

  Note that the init_fn is only run when initializing the model during the very
  first global step.

  Returns:
    An init function run by the supervisor.
  """
  if params['checkpoint_path'] is None:
    return None

  # Warn the user if a checkpoint exists in the train_dir. Then we'll be
  # ignoring the checkpoint anyway.
  if tf.train.latest_checkpoint(os.path.join(params['train_dir'], nni.get_trial_id())):
    tf.logging.info(
        'Ignoring --checkpoint_path because a checkpoint already exists in %s'
        % os.path.join(params['train_dir'], nni.get_trial_id()))
    return None

  exclusions = []


  # TODO(sguada) variables.filter_variables()
  variables_to_restore = []
  for var in slim.get_model_variables():
    for exclusion in exclusions:
      if var.op.name.startswith(exclusion):
        break
    else:
      variables_to_restore.append(var)

  if tf.gfile.IsDirectory(params['checkpoint_path']):
    checkpoint_path = tf.train.latest_checkpoint(params['checkpoint_path'])
  else:
    checkpoint_path = params['checkpoint_path']

  tf.logging.info('Fine-tuning from %s' % checkpoint_path)

  return slim.assign_from_checkpoint_fn(
      checkpoint_path,
      variables_to_restore,
      ignore_missing_vars=False)


def _get_variables_to_train():
  """Returns a list of variables to train.

  Returns:
    A list of variables to train by the optimizer.
  """
  if params['trainable_scopes'] is None:
    return tf.trainable_variables()
  else:
    scopes = [scope.strip() for scope in params['trainable_scopes'].split(',')]

  variables_to_train = []
  for scope in scopes:
    variables = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope)
    variables_to_train.extend(variables)
  return variables_to_train


def train_step_for_print(sess, train_op, global_step, train_step_kwargs):
  """Function that takes a gradient step and specifies whether to stop.
  Args:
    sess: The current session.
    train_op: An `Operation` that evaluates the gradients and returns the total
      loss.
    global_step: A `Tensor` representing the global training step.
    train_step_kwargs: A dictionary of keyword arguments.
  Returns:
    The total loss and a boolean indicating whether or not to stop training.
  Raises:
    ValueError: if 'should_trace' is in `train_step_kwargs` but `logdir` is not.
  """
  start_time = time.time()

  trace_run_options = None
  run_metadata = None
  if 'should_trace' in train_step_kwargs:
    if 'logdir' not in train_step_kwargs:
      raise ValueError('logdir must be present in train_step_kwargs when '
                       'should_trace is present')
    if sess.run(train_step_kwargs['should_trace']):
      trace_run_options = config_pb2.RunOptions(
          trace_level=config_pb2.RunOptions.FULL_TRACE)
      run_metadata = config_pb2.RunMetadata()

  total_loss, np_global_step = sess.run([train_op, global_step],
                                        options=trace_run_options,
                                        run_metadata=run_metadata)
  time_elapsed = time.time() - start_time

  if run_metadata is not None:
    tl = timeline.Timeline(run_metadata.step_stats)
    trace = tl.generate_chrome_trace_format()
    trace_filename = os.path.join(train_step_kwargs['logdir'],
                                  'tf_trace-%d.json' % np_global_step)
    logging.info('Writing trace to %s', trace_filename)
    file_io.write_string_to_file(trace_filename, trace)
    if 'summary_writer' in train_step_kwargs:
      train_step_kwargs['summary_writer'].add_run_metadata(
          run_metadata, 'run_metadata-%d' % np_global_step)

  if 'should_log' in train_step_kwargs:
    if sess.run(train_step_kwargs['should_log']):
      logging.info('global step %d: loss = %.4f (%.3f sec/step)',
                   np_global_step, total_loss, time_elapsed)

  if 'should_stop' in train_step_kwargs:
    should_stop = sess.run(train_step_kwargs['should_stop'])
  else:
    should_stop = False
  nni.report_intermediate_result(total_loss)
  return total_loss, should_stop


def main(params):
  if not params['dataset_dir']:
    raise ValueError('You must supply the dataset directory with --dataset_dir')

  tf.logging.set_verbosity(tf.logging.INFO)
  with tf.Graph().as_default():
    #######################
    # Config model_deploy #
    #######################
    deploy_config = model_deploy.DeploymentConfig(
        num_clones=2,
        clone_on_cpu=False,
        replica_id=0,
        num_replicas=1,
        num_ps_tasks=0)

    # Create global_step
    with tf.device(deploy_config.variables_device()):
      global_step = slim.create_global_step()

    ######################
    # Select the dataset #
    ######################
    dataset = dataset_factory.get_dataset(
        params['dataset_name'], params['dataset_split_name'], params['dataset_dir'])

    ######################
    # Select the network #
    ######################
    network_fn = nets_factory.get_network_fn(
        params['model_name'],
        num_classes=(dataset.num_classes - 0 ),
        weight_decay=0.00004,
        is_training=True)

    #####################################
    # Select the preprocessing function #
    #####################################
    preprocessing_name = params['model_name']
    image_preprocessing_fn = preprocessing_factory.get_preprocessing(
        preprocessing_name,
        is_training=True,
        use_grayscale=False)

    ##############################################################
    # Create a dataset provider that loads data from the dataset #
    ##############################################################
    with tf.device(deploy_config.inputs_device()):
      provider = slim.dataset_data_provider.DatasetDataProvider(
          dataset,
          num_readers=params['num_readers'],
          common_queue_capacity=20 * params['batch_size'],
          common_queue_min=10 * params['batch_size'])
      [image, label] = provider.get(['image', 'label'])
      label -=0

      train_image_size = params['train_image_size'] or network_fn.default_image_size

      image = image_preprocessing_fn(image, train_image_size, train_image_size)

      images, labels = tf.train.batch(
          [image, label],
          batch_size=params['batch_size'],
          num_threads=params['num_preprocessing_threads'],
          capacity=5 * params['batch_size'])
      labels = slim.one_hot_encoding(
          labels, dataset.num_classes - 0 )
      batch_queue = slim.prefetch_queue.prefetch_queue(
          [images, labels], capacity=2 * deploy_config.num_clones)

    ####################
    # Define the model #
    ####################
    def clone_fn(batch_queue):
      """Allows data parallelism by creating multiple clones of network_fn."""
      images, labels = batch_queue.dequeue()
      logits, end_points = network_fn(images)

      #############################
      # Specify the loss function #
      #############################
      if 'AuxLogits' in end_points:
        slim.losses.softmax_cross_entropy(
            end_points['AuxLogits'], labels,
            label_smoothing=0.0, weights=0.4,
            scope='aux_loss')
      slim.losses.softmax_cross_entropy(
          logits, labels, label_smoothing=0.0, weights=1.0)
      return end_points

    # Gather initial summaries.
    summaries = set(tf.get_collection(tf.GraphKeys.SUMMARIES))

    clones = model_deploy.create_clones(deploy_config, clone_fn, [batch_queue])
    first_clone_scope = deploy_config.clone_scope(0)
    # Gather update_ops from the first clone. These contain, for example,
    # the updates for the batch_norm variables created by network_fn.
    update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, first_clone_scope)

    # Add summaries for end_points.
    end_points = clones[0].outputs
    for end_point in end_points:
      x = end_points[end_point]
      summaries.add(tf.summary.histogram('activations/' + end_point, x))
      summaries.add(tf.summary.scalar('sparsity/' + end_point,
                                      tf.nn.zero_fraction(x)))
      if len(x.shape) <4:
        continue



    # Add summaries for losses.
    for loss in tf.get_collection(tf.GraphKeys.LOSSES, first_clone_scope):
      summaries.add(tf.summary.scalar('losses/%s' % loss.op.name, loss))

    # Add summaries for variables.
    for variable in slim.get_model_variables():
      summaries.add(tf.summary.histogram(variable.op.name, variable))


    #########################################
    # Configure the optimization procedure. #
    #########################################
    with tf.device(deploy_config.optimizer_device()):
      learning_rate = _configure_learning_rate(dataset.num_samples, global_step)
      optimizer = _configure_optimizer(learning_rate)
      summaries.add(tf.summary.scalar('learning_rate', learning_rate))


    # Variables to train.
    variables_to_train = _get_variables_to_train()

    #  and returns a train_tensor and summary_op
    total_loss, clones_gradients = model_deploy.optimize_clones(
        clones,
        optimizer,
        var_list=variables_to_train)
    # Add total_loss to summary.
    summaries.add(tf.summary.scalar('total_loss', total_loss))

    # Create gradient updates.
    grad_updates = optimizer.apply_gradients(clones_gradients,
                                             global_step=global_step)
    update_ops.append(grad_updates)

    update_op = tf.group(*update_ops)
    with tf.control_dependencies([update_op]):
      train_tensor = tf.identity(total_loss, name='train_op')

    # Add the summaries from the first clone. These contain the summaries
    # created by model_fn and either optimize_clones() or _gather_clone_loss().
    summaries |= set(tf.get_collection(tf.GraphKeys.SUMMARIES,
                                       first_clone_scope))

    # Merge all summaries together.
    summary_op = tf.summary.merge(list(summaries), name='summary_op')

    ###########################
    # Kicks off the training. #
    ###########################
    final_loss = slim.learning.train(
        train_tensor,
        train_step_fn=train_step_for_print,
        logdir=os.path.join(params['train_dir'], nni.get_trial_id()),
        master=params['master'],
        is_chief=True,
        init_fn=_get_init_fn(),
        summary_op=summary_op,
        number_of_steps=params['max_number_of_steps'],
        log_every_n_steps=params['log_every_n_steps'],
        save_summaries_secs=params['save_summaries_secs'],
        save_interval_secs=params['save_interval_secs'],
        sync_optimizer=None)

    nni.report_final_result(final_loss)




def get_params():
    ''' Get parameters from command line '''
    parser = argparse.ArgumentParser()
    parser.add_argument("--master", type=str, default='', help='The address of the TensorFlow master to use.')
    parser.add_argument("--train_dir", type=str, default='/***/workspace/mobilenetssd/models-master/research/slim/sentiment_cnn/model/training2')
    parser.add_argument("--num_preprocessing_threads", type=int, default=4)
    parser.add_argument("--log_every_n_steps", type=int, default=10)
    parser.add_argument("--save_summaries_secs", type=int, default=600)
    parser.add_argument("--save_interval_secs", type=int, default=600)
    parser.add_argument("--learning_rate", type=float, default=1e-4)
    parser.add_argument("--batch_num", type=int, default=2000)

    parser.add_argument("--dataset_name", type=str, default="sendata3_train")
    parser.add_argument("--dataset_split_name", type=str, default='train')
    parser.add_argument("--dataset_dir", type=str, default='/***/workspace/mobilenetssd/models-master/research/slim/sentiment_cnn/dataset/data_3')
    parser.add_argument("--model_name", type=str, default='mobilenet_v2')

    parser.add_argument("--batch_size", type=int, default=16)
    parser.add_argument("--train_image_size", type=int, default=80)
    parser.add_argument("--max_number_of_steps", type=int, default=60000)
    parser.add_argument("--trainable_scopes", type=str, default=None)

    parser.add_argument("--optimizer", type=str, default='adam')
    parser.add_argument("--learning_rate_decay_type", type=str, default='exponential')
    parser.add_argument("--learning_rate_decay_factor", type=float, default=0.8)

    parser.add_argument("--checkpoint_path", type=str, default=None)
    parser.add_argument("--num_readers", type=int, default=4)

    parser.add_argument("--num_epochs_per_decay", type=float, default=3.0)

    args, _ = parser.parse_known_args()
    return args

if __name__ == '__main__':
    try:
        # get parameters form tuner
        tuner_params = nni.get_next_parameter()
        logger.debug(tuner_params)
        params = vars(get_params())
        params.update(tuner_params)
        main(params)
    except Exception as exception:
        logger.exception(exception)
        raise

【注意】:需要依照自己设备、模型等实际情况,修改代码中的如下部分:

def get_params():
    ''' Get parameters from command line '''
    parser = argparse.ArgumentParser()
    parser.add_argument("--master", type=str, default='', help='The address of the TensorFlow master to use.')
    parser.add_argument("--train_dir", type=str, default='/***/workspace/mobilenetssd/models-master/research/slim/sentiment_cnn/model/training2')
    parser.add_argument("--num_preprocessing_threads", type=int, default=4)
    parser.add_argument("--log_every_n_steps", type=int, default=10)
    parser.add_argument("--save_summaries_secs", type=int, default=600)
    parser.add_argument("--save_interval_secs", type=int, default=600)
    parser.add_argument("--learning_rate", type=float, default=1e-4)
    parser.add_argument("--batch_num", type=int, default=2000)

    parser.add_argument("--dataset_name", type=str, default="sendata3_train")
    parser.add_argument("--dataset_split_name", type=str, default='train')
    parser.add_argument("--dataset_dir", type=str, default='/***/workspace/mobilenetssd/models-master/research/slim/sentiment_cnn/dataset/data_3')
    parser.add_argument("--model_name", type=str, default='mobilenet_v2')

    parser.add_argument("--batch_size", type=int, default=16)
    parser.add_argument("--train_image_size", type=int, default=80)
    parser.add_argument("--max_number_of_steps", type=int, default=60000)
    parser.add_argument("--trainable_scopes", type=str, default=None)

    parser.add_argument("--optimizer", type=str, default='adam')
    parser.add_argument("--learning_rate_decay_type", type=str, default='exponential')
    parser.add_argument("--learning_rate_decay_factor", type=float, default=0.8)

    parser.add_argument("--checkpoint_path", type=str, default=None)
    parser.add_argument("--num_readers", type=int, default=4)

    parser.add_argument("--num_epochs_per_decay", type=float, default=3.0)

    args, _ = parser.parse_known_args()
    return args

3. 结果

进一步压榨你的图像分类模型准确率叭!
例如,针对私有数据集训练Mobilenetv2以前只能到90%的准确率,现在能到92.2857%
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Cisco Catalyst 8000 Series Edge Platforms, IOS XE Release Dublin-17.11.01a ED

Cisco Catalyst 8000 Series Edge Platforms, IOS XE Release Dublin-17.11.01a ED Cisco Catalyst 8000 边缘平台系列 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-catalyst-8000/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&…

国企一面会问什么?

前言&#xff1a; \textcolor{Green}{前言&#xff1a;} 前言&#xff1a; &#x1f49e;快秋招了&#xff0c;那么这个专栏就专门来记录一下&#xff0c;同时呢整理一下常见面试题 &#x1f49e;部分题目来自自己的面试题&#xff0c;部分题目来自网络整理 国企注重的和私企会…

AI实战营第二期 第七节 《语义分割与MMSegmentation》——笔记8

文章目录 摘要主要特性 案例什么是语义分割应用&#xff1a;无人驾驶汽车应用&#xff1a;人像分割应用&#xff1a;智能遥感应用 : 医疗影像分析 三种分割的区别语义分割的基本思路按颜色分割逐像素份分类全卷积网络 Fully Convolutional Network 2015存在问题 基于多层级特征…

多元回归预测 | Matlab基于灰狼算法(GWO)优化混合核极限学习机HKELM回归预测, GWO-HKELM数据回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab基于灰狼算法(GWO)优化混合核极限学习机HKELM回归预测, GWO-HKELM数据回归预测,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 …

[游戏开发][Unity]出包真机运行花屏(已解决)

花屏真机截屏 原因 原因是启动项目时的第一个场景没有相机是 Skybox或者SolidColor模式&#xff0c;我的启动场景只有一个UI相机&#xff0c;且Clear Flags是DepthOnly 解释&#xff1a; https://blog.csdn.net/yanchezuo/article/details/79002318

三、1如何运用设计原则之SOLID原则写出高质量代码?

你好我是程序员雪球。接下来我们学习一些经典的设计原则。其中包括SOLID&#xff0c;KISS&#xff0c;YAGNI&#xff0c;DRY&#xff0c;LOD等。其实这些设计原则从字面意思理解并不难。但是“看懂”和“会用”是两回事&#xff0c;而“用好”就难上加难了。 先来了解SOLID原则…

(css)el-image图片完整显示,不拉伸收缩

(css)el-image图片完整显示&#xff0c;不拉伸收缩 <el-imagefit"contain" //重要设置src"../../../../1.png"altclass"chenguo_img_img" />

自动驾驶货车编队行驶-车辆通讯应用层数据交互要求

1 范围 本文件规定了合作式自动驾驶货车编队行驶时车辆通信应用层数据交互的通信系统架构、通用要求、 数据交互要求、消息层数据集定义等。本文件仅涉及编队成员内部进行编队控制及成员管理相关的车-车通 信交互&#xff0c;不涉及编队与其他实体&#xff08;云平台、路侧单元…

spring cloud 之 gateway

网关介绍 在微服务架构体系中&#xff0c;一个系统会被拆分为很多个微服务&#xff0c;那么作为客户端要如何去调用这么多的微服务呢&#xff1f;如果没有网关的存在&#xff0c;我们只能在客户端记录每个微服务的地址&#xff0c;然后分别调用&#xff0c;当然这样是不现实的…

高性能消息中间件 RabbitMQ

一、RabbitMQ概念 1.1 MQ是什么 消息队列 MQ全称Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中保存消息的容器。多用于系统之间的异步通信。 同步通信相当于两个人当面对话&#xff0c;你一言我一语。必须及时回复&#xff1a; 异步通信相…

基于Arduino UNO的循迹小车

目录 1.analogWrite函数的使用 2.红外循迹模块介绍 3.循迹小车代码实现 4.实物示例 1.analogWrite函数的使用 用analogWrite来替换digitalWrite 说明 将一个模拟数值写进Arduino引脚。这个操作可以用来控制LED的亮度, 或者控制电机的转速. 在Arduino UNO控制器中&#…

查询Oracle当前用户下,所有数据表的总条数

需求&#xff1a;查询Oracle当前用户下&#xff0c;所有数据表的总条数 方法&#xff1a;存储过程 右键点击Procedures&#xff0c;点击New 点击OK 把存储过程写进去&#xff0c;然后点击编译运行&#xff1a; create or replace procedure tables_count ist_count numbe…

xshell安装jdk1.8环境

xshell安装jdk1.8环境 大家好&#xff0c;今天我们来学习一下xshell安装jdk1.8环境&#xff0c;好好看&#xff0c;好好学&#xff0c;超详细的 第一步 进入xshell官网下载 第二步 打开xshell新建一个会话&#xff0c;如下图&#xff1a; 第三步 输入你的名称、主机ip、端口号(…

CSS中伪类详解和用法例子详解

文章目录 一、伪类介绍二、伪类选择器1.动态伪类2.结构伪类3.否定伪类4.状态伪类5.目标伪类 一、伪类介绍 用于已有元素处于某种状态时&#xff08;滑动、点击等&#xff09;为其添加对应的样式&#xff0c;这个状态是根据用户行为而动态变化的。 二、伪类选择器 动态伪类作…

Arduino uno 环境配置 for Mac

1、IDE 在官网下载 官网地址&#xff1a;https://www.arduino.cc/en/software 看到钱&#x1f4b0;不要怕&#xff0c;只是问你捐不捐款&#xff0c;不收钱&#xff0c;你直接安装就行 &#xff08;你也可以捐一点&#xff5e;&#xff09; 安装之后 2、安装驱动 地址 &…

互联网+洗鞋店预约小程序新模式;

互联网洗鞋店预约小程序 1、线上线下业务的结合。 传统的线下业务消费者到店可以向其推介线上的预约到家服务&#xff0c;让线下的消费者成为小程序内的会员&#xff0c;留存客户之后线上可直接触达&#xff0c;减少与消费者的距离&#xff0c;从等待客户到可以主动出击&…

黑客是什么?想成为黑客需要学习什么?

什么是黑客 在《黑客辞典》里有不少关于“黑客”的定义, 大多和“精于技术”或“乐于解决问题并超越极限”之类的形容相关。然而&#xff0c;若你想知道如何成为一名黑客&#xff0c;只要牢记两点即可。 这是一个社区和一种共享文化&#xff0c;可追溯到那群数十年前使…

6.Mysql主从复制

文章目录 Mysql主从复制读写分离概念&#xff1a;读写分离的作用&#xff1a;读写分离的作用条件&#xff1a;主从复制与读写分离mysq支持的复制类型主从复制的工作过程配置时间同步主服务器配置从服务器配置 读写分离MySQL 读写分离原理目前较为常见的 MySQL 读写分离分为以下…

A核与M核异构通信过程解析

现在越来越多的产品具有M core和A core的异构架构&#xff0c;既能达到M核的实时要求&#xff0c;又能满足A核的生态和算力。比如NXP的i.MX8系列、瑞萨的RZ/G2L系列以及TI的AM62x系列等等。虽然这些处理器的品牌及性能有所不同&#xff0c;但多核通信原理基本一致&#xff0c;都…

Linux - 那些年测试服务器带宽的 3 种方式

方式一 speedtest-cli wget -O speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.pychmod x speedtest-cliorcurl -Lo speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.pychmod x speedtest-c…