CQCL / lambeq

A high-level Python library for Quantum Natural Language Processing
https://cqcl.github.io/lambeq/
Apache License 2.0
435 stars 106 forks source link

TypeError when construct quantum circuits for multi-classification task #131

Open shinyoung3 opened 5 months ago

shinyoung3 commented 5 months ago

Hi, I’m trying to set up a model for multi-classification task with 4 classes (0 to 3) and wondering if I can ask some questions regarding allocation of qubits when building an ansatz. The dataset consists of label and its corresponding sentence as shown below:

0 joyful man entertains kid 2 young kid irritates man 3 frightened woman startles woman 1 old woman grieves kid 0 old man entertains woman

I encoded the classical labels into two-qubit representation such as label_mapping = {'0': (0, 0), '1': (0, 1), '2': (1, 0), '3': (1, 1)}.

  1. Cups reader: When Cups reader was used to process the data then turned into the string diagrams, I defined two qubits for each diagram, ansatz = IQPAnsatz({AtomicType.NOUN: 0, AtomicType.SENTENCE: 2}, n_layers=1), so that individual post-selected quantum state can be compared with label_mapping. But this approach gave me the Type error:

`309 for i, circuit in enumerate(circuits): 310 n_bits = len(circuit.post_processing.dom) --> 311 result = np.zeros((n_bits (2, ))) 312 for bitstring, count in counts[i].items(): 313 result[bitstring] = count

TypeError: Cannot interpret '2' as a data type

`

  1. Spiders reader: Using the spiders reader with the same ansatz specification also gave me the type error, but this time, the error was raised by not correctly defining domain and codomain, which I’ve never seen in the previous lambeq version:

`1927 dom_check = self.ob_with_cache(ar.dom) 1928 if ret.cod != cod_check or ret.dom != dom_check: -> 1929 raise TypeError(f'The arrow is ill-defined. Applying the functor ' 1930 f'to a box returns dom = {ret.dom}, cod = ' 1931 f'{ret.cod} expected dom = {dom_check}, cod = ' 1932 f'{cod_check}') 1933 return ret

TypeError: The arrow is ill-defined. Applying the functor to a box returns dom = , cod = qubit expected dom = , cod =`

It looks like changing the number of qubits in ansatz doesn’t help resolve the issue, so I wonder if anyone can help me with this.

Thanks! (+ I’m using the latest version of lambeq 0.4.0)

dimkart commented 4 months ago

Hi @shinyoung3 and sorry for the delay. The problem must be related to the fact you are using 0 qubits for nouns in your ansatz; try to use a number > 0 and let us know if it works.

shinyoung3 commented 3 months ago

Hi @dimkart , thanks for the help. I resolved the issue, but I found one more problem when constructing an ansatz with a two-qubit system. For a single-qubit system (e.g., ansatz = IQPAnsatz({AtomicType.NOUN: 1, AtomicType.SENTENCE: 1}, n_layers=1, n_single_qubit_params=3)), changing n_single_qubit_params gives adjustment of the number of parameters operated on each qubit. However, n_single_qubit_params always gives single Rz gates for a two-qubit system regardless of the n_single_qubit_params setup. So there's no change in the total number of circuit parameters, whether IQPAnsatz({AtomicType.NOUN: 2, AtomicType.SENTENCE: 2}, n_layers=1, n_single_qubit_params=3) or IQPAnsatz({AtomicType.NOUN: 2, AtomicType.SENTENCE: 2}, n_layers=1, n_single_qubit_params=6). Can anyone explain why this happens and how I can construct a circuit with three operations for each qubit (two Rx and single Rz gates? I don't think this is related to the version issue.

Thanks!

Slidejiveman commented 3 months ago

Hello, @dimkart, I am writing to report an error I'm experiencing with a program I'm writing for my doctoral research.

I experienced the same TypeError when working on my experiment to define a custom grammatical type in my ansatz.

This is the type error that was mentioned above that I am also experiencing. Cups reader: When Cups reader was used to process the data then turned into the string diagrams, I defined two qubits for each diagram, ansatz = IQPAnsatz({AtomicType.NOUN: 0, AtomicType.SENTENCE: 2}, n_layers=1), so that individual post-selected quantum state can be compared with label_mapping. But this approach gave me the Type error: `309 for i, circuit in enumerate(circuits): 310 n_bits = len(circuit.post_processing.dom) --> 311 result = np.zeros((n_bits (2, ))) 312 for bitstring, count in counts[i].items(): 313 result[bitstring] = count

TypeError: Cannot interpret '2' as a data type

Here is how I am defining my ansatz. Note that there is a custom type called "h". n, s, h = AtomicType.NOUN, AtomicType.SENTENCE, Ty ('h') ansatz = IQPAnsatz({n: 1, s: 1, h: 1}, n_layers=1, n_single_qubit_params=3)

Here is my output: Traceback (most recent call last): File "c:\Users\rdnot\vertical-meaning\vertical_meaning.py", line 323, in trainer.fit(train_dataset, val_dataset, early_stopping_interval=10) File "C:\Users\rdnot\vertical-meaning\Lib\site-packages\lambeq\training\quantum_trainer.py", line 201, in fit super().fit(train_dataset, File "C:\Users\rdnot\vertical-meaning\Lib\site-packages\lambeq\training\trainer.py", line 465, in fit t_loss = self._step_and_eval( ^^^^^^^^^^^^^^^^^^^^ File "C:\Users\rdnot\vertical-meaning\Lib\site-packages\lambeq\training\trainer.py", line 348, in _step_and_eval y_hat, loss = step_func(batch) ^^^^^^^^^^^^^^^^ File "C:\Users\rdnot\vertical-meaning\Lib\site-packages\lambeq\training\quantum_trainer.py", line 163, in training_step
loss = self.optimizer.backward(batch) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\rdnot\vertical-meaning\Lib\site-packages\lambeq\training\spsa_optimizer.py", line 157, in backward
y0 = self.model(diagrams) ^^^^^^^^^^^^^^^^^^^^ File "C:\Users\rdnot\vertical-meaning\Lib\site-packages\lambeq\training\quantum_model.py", line 175, in call out = self.forward(*args, *kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\rdnot\vertical-meaning\Lib\site-packages\lambeq\training\tket_model.py", line 129, in forward return self.get_diagram_output(x) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\rdnot\vertical-meaning\Lib\site-packages\lambeq\training\tket_model.py", line 98, in get_diagram_output
tensors = Circuit.eval( ^^^^^^^^^^^^^ File "C:\Users\rdnot\vertical-meaning\Lib\site-packages\lambeq\backend\quantum.py", line 311, in eval result = np.zeros(
(n_bits * (2, ))) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TypeError: Cannot interpret '2' as a data type

Question: Do you have any recommendations on how I can proceed with my experiment?

dimkart commented 3 months ago

@Slidejiveman As discussed in discord, this is due to the fact that the diagrams might have different codomains. Using the UnifyCodomainRewriter after the diagram creation should resolve the problem.

dimkart commented 3 months ago

Hi @dimkart , thanks for the help. I resolved the issue, but I found one more problem when constructing an ansatz with a two-qubit system. For a single-qubit system (e.g., ansatz = IQPAnsatz({AtomicType.NOUN: 1, AtomicType.SENTENCE: 1}, n_layers=1, n_single_qubit_params=3)), changing n_single_qubit_params gives adjustment of the number of parameters operated on each qubit. However, n_single_qubit_params always gives single Rz gates for a two-qubit system regardless of the n_single_qubit_params setup. So there's no change in the total number of circuit parameters, whether IQPAnsatz({AtomicType.NOUN: 2, AtomicType.SENTENCE: 2}, n_layers=1, n_single_qubit_params=3) or IQPAnsatz({AtomicType.NOUN: 2, AtomicType.SENTENCE: 2}, n_layers=1, n_single_qubit_params=6). Can anyone explain why this happens and how I can construct a circuit with three operations for each qubit (two Rx and single Rz gates? I don't think this is related to the version issue.

Thanks!

Hi, sorry for the delay, it seems that we missed this one. n_single_qubit_params is only relevant for wires that are mapped to a single qubit (as the name implies) in the ob_map dictionary. The reason for this is that while any ansatz provides a way to combine two or more qubit into a meaningful circuit, it usually becomes degenerate in the single qubit case. This needs to be handled explicitly, which we do through the n_single_qubit_params argument. We will improve the documentation of the ansatz to make this more clear. Further, note that 3 rotations are enough to specify any single-qubit unitary, so higher values for the argument do not make much sense.

@shinyoung3

shinyoung3 commented 2 months ago

Hi, thanks for the clarification. But I have one more question regarding this. If I understand correctly, for binary classification, the measurement outcome from a single qubit is only compared with the binary true label of 0 or 1, and the rest of the measurements are post-selected to be considered "0." So, if one sentence consisting of four words is mapped into individual qubits (a total of four qubits), the probability of getting an accurate prediction is 1/16, assuming all possible outcomes have a uniform distribution.

If this is correct, for multi-class classification, let's say we have four classes, then the circuit for the same sentence should be duplicated to have eight qubits, since only a single qubit can represent the label-specific quantum state (whether it is 0 or 1). So, the outcome from two qubits out of eight can represent 00 to 11 to be compared with the true label, further increasing statistical limitation. I don't know if this concept is correct, but I would like to know if you can give me some insight into this.

Thanks a lot!

@dimkart

dimkart commented 2 months ago

In lambeq diagrams, the output of a circuit is delivered from the free wires, which usually get the type 's' (sentence). If you define s=1 in your ansatz (ob_map argument), you assign 1 qubit to your sentence wire which means you have two possible states $|0\rangle$ and $|1\rangle$, and the result of the circuit will be a vector $(a1, a2)$, giving the probabilities of these two states. This is the setting you use in standard binary classification tasks. Now, imagine you have 4 classes. You will need to set s=2 in your ansatz, so you have 4 ($2^2$) different possible states, $|00\rangle$, $|01\rangle$, $|10\rangle$, $|11\rangle$. In that case the result of the model will be a vector with 4 elements, each one providing the probability for a specific state. With 3 qubits at the output wire, you have 8 states, so you can represent up to 8 classes, and the output vector will have 8 elements, and so on. For $\text{s}=n$ in your ansatz, you can represent up to $2^n$ classes. Does this help?

shinyoung3 commented 2 months ago

Hi, thank you for the quick response. I understood the relationship between the number of sentence wires (i.e., s=1, 2, or 3) and the output of the circuit we obtain, but what I wanted to ask was how the concept of post-selection would result in different statistical limitations when we change from s=1 to s=2 on the same sentence.

When I construct a quantum circuit based on the bag-of-words model for the sentence, "l like you," each word (I, like, you) is mapped into individual qubits, and the free wire is connected to "I"-qubit for the binary classification (s=1). So when the circuit is measured, we obtain quantum states of $|0\rangle$ and $|1\rangle$, which are from this "I"-qubit. If I understand correctly, this means that we restrict the circuit measurement by post-selecting two qubits ("like" qubit and "you" qubit) to be considered as they are at $|0\rangle$ state and neglect their result to be considered in the measurement outcome. And that's why the "like" and "you" qubits' wires are connected to "0" in the downward triangle in the circuit shown below.

So my question is, does it mean that the actual probability of obtaining a meaningful result is 2/8 (assuming all possible outcomes have a uniform distribution) for s=1 in this example (since 3 qubits are required to represent the sentence but we only consider measurement outcome from a single qubit)?

s_1

In addition, the quantum circuit of s=2 looks like two circuits of s=1 are connected in parallel with controlled Rz gates. If the above question is valid, the circuit for s=2 gives the result of $|00\rangle$, $|01\rangle$, $|10\rangle$, and $|11\rangle$ by only using two free wire qubits (i.e."I"-qubit) to be considered as the measurement outcome but actually representing the whole sentence with the total of six qubits (by mapping two qubits per word). In this case, doesn't the probability of obtaining a meaningful result now become 4/64 (assuming all possible outcomes have a uniform distribution) for s=2 while s=1 is 2/8? I guess this difference comes from the change in the number of post-selected qubits (s=1 has two while s=2 has four post-selected qubits).

image

Sorry for the long conceptual question, but I thought this was the best place to obtain good advice on understanding the QNLP algorithm. I really appreciate the help!

Thanks

dimkart commented 2 months ago

Hi again. You are right, as the number of post-selections increase, you need an exponentially higher number of circuit executions (shots) to gather meaningful statistics. However, in lambeq post-selections are relevant only to specific models, such as the DisCoCat model, that contain "cups"; compositional schemes such as StairsReader and TreeReader do not use post-selections and can be run efficiently on quantum hardware. Check this discussion for more insights and some ways to reduce or completely avoid post-selections.

shinyoung3 commented 2 months ago

Hi, thank you for the response. Based on your explanation and the discussion link you provided, it looks like a spider reader should not adopt post-selection since there are no cups. But what I got confused about is that no matter which models I chose (e.g., DisCoCat, TreeReader, StairReader, SpiderReader, etc.), the quantum circuit based on the choice of the models always looks like it has a single open wire qubit (i.e., free wire), and the rest of the qubits are post-selected (qubits connected to "0" in the downward triangle) for s=1. What does it mean when TreeReader does not use post-selections, given that its structure appears very similar to the circuit based on Spider Reader? (i.e., how can I distinguish between a model that does not use post-selections and one that does?)

image

nikhilkhatri commented 1 month ago

Hi @shinyoung3, Postselections appear in 3 broad cases:

  1. Cups
  2. Spiders
  3. Any box with cod qubits > dom qubits (more inputs than outputs), unless using discards

The first 2 cases are unavoidable, but the 3rd can be handled in the ansatz.

When creating the ansatz, specify discard=True. This replaces the postselections with a quantum discard.

For example:

ansatz = IQPAnsatz({N: 1, S: 1}, n_layers=2, discard=True)

Docs for this: https://cqcl.github.io/lambeq/lambeq.ansatz.html#lambeq.ansatz.IQPAnsatz

For your usecase, you can try using the tree reader, and then make sure you use discards. This has no cups or spiders natively.