TensorFlow搭建神经网络最佳实践

一、TensorFLow完整样例

在MNIST数据集上,搭建一个简单神经网络结构,一个包含ReLU单元的非线性化处理的两层神经网络。在训练神经网络的时候,使用带指数衰减的学习率设置、使用正则化来避免过拟合、使用滑动平均模型来使得最终的模型更加健壮。

程序将计算神经网络前向传播的部分单独定义一个函数inference,训练部分定义一个train函数,再定义一个主函数main。

完整程序:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu May 25 08:56:30 2017 

@author: marsjhao
""" 

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data 

INPUT_NODE = 784 # 输入节点数
OUTPUT_NODE = 10 # 输出节点数
LAYER1_NODE = 500 # 隐含层节点数
BATCH_SIZE = 100
LEARNING_RETE_BASE = 0.8 # 基学习率
LEARNING_RETE_DECAY = 0.99 # 学习率的衰减率
REGULARIZATION_RATE = 0.0001 # 正则化项的权重系数
TRAINING_STEPS = 10000 # 迭代训练次数
MOVING_AVERAGE_DECAY = 0.99 # 滑动平均的衰减系数 

# 传入神经网络的权重和偏置,计算神经网络前向传播的结果
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
  # 判断是否传入ExponentialMovingAverage类对象
  if avg_class == None:
    layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)
    return tf.matmul(layer1, weights2) + biases2
  else:
    layer1 = tf.nn.relu(tf.matmul(input_tensor, avg_class.average(weights1))
                   + avg_class.average(biases1))
    return tf.matmul(layer1, avg_class.average(weights2))\
             + avg_class.average(biases2) 

# 神经网络模型的训练过程
def train(mnist):
  x = tf.placeholder(tf.float32, [None,INPUT_NODE], name='x-input')
  y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y-input') 

  # 定义神经网络结构的参数
  weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE],
                        stddev=0.1))
  biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))
  weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE],
                        stddev=0.1))
  biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE])) 

  # 计算非滑动平均模型下的参数的前向传播的结果
  y = inference(x, None, weights1, biases1, weights2, biases2) 

  global_step = tf.Variable(0, trainable=False) # 定义存储当前迭代训练轮数的变量 

  # 定义ExponentialMovingAverage类对象
  variable_averages = tf.train.ExponentialMovingAverage(
            MOVING_AVERAGE_DECAY, global_step) # 传入当前迭代轮数参数
  # 定义对所有可训练变量trainable_variables进行更新滑动平均值的操作op
  variables_averages_op = variable_averages.apply(tf.trainable_variables()) 

  # 计算滑动模型下的参数的前向传播的结果
  average_y = inference(x, variable_averages, weights1, biases1, weights2, biases2) 

  # 定义交叉熵损失值
  cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
          logits=y, labels=tf.argmax(y_, 1))
  cross_entropy_mean = tf.reduce_mean(cross_entropy)
  # 定义L2正则化器并对weights1和weights2正则化
  regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
  regularization = regularizer(weights1) + regularizer(weights2)
  loss = cross_entropy_mean + regularization # 总损失值 

  # 定义指数衰减学习率
  learning_rate = tf.train.exponential_decay(LEARNING_RETE_BASE, global_step,
          mnist.train.num_examples / BATCH_SIZE, LEARNING_RETE_DECAY)
  # 定义梯度下降操作op,global_step参数可实现自加1运算
  train_step = tf.train.GradientDescentOptimizer(learning_rate)\
             .minimize(loss, global_step=global_step)
  # 组合两个操作op
  train_op = tf.group(train_step, variables_averages_op)
  '''''
  # 与tf.group()等价的语句
  with tf.control_dependencies([train_step, variables_averages_op]):
    train_op = tf.no_op(name='train')
  '''
  # 定义准确率
  # 在最终预测的时候,神经网络的输出采用的是经过滑动平均的前向传播计算结果
  correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))
  accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 

  # 初始化回话sess并开始迭代训练
  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    # 验证集待喂入数据
    validate_feed = {x: mnist.validation.images, y_: mnist.validation.labels}
    # 测试集待喂入数据
    test_feed = {x: mnist.test.images, y_: mnist.test.labels}
    for i in range(TRAINING_STEPS):
      if i % 1000 == 0:
        validate_acc = sess.run(accuracy, feed_dict=validate_feed)
        print('After %d training steps, validation accuracy'
           ' using average model is %f' % (i, validate_acc))
      xs, ys = mnist.train.next_batch(BATCH_SIZE)
      sess.run(train_op, feed_dict={x: xs, y_:ys}) 

    test_acc = sess.run(accuracy, feed_dict=test_feed)
    print('After %d training steps, test accuracy'
       ' using average model is %f' % (TRAINING_STEPS, test_acc)) 

# 主函数
def main(argv=None):
  mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
  train(mnist) 

# 当前的python文件是shell文件执行的入口文件,而非当做import的python module。
if __name__ == '__main__': # 在模块内部执行
  tf.app.run() # 调用main函数并传入所需的参数list

二、分析与改进设计

1. 程序分析改进

第一,计算前向传播的函数inference中需要将所有的变量以参数的形式传入函数,当神经网络结构变得更加复杂、参数更多的时候,程序的可读性将变得非常差。

第二,在程序退出时,训练好的模型就无法再利用,且大型神经网络的训练时间都比较长,在训练过程中需要每隔一段时间保存一次模型训练的中间结果,这样如果在训练过程中程序死机,死机前的最新的模型参数仍能保留,杜绝了时间和资源的浪费。

第三,将训练和测试分成两个独立的程序,将训练和测试都会用到的前向传播的过程抽象成单独的库函数。这样就保证了在训练和预测两个过程中所调用的前向传播计算程序是一致的。

2. 改进后程序设计

mnist_inference.py

该文件中定义了神经网络的前向传播过程,其中的多次用到的weights定义过程又单独定义成函数。

通过tf.get_variable函数来获取变量,在神经网络训练时创建这些变量,在测试时会通过保存的模型加载这些变量的取值,而且可以在变量加载时将滑动平均值重命名。所以可以直接通过同样的名字在训练时使用变量自身,在测试时使用变量的滑动平均值。

mnist_train.py

该程序给出了神经网络的完整训练过程。

mnist_eval.py

在滑动平均模型上做测试。

通过tf.train.get_checkpoint_state(mnist_train.MODEL_SAVE_PATH)获取最新模型的文件名,实际是获取checkpoint文件的所有内容。

三、TensorFlow最佳实践样例

mnist_inference.py

import tensorflow as tf 

INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500 

def get_weight_variable(shape, regularizer):
  weights = tf.get_variable("weights", shape,
         initializer=tf.truncated_normal_initializer(stddev=0.1))
  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.matmul(layer1, weights) + biases 

  return layer2

mnist_train.py

import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_inference 

BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001
TRAINING_STEPS = 10000
MOVING_AVERAGE_DECAY = 0.99 

MODEL_SAVE_PATH = "Model_Folder/"
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(REGULARIZATION_RATE)
  y = mnist_inference.inference(x, regularizer)
  # 定义当前训练轮数及滑动平均模型
  global_step = tf.Variable(0, trainable=False)
  variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,
                             global_step)
  variables_averages_op = variable_averages.apply(tf.trainable_variables())
  # 定义损失函数
  cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,
                          labels=tf.argmax(y_, 1))
  cross_entropy_mean = tf.reduce_mean(cross_entropy)
  loss = cross_entropy_mean + tf.add_n(tf.get_collection('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)
  train_op = tf.group(train_step, variables_averages_op)
  # 定义Saver类对象,保存模型,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, loss, global_step],
                      feed_dict={x: xs, y_: ys})
      if i % 1000 == 0:
        print("After %d training step(s), loss on training batch is %g."\
            % (step, loss_value))
        # save方法的global_step参数可以让每个被保存的模型的文件名末尾加上当前训练轮数
        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

import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_inference
import mnist_train 

EVAL_INTERVAL_SECS = 10 

def evaluate(mnist):
  with tf.Graph().as_default() as g:
    # 定义输入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')
    # 定义feed字典
    validate_feed = {x: mnist.validation.images, y_: mnist.validation.labels}
    # 测试时不加参数正则化损失
    y = mnist_inference.inference(x, None)
    # 计算正确率
    correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    # 加载滑动平均模型下的参数值
    variable_averages = tf.train.ExponentialMovingAverage(
                   mnist_train.MOVING_AVERAGE_DECAY)
    saver = tf.train.Saver(variable_averages.variables_to_restore()) 

    # 每隔EVAL_INTERVAL_SECS秒启动一次会话
    while True:
      with tf.Session() as sess:
        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)
          # 取checkpoint文件中的当前迭代轮数global_step
          global_step = ckpt.model_checkpoint_path\
                   .split('/')[-1].split('-')[-1]
          accuracy_score = sess.run(accuracy, feed_dict=validate_feed)
          print("After %s training step(s), 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深度学习之卷积神经网络CNN
  • TensorFlow实现卷积神经网络CNN
  • Tensorflow实现卷积神经网络用于人脸关键点识别
  • 利用TensorFlow训练简单的二分类神经网络模型的方法
  • TensorFlow实现RNN循环神经网络
  • tensorflow入门之训练简单的神经网络方法
  • TensorFlow 实战之实现卷积神经网络的实例讲解
  • tensorflow建立一个简单的神经网络的方法
  • TensorFlow神经网络优化策略学习
(0)

相关推荐

  • 利用TensorFlow训练简单的二分类神经网络模型的方法

    利用TensorFlow实现<神经网络与机器学习>一书中4.7模式分类练习 具体问题是将如下图所示双月牙数据集分类. 使用到的工具: python3.5    tensorflow1.2.1   numpy   matplotlib 1.产生双月环数据集 def produceData(r,w,d,num): r1 = r-w/2 r2 = r+w/2 #上半圆 theta1 = np.random.uniform(0, np.pi ,num) X_Col1 = np.random.unifo

  • TensorFlow 实战之实现卷积神经网络的实例讲解

    本文根据最近学习TensorFlow书籍网络文章的情况,特将一些学习心得做了总结,详情如下.如有不当之处,请各位大拿多多指点,在此谢过. 一.相关性概念 1.卷积神经网络(ConvolutionNeural Network,CNN) 19世纪60年代科学家最早提出感受野(ReceptiveField).当时通过对猫视觉皮层细胞研究,科学家发现每一个视觉神经元只会处理一小块区域的视觉图像,即感受野.20世纪80年代,日本科学家提出神经认知机(Neocognitron)的概念,被视为卷积神经网络最初

  • tensorflow建立一个简单的神经网络的方法

    本笔记目的是通过tensorflow实现一个两层的神经网络.目的是实现一个二次函数的拟合. 如何添加一层网络 代码如下: def add_layer(inputs, in_size, out_size, activation_function=None): # add one more layer and return the output of this layer Weights = tf.Variable(tf.random_normal([in_size, out_size])) bia

  • TensorFlow神经网络优化策略学习

    在神经网络模型优化的过程中,会遇到许多问题,比如如何设置学习率的问题,我们可通过指数衰减的方式让模型在训练初期快速接近较优解,在训练后期稳定进入最优解区域:针对过拟合问题,通过正则化的方法加以应对:滑动平均模型可以让最终得到的模型在未知数据上表现的更加健壮. 一.学习率的设置 学习率设置既不能过大,也不能过小.TensorFlow提供了一种更加灵活的学习率设置方法--指数衰减法.该方法实现了指数衰减学习率,先使用较大的学习率来快速得到一个比较优的解,然后随着迭代的继续逐步减小学习率,使得模型在训

  • TensorFlow实现卷积神经网络CNN

    一.卷积神经网络CNN简介 卷积神经网络(ConvolutionalNeuralNetwork,CNN)最初是为解决图像识别等问题设计的,CNN现在的应用已经不限于图像和视频,也可用于时间序列信号,比如音频信号和文本数据等.CNN作为一个深度学习架构被提出的最初诉求是降低对图像数据预处理的要求,避免复杂的特征工程.在卷积神经网络中,第一个卷积层会直接接受图像像素级的输入,每一层卷积(滤波器)都会提取数据中最有效的特征,这种方法可以提取到图像中最基础的特征,而后再进行组合和抽象形成更高阶的特征,因

  • TensorFlow实现RNN循环神经网络

    RNN(recurrent neural Network)循环神经网络 主要用于自然语言处理(nature language processing,NLP) RNN主要用途是处理和预测序列数据 RNN广泛的用于 语音识别.语言模型.机器翻译 RNN的来源就是为了刻画一个序列当前的输出与之前的信息影响后面节点的输出 RNN 是包含循环的网络,允许信息的持久化. RNN会记忆之前的信息,并利用之前的信息影响后面节点的输出. RNN的隐藏层之间的节点是有相连的,隐藏层的输入不仅仅包括输入层的输出,还包

  • Tensorflow实现卷积神经网络用于人脸关键点识别

    今年来人工智能的概念越来越火,AlphaGo以4:1击败李世石更是起到推波助澜的作用.作为一个开挖掘机的菜鸟,深深感到不学习一下deep learning早晚要被淘汰. 既然要开始学,当然是搭一个深度神经网络跑几个数据集感受一下作为入门最直观了.自己写代码实现的话debug的过程和运行效率都会很忧伤,我也不知道怎么调用GPU- 所以还是站在巨人的肩膀上,用现成的框架吧.粗略了解一下,现在比较知名的有caffe.mxnet.tensorflow等等.选哪个呢?对我来说选择的标准就两个,第一要容易安

  • TensorFlow深度学习之卷积神经网络CNN

    一.卷积神经网络的概述 卷积神经网络(ConvolutionalNeural Network,CNN)最初是为解决图像识别等问题设计的,CNN现在的应用已经不限于图像和视频,也可用于时间序列信号,比如音频信号和文本数据等.CNN作为一个深度学习架构被提出的最初诉求是降低对图像数据预处理的要求,避免复杂的特征工程.在卷积神经网络中,第一个卷积层会直接接受图像像素级的输入,每一层卷积(滤波器)都会提取数据中最有效的特征,这种方法可以提取到图像中最基础的特征,而后再进行组合和抽象形成更高阶的特征,因此

  • tensorflow入门之训练简单的神经网络方法

    这几天开始学tensorflow,先来做一下学习记录 一.神经网络解决问题步骤: 1.提取问题中实体的特征向量作为神经网络的输入.也就是说要对数据集进行特征工程,然后知道每个样本的特征维度,以此来定义输入神经元的个数. 2.定义神经网络的结构,并定义如何从神经网络的输入得到输出.也就是说定义输入层,隐藏层以及输出层. 3.通过训练数据来调整神经网络中的参数取值,这是训练神经网络的过程.一般来说要定义模型的损失函数,以及参数优化的方法,如交叉熵损失函数和梯度下降法调优等. 4.利用训练好的模型预测

  • TensorFlow搭建神经网络最佳实践

    一.TensorFLow完整样例 在MNIST数据集上,搭建一个简单神经网络结构,一个包含ReLU单元的非线性化处理的两层神经网络.在训练神经网络的时候,使用带指数衰减的学习率设置.使用正则化来避免过拟合.使用滑动平均模型来使得最终的模型更加健壮. 程序将计算神经网络前向传播的部分单独定义一个函数inference,训练部分定义一个train函数,再定义一个主函数main. 完整程序: #!/usr/bin/env python3 # -*- coding: utf-8 -*- ""&

  • 基于Tensorflow搭建一个神经网络的实现

    一.Tensorlow结构 import tensorflow as tf import numpy as np #创建数据 x_data = np.random.rand(100).astype(np.float32) y_data = x_data*0.1+0.3 #创建一个 tensorlow 结构 weights = tf.Variable(tf.random_uniform([1], -1.0, 1.0))#一维,范围[-1,1] biases = tf.Variable(tf.zer

  • 使用TensorFlow搭建一个全连接神经网络教程

    说明 本例子利用TensorFlow搭建一个全连接神经网络,实现对MNIST手写数字的识别. 先上代码 from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf # prepare data mnist = input_data.read_data_sets('MNIST_data', one_hot=True) xs = tf.placeholder(tf.float32, [None,

  • 基于AngularJS前端云组件最佳实践

    AngularJS是google设计和开发的一套前端开发框架,他能帮助开发人员更便捷地进行前端开发.AngularJS是为了克服HTML在构建应用上的不足而设计的,它非常全面且简单易学习,因此AngularJS快速的成为了javascript的主流框架. 一.Amazing的Angular AnguarJS的特性 方便的REST: RESTful逐渐成为了一种标准的服务器和客户端沟通的方式.你只需使用一行javascript代码,就可以快速的从服务器端得到数据.AugularJS将这些变成了JS

  • 用tensorflow搭建CNN的方法

    CNN(Convolutional Neural Networks) 卷积神经网络简单讲就是把一个图片的数据传递给CNN,原涂层是由RGB组成,然后CNN把它的厚度加厚,长宽变小,每做一层都这样被拉长,最后形成一个分类器 在 CNN 中有几个重要的概念: stride padding pooling stride,就是每跨多少步抽取信息.每一块抽取一部分信息,长宽就缩减,但是厚度增加.抽取的各个小块儿,再把它们合并起来,就变成一个压缩后的立方体. padding,抽取的方式有两种,一种是抽取后的

  • 详解springboot+aop+Lua分布式限流的最佳实践

    一.什么是限流?为什么要限流? 不知道大家有没有做过帝都的地铁,就是进地铁站都要排队的那种,为什么要这样摆长龙转圈圈?答案就是为了限流!因为一趟地铁的运力是有限的,一下挤进去太多人会造成站台的拥挤.列车的超载,存在一定的安全隐患.同理,我们的程序也是一样,它处理请求的能力也是有限的,一旦请求多到超出它的处理极限就会崩溃.为了不出现最坏的崩溃情况,只能耽误一下大家进站的时间. 限流是保证系统高可用的重要手段!!! 由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动,

  • Python实现Keras搭建神经网络训练分类模型教程

    我就废话不多说了,大家还是直接看代码吧~ 注释讲解版: # Classifier example import numpy as np # for reproducibility np.random.seed(1337) # from keras.datasets import mnist from keras.utils import np_utils from keras.models import Sequential from keras.layers import Dense, Act

  • Pytorch自动求导函数详解流程以及与TensorFlow搭建网络的对比

    一.定义新的自动求导函数 在底层,每个原始的自动求导运算实际上是两个在Tensor上运行的函数.其中,forward函数计算从输入Tensor获得的输出Tensors.而backward函数接收输出,Tensors对于某个标量值得梯度,并且计算输入Tensors相对于该相同标量值得梯度. 在Pytorch中,可以容易地通过定义torch.autograd.Function的子类实现forward和backward函数,来定义自动求导函数.之后就可以使用这个新的自动梯度运算符了.我们可以通过构造一

  • TensorFlow卷积神经网络AlexNet实现示例详解

    2012年,Hinton的学生Alex Krizhevsky提出了深度卷积神经网络模型AlexNet,它可以算是LeNet的一种更深更宽的版本.AlexNet以显著的优势赢得了竞争激烈的ILSVRC 2012比赛,top-5的错误率降低至了16.4%,远远领先第二名的26.2%的成绩.AlexNet的出现意义非常重大,它证明了CNN在复杂模型下的有效性,而且使用GPU使得训练在可接受的时间范围内得到结果,让CNN和GPU都大火了一把.AlexNet可以说是神经网络在低谷期后的第一次发声,确立了深

  • react后台系统最佳实践示例详解

    目录 一.中后台系统的技术栈选型 1. 要做什么 2. 要求 3. 技术栈怎么选 二.hooks时代状态管理库的选型 context redux recoil zustand MobX 三.hooks的使用问题与解决方案 总结 一.中后台系统的技术栈选型 本文主要讲三块内容:中后台系统的技术栈选型.hooks时代状态管理库的选型以及hooks的使用问题与解决方案. 1. 要做什么 我们的目标是搭建一个适用于公司内部中后台系统的前端项目最佳实践. 2. 要求 由于业务需求比较多,一名开发人员需要负

随机推荐