第三章的终极目标是量子对抗生成网络. 但在此之前, 我们不妨先从最简单的量子强化学习学起. 从这里开始, 我假设大家已经足够熟悉 Julia 语法并附上我最后的波纹 Julia 官方文档传送门 https://docs.julialang.org/en/stable/ .
理论基础
定义问题
令一个量子线路为策略生成器, 目标是使得一个由经典程序构成的环境对该线路生成的策略给出最优评价.
解决方案
解决这个问题的关键在于, 构造一个可调参数, 可微分的量子线路(类比神经网络). 可微分线路在 Quantum Circuit Learning http://arxiv.org/abs/1803.00745 这篇文章中有详细的介绍. 同时, 我在文后附录里面放了更加细致的推导.
为了演示, 文中的训练仅涉及简单的梯度下降, 有很多优秀的训练方案可以替代这种简单的策略, 能获得更加好的效果. 包含这些方法的 Julia 包有
https://github.com/JuliaNLSolvers/Optim.jl 包含 BFGS,CGD 等优化算法.
https://github.com/denizyuret/Knet.jl 神经网络库, 包含了 Adam,RMSProp 等一阶自适应梯度方法.
另一方面, 尽管你可以零散的找到一些优化算法, 但 Julia 仍然缺少一个全面的, 专门的梯度优化包.
代码实现
1) 定义游戏
首先, 定义强化学习的环境, 也就是游戏的奖励机制. 在这里是一个简单的二分类器: 如果输出构型对应横条或者竖条的图片, 那么获得奖励, 否则, 不给于奖励. 这里图片尺寸为 2 x 2, 可以像下面这样定义 score 函数来评价输出概率的好坏.
- using Compat
- using Compat.Test
- using Yao
- using Yao.Zoo
- using Yao.Blocks
- witness_vec = zeros(1<<4)
- witness_vec[[0, 3, 5, 10, 12, 15].+1] = 1
- score_func(out_probs::Vector) = witness_vec'*out_probs
这里直接拿输出的概率来评估, 反应了 score 的期望值. 上面的 0,3,5,10,12,15 这些数字分别对应于如下 6 个横条或竖条图
2 x 2 方格子上的所有横条和竖条图形
这个 "生成横条和竖条才能赢的游戏" 的游戏, 可能是量子神经网络平台上的 2018 年度最佳单机游戏. 它的联机版本 "对抗生成横条和竖条" 也将很快发布, 尽请期待.
2)量子线路玩家
开箱评测, 所以接下来定义微分线路, 试玩这个游戏吧!
generator 任务是把全为 0 的直积态演化为某个概率分布, 并根据这个概率分布给出一张 2 x 2 的图片 (其实是图片的分布) 交给评估函数.
- n = 4
- depth = 4
- pairs = [1=>2, 3=>4, 2=>3, 4=>1]
- generator = diff_circuit(n, depth, pairs)
微分线路示意图, 由旋转模块和纠缠模块构成纠缠层的结构, 示意图和实际结构如下
微分线路示意图, 由旋转模块和纠缠模块构成
- julia> generator
- Total: 4, DataType: Complex{Float64}
- chain
- roller
- chain
- Rot X gate: 5.1896083629882686
- Rot Z gate: 4.600318172079319
- chain
- Rot X gate: 4.304949691236676
- Rot Z gate: 0.7567163433443906
- chain
- Rot X gate: 1.4849756696985823
- Rot Z gate: 5.790912213465895
- chain
- Rot X gate: 0.6796959335380712
- Rot Z gate: 0.6043810268678503
- chain (Cached)
- control(1)
- (2,)=>X gate
- control(3)
- (4,)=>X gate
- control(2)
- (3,)=>X gate
- control(4)
- (1,)=>X gate
- roller
- chain
- Rot Z gate: 6.256891543416191
- Rot Z gate: 1.0032182696272236
- Rot Z gate: 5.837598218699375
- chain
- Rot Z gate: 3.7876170863960477
- Rot Z gate: 2.0727622836375756
- Rot Z gate: 4.204092777872863
- chain
- Rot Z gate: 6.069833070053499
- Rot Z gate: 1.7306442447855888
- Rot Z gate: 5.404604635310123
- chain
- Rot Z gate: 6.014956323778836
- Rot Z gate: 2.3266747140980564
- Rot Z gate: 2.7732010911358422
- chain (Cached)
- control(1)
- (2,)=>X gate
- control(3)
- (4,)=>X gate
- control(2)
- (3,)=>X gate
- control(4)
- (1,)=>X gate
- roller
- chain
- Rot Z gate: 6.233836041708481
- Rot Z gate: 0.19439163466296905
- Rot Z gate: 0.028048349035550518
- chain
- Rot Z gate: 5.310627045308177
- Rot Z gate: 2.842446454780701
- Rot Z gate: 4.239742748409749
- chain
- Rot Z gate: 1.1661260049884359
- Rot Z gate: 1.1808390479397712
- Rot Z gate: 2.949381817697446
- chain
- Rot Z gate: 5.009762986166179
- Rot Z gate: 1.3762110915124863
- Rot Z gate: 4.103192971694813
- chain (Cached)
- control(1)
- (2,)=>X gate
- control(3)
- (4,)=>X gate
- control(2)
- (3,)=>X gate
- control(4)
- (1,)=>X gate
- roller
- chain
- Rot Z gate: 5.19909864757747
- Rot Z gate: 1.0689196334722773
- Rot Z gate: 0.5314655100827278
- chain
- Rot Z gate: 4.4961899710503035
- Rot Z gate: 4.209504183771021
- Rot Z gate: 3.6539845729141964
- chain
- Rot Z gate: 0.414797368005785
- Rot Z gate: 5.071715201020041
- Rot Z gate: 0.22243140366602923
- chain
- Rot Z gate: 2.334284985677479
- Rot Z gate: 4.721327828009076
- Rot Z gate: 1.2574404090921152
- chain (Cached)
- control(1)
- (2,)=>X gate
- control(3)
- (4,)=>X gate
- control(2)
- (3,)=>X gate
- control(4)
- (1,)=>X gate
- roller
- chain
- Rot Z gate: 5.55723015842331
- Rot X gate: 1.683218891332434
- chain
- Rot Z gate: -0.14134855573397337
- Rot X gate: 4.805974583093846
- chain
- Rot Z gate: 4.058779704551807
- Rot X gate: 1.607726680323197
- chain
- Rot Z gate: 1.7107812582002229
- Rot X gate: 0.10514674202517953
观察这个微分线路, 会发现它和普通量子线路并无区别, 之所以称之为微分线路, 是因为里面的每个可调参数都被放在了旋转门上, 而这些旋转门的参数是可微的. 同时这个微分线路中的任意单比特旋转单元 , 最开头和结尾的 都被省略了, 因为它们不影响概率分布.
这里有个小细节要注意下, Entangler Block 中的 Cached 标志标注了该模块的矩阵值被缓存, 它也是 Tag 系统的一员
CachedBlock <: TagBlock
, 这类似于之前遇到过
Daggered <: TagBlock
(见 Yao.jl 的 Tag 系统). 把矩阵存储是为了加速随后的运算, 这种重复计算的多个 CNOT 门的结构就非常适合用 Cache 来加速.
3)Loss 与梯度
Loss 可以定义为评估函数的负数, 这里我们把 generator 生成的波函数用 probs 函数转换为对应的经典图片概率分布 作为评估函数的输入. gradient 函数则需要借用到 Yao.Zoo 里专门为微分线路设计的 perturb 函数来构造, 输入 rots 是 RotationGate 的矢量, 而这里用到了对算符的微分可以等价为对每个门进行 的扰动后期望值的相减
,perturb 的输出为输入的第一个函数参数的结果的集合, 是个 M x 2 的矢量, 其中第一列和第二列分别对应 . 对于可观测量的梯度推导, 见文后附录. 那么为何量子系统中会需要这种求梯度的方式呢?
对比差分方案, 它对抽样噪声 (sampling error) 更加稳定, 而且不存在系统误差.
如果是经典模拟, 直接用类似的 BP 方案也能成功, 但是这种方案无法适用于量子模拟.
这种求导方案有若干个局限
RotationGate 只能允许对 reflexive, hermitian(见 Yao.jl 的 Operator Traits https://quantumbfs.github.io/Yao.jl/latest/dev/extending-blocks/#Adding-Operator-Traits-to-Your-Blocks-1 , 包括 X, Y, Z, H, CNOT, ...)的门构造它的的 U(1)旋转, 只有这样的门才可导.
这种求导规则的求导对象必须是客观测量, 恰好适用于此处的 loss.V-statistic 类型的 Loss 有和可观测量不一样的求导规则, 而像 KL-Divergence 这种 Loss 则没有已知的方案求导.
- loss_func = () -> -score_func(apply!(zero_state(4), generator) |> probs)
- import Base: gradient
- function gradient(rots::Vector{<:RotationGate})
- ptb = perturb(loss_func, rots, π/2)
- (ptb[:,1] - ptb[:,2])/2
- end
4)训练
训练过程用到的是简单的梯度下降, 只要熟悉参数分发函数 dispatch! 即可, 相应的收集函数为 parameters. collect_rotblocks 也是 Yao.Zoo 里面的针对微分线路设计的一个函数, 它把一个线路中所有可微分的旋转门整理到 Vector 中 (这里是引用, 不是拷贝) 并返回, 这样方便了很多针对参数的操作.
- function train(generator::AbstractBlock, g_learning_rate::Real, niter::Int)
- rots = collect_rotblocks(generator)
- for i in 1:niter
- ggrad = gradient(rots)
- dispatch!(-, generator, ggrad.*g_learning_rate)
- if i%5==1 println("Step $i, loss = $(loss_func())") end
- end
- end
- train(generator, 0.1, 100)
你会看到输出如下
- Step 1, loss = -0.6189302122066311
- Step 6, loss = -0.6983584546939159
- Step 11, loss = -0.7395667309391476
- Step 16, loss = -0.7657103904645828
- Step 21, loss = -0.786364793265757
- Step 26, loss = -0.8048834179532757
- Step 31, loss = -0.8223622992762194
- Step 36, loss = -0.839209996951542
- Step 41, loss = -0.8556845591416706
- Step 46, loss = -0.8720096400485509
- Step 51, loss = -0.8883121889703617
- Step 56, loss = -0.9045259297119229
- Step 61, loss = -0.9203497512794501
- Step 66, loss = -0.9352975452825096
- Step 71, loss = -0.9488294829891736
- Step 76, loss = -0.9605123626513382
- Step 81, loss = -0.9701354847241247
- Step 86, loss = -0.977733957012821
- Step 91, loss = -0.9835290426171263
- Step 96, loss = -0.9878350142653813
5)结果分析
用 PyPlot, 我们可以画出概率密度如下
- using PyPlot
- pl = apply!(zero_state(n), generator)|>probs
- bar(0:1<<n-1, pl)
显然, 这些高概率构型 0,3,5,10,12,15 对应于让玩家得分的横条和竖条. 看来量子线路还是很机智的嘛 ~
那同样机智的你有没有学会这一章的内容呢? 欢迎评论区提问~
附录
微分线路的求导
来源: http://www.tuicool.com/articles/go/Rbuiua7