3月9日谷歌强势开源了Tensorflow的量子计算版 TFQ(Tensorflow Quantum)@GitHub,
笔者第一时间取官方MNIST范例做了尝鲜(+个人KM笔记),但因项目在身,等一个月后又浏览了一份二次方程回归训练的范例、才有了这份略微迟到的体验分享(包含对官方范例的2处纠错)。
本文并不涉及“如何基于任务类型设计量子门模型”的量子回路原理,仅作TFQ总工作流的简介。
概述&准备
- 先说结论,Tensorflow Quantum版目前尚不能达成比传统框架/模型更高的准确率——事实上其
acc
反而可能低很多,而是强在其“斩破时空”级别的计算速度——谷歌去年10月已用53个qubit(量子位)、验证仅用200秒就能完成百万核CPU超级计算机需用10,000年完成的计算任务(=量子制霸or
量子优势)[1]
注:验证基于[薛定谔-费曼算法(SFA)](https://arxiv.org/abs/1807.10749),不过也受到IBM质疑“传统超算只需_2.5天_”
- 谷歌已改变之前选用D-Wave时的退火方式(Annealing),而采用与IBM一样的量子门方式,因此上述的成绩已更具通用性。
- 相关的云计算业务可能会成为新增长点——谷歌预计明年就能进入NISQ时代
(提供10-100位的中规模含噪音量子计算设备,噪音=退相干等造成的容错问题)
,相关产业链的业者可从熟悉其开发流程起步。 - (准备)量子计算背景知识自检
- (准备)深度学习背景知识自检
- 从MNIST数据集(一个最简单的手写字图片集)加载、到tf.Keras模型的compile, fit, evaluate, predict流程:TFQ范例中将沿用该工作流,有一篇入门教程可供体验传统版的MNIST分类任务解决范例
MNIST手写字分类任务量子版(官方)
请先按官方文档安装好本地环境(pip方式,TF2.1
),对已有TF经验者来说简单无坑;
也可直接云端体验ipynb版。
MNIST分类任务尽管是TFQ官方的第二个范例,但适合直接用来入门,完整代码请自行拼合至本地运行,以下仅对量子版相关代码做择要说明。
0.加载依赖库 & 增添SVG保存
import tensorflow_quantum as tfq
import cirq
import sympy
from cirq.contrib.svg import SVGCircuit, circuit_to_svg
tfq
就是量子版深度学习的核心库了,但它所做的主要是对更为核心的专用于量子计算的cirq
库(API丰富易用)作深度学习相关的封装。sympy
则是解符号方程的常用库,使得能以符号方式命名变量,供cirq
使用。
cirq..SVGCircuit
用于将cirq
的量子回路可视化成SVG对象,本地调试建议多import一个circuit_to_svg
将SVG dump成文件查看(注:matplot需再添加第三方库才能支持SVG显示)
,相关自制代码:
def save_text(src: str, dest_path=None, file_ext="svg"):
if dest_path is None:
import uuid
dest_path = str(uuid.uuid4()) + ".{}".format(file_ext)
with open(dest_path, 'w', encoding='utf-8') as f:
f.write(src)
def dump_circuit(circuit: 'cirq.Circuit', dest_path=None):
save_text(circuit_to_svg(circuit), dest_path=dest_path)
1.将传统数据转为量子回路
50000行MNIST数据集的加载一如寻常,量子版范例出于目前的处理能力多做了4步简化:
- 仅筛选出‘3’和‘6’的手写数字图片,将十元分类任务简化成只需做二元分类
- 将图片进一步从28x28缩小到4x4,减少所需的量子比特数量
(毕竟53qubit已是领先工艺)
- 清理掉同时被标注为‘3’和‘6’的“冲突样本”(不过后续的二值化将会导入更多标签“冲突”)
- 二值化
(注:非常影响准确率的一个步骤,但限于量子计算的处理能力而做)
,大于0.5
的像素才视作白色
注意,这里可以添加一次冲突清理,你会发现,
4x4
的图片再经过二值化处理其实只剩下193个无冲突标注的有效样本用于训练…
本修改已PR给了官方:print("----- remove_contradicting again on x_train_bin -----") x_train_bin, y_train_nocon = remove_contradicting(x_train_bin, y_train_nocon) # ----- remove_contradicting again on x_train_bin ----- # Number of unique images: 193 # Number of 3s: 124 # Number of 6s: 113 # Number of contradictory images: 44 # # Initial number of examples: 11520 # Remaining non-contradictory examples: 3731
最关键的步骤,将经过清理、简化的传统数据转化为量子回路:
def convert_to_circuit(image):
"""Encode truncated classical image into quantum datapoint."""
values = np.ndarray.flatten(image)
qubits = cirq.GridQubit.rect(4, 4)
circuit = cirq.Circuit()
for i, value in enumerate(values):
if value:
circuit.append(cirq.X(qubits[i]))
return circuit
x_train_circ = [convert_to_circuit(x) for x in x_train_bin]
x_test_circ = [convert_to_circuit(x) for x in x_test_bin]
每个cirq.GridQubit.rect()
=>文档
会声明一组与单张图片尺寸相符的网格用量子比特,相当于一个2D的tf.Tensor(shape=(h,w))
。
逐一扫描传统的像素数据,当为白色,就对相应坐标位置的量子比特配置一个X门量子操作——其作用是将初始值为|0>
基态的量子比特激活到|1>
激活态,并将这些操作依次添加到量子回路circuit
中——这种cirq.Circuit
对象=>文档
本质上就是一个管理着多组对特定位置qubit操作的数组,也是cirq
的核心类。
对x_train_bin
中的每张图片都进行转化后,即可获得量子回路版的数据集x_train_circ
。
扩展阅读#1:Cirq API文档,系统理解
cirq
包中的量子回路概念。
本地运行的话,可用之前自制的dump_circuit()
观察第一张图片样本的量子回路版本,可见数据量子回路的(2,2)
与(3,1)
坐标位上已被安置了激活用的X门 (注:(2,1)上的像素因小于0.5已被二值化去除)
dump_circuit(x_train_circ[0])
记得,cirq
域的量子回路还需被转入tfq
域、供tf.keras
使用:
x_train_tfcirc = tfq.convert_to_tensor(x_train_circ)
x_test_tfcirc = tfq.convert_to_tensor(x_test_circ)
2.构建模型的量子回路
分类任务的QNN(量子神经网络)应如何设计需扩展阅读相关论文 Farhi et al.,概要地说,需用到2个量子位门(对每个像素XX和ZZ),且都带一个readout比特把结果提供给外部。
- 范例中声明了一个能创建满足该需求的工具类:
class CircuitLayerBuilder():
def __init__(self, data_qubits, readout):
self.data_qubits = data_qubits
self.readout = readout
def add_layer(self, circuit, gate, prefix):
for i, qubit in enumerate(self.data_qubits):
symbol = sympy.Symbol(prefix + '-' + str(i))
circuit.append(gate(qubit, self.readout)**symbol)
这一Builder,引用/依赖于一组数据量子比特、以及一个readout量子比特,提供一个add_layer
方法用于遍历每个数据量子比特中、给它配置一个gate
所指定的门操作(命名为prefix
加自增序号),并将所有操作作为添加到指定的量子回路circuit
中——相当于添加了一个层(借用ML概念,而非cirq
概念)。
- 简单测试理解该Builder类的作用:
demo_builder = CircuitLayerBuilder(data_qubits = cirq.GridQubit.rect(4,1),
readout=cirq.GridQubit(-1,-1))
circuit = cirq.Circuit()
demo_builder.add_layer(circuit, gate = cirq.XX, prefix='xx')
dump_circuit(circuit)
这里的XX门,是两个X门的张量乘积=>cirq文档,
=>张量乘积解释
。4个量子比特中的每个、都被同readout比特(位于(-1,-1))
、关联配置了XX门操作。
这一cirq.Circuit
对象的内部结构(深入调查请系统参考Cirq文档),注意其中的exponent={Symbol}xx-0
,门操作GateOperation
的exponent
参数意味着旋转的角度、值为1.
时相当于180°
,TFQ会收集这些Symbol
作为模型训练的参数(即PQC
中的P
:Parameterized
)作运行时调整。
- MNIST任务中对Builder类的实际使用:
def create_quantum_model():
data_qubits = cirq.GridQubit.rect(4, 4)
......
builder = CircuitLayerBuilder(
data_qubits = data_qubits,
readout=readout)
# Then add layers (experiment by adding more).
builder.add_layer(circuit, cirq.XX, "xx1")
builder.add_layer(circuit, cirq.ZZ, "zz1")
......
return circuit, cirq.Z(readout)
model_circuit, model_readout = create_quantum_model()
分类任务用QNN模型(量子回路)的一部分
create_quantum_model()
中省略的...
代码,是对readout的置位初始化以及H门操作(变为叠加态,如下图)。- 扩展阅读#2:IBM量子体验教程,动图可视化理解各量子门作用等。
3.封装量子模型(到tf.keras)
模型的输入将是"数据量子回路"(x_train_circ)
,适配为tf.string
类型,并需用tfq.layers.PQC
(Parametrized Quantum Circuit)来处理、训练模型。
readout比特的期望值将被传给PQC层,而因为该预测值将介于[-1,1]
,适合将标签值(y_train)
映射至该区间、并用hinge loss作损失函数;而如果将标签保持在[0,1]
区间(即做是否为'3'的二分判断),则需用tf.losses.BinaryCrossentropy
。范例采用前者。
# Build the Keras model.
model = tf.keras.Sequential([
# The input is the data-circuit, encoded as a tf.string
tf.keras.layers.Input(shape=(), dtype=tf.string),
# The PQC layer returns the expected value of the readout gate, range [-1,1].
tfq.layers.PQC(model_circuit, model_readout),
])
model.compile(
loss=tf.keras.losses.Hinge(),
optimizer=tf.keras.optimizers.Adam(),
metrics=[hinge_accuracy])
获得的模型有32个参数:
print(model.summary())
# Model: "sequential"
# _________________________________________________________________
# Layer (type) Output Shape Param #
# =================================================================
# pqc (PQC) (None, 1) 32
# =================================================================
# Total params: 32
# Trainable params: 32
# Non-trainable params: 0
# _________________________________________________________________
# None
注意,这里官方源码有一处错误,训练时用到的
y_train_hinge
竟然是从去冲突前的y_train
转换而来,这会造成输入集与标签集的索引不匹配,使得训练实际上是基于随机的匹配关系在进行…
本修改已PR给了官方:# y_train_hinge = 2.0*y_train-1.0 y_train_hinge = 2.0*y_train_nocon-1.0
4.训练模型(如tf.keras常规流程)
NUM_EXAMPLES = 500 # len(x_train_tfcirc)完整测试的话、需45分钟
x_train_tfcirc_sub = x_train_tfcirc[:NUM_EXAMPLES]
y_train_hinge_sub = y_train_hinge[:NUM_EXAMPLES]
qnn_history = model.fit(
x_train_tfcirc_sub, y_train_hinge_sub,
batch_size=32,
epochs=3,
verbose=1,
validation_data=(x_test_tfcirc, y_test_hinge))
qnn_results = model.evaluate(x_test_tfcirc, y_test)
完整训练能获得约59.2%
的准确率。尽管有图片样本仅4x4
且经二值化信息损耗等原因,但使用同样数据、并将模型简化到参数也是32个左右的情况下,传统CNN模型都能达到约91.97%
的准确率[2]。
- 由MNIST案例可见,目前量子计算深度学习还处于迎接NISQ(中规模含噪音量子计算)时代的热身阶段
(注:预计明年能上100位、且增强容错校验)
,使得广大ML研发者能熟悉起运用(谷歌家的)
量子计算能力所需的开发方式- 相关的云计算业务可能成为新的增长点
(注:官方预见的运用以科研为主,但包括了飞机/汽车电池的轻量化设计)
,推动产业良性升级
二次方程回归任务[3]
在浅尝过MNIST分类任务的基础上,再快速用一个角度相近的回归任务(regression)增加认识。
人类自身的学习/认知过程(Human Learning)其实不就包含着“拟合”、“梯度”、“权重(形成)”么 :)
假设读者已读过之前对MNIST范例的介绍,仅对核心部分作说明:
0.以随机数方式准备训练&测试数据
num_x_train = 200
x_train = x_min + (x_max - x_min) * np.random.rand(num_x_train)
y_train = [x**2-0.5 for x in x_train]
mag_noise = 0.025
y_train = y_train + mag_noise * np.random.randn(num_x_train)
plt.plot(x_train, y_train, "o"); plt.show()
以一元二次方程的自变量X作输入,因变量Y作标签,产生200个随机数据,其中50个留作测试集。
1.将传统数据转为量子回路
nqubit = 5
def convert_to_circuit(x):
y = np.arcsin(x)
z = np.arccos(x**2)
qubits = cirq.GridQubit.rect(nqubit, 1)
circuit = cirq.Circuit()
for i in range(nqubit):
circuit.append(cirq.ry(y).on(qubits[i]))
circuit.append(cirq.rz(z).on(qubits[i]))
return circuit
x_train_circ = [convert_to_circuit(x) for x in x_train]
x_test_circ = [convert_to_circuit(x) for x in x_test]
会为每个传统浮点数数据,配置一个5x1
的量子比特网格(注:5位取决于所希望的浮点精度)
,并对每个量子比特配置一个Ry门操作(绕Y轴旋转指定角度)
和Rz门操作(绕Z轴旋转指定角度)
。
参考量子回路理论[4]可知,将多量子位|00..0>
置位为x
需要(其中j为位数)
:
可再用自制的dump_circuit()观察第一个浮点数样本的量子回路版本,
dump_circuit(x_train_circ[0])
2.构建模型的量子回路
class CircuitLayerBuilder():
...... # 略,__init__, add_layer方法与MNIST范例相同
def add_layer_single(self,circuit,gate,prefix):
symbol = sympy.Symbol(prefix + '-' + str(0))
circuit.append(gate(symbol).on(self.readout))
def add_entangler(self,circuit,len_qubit):
circuit.append(cirq.CZ(self.readout,self.data_qubits[0]))
for i in range(len_qubit-1):
circuit.append(cirq.CZ(self.data_qubits[i],self.data_qubits[(i+1)%len_qubit]))
circuit.append(cirq.CZ(self.readout,self.data_qubits[-1]))
回归任务的Builder类增添了add_layer_single
(仅对readout位配置操作门)和add_entangler
(纠缠关联)两种方法。
def create_quantum_model(c_depth=3):
data_qubits = cirq.GridQubit.rect(nqubit,1)
......
builder = CircuitLayerBuilder(
data_qubits = data_qubits,
readout = readout
)
for i in range(c_depth):
builder.add_entangler(circuit,nqubit)
builder.add_layer(circuit, gate = cirq.XX, prefix='xx'+str(i))
builder.add_layer(circuit, gate = cirq.ZZ, prefix='zz'+str(i))
builder.add_layer(circuit, gate = cirq.XX, prefix='xx1'+str(i))
builder.add_layer_single(circuit, gate = cirq.rz, prefix='z1'+str(i))
builder.add_layer_single(circuit, gate = cirq.rx, prefix='x1'+str(i))
builder.add_layer_single(circuit, gate = cirq.rz, prefix='z2'+str(i))
return circuit, cirq.Z(readout)
model_circuit, model_readout = create_quantum_model()
回归任务用QNN模型(量子回路)的一部分
- 回归任务用的量子回路设计原理需参考原作者推荐文章(基于
qulacs
库)。
3.封装量子模型(到tf.keras)
使用tfq.layers.PQC
封装到tf.keras.Sequential
中,与MNIST范例一样;compile()
时所用的损失函数则需用MSE均方差。
model.compile(
loss=tf.keras.losses.mse,
optimizer=tf.keras.optimizers.Adam(),
metrics=['mae'])
获得的模型将有54个参数,比MNIST任务多出不少。
4.训练模型(如tf.keras常规流程)
fit
, evaluate
与常规流程,即MNIST范例无异,评估方差为0.0262
;回归任务还对测试集做了predict
、以比对观察模型的训练成果,
y_pred = model.predict(x_test_tfcirc)
plt.plot(x_test,y_pred,"o",label="pred")
plt.plot(x_test,y_test,"xr",label="test")
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.show()
红叉所示的预测值很贴近训练样本的规律,二次曲线回归任务的准确率确实比MNIST图片分类任务高出许多!
小结
Tensorflow Quantum版目前通过将Cirq量子计算库的结果封装后与传统tf.Keras衔接的方式作为其工作流,本文通过简化版MNIST分类任务与二次曲线回归任务、在代码级别体验了衔接方法(i.e.将cirq.Circuit
数据量子回路传递给经过tfq.layers.PQC
封装的QNN模型量子回路),扩展到Cirq文档及量子门文档,包括纠正了官方范例发布一个多月以来的2处小错误。
QNN(或者说QML(量子机器学习))
模型的量子回路设计(量子门排列)才是通向实用的重点,需针对不同深度学习任务参考相关文献。
就效果而言,二次曲线回归任务的训练成果还是相当不错的,更令人感兴趣的图片分类任务则主要因近期的量子位数量限制而表现欠佳、但已算法可行。明年可能如预期进入NISQ时代开放相关云计算服务,有志于相关产业链的业者可提前布局、从熟悉其开发流程起步。
网友评论