TensorFlow Tensor 学习笔记

本篇博客主要介绍官方文档中给出的 第一个例子,这是一个经典神经网络与量子电路结合的案例。

基本效果

我们要实现的效果如下:

这个图貌似有错,根据配套的代码可知,下面的旋转门应该为两组 $R_z, R_y, R_x$,并参数应当是从 1 到 3,而不是2。

这是一个单量子比特电路,前面三个以 $\beta_i$ 为参数的旋转门会给该量子比特增加一定的噪声,而后面三个量子比特的参数 $\theta_i$ 为神经网络的输出值,其目的在于抵消噪声。而我们希望实现的效果是,当我们输入的指令 Command 为“0”时,对该量子比特在 $Z$ 基下测量能得到平均值 $-1$,而如果 Command 为“1”时,测量的平均值为 $+1$。最后我们通过计算期望的输出与实际的输出的均方误差作为损失函数,对神经网络的参数进行训练,得到参数。

这个例子几乎没有什么难度,也没有什么实际的价值,但是可以帮助我们理解 TensorFlow Quantum 和 Cirq 的基本知识。

建立模型

定义量子电路的第一步就是定义参数,这些参数是在量子电路中使用的,这里使用 sympy 来实现,它是一套在 python 中进行符号化计算的 SDK,我们可以使用它来定义符号,当然这并不是必须的:

control_params = sympy.symbols("theta_1 theta_2 theta_3")

接下来就是定义量子电路了,

qubit = cirq.GridQubit(0, 0)

# 这里注意在特定量子比特上执行特定量子门的两种写法是等价的:
# cirq.rz(control_params[0]).on(qubit)

# cirq.ry(control_params[1])(qubit)

circuit = cirq.Circuit(
    
    cirq.rz(control_params[0])(qubit),

    cirq.ry(control_params[1])(qubit),
    cirq.rx(control_params[2])(qubit)

)

其中定义了三个量子门,分别使用了前面定义的三个参数。这三个参数实际上是被经典神经网络所控制的。因此现在我们来定义视神经网络:

controller = tf.keras.Sequential([
    tf.keras.layers.Dense(10, activation='elu'),
    tf.keras.layers.Dense(3)
])

这里也很简单,可以看出一共有两层,Dense 表示全链接层,第一个参数表示节点个数。但这里需要重点介绍一下 Controller,它是由 Keras 中的 Sequential 类实例化产生的,是 Layers 的堆叠,表示的是一个神经网络的模型,继承自 Train.Model

接下来我们需要将这个 Sequential 控制器连接到 量子电路:

circuits_input = tf.keras.Input(

    shape=(),

    dtype=tf.string,

    name='circuits_input'

)
commands_input = tf.keras.Input(

    shape=(1,),

    dtype=tf.float32,

    name='commands_input'

)
dense_2 = controller(commands_input)

上面定义了两种输入,分别是电路的输入和指令输入,这里的电路输入指的是,前面三个以 $\beta$ 为参数的三个量子电路。

这里的 tf.keras.Input() 是一个函数,而不是一个类型,其返回值是一个 Keras 张量,对于 circuits_input 来说其 shapenone 代表其大小未知,并且其数据类型为字符串(看起来很怪,应该是为了使用 sympy 的值才这么设定的)。而 commands_input 代表的是指令很容易理解。

这里要特别注意 controller(commands_input),这是魔法方法,controller 是我们前面定义的 Sequential 的实例化对象,因而我们将其作为函数调用时,就会调用 Sequential 中的 __call__() 函数,而在 Tensorflow 中,__call__()方法又调用了类中的 call() 方法,因而我们可以查询一下 tf.keras.Sequential.call 方法的帮助文档,其中给出了解释:

Calls the model on new inputs.
In this case `call` just reapplies all ops in the graph to the new inputs (e.g. build a new computational graph from the provided inputs). 

Arguments:
    inputs: A tensor or list of tensors.
    training: Boolean or boolean scalar tensor, indicating whether to run the `Network` in training mode or inference mode.
    mask: A mask or list of masks. A mask can be either a tensor or None (no mask). 

Returns: 
    A tensor if there is a single output, or a list of tensors if there are more than one outputs.

换句话说,就是 dense_2 = controller(commands_input) 相当于对这个神经网络的控制器进行了输入,然后得到了一个新的张量 dense_2,这个张量=神经网络的结构+输入张量的结构。

这里还需要解释的就是 Keras 张量,我们把刚才的 commands_input 打印出来看看是这样的:

Tensor("commands_input:0", shape=(None, 1), dtype=float32)

此时,我们所定义的张量,都只是一些数据结构,并没有具体的值,具体的值是在后面指定的。我们继续来看,现在做的事情是定义计算期望的层:

expectation_layer = tfq.layers.ControlledPQC(

    circuit,

    operators=cirq.Z(qubit)

)
expectation = expectation_layer([circuits_input, dense_2])

注意,这里的 circuit 指的是那三个受神经网络控制的量子旋转门,而后面的operators 指定了测量操作的基。这里需要注意的是 ControlledPQC,这是一个受控量子电路层(controlled quantum circuit layer),在上面的这个例子中,“受控”体现在神经网络对量子门的参数 $\theta_1, \theta_2, \theta_3$ 的控制。

模型的结构

这个模型本身是比较简单的,但是由于对 TensorFlow 的架构还不是很熟悉,因此感觉会比较复杂,特别是各种张量之间的嵌套结构非常复杂,因此我们可以使用 pydot 将整个结构绘制成图像,可起来是这样的:

commands_input 是神经网络输入的指令,即0或1,是一个标量。sequential 是神经网络的整体结构,输入为 commands_input,输出为ControlledPQC 中三个参数 $\theta_1, \theta_2, \theta_3$。circuits_input 为添加噪声的量子电路,其中的三个参数 $\beta_1, \beta_2, \beta_3$ 为随机生成的(此时还没有生成),但是其类型为字符串,是从 cirq.circuit 转换过来的,代表的是量子电路的结构,在模型运行时,就会变成具体的数值。最后上面的噪声电路和神经网络的输出值,会传入 controlled_pqc,这是一个受控量子电路,输出值会存在一个名为 expectation 的张量中。

再次强调,在模型建立的过程中,上面定义的张量在训练之前,都只是一个结构,而没有具体的数据。

训练模型

这一步就很简单了,因为之前我们已经将所有的模型搭建出来了。在训练之前,我们需要把数据初始化的工作做了:

commands = np.array([[0], [1]], dtype=np.float32)
expected_outputs = np.array([[1], [-1]], dtype=np.float32)

这里指定了输入的指令和期望的输出的对应关系。接下来就是定义产生噪声的量子电路,可以使用随机数产生噪声,指的注意的是 tfq.convert_to_tensor,由于噪声已经完全定义好了,因此我们可以使用上述函数将其转换成张量:

random_rotation = np.random.uniform(0, 2*np.pi, 3)

noisy_preparation = cirq.Circuit(

    cirq.rz(random_rotation[0]).on(qubit),

    cirq.ry(random_rotation[1]).on(qubit),
    cirq.rx(random_rotation[2]).on(qubit)

)

datapoint_circuits = tfq.convert_to_tensor([noisy_preparation] * 2)

我们可以看出此时打印张量 datapoint_circuits 和之前打印的张量的形式不一样:

tf.Tensor(
[b'\n\x0e\n\x0ctfq_gate_set\x12\x93\x02\x08\x01\x12Y\nW\n\x04\n\x02ZP\x12\x1a\n\x0fexponent_scalar\x12\x07\n\x05\r\x00\x00\x80?\x12\x13\n\x08exponent\x12\x07\n\x05\r\xe8\\\x7f?\x12\x17\n\x0cglobal_shift\x12\x07\n\x05\r\x00\x00\x00\xbf\x1a\x05\x12\x030_0\x12Y\nW\n\x04\n\x02YP\x12\x1a\n\x0fexponent_scalar\x12\x07\n\x05\r\x00\x00\x80?\x12\x13\n\x08exponent\x12\x07\n\x05\rP\xba\xff?\x12\x17\n\x0cglobal_shift\x12\x07\n\x05\r\x00\x00\x00\xbf\x1a\x05\x12\x030_0\x12Y\nW\n\x04\n\x02XP\x12\x1a\n\x0fexponent_scalar\x12\x07\n\x05\r\x00\x00\x80?\x12\x13\n\x08exponent\x12\x07\n\x05\r\xcd)j?\x12\x17\n\x0cglobal_shift\x12\x07\n\x05\r\x00\x00\x00\xbf\x1a\x05\x12\x030_0'
 b'\n\x0e\n\x0ctfq_gate_set\x12\x93\x02\x08\x01\x12Y\nW\n\x04\n\x02ZP\x12\x1a\n\x0fexponent_scalar\x12\x07\n\x05\r\x00\x00\x80?\x12\x13\n\x08exponent\x12\x07\n\x05\r\xe8\\\x7f?\x12\x17\n\x0cglobal_shift\x12\x07\n\x05\r\x00\x00\x00\xbf\x1a\x05\x12\x030_0\x12Y\nW\n\x04\n\x02YP\x12\x1a\n\x0fexponent_scalar\x12\x07\n\x05\r\x00\x00\x80?\x12\x13\n\x08exponent\x12\x07\n\x05\rP\xba\xff?\x12\x17\n\x0cglobal_shift\x12\x07\n\x05\r\x00\x00\x00\xbf\x1a\x05\x12\x030_0\x12Y\nW\n\x04\n\x02XP\x12\x1a\n\x0fexponent_scalar\x12\x07\n\x05\r\x00\x00\x80?\x12\x13\n\x08exponent\x12\x07\n\x05\r\xcd)j?\x12\x17\n\x0cglobal_shift\x12\x07\n\x05\r\x00\x00\x00\xbf\x1a\x05\x12\x030_0'], shape=(2,), dtype=string)

上述张量使用字符串来表示具体的量子电路,虽然其存储的格式还不是非常清楚,但可以看出这是具体的张量,而不是表示的数据结构。然后开始正式调用训练函数:

optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)

loss = tf.keras.losses.MeanSquaredError()
model.compile(optimizer=optimizer, loss=loss)

history = model.fit(

    x=[datapoint_circuits, commands],

    y=expected_outputs,

    epochs=30,

    verbose=0

)

看看训练的效果:

在使用提督下降算法迭代6次之后,误差基本趋近于稳定。并且在使用该神经网络训练后,得到的输出:

For a desired output (expectation) of [1.] with noisy preparation, the controller
network found the following values for theta: [-1.3984149  2.0924456  1.2654604]
Which gives an actual expectation of: 0.9771392345428467

For a desired output (expectation) of [-1.] with noisy preparation, the controller
network found the following values for theta: [-0.09738648 -1.6105205  -0.3264356 ]
Which gives an actual expectation of: -0.9835687875747681

可以看出效果还是很好的。

Leave a Reply

Your email address will not be published. Required fields are marked *