At LHCb, b-jets are tagged using several methods, some of them with high efficiency but low purity or high purity and low efficiency. Particularly our aim is to identify jets produced by and quarks, which is fundamental to perform important physics searches, e.g. the measurement of the charge asymmetry, which could be sensitive to New Physics processes.
Since this is a classification task, Machine Learning (ML) algorithms can be used to achieve a good separation between - and -jets: classical ML algorithms such as Deep Neural Networks have been proved to be far more efficient than standard methods since they rely on the whole jet substructure. In this work, we present a new approach to -tagging based on Quantum Machine Learning (QML) which makes use of the QML Python library Pennylane integrated with the classical ML frameworks PyTorch and Tensorflow. Official LHCb simulated events of di-jets are studied and different quantum circuit geometries are considered. Performances of QML algorithms are compared with standard tagging methods and classical ML algorithms, preliminarily showing that QML algorithms perform almost as good as classical ML algorithms, despite using fewer events due to time and computational constraints.
This exercise is based on official LHCb Open Data
Copy
import numpy as np
import matplotlib.pyplot as plt
Dataset
The dataset is stored in two csv files produced using LHCb OpenData tuples that are publicly available.
Test dataset contains also variables needed afterwards for analysis.
Copy
import pandas as pd
# All Input variables
allvariables = ['idx', # jet index
'Jet_foundMuon', 'Jet_muon_q', 'Jet_muon_PT', # global (anti-)muon variables
'Jet_PT', 'Jet_ETA', 'Jet_PHI', # global jet variables
'mu_Q', 'mu_pTrel','mu_dist', # (anti-)muon
'k_Q', 'k_pTrel','k_dist', # (anti-)kaon
'pi_Q', 'pi_pTrel','pi_dist', # (anti-)pion
'e_Q', 'e_pTrel','e_dist', # (anti-)electron
'p_Q', 'p_pTrel','p_dist', # (anti-)proton
'Jet_QTOT', # Weighted Jet Total Charge
'Jet_LABEL'] # Ground Truth label
# Input variables of the quantum circuit
variables = ['idx', # jet index
'mu_Q', 'mu_pTrel','mu_dist', # (anti-)muon
'k_Q', 'k_pTrel','k_dist', # (anti-)kaon
'pi_Q', 'pi_pTrel','pi_dist', # (anti-)pion
'e_Q', 'e_pTrel','e_dist', # (anti-)electron
'p_Q', 'p_pTrel','p_dist', # (anti-)proton
'Jet_QTOT', # Weighted Jet Total Charge
'Jet_LABEL'] # Ground Truth label
# Events are split in two subsets for training and testing
trainData = pd.read_csv("./data/trainData.csv")[variables]
testData = pd.read_csv("./data/testData.csv")[allvariables]
The variables need to be preprocessed before the training process. Here we adopt a technique that suited well for our QML application: firstly data are normalized using a StandardScaler, then the arctan function is applied.
import pennylane as qml
n_qubits = 4
n_layers = 4
# default.qubit.tf is a quantum simulator that uses Tensorflow as backend
# it supports back-propagation, which is useful with a large number of
# parameters
dev = qml.device("default.qubit.tf",wires=n_qubits) # Device initialization
# In pennylane, quantum circuits are mostly defined via the qnode decorator
# applied to a function which describes the quantum gates to be applied to
# each qubit and, finally, the measurement to be performed.
@qml.qnode(dev, interface="tf", diff_method='backprop')
def circuit(inputs,weights):
# The AmplitudeEmbedding template implements the embedding of 2**N features
# in N qubits. Amplitudes must be < 0, so a normalization is performed.
qml.templates.AmplitudeEmbedding(inputs,wires=range(n_qubits),normalize=True)
# The StronglyEntanglingLayers templates implements a variable number of
# layers made of rotation around all the axes and CNOT gates. Rotation angles
# are passed via the weights parameter of dimension (n_layers,n_qubits,3)
qml.templates.StronglyEntanglingLayers(weights, wires=range(4))
# Finally, the expectation value of the Sigma_Z Pauli matrix of the first qubit
# is measured. It will range from -1 to 1 and will be mapped to a label prediction
return qml.expval(qml.PauliZ(0))
2021-07-07 18:03:13.167076: I tensorflow/compiler/jit/xla_cpu_device.cc:41] Not creating XLA devices, tf_xla_enable_xla_devices not set
2021-07-07 18:03:13.168954: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: SSE4.1 SSE4.2 AVX AVX2
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-07-07 18:03:13.172172: I tensorflow/core/common_runtime/process_util.cc:146] Creating new thread pool with default inter op setting: 2. Tune using inter_op_parallelism_threads for best performance.
Keras model
A great feature of pennylane is that it can be integrated flawlessly inside Keras and PyTorch models. This allows for the developing of hybrid quantum-classical machine learning models.
Copy
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Activation
# We generate the quantum Keras layer, specifiying the dimension of the
# trainable parameters and of the output of the layer.
quantum_layer = qml.qnn.keras.KerasLayer(circuit,weight_shapes={'weights':(n_layers,n_qubits,3)},output_dim=1)
# Finally, we build a Sequencial model appending a sigmoid function to
# the quantum layer
model = Sequential([quantum_layer,Activation('sigmoid')])
Training
For the training part you can define:
learning rate
loss function
training and validation size
number of epochs
Copy
from tensorflow.keras.optimizers import Adam
# Let's pick an optimizer
opt = Adam(learning_rate=0.02)
# We choose the Mean Square Error as our loss function and a binary
# accuracy as our metric
model.compile(opt, loss="mse",metrics=['binary_accuracy'])
# We fix the size of the training dataset and validation dataset
training_size = 200
validation_size = 200
# And build a balanced training sample ...
bsample = trainData[trainData.Jet_LABEL == 0].sample(training_size//2)
bbarsample = trainData[trainData.Jet_LABEL == 1].sample(training_size//2)
training_sample = pd.concat([bsample,bbarsample]).sample(frac=1.0)
X = training_sample[variables[1:-1]]
Y = training_sample[variables[-1]]
# And validation sample
val_bsample = testData[testData.Jet_LABEL == 0].sample(validation_size//2)
val_bbarsample = testData[testData.Jet_LABEL == 1].sample(validation_size//2)
validation_sample = pd.concat([val_bsample,val_bbarsample]).sample(frac=1.0)
val_X = validation_sample[variables[1:-1]]
val_Y = validation_sample[variables[-1]]
# Finally, we start the training process
n_epochs = 10
history_fit = model.fit(X,Y,epochs=n_epochs,validation_data=(val_X,val_Y))
2021-07-07 18:03:13.467149: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:116] None of the MLIR optimization passes are enabled (registered 2)
2021-07-07 18:03:13.473782: I tensorflow/core/platform/profile_utils/cpu_utils.cc:112] CPU Frequency: 2999985000 Hz
Standard training results such as the loss function and the accuracy can be plotted with respect to the number of epochs, both for the training and the evaluation datasets.
# Finally, we can save our model prediction for future analysis (bbar asymmetry estimation,
# tagging performance analysis, ...)
testing_sample.to_csv("data_with_predictions.csv",index=False)
Once you got your prediction you can use these two functions to apply (ApplyAlgTagging) and check (ApplyAlgChecking) the classifier response with respect to true information.
Copy
def ApplyAlgTagging(df, netype="Quantum", mu_cut=5000):
"""
Performs the algorithm tagging for each jet, and applies a probabilities R
"""
df['Jet_%s_Charge'%netype] = 0
tt_Jet_1 = (df["Jet_%sProb"%netype] > 0.5)
tt_Jet_0 = (df["Jet_%sProb"%netype] < 0.5)
df.loc[tt_Jet_1,'Jet_%s_Charge'%netype] = 1
df.loc[tt_Jet_0,'Jet_%s_Charge'%netype] = 0
def ApplyAlgChecking(df, netype="Quantum"):
"""
Checks the result of the algorithm tagging by comparing the classifier response with the MC-truth.
"""
df['Jet_%s_Check'%netype] = 0
tt_correct_jet = df['Jet_%s_Charge'%netype] == df.Jet_MATCHED_CHARGE
tt_notcorrect_jet = df['Jet_%s_Charge'%netype] != df.Jet_MATCHED_CHARGE
df.loc[tt_correct_jet, 'Jet_%s_Check'%netype] = 1
df.loc[tt_notcorrect_jet, 'Jet_%s_Check'%netype] = -1
Copy
#rename some variables just for the sake of completeness
testing_sample.rename(columns={"Jet_LABEL": "Jet_MATCHED_CHARGE"}, inplace=True)
testing_sample.rename(columns={"RawPred": "Jet_QuantumProb"}, inplace=True)
#apply algorithm tagging and checking
ApplyAlgTagging(testing_sample, "Quantum")
ApplyAlgChecking(testing_sample, "Quantum")
Get classifier performances
The "GetAlgPerformances" function extracts the classifier performance and associated uncertainties.
The following quantities are defined:
efficiency: εeff=NtotNtag
mistag: εmistag=NtagNwrong
tagging power: εtag=εeff(1−2εmistag)2
Keep in mind that:
efficiency will always be 100% since the classifier will always classify a jet
the greater the tagging power, the better the classifier (e.g. the tagging power is directly related to the statistical uncertainty of the b−bˉ asymmetry)
Copy
# these two functions are used in the error estimitation of the tagging power
def Deps_DNtag(N_tot,N_w,N_tag):
return 1.0/N_tot * (1 - (float(N_w)**2)/(N_tag**2))
def Deps_DNw(N_tot,N_w,N_tag):
return 1.0/N_tot * (-4 + 8*float(N_w)/N_tag)
def GetAlgPerformances(jet_df, netype="Quantum"):
N_tot = len(jet_df)
N_tag = jet_df['Jet_%s_Check'%netype].value_counts()[1] + jet_df['Jet_%s_Check'%netype].value_counts()[-1]
N_wrong = jet_df['Jet_%s_Check'%netype].value_counts()[-1]
#Efficiency
eps_eff = float(N_tag)/N_tot
#Efficiency error
err_eps_eff = np.sqrt(eps_eff*(1-eps_eff)/N_tot)
#Mistag
omega = float(N_wrong)/(N_tag)
#Mistag error
err_omega = np.sqrt(omega*(1-omega)/N_tag)
#Accuracy
accuracy = 1- omega
#Tagging Power
eps_tag = (eps_eff * (1-2*omega)**2)
#Tagging power error
err_eps_tag = np.sqrt((N_tot*(eps_eff)*(1-eps_eff)) * Deps_DNtag(N_tot,N_wrong,N_tag)**2 + (N_tag*(omega)*(1-omega)) * Deps_DNw(N_tot,N_wrong,N_tag)**2)
return eps_eff,omega,eps_tag,err_eps_eff,err_omega,err_eps_tag,N_tot,accuracy
The function "Analysis" just compute all the quantities described above in different bins of jet pT. So first of all you define jet pT bins such as they have the same number of jets, and then you compute all the relevant quantities.