Quantum machine learning has received a lot of hype. But there is a simple way to see if the hype is just hype: classical simulation! In an apples-to-apples comparison, can a quantum circuit outperform a classical neural network?

Recently, I was reading this very nice article by Taylor Patti (Harvard, Nvidia) and collaborators (see arXiv) and they found a nice application of an idea I had: use pytorch to simulate quantum circuits. They managed to simulate a quantum circuit with 512 qubits that is 13 gates deep and were motivated to do so for an application with time reversal symmetry that allows them to use real numbers instead of complex ones which pytorch is not well setup to handle. The results from their GPU seem impressive and strengthens the motivation for this post: is a quantum circuit good at machine learning and for what applications is it good at? Combinatorial optimization?

Of course, machine learning is a broad subject and neural networks are not the only machines around. There are many methods and nobody has created Pedro Domingo’s “Master algorithm”. It could be that say at image classification or some other common neural network application, a quantum circuit fails miserably compared to neural networks. But if this is the case, it doesn’t mean the quantum circuit is a poor machine in general, just for this one application. So a weaker version of this post’s question is: is there an application where a quantum circuit shines compared to other machine learning algorithms?

A quantum circuit requires some background to understand. A quick definition is that a quantum circuit is a non-deterministic map from quantum data to classical bit strings. But for more details see for example qiskit’s discussion, wikipedia’s discussion, or a good book on quantum computing that does not require a physics background such as Nielsen and Chuang. So there are two properties that make quantum circuits different from neural networks: they act on quantum data (a vector in a tensor product space \(V_d\otimes V_d\otimes\ldots\)) instead of \({\mathbb R}^N\) and produce a distribution of outputs for the same input.

Here is some code I wrote to create a simple quantum circuit based machine in pytorch. The weight matrix is an orthogonal matrix generated by a QR decomposition. A measurement is performed on the first qubit and a single bit representing the outcome of the measurement is performed along with the collapsed state after measurement is returned.

class QC(nn.Module):
    def __init__(self,N):
        super().__init__()

        self.N = N
        
        self.weights = torch.randn([2 for i in range(2*self.N)])
        self.qr_correct_weights()
        self.weights.requires_grad = True
        
        self.w_indices = ''.join((chr(97+i) for i in range(2*self.N)))
        self.x_indices = ''.join((chr(97+i) for i in range(self.N)))
        
    def forward(self,x):
        x = torch.einsum(self.x_indices+','+self.w_indices,x,self.weights)
        
        proj0_x = torch.einsum(self.x_indices+','+'az',x,torch.tensor([[1.0,0.0],[0.0,0.0]]))
        proj1_x = torch.einsum(self.x_indices+','+'az',x,torch.tensor([[0.0,0.0],[0.0,1.0]]))
        norm0 = torch.einsum(self.x_indices+','+self.x_indices,proj0_x,proj0_x)
        norm1 = torch.einsum(self.x_indices+','+self.x_indices,proj1_x,proj1_x)
        p0 = norm0/(norm0+norm1)
        
        if torch.randn(()) < p0:
            return ('0b0',proj0_x/torch.sqrt(p0))
        else:
            return ('0b1',proj1_x/torch.sqrt(1-p0))

    def qr_correct_weights(self):
        # We may need to call this every now and then to fix numerical errors (maybe once per epoch?)
        Q,R = torch.qr(self.weights.data.view((2**self.N,2**self.N)))
        self.weights.data = (Q@torch.diag(torch.sign(torch.diag(R)))).view([2 for i in range(2*self.N)])
        
    def grad_symmetrize_(self):
        # If gradient is a symmetric matrix, then it will preserve the orthogonality of the weights
        self.weights.grad.data = 0.5*(self.weights.grad.data + 
                                    self.weights.grad.data.permute([self.N+i for i in range(2*self.N)]))
    def backward(self):
        super().backward()
        self.grad_symmetrize_() # This line doesn't seem to matter!

It is a beginning only, but seems to show some promise. I was able to show the weight matrix remained orthogonal after a little training.

Now to pose the real test we need a database of quantum data: a collection of vectors in a tensor product space and a problem associated with this data which can be solved either by a quantum circuit or a neural network (by flattening the tensor product space). In a future post, we may post an ideal data set or data sets for this purpose: one composed of high rank tensors found in quantum simulations of quantum materials. However, an alternative in the mean time might be to study 4D data such as 3D videos like ScanNet. Neural networks perform well on one (bar codes?) and two dimensional (images) data sets but they seem to struggle more as you go up in dimension. Perhaps this is an opportunity for machines built out of quantum circuits or inspiried by quantum circuits.

I will proprose this problem one day for a student in my group, but if you find this idea interesting, feel please go ahead with your own “experiments” and let us know how it went!