Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/zh-cn/quantum/tutorials/mnist.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

MNIST 分类

本教程会构建一个量子神经网络 (QNN) 来分类 MNIST 的简单版本,这与在 Farhi 等人的论文中使用的方式类似。我们会比较量子神经网络与经典神经网络解决一个经典数据问题的性能。

设置

!pip install tensorflow==2.7.0

安装 TensorFlow Quantum:

!pip install tensorflow-quantum==0.7.2
# Update package resources to account for version changes. import importlib, pkg_resources importlib.reload(pkg_resources)

现在,导入 TensorFlow 和模块依赖项:

import tensorflow as tf import tensorflow_quantum as tfq import cirq import sympy import numpy as np import seaborn as sns import collections # visualization tools %matplotlib inline import matplotlib.pyplot as plt from cirq.contrib.svg import SVGCircuit

1. 加载数据

在本教程中,根据 Farhi 等人的论文,您将构建一个二元分类器来区分 3 位数和 6 位数。本部分介绍了以下操作的数据处理:

  • 从 Keras 加载原始数据。

  • 筛选数据集中的 3 位数和 6 位数。

  • 缩小图像,使其适合量子计算机。

  • 移除所有矛盾样本。

  • 将二值图像转换为 Cirq 电路。

  • 将 Cirq 电路转换为 TensorFlow Quantum 电路。

1.1 加载原始数据

加载通过 Keras 分布的 MNIST 数据集。

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() # Rescale the images from [0,255] to the [0.0,1.0] range. x_train, x_test = x_train[..., np.newaxis]/255.0, x_test[..., np.newaxis]/255.0 print("Number of original training examples:", len(x_train)) print("Number of original test examples:", len(x_test))

筛选数据集,仅保留 3 位数和 6 位数,移除其他类。同时,将标签 y 转换为布尔值:3True,6 为 False

def filter_36(x, y): keep = (y == 3) | (y == 6) x, y = x[keep], y[keep] y = y == 3 return x,y
x_train, y_train = filter_36(x_train, y_train) x_test, y_test = filter_36(x_test, y_test) print("Number of filtered training examples:", len(x_train)) print("Number of filtered test examples:", len(x_test))

显示第一个样本:

print(y_train[0]) plt.imshow(x_train[0, :, :, 0]) plt.colorbar()

1.2 缩小图像

对目前的量子计算机来说,28x28 的图像太大。将图像大小调整至 4x4:

x_train_small = tf.image.resize(x_train, (4,4)).numpy() x_test_small = tf.image.resize(x_test, (4,4)).numpy()

调整大小后,重新显示上面的第一个训练样本:

print(y_train[0]) plt.imshow(x_train_small[0,:,:,0], vmin=0, vmax=1) plt.colorbar()

1.3 移除矛盾样本

根据 Farhi 等人论文的 3.3 学习区分数据部分,筛选数据集以移除同时标记为两个类的图像。

这不是标准机器学习步骤,但是为了便于继续学习该论文,我们包括了这一部分。

def remove_contradicting(xs, ys): mapping = collections.defaultdict(set) orig_x = {} # Determine the set of labels for each unique image: for x,y in zip(xs,ys): orig_x[tuple(x.flatten())] = x mapping[tuple(x.flatten())].add(y) new_x = [] new_y = [] for flatten_x in mapping: x = orig_x[flatten_x] labels = mapping[flatten_x] if len(labels) == 1: new_x.append(x) new_y.append(next(iter(labels))) else: # Throw out images that match more than one label. pass num_uniq_3 = sum(1 for value in mapping.values() if len(value) == 1 and True in value) num_uniq_6 = sum(1 for value in mapping.values() if len(value) == 1 and False in value) num_uniq_both = sum(1 for value in mapping.values() if len(value) == 2) print("Number of unique images:", len(mapping.values())) print("Number of unique 3s: ", num_uniq_3) print("Number of unique 6s: ", num_uniq_6) print("Number of unique contradicting labels (both 3 and 6): ", num_uniq_both) print() print("Initial number of images: ", len(xs)) print("Remaining non-contradicting unique images: ", len(new_x)) return np.array(new_x), np.array(new_y)

结果计数与报告值不完全相符,但并未指定具体步骤。

这里还要注意的一点是,此时应用矛盾样本筛选并不能完全阻止模型收到矛盾的训练样本:下一步会对数据进行二值化,因而会产生更多冲突样本。

x_train_nocon, y_train_nocon = remove_contradicting(x_train_small, y_train)

1.4 将数据编码为量子电路

为了使用量子计算机处理图像,Farhi 等人提出使用量子位表示每个像素,这样,量子位的状态就取决于像素值。第一步是转换为二进制编码。

THRESHOLD = 0.5 x_train_bin = np.array(x_train_nocon > THRESHOLD, dtype=np.float32) x_test_bin = np.array(x_test_small > THRESHOLD, dtype=np.float32)

如果您这时移除矛盾图像,可能只剩 193 个图像,很可能无法进行有效的训练。

_ = remove_contradicting(x_train_bin, y_train_nocon)

对于值超过阈值的像素索引处的量子位,将通过 XX 门进行旋转。

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]

下面是为第一个样本创建的电路(电路图没有显示带零个门的量子位):

SVGCircuit(x_train_circ[0])

将此电路与图像值超过阈值的索引进行比较:

bin_img = x_train_bin[0,:,:,0] indices = np.array(np.where(bin_img)).T indices

将这些 Cirq 电路转换为 tfq 的张量:

x_train_tfcirc = tfq.convert_to_tensor(x_train_circ) x_test_tfcirc = tfq.convert_to_tensor(x_test_circ)

2. 量子神经网络

有关分类图像的量子电路结构的指导很少。由于分类基于对量子位读数的期望,因此,Farhi 等人提出使用两个量子位门,从而始终根据量子位读数进行响应。这在一定程度上与在像素中运行一个小的酉 RNN 类似。

2.1 构建模型电路

下面的示例介绍了这种分层方式。每个层使用同一个门的 n 个实例,其中每个数据量子位根据量子位读数进行响应。

首先,我们来看一个将这些门的层添加到电路的简单类:

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)

构建一个示例电路层,了解其结构:

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') SVGCircuit(circuit)

现在,构建一个匹配数据电路大小的两层模型,并包括准备和读数操作。

def create_quantum_model(): """Create a QNN model circuit and readout operation to go along with it.""" data_qubits = cirq.GridQubit.rect(4, 4) # a 4x4 grid. readout = cirq.GridQubit(-1, -1) # a single qubit at [-1,-1] circuit = cirq.Circuit() # Prepare the readout qubit. circuit.append(cirq.X(readout)) circuit.append(cirq.H(readout)) 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") # Finally, prepare the readout qubit. circuit.append(cirq.H(readout)) return circuit, cirq.Z(readout)
model_circuit, model_readout = create_quantum_model()

2.2 在 tfq-keras 模型中封装模型电路

使用量子组件构建 Keras 模型。从 x_train_circ(对经典数据进行编码)向此模型馈送“量子数据”。它使用参数化量子电路tfq.layers.PQC,在量子数据上训练模型电路。

为了对这些图像进行分类,Farhi 等人提出获取参数化电路中的量子位读数期望。该期望会返回一个 1 到 -1 之间的值。

# 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), ])

下面,我们介绍使用 compile 方法训练模型的步骤。

由于预期读数在 [-1,1] 的范围内,因此,优化铰链损失是很自然的选择。

注:另一种有效方式可能是将输出范围转换为 [0,1],并将其视为模型分配给类 3 的几率。这可以与标准的 tf.losses.BinaryCrossentropy 损失一起使用。

要在此处使用铰链损失,您需要对两处稍作调整。其一是转换标签 y_train_nocon,将其从布尔值转换为 [-1,1] 的范围,使其符合铰链损失的预期。

y_train_hinge = 2.0*y_train_nocon-1.0 y_test_hinge = 2.0*y_test-1.0

其二,使用可将 [-1, 1] 作为 y_true 标签参数正确处理的自定义 hinge_accuracy 指标。tf.losses.BinaryAccuracy(threshold=0.0) 预期的 y_true 是一个布尔值,因此,不能与铰链损失一起使用。

def hinge_accuracy(y_true, y_pred): y_true = tf.squeeze(y_true) > 0.0 y_pred = tf.squeeze(y_pred) > 0.0 result = tf.cast(y_true == y_pred, tf.float32) return tf.reduce_mean(result)
model.compile( loss=tf.keras.losses.Hinge(), optimizer=tf.keras.optimizers.Adam(), metrics=[hinge_accuracy])
print(model.summary())

训练量子模型

现在,开始训练模型,这个过程大约需要 45 分钟。如果您不想等待太长时间,请使用一小部分数据(按如下设置 NUM_EXAMPLES=500)。这不会对模型在训练期间的进展造成实际影响(它仅包含 32 个参数,不需要太多数据来约束)。使用较少的样本只会让训练更快结束(5 分钟),但是运行时间已经足以在验证日志中表明取得进展。

EPOCHS = 3 BATCH_SIZE = 32 NUM_EXAMPLES = len(x_train_tfcirc)
x_train_tfcirc_sub = x_train_tfcirc[:NUM_EXAMPLES] y_train_hinge_sub = y_train_hinge[:NUM_EXAMPLES]

将此模型训练至收敛,可以在测试集上达到 85% 以上的准确率。

qnn_history = model.fit( x_train_tfcirc_sub, y_train_hinge_sub, batch_size=32, epochs=EPOCHS, verbose=1, validation_data=(x_test_tfcirc, y_test_hinge)) qnn_results = model.evaluate(x_test_tfcirc, y_test)

注:训练准确率可报告整个周期的平均值。验证准确率在每个周期结束时进行评估。

3. 经典神经网络

虽然量子神经网络可以解决这种简单的 MNIST 问题,但是,对于这种任务,基本的经典神经网络的效果明显更优。经过一个训练周期后,经典神经网络在保留集上可以达到 98% 以上的准确率。

在以下示例中,经典神经网络使用 28x28 的全尺寸图像(而不是对图像进行下采样)解决 3-6 分类问题。这在测试集上可以轻松收敛至接近 100% 的准确率。

def create_classical_model(): # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/ model = tf.keras.Sequential() model.add(tf.keras.layers.Conv2D(32, [3, 3], activation='relu', input_shape=(28,28,1))) model.add(tf.keras.layers.Conv2D(64, [3, 3], activation='relu')) model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) model.add(tf.keras.layers.Dropout(0.25)) model.add(tf.keras.layers.Flatten()) model.add(tf.keras.layers.Dense(128, activation='relu')) model.add(tf.keras.layers.Dropout(0.5)) model.add(tf.keras.layers.Dense(1)) return model model = create_classical_model() model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), optimizer=tf.keras.optimizers.Adam(), metrics=['accuracy']) model.summary()
model.fit(x_train, y_train, batch_size=128, epochs=1, verbose=1, validation_data=(x_test, y_test)) cnn_results = model.evaluate(x_test, y_test)

上面的模型包含接近 120 万个参数。为了进行更公平的比较,请尝试使用一个包含 37 个参数的模型,在下采样的图像上进行训练:

def create_fair_classical_model(): # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/ model = tf.keras.Sequential() model.add(tf.keras.layers.Flatten(input_shape=(4,4,1))) model.add(tf.keras.layers.Dense(2, activation='relu')) model.add(tf.keras.layers.Dense(1)) return model model = create_fair_classical_model() model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), optimizer=tf.keras.optimizers.Adam(), metrics=['accuracy']) model.summary()
model.fit(x_train_bin, y_train_nocon, batch_size=128, epochs=20, verbose=2, validation_data=(x_test_bin, y_test)) fair_nn_results = model.evaluate(x_test_bin, y_test)

4. 比较

输入的分辨率越高,模型越强大,CNN 解决此问题越轻松。但是,能力相近(约 32 个参数)的经典模型只需少量时间就可以达到同等准确率。不管怎样,经典神经网络都明显优于量子神经网络。对于经典数据,很难找到比经典神经网络更好的方案。

qnn_accuracy = qnn_results[1] cnn_accuracy = cnn_results[1] fair_nn_accuracy = fair_nn_results[1] sns.barplot(x=["Quantum", "Classical, full", "Classical, fair"], y=[qnn_accuracy, cnn_accuracy, fair_nn_accuracy])