美文网首页量子计算机器学习和人工智能入门
【技术】谷歌Tensorflow量子版的分类与回归范例浅释

【技术】谷歌Tensorflow量子版的分类与回归范例浅释

作者: 天空之诚 | 来源:发表于2020-04-20 03:11 被阅读0次
    TFQ 技术栈

    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位的中规模含噪音量子计算设备,噪音=退相干等造成的容错问题),相关产业链的业者可从熟悉其开发流程起步。
    • (准备)量子计算背景知识自检
      • 量子比特远比传统比特高效的根本原因:量子叠加态,参考“开关组游戏”
      • 量子位的可视化表征(布洛赫球面)、量子位门(X门,Z门,CNOT等):参考相关关键字
    • (准备)深度学习背景知识自检
      • 从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就是量子版深度学习的核心库了,但它所做的主要是对更为核心的专用于量子计算的cirqAPI丰富易用)作深度学习相关的封装。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门操作。

    XX门

    这一cirq.Circuit对象的内部结构(深入调查请系统参考Cirq文档),注意其中的exponent={Symbol}xx-0,门操作GateOperationexponent参数意味着旋转的角度、值为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)其实不就包含着“拟合”、“梯度”、“权重(形成)”么 :)

    完整代码保存至本地,原文[3]来自日文Qitta网站。

    假设读者已读过之前对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时代开放相关云计算服务,有志于相关产业链的业者可提前布局、从熟悉其开发流程起步。


    1. 量子优越性的验证实验等,2019/10,谷歌官方日志

    2. 官方MNIST分类任务:量子模型与传统模型的成绩对比

    3. 二次方程回归任务范例(来自日文Qitta网站)

    4. 通过Ry门Rz门旋转操作对多量子位赋值的原理

    相关文章

      网友评论

        本文标题:【技术】谷歌Tensorflow量子版的分类与回归范例浅释

        本文链接:https://www.haomeiwen.com/subject/lecdvhtx.html