进化算法是一类算法簇,这类算法模拟的是自然界当中生物种群的进化机制。进化算法在解决最优化问题或者机器学习问题的时候,通常会维护一个解的集合 X = { x 1 , x 2 , … , x N } X=\{x_1,x_2,\dots,x_N\} X={ x1,x2,…,xN},称之为种群, N N N称为种群规模。算法运行的机制,是在每一轮迭代当中,对种群进行变异、交叉等操作,形成新的个体添加到种群当中,然后对种群进行筛选,使得种群的规模始终维持在 N N N. 筛选的规则是根据种群个体的适应度 f ( x i ) f(x_i) f(xi)设计的,理想的筛选规则应当保留适应度高的个体,淘汰适应度低的个体。
我们这里介绍的进化算法,主要是三种:遗传算法、粒子群算法和标准差分算法。
首先我们先定义基本的问题背景:在一个环境 env \text{env} env(可能是一个最优化问题、一个强化学习场景、一个实际的工程模拟)当中,每一个解都表示为向量 x i ∈ R d x_i\in\R^d xi∈Rd. 存在一个适应度函数 f ( x i ) f(x_i) f(xi)能够评价每一个解的适应性,函数评分越高,说明解越适应于该环境。
我们接下来分别介绍三种算法解决该问题的流程。
遗传算法模拟的是自然界中生物种群的繁衍以及自然选择机制。一个遗传算法的流程可以表示为:
需要注意的是,遗传算法同样是一个算法簇,而不是某一具体算法。对于具体的实现,筛选、交叉、变异算子在算法流程中的顺序,以及它们对当前种群的更新机制会有一定的差异。
在这个流程当中,最为关键的是三个遗传算子: select ( ⋅ ) , crossover ( ⋅ ) , mutation ( ⋅ ) \text{select}(\cdot),\text{crossover}(\cdot),\text{mutation}(\cdot) select(⋅),crossover(⋅),mutation(⋅),还有一个非关键的更新算子 Reproduction \text{Reproduction} Reproduction.
假设每一个个体的适应度 f ( x i ) f(x_i) f(xi)都是非负的,那么进行变换
p i = f ( x i ) ∑ j = 1 N f ( x j ) p_i=\frac{f(x_i)}{\sum_{j=1}^Nf(x_j)} pi=∑j=1Nf(xj)f(xi)
在选择过程中,每个个体被选中加入交叉种群 X c X_c Xc的概率是 p i p_i pi. 实现这种选择的方法,可以采用轮盘抽奖的算法。假设我们在面积为 1 1 1的抽奖圆盘上,为每一个个体分配面积为 p i p_i pi的扇面,然后转动圆盘,当圆盘停止时,指针指向第 i i i个个体的扇面的概率恰好是 p i p_i pi. 如图:
从计算机程序实现角度来说,我们可以规定一个起始坐标 q 0 = 0 q_0=0 q0=0,然后用累加概率的方式找出剩下的各个分界点坐标
q i = ∑ j = 1 i p i q_i=\sum_{j=1}^ip_i qi=j=1∑ipi
随机取一个 [ q 0 , q N ] [q_0,q_N] [q0,qN]之间的点 λ \lambda λ,如果 λ ∈ [ q i − 1 , q i ] \lambda\in[q_{i-1},q_i] λ∈[qi−1,qi],那就意味着指针落在了 x i x_i xi对应的圆盘扇面内, x i x_i xi被选中加入 X c X_c Xc。把这个过程重复 N N N次,就会选择出期望为 N p c Np_c Npc个的种群个体。
这种采样过程产生的交叉种群数 N c N_c Nc是不固定的,而且比原始种群数 N N N要少,需要一定的机制,例如将交叉运算后的个体添加回原种群当中,才能保证种群的数目不会随着迭代次数增加而减少。
最优选择算子:这种算子通常指定一个数量 c < N c<N c<N,在每一轮迭代当中,它用当前种群里适应度最高的 c c c个个体,替换掉适应度最低的 c c c个个体,然后直接用得到的新种群作为 X c X_c Xc。更新后的种群继续进行交叉、变异算子的操作。
最优保存算子:这种算子不对种群进行筛选, X c = X X_c=X Xc=X. 只是记录下当前适应度最高的个体。最后在进行交叉、变异结束之后的种群 X ′ X' X′中,将最优个体插入其中。使得最优个体保持到下一轮迭代。
生存数目算子:计算每一个个体适应度与平均适应度的比值,并且下取整:
N i = ⌊ N f ( x i ) ∑ j = 1 N f ( x j ) ⌋ N_i=\lfloor\frac{Nf(x_i)}{\sum_{j=1}^Nf(x_j)}\rfloor Ni=⌊∑j=1Nf(xj)Nf(xi)⌋
将原种群当中的每一个个体 x i x_i xi,复制 N i N_i Ni次,加入到交叉种群 X c X_c Xc当中。按照适应度对于 X c X_c Xc排序,截取其前 N N N个个体。
一般来说,交叉算子总是规定了交叉概率 P c P_c Pc,并且希望交叉运算产生的子代数目期望为 N c P c N_cP_c NcPc. 交叉算子有一个重要的问题是,交叉的两个父本如何选择。这个问题没有固定解法。有的算法当中,会将交叉种群 X c X_c Xc打乱顺序,之后按顺序两两匹配,对于每一个匹配都以 P c P_c Pc的概率进行交叉或不交叉;有的算法会随机抽取 n n n轮,每轮抽取一对父本以 P c P_c Pc的概率进行交叉或不交叉;有的算法会随机产生一个 ( 0 , 1 ) (0,1) (0,1)之间的随机数 λ \lambda λ,如果 λ < P c \lambda<P_c λ<Pc,就对整个 N c N_c Nc进行打乱、按顺序两两匹配、交叉运算,否则就完全不进行交叉运算。
交叉算子最常用的是单点交叉算子,即随机地在个体向量上取一个点位 min \text{min} min. 对于配对的两个向量 x i , x j x_i,x_j xi,xj,将它们从点位 min \text{min} min开始直到结束的片段交换:
x ‾ i , n = x j , n , n ≥ min x ‾ j , n = x i , n , n ≥ min \overline x_{i,n}=x_{j,n},\ n\geq\text{min}\\ \overline x_{j,n}=x_{i,n},\ n\geq\text{min} xi,n=xj,n, n≥minxj,n=xi,n, n≥min
其次还有双点位交叉,即选择两个点位 min , max \text{min},\text{max} min,max,交换父本在这两个点位之间的片段。
有的时候,还会使用凸组合交叉算子,即随机采样一个 a ∈ ( 0 , 1 ) a\in(0,1) a∈(0,1),令
x ‾ i = a x i + ( 1 − a ) x j x ‾ j = ( 1 − a ) x i + a x j \overline x_i=ax_i+(1-a)x_j\\ \overline x_j=(1-a)x_i+ax_j xi=axi+(1−a)xjxj=(1−a)xi+axj
这种算子适用于解空间是高维凸域的情形。
最后一个问题是,交叉算子产生的子代和父代如何进行取舍。有的算法是将子代添加到种群 X c X_c Xc中组合形成 X ′ X' X′,然后通过更新算子 Reproduction ( X ′ ) \text{Reproduction}(X') Reproduction(X′)进行取舍(例如选择出适应度前 N N N个个体)。有的算法会比较子代与父代的适应度,然后选择保留适应度强的子代。
变异算子同样指定一个变异概率 P m P_m Pm,并且试图使变异的个体数期望为 N m P m N_mP_m NmPm. 而实现的方法,可以是对于 X m X_m Xm的每一个个体都进行二分决策,以 P m P_m Pm的概率进行突变,也可以是对整个 X m X_m Xm进行二分决策,突变或者不突变。
对于每一个突变的个体,如果是二进制串(二进制的向量),可以直接进行位反转。
对于一般的向量,还可以随机选取一个片段进行倒置(顺序颠倒),或者选择两个点位互换数值。
和交叉算子类似,变异算子也可以采用线性组合的方式进行变异:随机选取一个 v ∈ R d v\in\R^d v∈Rd和一个 λ ∈ ( 0 , 1 ) \lambda\in(0,1) λ∈(0,1),令
x ^ i = x i + λ v \hat x_i=x_i+\lambda v x^i=xi+λv
更新算子的实现很简单,如果选择、交叉、变异算子最终产生的种群 X ′ X' X′规模是 N N N,可以直接将其更新到 X X X上. 如果数目超过 N N N,那么可以选择保留适应度最高的 N N N个个体。(也就是需要再次遍历并且进行适应度评价)
粒子群算法和遗传算法的区别在于,粒子群算法没有交叉、变异的相关概念,而是会在每一轮迭代当中,更新每一个个体的值。在粒子群算法当中,种群当中的个体称为粒子,由两个 d d d维的向量 x i ∈ R d , v i ∈ R d x_i\in\R^d,v_i\in\R^d xi∈Rd,vi∈Rd表示, x i x_i xi称为粒子的位置, v i v_i vi称为粒子的速度。一个种群可以表示为 E = ( X , V ) E=(X,V) E=(X,V),其中 X = ( x 1 , x 2 , … , x N ) X=(x_1,x_2,\dots,x_N) X=(x1,x2,…,xN), V = ( v 1 , v 2 , … , v N ) V=(v_1,v_2,\dots,v_N) V=(v1,v2,…,vN). 环境中的适应性函数仅仅取决于位置 f ( x i ) f(x_i) f(xi).
粒子群算法当中,每一个粒子都需要维护两个变量 p i ∈ R , P i ∈ R d p_i\in\R, P_i\in\R^d pi∈R,Pi∈Rd,其中 p i p_i pi是在以往迭代轮数当中,粒子 i i i所取得的最大适应度,而 P i P_i Pi是取得该适应度时的位置。
同时在每一轮迭代当中,还要找出两个变量 s ∈ R , S ∈ R d s\in\R,S\in\R^d s∈R,S∈Rd,分别是当前种群当中,适应度的最大值,以及适应度最大的粒子所处位置。
标准粒子群算法的参数包括:
标准粒子群算法的流程可以描述为:
1 初始化
随机生成(或按照一定分布)粒子群 X ( t = 1 ) , V ( t = 1 ) X^{(t=1)},V^{(t=1)} X(t=1),V(t=1),根据初始化条件计算 p i ( 0 ) , P i ( 0 ) p_i^{(0)},P_i^{(0)} pi(0),Pi(0),迭代轮数置为 t = 1 t=1 t=1
2 计算适应度
计算每一个粒子的适应度 f ( x i ) f(x_i) f(xi),若 f ( x i ) > p i ( t − 1 ) f(x_i)>p_i^{(t-1)} f(xi)>pi(t−1),则更新 p i ( t ) = f ( x i ) , P i ( t ) = x i p_i^{(t)}=f(x_i),P_i^{(t)}=x_i pi(t)=f(xi),Pi(t)=xi,否则 p i ( t ) = p i ( t − 1 ) , P i ( t ) = P i ( t − 1 ) p_i^{(t)}=p_i^{(t-1)}, P_i^{(t)}=P_i^{(t-1)} pi(t)=pi(t−1),Pi(t)=Pi(t−1)
计算出当前种群最优: s ( t ) , S ( t ) s^{(t)}, S^{(t)} s(t),S(t)
3 更新粒子
按照如下公式更新粒子的速度和位置:
v i ( t + 1 ) = ω ( t ) v i ( t ) + c 1 r 1 ( P i ( t ) − x i ( t ) ) + c 2 r 2 ( S ( t ) − x i ( t ) ) x i ( t + 1 ) = x i ( t ) + v i ( t ) \begin{align*} &v_i^{(t+1)}=\omega(t)v_i^{(t)}+c_1r_1(P_i^{(t)}-x_i^{(t)})+c_2r_2(S^{(t)}-x_i^{(t)})\\ &x_i^{(t+1)}=x_i^{(t)}+v_i^{(t)} \end{align*} vi(t+1)=ω(t)vi(t)+c1r1(Pi(t)−xi(t))+c2r2(S(t)−xi(t))xi(t+1)=xi(t)+vi(t)
其中 ω ( t ) \omega(t) ω(t)是随迭代轮数自适应的惯性因子,而 r 1 r_1 r1和 r 2 r_2 r2是两个 ( 0 , 1 ) (0,1) (0,1)间的随机数.
4 检查结束条件
如果满足结束条件或 t > G t>G t>G,则算法终止,否则返回到2,并且设置 t = t + 1 t=t+1 t=t+1.
粒子群算法当中的位置和速度更新公式
v i ( t + 1 ) = ω ( t ) v i ( t ) + c 1 r 1 ( P i ( t ) − x i ( t ) ) + c 2 r 2 ( S ( t ) − x i ( t ) ) x i ( t + 1 ) = x i ( t ) + v i ( t ) \begin{align*} &v_i^{(t+1)}=\omega(t)v_i^{(t)}+c_1r_1(P_i^{(t)}-x_i^{(t)})+c_2r_2(S^{(t)}-x_i^{(t)})\\ &x_i^{(t+1)}=x_i^{(t)}+v_i^{(t)} \end{align*} vi(t+1)=ω(t)vi(t)+c1r1(Pi(t)−xi(t))+c2r2(S(t)−xi(t))xi(t+1)=xi(t)+vi(t)
模拟的是单个智能体的决策过程。每个智能体都有回到其历史最优值的趋势 P i ( t ) − x i ( t ) P_i^{(t)}-x_i^{(t)} Pi(t)−xi(t),前往种群最优值的趋势 S ( t ) − x i ( t ) S^{(t)}-x_i^{(t)} S(t)−xi(t),智能体通过一个随机权值组合 ( c 1 r 1 , c 2 r 2 ) (c_1r_1,c_2r_2) (c1r1,c2r2)来将这两个趋势结合起来。同时,智能体由于先前的速度,在物理空间中存在惯性 ω ( t ) v i ( t ) \omega(t)v_i^{(t)} ω(t)vi(t). 这三个部分组合在一起,就构成了对速度的改变量。而位置的改变,就是速度,这是符合物理直觉的。
为了增加更多的随机性,避免算法陷入局部最优值,我们还可以对标准粒子群算法进行修改,得到局部粒子群算法。局部粒子群算法相比较于标准粒子群算法的改动在于:当前迭代轮次当中,每个粒子都计算一个局部适应度最优值 s i ( t ) s_i^{(t)} si(t)和局部最优位置 S i ( t ) S_i^{(t)} Si(t). 局部 s i ( t ) s_i^{(t)} si(t)和 S i ( t ) S_i^{(t)} Si(t)的选择范围,通常是编号与 i i i相近的个体。例如我们可以取
s i ( t ) = Best ( f ( x i − 1 ( t ) ) , f ( x i ( t ) ) , f ( x i + 1 ( t ) ) ) S i ( t ) = BestX ( f ( x i − 1 ( t ) ) , f ( x i ( t ) ) , f ( x i + 1 ( t ) ) ) s_i^{(t)}=\text{Best}(f(x_{i-1}^{(t)}),f(x_i^{(t)}),f(x_{i+1}^{(t)}))\\ S_i^{(t)}=\text{BestX}(f(x_{i-1}^{(t)}),f(x_i^{(t)}),f(x_{i+1}^{(t)})) si(t)=Best(f(xi−1(t)),f(xi(t)),f(xi+1(t)))Si(t)=BestX(f(xi−1(t)),f(xi(t)),f(xi+1(t)))
相应的,粒子的更新算法也要修改为
v i ( t + 1 ) = ω ( t ) v i ( t ) + c 1 r 1 ( P i ( t ) − x i ( t ) ) + c 2 r 2 ( S i ( t ) − x i ( t ) ) x i ( t + 1 ) = x i ( t ) + v i ( t ) \begin{align*} &v_i^{(t+1)}=\omega(t)v_i^{(t)}+c_1r_1(P_i^{(t)}-x_i^{(t)})+c_2r_2(S_i^{(t)}-x_i^{(t)})\\ &x_i^{(t+1)}=x_i^{(t)}+v_i^{(t)} \end{align*} vi(t+1)=ω(t)vi(t)+c1r1(Pi(t)−xi(t))+c2r2(Si(t)−xi(t))xi(t+1)=xi(t)+vi(t)
差分算法类似于遗传算法,具有选择、交叉、变异三个流程。
标准的差分算法可以表述为:
1 初始化
随机初始化一个由 N N N个 d d d维行向量组成的集合: X ( 0 ) = { x i ( 0 ) ∈ R d ∣ i = 1 , 2 , … , N } X^{(0)}=\{x_i^{(0)}\in\R^d|i=1,2,\dots,N\} X(0)={ xi(0)∈Rd∣i=1,2,…,N},称为种群
初始化变异矩阵 H ( 0 ) = 0 ∈ R N × d H^{(0)}=\bm 0\in \R^{N\times d} H(0)=0∈RN×d
初始化子代矩阵 V ( 0 ) = 0 ∈ R N × d V^{(0)}=\bm 0\in \R^{N\times d} V(0)=0∈RN×d
初始化进化代数 t = 0 t=0 t=0
2 差分变异
对于每一个个体 x i ( t ) x_{i}^{(t)} xi(t),随机选择三个各不相同的个体 x p 1 ( t ) , x p 2 ( t ) , x p 3 ( t ) , ( i ≠ p 1 ≠ p 2 ≠ p 3 ) x_{p_1}^{(t)},x_{p_2}^{(t)},x_{p_3}^{(t)},\ (i\neq p_1\neq p_2\neq p_3) xp1(t),xp2(t),xp3(t), (i=p1=p2=p3). 计算出变异向量:
h i ( t + 1 ) = x p 1 ( t ) + F ( x p 2 ( t ) − x p 3 ( t ) ) h_i^{(t+1)}=x_{p_1}^{(t)}+F(x_{p_2}^{(t)}-x_{p_3}^{(t)}) hi(t+1)=xp1(t)+F(xp2(t)−xp3(t))
如果不存在局部最优问题,也就是环境当中的局部最优适应度等价于整体最优适应度,那么可以把 x p 1 ( t ) x_{p_1}^{(t)} xp1(t)替换为当前种群中适应度最高的个体 x b t x_b^{t} xbt,即
h i ( t + 1 ) = x b ( t ) + F ( x p 2 ( t ) − x p 3 ( t ) ) h_i^{(t+1)}=x_{b}^{(t)}+F(x_{p_2}^{(t)}-x_{p_3}^{(t)}) hi(t+1)=xb(t)+F(xp2(t)−xp3(t))
这样会使得收敛更快
3 交叉操作
对于每一个个体 i , j i,j i,j,随机采样一个 λ ∈ ( 0 , 1 ) \lambda\in(0,1) λ∈(0,1),计算子代单点位值:
v i j ( t + 1 ) = { h i j ( t + 1 ) , λ < c x i j ( t ) , λ ≥ c v_{ij}^{(t+1)}=\begin{cases} h_{ij}^{(t+1)},&\lambda<c\\ x_{ij}{(t)},&\lambda\geq c \end{cases} vij(t+1)={ hij(t+1),xij(t),λ<cλ≥c
4 选择操作
对于每一个个体编号 i i i,判断子代向量 v i ( t + 1 ) v_i^{(t+1)} vi(t+1)和父代 x i ( t ) x_i^{(t)} xi(t)的适应度,保留适应度更大的一个:
x i ( t + 1 ) = { x i ( t ) , f ( x i ( t ) ) ≥ f ( v i ( t + 1 ) ) v i ( t + 1 ) , f ( x i ( t ) ) < f ( v i ( t + 1 ) ) x_i^{(t+1)}=\begin{cases} x_i^{(t)},&f(x_i^{(t)})\geq f(v_i^{(t+1)})\\ v_i^{(t+1)},&f(x_i^{(t)})<f(v_i^{(t+1)}) \end{cases} xi(t+1)={ xi(t),vi(t+1),f(xi(t))≥f(vi(t+1))f(xi(t))<f(vi(t+1))
Rosenbrock函数的定义式为:
{ f ( x 1 , x 2 ) = 100 ( x 1 2 − x 2 2 ) + ( 1 − x 1 ) 2 − 2.048 ≤ x i ≤ 2.048 , i = 1 , 2 \begin{cases} f(x_1,x_2)=100(x_1^2-x_2^2)+(1-x_1)^2\\ -2.048\leq x_i\leq 2.048,&i=1,2 \end{cases} { f(x1,x2)=100(x12−x22)+(1−x1)2−2.048≤xi≤2.048,i=1,2
我们想要求解如下最优化问题
max x 1 , x 2 f ( x 1 , x 2 ) s.t. − 2.048 ≤ x i ≤ 2.048 , i = 1 , 2 \begin{align*} \mathop\text{max}\limits_{x_1,x_2}\ &f(x_1,x_2)\\ \text{s.t.}\ &-2.048\leq x_i\leq 2.048,&i=1,2 \end{align*} x1,x2max s.t. f(x1,x2)−2.048≤xi≤2.048,i=1,2
Rosenbrock最优化的可行解空间是 [ − 2.048 , 2.048 ] 2 ⊂ R 2 [-2.048,2.048]^2\sub\R^2 [−2.048,2.048]2⊂R2,我们可以把这个空间离散化,在 x 1 , x 2 x_1,x_2 x1,x2轴上,分别以长度 0.004 0.004 0.004为单位,划分成离散的区间片段。以区间 [ − 2.048 , − 2.044 ) [-2.048,-2.044) [−2.048,−2.044)编号为 0 0 0,沿着实数轴的正向递增地编号为 0 , 1 , … , 1023 0,1,\dots,1023 0,1,…,1023.
在两个轴上分别取一个区间,编号组合为 ( i , j ) (i,j) (i,j),它们的交叉部分就是一个可行解空间上的矩形区域。把 i , j i,j i,j都转换成10位的二进制串,再首位拼接为20位的二进制串 x x x,就把这个二进制串视为区间 [ 0.004 i − 2.048 , 0.004 i − 2.044 ] × [ 0.004 j − 2.048 , 0.004 j − 2.044 ] [0.004i-2.048,0.004i-2.044]\times[0.004j-2.048,0.004j-2.044] [0.004i−2.048,0.004i−2.044]×[0.004j−2.048,0.004j−2.044]内任何一个点的编码,而反过来,用点 ( 0.004 i − 2.048 , 0.004 j − 2.048 ) (0.004i-2.048,0.004j-2.048) (0.004i−2.048,0.004j−2.048)作为 x x x的解码值。
把编码和解码的流程写作算子 encode ( ⋅ ) \text{encode}(\cdot) encode(⋅), decode ( ⋅ ) \text{decode}(\cdot) decode(⋅)
参数:
流程:
E c = Select ( E ) E_c=\text{Select}(E) Ec=Select(E)是这样计算的:
E m = Crossover ( E c ) E_m=\text{Crossover}(E_c) Em=Crossover(Ec)的计算规则是这样的:
E = Mutation ( E m ) E=\text{Mutation}(E_m) E=Mutation(Em)是这样计算的:
参数:
算法流程:
参数:
流程:
h i = x r 1 + F ( x r 2 − x r 3 ) h_i=x_{r_1}+F(x_{r_2}-x_{r_3}) hi=xr1+F(xr2−xr3)
v i j = { h i j , λ < c x i j , λ ≥ c v_{ij}=\begin{cases} h_{ij},&\lambda<c\\ x_{ij},&\lambda\geq c \end{cases} vij={ hij,xij,λ<cλ≥c
x i = { x i , f ( x i ) ≥ f ( v i ) v i , f ( x i ) < f ( v i ) x_i=\begin{cases} x_i,&f(x_i)\geq f(v_i)\\ v_i,&f(x_i)<f(v_i) \end{cases} xi={ xi,vi,f(xi)≥f(vi)f(xi)<f(vi)
文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib
文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang
文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些
文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器
文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距
文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器
文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn
文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios
文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql
文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...
文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120
文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数