第三章的终极目标是量子对抗生成网络。但在此之前,我们不妨先从最简单的量子强化学习学起。从这里开始,我假设大家已经足够熟悉 Julia 语法并附上我最后的波纹 Julia官方文档传送门。
理论基础
定义问题
令一个量子线路为策略生成器,目标是使得一个由经典程序构成的环境对该线路生成的策略给出最优评价。
解决方案
解决这个问题的关键在于,构造一个可调参数,可微分的量子线路(类比神经网络)。 可微分线路在Quantum Circuit Learning 这篇文章中有详细的介绍。同时,我在文后附录里面放了更加细致的推导。
为了演示,文中的训练仅涉及简单的梯度下降,有很多优秀的训练方案可以替代这种简单的策略,能获得更加好的效果。包含这些方法的Julia包有
另一方面,尽管你可以零散的找到一些优化算法,但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个横条或竖条图
这个“生成横条和竖条才能赢的游戏”的游戏,可能是量子神经网络平台上的 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,包括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 对应于让玩家得分的横条和竖条。看来量子线路还是很机智的嘛 ~
那同样机智的你有没有学会这一章的内容呢?欢迎评论区提问~
附录
微分线路的求导
懒得搬运了,看我Blog吧。。。
来源:知乎 www.zhihu.com
作者:Leo
【知乎日报】千万用户的选择,做朋友圈里的新鲜事分享大牛。
点击下载