Actor-Critic,演员评论家算法是强化学习中的一种很实用的方法。
比较详细的推导可以看:https://datawhalechina.github.io/easy-rl/#/chapter9/chapter9?id=actor-critic-1

文章目录

  • 1. 简介
  • 2. Review: Policy Gradient
  • 3. Review: Q-Learning
  • 4. Actor-Critic
  • 5. Advantage Actor-Critic
    • 整个过程如下:
    • 实现过程中的两个Tips:
  • 6.A3C, Asynchronous Advantage Actor-Critic
  • 7.与GAN的关系

1. 简介

演员-评论家算法(Actor-Critic Algorithm) 是一种结合 policy base 和 value base 思想的算法,Actor是利用策略梯度算法实现,Critic使用时序差分实现。

  • Actor(演员)是策略函数πθ(s)\pi_\theta(s)πθ​(s),一般用神经网络实现,输入是当前状态,输出是一个动作。该网络的训练目标是最大化累计回报的期望
  • Critic(评论家)是值函数Vπ(s)V^\pi(s)Vπ(s),该网络可以对当前策略的值函数进行估计,也就是可以评价Actor(策略函数)的好坏。
  • 原始的Actor(策略梯度法)是使用累计回报的期望作为训练依据,这样做 只有等到回合结束才能更新πθ(s)\pi_\theta(s)πθ​(s)的参数。

在 Actor-Critic 算法 里面,最知名的方法就是 A3C(Asynchronous Advantage Actor-Critic)。

  • 如果去掉 Asynchronous,只有 Advantage Actor-Critic,就叫做 A2C。
  • 如果加了 Asynchronous,变成 Asynchronous Advantage Actor-Critic,就变成 A3C。

2. Review: Policy Gradient

策略梯度法(Policy Gradient)可以参考:https://blog.csdn.net/qq_33302004/article/details/115495686

我们回顾一下policy gradient,其主要过程就是:

  • 先初始化一个策略网络θ\thetaθ
  • 用这个策略网络进行NNN次游戏,产生NNN个τ\tauτ(游戏记录):
    τ1:{s11,a11,s21,a21,...},R(τ1)τ2:{s12,a12,s22,a22,...},R(τ2)...τN:{s1N,a1N,s2N,a2N,...},R(τN)\tau^1: \{s_1^1, a_1^1, s_2^1, a_2^1, ... \}, R(\tau^1) \\ \tau^2: \{s_1^2, a_1^2, s_2^2, a_2^2, ... \}, R(\tau^2) \\ ... \\ \tau^N: \{s_1^N, a_1^N, s_2^N, a_2^N, ... \}, R(\tau^N) \\ τ1:{s11​,a11​,s21​,a21​,...},R(τ1)τ2:{s12​,a12​,s22​,a22​,...},R(τ2)...τN:{s1N​,a1N​,s2N​,a2N​,...},R(τN)
  • 我们利用这NNN个τ\tauτ进行梯度上升,调整策略网络的参数:
    ∇Rˉ(τ)=1N∑n=1N∑t=1Tn(∑t′=tTrt′nγt′−t−b)∇logpθ(atn∣stn),γ∈[0,1]\nabla\bar{R}(\tau) = \frac1N \sum_{n=1}^N \sum_{t=1}^{T_n} (\sum_{t'=t}^T r_{t'}^n \gamma^{t'-t} - b) \nabla log p_\theta(a_t^n|s_t^n) , \gamma \in[0,1] ∇Rˉ(τ)=N1​n=1∑N​t=1∑Tn​​(t′=t∑T​rt′n​γt′−t−b)∇logpθ​(atn​∣stn​),γ∈[0,1]
  • 再如此重复2、3步。

3. Review: Q-Learning

Q-Learning可以参考:https://blog.csdn.net/qq_33302004/article/details/114871232

简而言之,Q-Learning就是一种 value base 的方法,该方法会建立一个Q表,里面存储了每一对(s,a)(s,a)(s,a)对应的value值,agent会根据Q表中的值决定在状态sss下采用哪种动作。以上面的方法,agent不断与环境进行互动,并更新Q表,最终Q表不断收敛,机器人也取得了比较好的效果。

大致过程如下:

  • 初始化Q表
  • Repeat (for each episode):
    • 初始化状态s;
    • Repeat (for each step of episode):
      • 依据某种策略,从Q表中根据当前状态s,选择一个动作a;
      • 执行动作a,进入状态s’,获得环境反馈r;
        • Q估计=Q(s,a)Q_{估计} = Q(s,a)Q估计​=Q(s,a)
        • Q现实=r+γ∗maxQ(s′)Q_{现实} = r + \gamma *maxQ(s')Q现实​=r+γ∗maxQ(s′),其中rrr是在sss状态下执行动作aaa的直接回报,γ\gammaγ是衰减函数,s′s's′是下一个状态
        • 我们利用公式更新Q表中的值:Q(s,a)=Q估计+α(Q现实−Q估计)Q(s,a) = Q_{估计} + \alpha (Q_{现实} - Q_{估计} )Q(s,a)=Q估计​+α(Q现实​−Q估计​),其中α\alphaα是学习效率,最终公式为:
          Q′(s,a)=Q(s,a)+α∗[r+γ∗maxQ(s′)−Q(s,a)];Q'(s, a) = Q(s, a) + \alpha* [r + \gamma * maxQ(s') - Q(s, a)]; Q′(s,a)=Q(s,a)+α∗[r+γ∗maxQ(s′)−Q(s,a)];
      • 更新Q表,令$ Q(s, a) = Q’(s, a) $
      • 更新当前状态,$ s = s’$
    • until s is terminal

上面的Q-Learning是举了一个例子,其中value值的计算方法采用了时间差分(TD) 方法,在使用中还可以使用蒙特卡洛(MC) 的方法实现。

无论用TD还是MC,Q-Learning都是一种value base的方法,其核心就是计算两种函数:

  • Vπ(s)V^\pi(s)Vπ(s)状态值函数,在策略π\piπ中,状态sss接下来会有多少价值;
  • Qπ(s,a)Q^\pi(s,a)Qπ(s,a)动作状态值函数,在策略π\piπ中,状态sss下执行动作aaa,接下来会有多少价值;
  • VπV^\piVπ输入sss,会输出一个标量;
  • QπQ^\piQπ输入sss,会输出一个向量,给每一个aaa都分配一个value值;
  • VπV^\piVπ和QπQ^\piQπ可以看做是两种Critic。

想要更深入理解这两种值函数,可以参考:https://blog.csdn.net/qq_33302004/article/details/115189857

4. Actor-Critic

Actor是一个policy base的方法,负责做决策,并且更新决策;Critic是一个value base的方法,负责估计当前Actor的Vπ(s)V^\pi(s)Vπ(s)和Qπ(s,a)Q^\pi(s,a)Qπ(s,a)。

Actor就是我们第二部分中提到的policy gradient,这个方法的和核心就是利用回报梯度更新网络,最终公式如下:
∇Rˉ(τ)=1N∑n=1N∑t=1Tn(∑t′=tTrt′nγt′−t−b)∇logpθ(atn∣stn),γ∈[0,1]\nabla\bar{R}(\tau) = \frac1N \sum_{n=1}^N \sum_{t=1}^{T_n} (\sum_{t'=t}^T r_{t'}^n \gamma^{t'-t} - b) \nabla log p_\theta(a_t^n|s_t^n) , \gamma \in[0,1] ∇Rˉ(τ)=N1​n=1∑N​t=1∑Tn​​(t′=t∑T​rt′n​γt′−t−b)∇logpθ​(atn​∣stn​),γ∈[0,1]

其中,我们将R−bR-bR−b这一项定义为 Advantage function(比较优势) 符号表示为 Aθ(st,at)A^{\theta}(s_t, a_t)Aθ(st​,at​):
Aθ(st,at)=∑t′=tTrt′nγt′−t−bA^{\theta}(s_t, a_t) = \sum_{t'=t}^T r_{t'}^n \gamma^{t'-t} - b Aθ(st​,at​)=t′=t∑T​rt′n​γt′−t−b

我们可以看出Advantage function(比较优势) 就是利用蒙特卡洛方法计算的累计回报期望-baseline,那么我们是否可以不在Actor中计算回报,而是直接使用Critic来更新Actor呢?


其实累计回报期望:E[Gtn]≈∑t′=tTrt′nγt′−tE[G_t^n] \approx \sum_{t'=t}^T r_{t'}^n \gamma^{t'-t}E[Gtn​]≈∑t′=tT​rt′n​γt′−t,就是Qπθ(stn,atn)Q^{\pi_\theta}(s_t^n,a_t^n)Qπθ​(stn​,atn​)。

因为这个就是 Q 的定义。Q-function 的定义就是在某一个状态 s,采取某一个动作 a,假设 policy 就是 π\piπ的情况下会得到的累积奖励的期望值有多大,而这个东西就是 G 的期望值。累积奖励的期望值就是 G 的期望值。

所以我们把 Q-function 套在这里就结束了,就可以把 Actor 跟 Critic 这两个方法结合起来。

对于baseline有许多不同的表示方法,但一个常见的做法是用价值函数Vπθ(stn)V^{\pi_\theta}(s_t^n)Vπθ​(stn​) 来表示 baseline。

价值函数是说,假设 policy 是π\piπ,在某一个状态 s 一直互动到游戏结束,期望奖励(expected reward) 有多大。Vπθ(stn)V^{\pi_\theta}(s_t^n)Vπθ​(stn​) 是状态stns_t^nstn​下,执行所有atna_t^natn​所产生的Qπθ(stn,atn)Q^{\pi_\theta}(s_t^n,a_t^n)Qπθ​(stn​,atn​)期望值,所以Qπθ(stn,atn)−Vπθ(stn)Q^{\pi_\theta}(s_t^n,a_t^n) - V^{\pi_\theta}(s_t^n)Qπθ​(stn​,atn​)−Vπθ​(stn​) 一定有正有负。

所以最终Actor的训练函数为:
∇Rˉ(τ)=1N∑n=1N∑t=1Tn(Qπθ(stn,atn)−Vπθ(stn))∇logpθ(atn∣stn)\nabla\bar{R}(\tau) = \frac1N \sum_{n=1}^N \sum_{t=1}^{T_n} (Q^{\pi_\theta}(s_t^n,a_t^n) - V^{\pi_\theta}(s_t^n)) \nabla log p_\theta(a_t^n|s_t^n) ∇Rˉ(τ)=N1​n=1∑N​t=1∑Tn​​(Qπθ​(stn​,atn​)−Vπθ​(stn​))∇logpθ​(atn​∣stn​)

5. Advantage Actor-Critic

在上章中我们推导出Advantage function(比较优势)
Aθ(stn,atn)=Qπθ(stn,atn)−Vπθ(stn)A^{\theta}(s_t^n, a_t^n) = Q^{\pi_\theta}(s_t^n,a_t^n) - V^{\pi_\theta}(s_t^n) Aθ(stn​,atn​)=Qπθ​(stn​,atn​)−Vπθ​(stn​)
从公式中可以看出,我们要完成两种估计,Vπ(s)V^\pi(s)Vπ(s)和Qπ(s,a)Q^\pi(s,a)Qπ(s,a),那么直观思路就是我们需要两个网络:Q-network 和 V-network,如果这样做估测不准的风险就变成两倍。所以我们何不只估测一个网络?

事实上在这个 Actor-Critic 方法里面,我们可以只估测 V 这个网络,并且用 V 的值来表示 Q 的值,即:
Qπθ(stn,atn)=E[rtn+Vπθ(st+1n)]Q^{\pi_\theta}(s_t^n,a_t^n) = E[r_t^n+ V^{\pi_\theta}(s_{t+1}^n)] Qπθ​(stn​,atn​)=E[rtn​+Vπθ​(st+1n​)]

你在状态 sss 采取动作 aaa,会得到奖励 rrr,然后跳到状态 st+1s_{t+1}st+1​ 。但是你会得到什么样的奖励 rrr,跳到什么样的状态 st+1s_{t+1}st+1​ ,它本身是有随机性的。所以要把右边这个式子,取期望值它才会等于 Q-function。

但是如果我们能够比较准确的估计rtnr_t^nrtn​就可以把期望去掉:
Qπθ(stn,atn)=rtn+Vπθ(st+1n)Q^{\pi_\theta}(s_t^n,a_t^n) = r_t^n+ V^{\pi_\theta}(s_{t+1}^n) Qπθ​(stn​,atn​)=rtn​+Vπθ​(st+1n​)

把这个期望值去掉的好处就是你不需要估计 Q 了,你只需要估计 V 就够了,你只要估计 一个网络就够了。但这样你就引入了一个随机的东西 r ,它是有随机性的,它是一个随机变量。但是这个随机变量,相较于累积奖励 G 可能还好,因为它是某一个步骤会得到的奖励,而 G 是所有未来会得到的奖励的总和。G 的方差比较大,r 虽然也有一些方差,但它的方差会比 G 要小。所以把原来方差比较大的 G 换成方差比较小的 r 也是合理的。

Q: 为什么可以直接把期望值拿掉?

A: 原始的 A3C paper 试了各种方法,最后做出来就是这个最好。当然你可能说,搞不好估计 Q 和 V,也可以估计 很好,那我告诉你就是做实验的时候,最后结果就是这个最好,所以后来大家都用这个。

由此我们的Advantage function(比较优势) 公式变成了如下的样子:
Aθ(stn,atn)=rtn+Vπθ(st+1n)−Vπθ(stn)A^{\theta}(s_t^n, a_t^n) = r_t^n+ V^{\pi_\theta}(s_{t+1}^n) - V^{\pi_\theta}(s_t^n) Aθ(stn​,atn​)=rtn​+Vπθ​(st+1n​)−Vπθ​(stn​)
训练梯度公式如下:
∇Rˉ(τ)=1N∑n=1N∑t=1Tn(rtn+Vπθ(st+1n)−Vπθ(stn))∇logpθ(atn∣stn)\nabla\bar{R}(\tau) = \frac1N \sum_{n=1}^N \sum_{t=1}^{T_n} (r_t^n+ V^{\pi_\theta}(s_{t+1}^n) - V^{\pi_\theta}(s_t^n)) \nabla log p_\theta(a_t^n|s_t^n) ∇Rˉ(τ)=N1​n=1∑N​t=1∑Tn​​(rtn​+Vπθ​(st+1n​)−Vπθ​(stn​))∇logpθ​(atn​∣stn​)
因为利用了Advantage function(比较优势),所以这个方法叫做Advantage Actor-Critic,也可以叫做A2C

整个过程如下:

  • 我们有一个Actor,他是 policy gradient 的策略网络 π\piπ,其参数用θ\thetaθ表示;
  • 先利用Actor与环境互动N个回合,产生样本τ\tauτ;
  • 然后我们利用样本τ\tauτ去训练Critic,也就是价值网络,得到Vπ(s)V^{\pi}(s)Vπ(s),这一步可以采用TD或者MC的方法实现;
  • 而后利用Vπ(s)V^{\pi}(s)Vπ(s),我们就可以计算出∇Rˉ(τ)\nabla\bar{R}(\tau)∇Rˉ(τ),从而更新π\piπ的参数,得到θ′\theta'θ′;
  • 更新π\piπ,再重新与环境、产生样本、更新Critic、更新Actor,如此重复。

实现过程中的两个Tips:


实现 Actor-Critic 的时候,有两个一定会用的 tip:

  • 第一个 tip 是说,我们需要估计两个网络:V function 和 policy 的网络(也就是 actor)。

    • Critic 网络Vπ(s)V^{\pi}(s)Vπ(s)输入一个状态,输出一个标量。
    • Actor网络π(s)\pi(s)π(s)输入一个状态:
      • 如果动作是离散的,输出就是一个动作的分布。
      • 如果动作是连续的,输出就是一个连续的向量。
    • 我们可以想到这两个网络,Actor 和 Critic 的输入都是sss,所以它们前面几个层(layer),其实是可以共享的
      • 尤其是假设你今天是玩 Atari 游戏,输入都是图像。输入的图像都非常复杂,图像很大,通常你前面都会用一些 CNN 来处理,把那些图像抽象成高级(high level)的信息。把像素级别的信息抽象成高级信息这件事情,其实对 actor 跟 critic 来说是可以共用的。所以通常你会让 actor 跟 critic 的共享前面几个层,你会让 actor 跟 critic 的前面几个层共用同一组参数,那这一组参数可能是 CNN 的参数。
      • 先把输入的像素变成比较高级的信息,然后再给 actor 去决定说它要采取什么样的行为,给这个 critic,给价值函数去计算期望奖励。
  • 第二个 tip 是我们一样需要探索(exploration)的机制。在做 Actor-Critic 的时候,有一个常见的探索的方法是你会对你的 \piπ 的输出的分布下一个约束。这个约束是希望这个分布的熵(entropy)不要太小,希望这个分布的熵可以大一点,也就是希望不同的动作它的被采用的概率平均一点。这样在测试的时候,它才会多尝试各种不同的动作,才会把这个环境探索的比较好,才会得到比较好的结果。

6.A3C, Asynchronous Advantage Actor-Critic

强化学习有一个问题就是它很慢,那怎么增加训练的速度呢?举个例子,火影忍者就是有一次鸣人说,他想要在一周之内打败晓,所以要加快修行的速度,他老师就教他一个方法:用影分身进行同样修行。两个一起修行的话,经验值累积的速度就会变成 2 倍,所以鸣人就开了 1000 个影分身来进行修行。这个其实就是 Asynchronous(异步的) Advantage Actor-Critic,也就是 A3C 这个方法的精神。


A3C 这个方法就是同时开很多个 worker,那每一个 worker 其实就是一个影分身。那最后这些影分身会把所有的经验,通通集合在一起。你如果没有很多个 CPU,可能也是不好实现的,你可以实现 A2C 就好。

A3C的过程如下:

  • A3C 一开始有一个 global network。那我们刚才有讲过,其实 policy network 跟 value network 是绑(tie)在一起的,它们的前几个层会被绑一起。我们有一个 global network,它们有包含 policy 的部分和 value 的部分。
  • 假设 global network 的参数是 θ1\theta_1θ1​,你会开很多个 worker。每一个 worker 就用一张 CPU 去跑。比如你就开 8 个 worker,那你至少 8 张 CPU。每一个 worker 工作前都会 global network 的参数复制过来。
  • 接下来你就去跟环境做互动,每一个 actor 去跟环境做互动的时候,要收集到比较多样性的数据。举例来说,如果是走迷宫的话,可能每一个 actor 起始的位置都会不一样,这样它们才能够收集到比较多样性的数据。
  • 每一个 actor 跟环境做互动,互动完之后,你就会计算出梯度。计算出梯度以后,你要拿梯度去更新你的参数。你就计算一下你的梯度,然后用你的梯度去更新 global network 的参数。就是这个 worker 算出梯度以后,就把梯度传回给中央的控制中心,然后中央的控制中心就会拿这个梯度去更新原来的参数。
  • 注意,所有的 actor 都是平行跑的,每一个 actor 就是各做各的,不管彼此。所以每个人都是去要了一个参数以后,做完就把参数传回去。所以当第一个 worker 做完想要把参数传回去的时候,本来它要的参数是θ1\theta_1θ1​,等它要把梯度传回去的时候。可能别人已经把原来的参数覆盖掉,变成θ2\theta_2θ2​了。但是没有关系,它一样会把这个梯度就覆盖过去就是了。Asynchronous actor-critic 就是这么做的,这个就是 A3C。

7.与GAN的关系

其实 GAN 跟 Actor-Critic 的方法是非常类似的。这边就不细讲,你可以去找到一篇 paper 叫做 Connecting Generative Adversarial Network and Actor-Critic Methods

Q: 知道 GAN 跟 Actor-Critic 非常像有什么帮助呢?

A: 一个很大的帮助就是 GAN 跟 Actor-Critic 都是以难训练而闻名的。所以在文献上就会收集各式各样的方法,告诉你说怎么样可以把 GAN 训练起来。怎么样可以把 Actor-Critic 训练起来。但是因为做 GAN 跟 Actor-Critic 的人是两群人,所以这篇 paper 里面就列出说在 GAN 上面有哪些技术是有人做过的,在 Actor-Critic 上面,有哪些技术是有人做过的。也许在 GAN 上面有试过的技术,你可以试着应用在 Actor-Critic 上,在 Actor-Critic 上面做过的技术,你可以试着应用在 GAN 上面,看看是否 work。

【详解+推导!!】Actor-Critic 演员评论家算法相关推荐

  1. 国密算法Go语言实现(详解)(十) ——SM2(椭圆曲线公钥密码算法)

    国密算法Go语言实现(详解)(十) --SM2(椭圆曲线公钥密码算法) 原创代码:https://github.com/ZZMarquis/gm 引用时,请导入原创代码库.本文仅以注释方式详解代码逻辑 ...

  2. java斐波那契查找_详解Java Fibonacci Search斐波那契搜索算法代码实现

    一, 斐波那契搜索算法简述 斐波那契搜索(Fibonacci search) ,又称斐波那契查找,是区间中单峰函数的搜索技术. 斐波那契搜索采用分而治之的方法,其中我们按照斐波那契数列对元素进行不均等 ...

  3. 详解利用基于gensim的TF-IDF算法实现基于文本相似度的推荐算法

    详解利用基于gensim的TF-IDF算法实现基于文本相似度的推荐算法 TF-IDF的基本原理 算法思想 计算公式 相似度计算原理 微型图书推荐案例 案例背景 开发工具 数据预处理 TF-IDF模型建 ...

  4. 国密算法Go语言实现(详解)(九) ——SM2(椭圆曲线公钥密码算法)

    国密算法Go语言实现(详解)(九) --SM2(椭圆曲线公钥密码算法) 原创代码:https://github.com/ZZMarquis/gm 引用时,请导入原创代码库.本文仅以注释方式详解代码逻辑 ...

  5. Paxos算法细节详解(一)--通过现实世界描述算法

    Paxos算法细节详解(一)--通过现实世界描述算法 Paxos分析 最近研究paxos算法,看了许多相关的文章,概念还是很模糊,觉得还是没有掌握paxos算法的精髓,所以花了3天时间分析了libpa ...

  6. 【最短路径Floyd算法详解推导过程】看完这篇,你还能不懂Floyd算法?还不会?...

    简介 Floyd-Warshall算法(Floyd-Warshall algorithm),是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似.该算法名称以 ...

  7. 【H.264/AVC视频编解码技术详解】十九:熵编码算法(5)——H.264的CABAC(上):语法元素的二值化方法...

    <H.264/AVC视频编解码技术详解>视频教程已经在"CSDN学院"上线,视频中详述了H.264的背景.标准协议和实现,并通过一个实战工程的形式对H.264的标准进行 ...

  8. LSTM公式详解推导

    书籍简介 LSTM理解 LSTM流程简介 算法及公式 一些函数 一些符号 前向传播 反向传播 关于误差的定义 公式推导 总结 书籍简介 <Surpervised Sequence Labelli ...

  9. 【OpenCV 4开发详解】分割图像——Mean-Shift分割算法

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

最新文章

  1. PCL中3D特征描述子Shot详解
  2. C#的多线程机制探索4
  3. mysql for macOS安装
  4. iOS开发拓展篇—UIDynamic(捕捉行为)
  5. Access denied (403) see security.limit_extensions
  6. 在win10下安装Linux双系统
  7. bp神经网络预测模型流程图,bp神经网络实例分析
  8. 读书会 | 第一季读书会《蛤蟆先生去看心理医生》完美收官啦
  9. linux防恶意软件防病毒 防护工具
  10. linux中rcf命名管道,RCF-进程间为C通讯
  11. 世界上最美的情诗绝句
  12. 选择一款对的固定资产管理系统让固定资产管理轻松无比
  13. idea中设置jdk
  14. 2022年重新启航规划
  15. Windows10系统VC++6.0安装教程
  16. 又一年神仙打架,清华大学特等奖学金答辩会上15位大神现场竞争!
  17. turtle库制作简单动画和总结
  18. python 奶茶系统2.0
  19. 塞尔达 amiibo_极客历史的本周:塞尔达(Zelda)25岁,印刷机的诞生,以及ENIAC的揭幕...
  20. 第8章 Drupal 主题系统( Drupal theme)(4) 高级特性--1,覆写主题函数

热门文章

  1. IOS开发语言OC的基本语法以及结构
  2. 安卓开发入门教程-通过WebView打开网页
  3. 2100年彻底颠覆世界的“十大未来科技”
  4. Excel打印预览时反应很慢
  5. java 气泡图_D3.js实现散点图和气泡图的方法详解
  6. asp.net权限认证:摘要认证(digest authentication)
  7. c语言贪吃蛇地图自动打印,架构练习:c语言实现贪吃蛇(一):画地图和蘑菇...
  8. 《中谷项目》--第一滴血
  9. 【iPhone8iPhone X】高科技技术必须学会的人脸识别术
  10. 全国专业技术人员计算机应用能力考试实战技巧