diff --git a/Chemistry/src/DataModel/DataModel.csproj b/Chemistry/src/DataModel/DataModel.csproj index 14086f84be6..4e1f891c5b3 100644 --- a/Chemistry/src/DataModel/DataModel.csproj +++ b/Chemistry/src/DataModel/DataModel.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 @@ -35,7 +35,7 @@ - + diff --git a/Chemistry/src/Jupyter/Jupyter.csproj b/Chemistry/src/Jupyter/Jupyter.csproj index 57c676d8836..05d9aa2329a 100644 --- a/Chemistry/src/Jupyter/Jupyter.csproj +++ b/Chemistry/src/Jupyter/Jupyter.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 x64 @@ -26,8 +26,8 @@ - - + + diff --git a/Chemistry/src/Runtime/Runtime.csproj b/Chemistry/src/Runtime/Runtime.csproj index 442a13d14c9..e95a5c76452 100644 --- a/Chemistry/src/Runtime/Runtime.csproj +++ b/Chemistry/src/Runtime/Runtime.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 Microsoft.Quantum.Chemistry.Runtime @@ -15,7 +15,7 @@ - + diff --git a/Chemistry/tests/ChemistryTests/QSharpTests.csproj b/Chemistry/tests/ChemistryTests/QSharpTests.csproj index 66e2888e2c0..96c76d44343 100644 --- a/Chemistry/tests/ChemistryTests/QSharpTests.csproj +++ b/Chemistry/tests/ChemistryTests/QSharpTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 x64 @@ -11,8 +11,8 @@ - - + + diff --git a/Chemistry/tests/DataModelTests/CSharpTests.csproj b/Chemistry/tests/DataModelTests/CSharpTests.csproj index 9c3df4a3cce..754a219c1fc 100644 --- a/Chemistry/tests/DataModelTests/CSharpTests.csproj +++ b/Chemistry/tests/DataModelTests/CSharpTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 @@ -24,8 +24,8 @@ - - + + diff --git a/Chemistry/tests/SamplesTests/SamplesTests.csproj b/Chemistry/tests/SamplesTests/SamplesTests.csproj index af9581bbb83..206c9c3b679 100644 --- a/Chemistry/tests/SamplesTests/SamplesTests.csproj +++ b/Chemistry/tests/SamplesTests/SamplesTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 @@ -18,8 +18,8 @@ - - + + diff --git a/Chemistry/tests/SystemTests/SystemTests.csproj b/Chemistry/tests/SystemTests/SystemTests.csproj index 0da8e8b4d71..8bcec6ef3cd 100644 --- a/Chemistry/tests/SystemTests/SystemTests.csproj +++ b/Chemistry/tests/SystemTests/SystemTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 @@ -18,8 +18,8 @@ - - + + diff --git a/MachineLearning/src/DataModel/DataModel.csproj b/MachineLearning/src/DataModel/DataModel.csproj index 1d1f8447711..fc1d86492a9 100644 --- a/MachineLearning/src/DataModel/DataModel.csproj +++ b/MachineLearning/src/DataModel/DataModel.csproj @@ -32,7 +32,7 @@ - + diff --git a/MachineLearning/src/Runtime/Circuits.qs b/MachineLearning/src/Runtime/Circuits.qs index 51bbd02a70e..49df95cd18e 100644 --- a/MachineLearning/src/Runtime/Circuits.qs +++ b/MachineLearning/src/Runtime/Circuits.qs @@ -2,536 +2,385 @@ // Licensed under the MIT License. namespace Microsoft.Quantum.MachineLearning { - open Microsoft.Quantum.Math; - open Microsoft.Quantum.Arrays; - open Microsoft.Quantum.Arithmetic; - open Microsoft.Quantum.Canon; - open Microsoft.Quantum.Intrinsic; - open Microsoft.Quantum.Convert; - open Microsoft.Quantum.Diagnostics; - open Microsoft.Quantum.Preparation; - open Microsoft.Quantum.Characterization; - - /// WARNING: the downstream EstimateFrequencyA counts the frequency of Zero - - operation measureLastQubit(nQubits : Int): (Qubit[] => Result) { - let paulis = ConstantArray(nQubits, PauliI) w/ (nQubits - 1) <- PauliZ; - return Measure(paulis, _); - } - - operation _endToEndPreparation(enc: (LittleEndian => Unit is Adj + Ctl), parameters: Double[], gates: GateSequence, reg: Qubit[]): Unit is Adj - { - enc(LittleEndian(reg)); - _ApplyGates(parameters, gates, reg); - } - - operation endToEndPreparation(enc: (LittleEndian => Unit is Adj + Ctl), parameters: Double[], gates: GateSequence) : (Qubit[] => Unit is Adj) - { - return _endToEndPreparation(enc,parameters, gates, _); - } - - function collectNegativeLocs(cNegative: Int, coefficients : ComplexPolar[]) : Int[] - { - mutable negLocs = ConstantArray(cNegative, -1); - mutable nlx = 0; - for (idx in 0 .. Length(coefficients) - 1) + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Preparation; + open Microsoft.Quantum.Characterization; + + /// WARNING: the downstream EstimateFrequencyA counts the frequency of Zero + + operation measureLastQubit(nQubits : Int): (Qubit[] => Result) { + let paulis = ConstantArray(nQubits, PauliI) w/ (nQubits - 1) <- PauliZ; + return Measure(paulis, _); + } + + operation _endToEndPreparation(enc: (LittleEndian => Unit is Adj + Ctl), parameters: Double[], gates: GateSequence, reg: Qubit[]): Unit is Adj + { + enc(LittleEndian(reg)); + _ApplyGates(parameters, gates, reg); + } + + operation endToEndPreparation(enc: (LittleEndian => Unit is Adj + Ctl), parameters: Double[], gates: GateSequence) : (Qubit[] => Unit is Adj) + { + return _endToEndPreparation(enc,parameters, gates, _); + } + + function collectNegativeLocs(cNegative: Int, coefficients : ComplexPolar[]) : Int[] + { + mutable negLocs = ConstantArray(cNegative, -1); + mutable nlx = 0; + for (idx in 0 .. Length(coefficients) - 1) { - let (r,a) = (coefficients[idx])!; - if (AbsD(a - PI()) < 1E-9) { - if (nlx < cNegative) - { - set negLocs w/= nlx <- idx; - set nlx = nlx+1; - } - } + let (r,a) = (coefficients[idx])!; + if (AbsD(a - PI()) < 1E-9) { + if (nlx < cNegative) + { + set negLocs w/= nlx <- idx; + set nlx = nlx+1; + } + } } - return negLocs; - } //collectNegativeLocs + return negLocs; + } //collectNegativeLocs - // NOTE: the last qubit of 'reg' in this context is the auxillary qubit used in the Hadamard test. - operation _endToEndHTcircuit(enc: (LittleEndian => Unit is Adj + Ctl), param1 : Double[], gates1: GateSequence, param2 : Double[], gates2: GateSequence, reg: Qubit[]): Unit is Adj + Ctl { + // NOTE: the last qubit of 'reg' in this context is the auxillary qubit used in the Hadamard test. + operation _endToEndHTcircuit(enc: (LittleEndian => Unit is Adj + Ctl), param1 : Double[], gates1: GateSequence, param2 : Double[], gates2: GateSequence, reg: Qubit[]): Unit is Adj + Ctl { let L = Length(reg) - 1; let g1 = _ApplyGates(param1,gates1,_); let g2 = _ApplyGates(param2,gates2,_); enc(LittleEndian(reg[0..(L-1)])); - within { - H(Tail(reg)); - } apply { - (Controlled g1) ([reg[L]], reg[0..(L-1)]); - within { - X(Tail(reg)); - } apply { - (Controlled g2) ([reg[L]], reg[0..(L-1)]); - (Controlled Z) ([reg[L]], reg[(L-1)]); - } - } + within { + H(Tail(reg)); + } apply { + (Controlled g1) ([reg[L]], reg[0..(L-1)]); + within { + X(Tail(reg)); + } apply { + (Controlled g2) ([reg[L]], reg[0..(L-1)]); + (Controlled Z) ([reg[L]], reg[(L-1)]); + } + } + } + + operation endToEndHTcircuit(enc: (LittleEndian => Unit is Adj + Ctl),param1 : Double[], gates1: GateSequence, param2 : Double[], gates2: GateSequence) : (Qubit[] => Unit is Adj) { + return _endToEndHTcircuit(enc,param1, gates1, param2, gates2, _); } - operation endToEndHTcircuit(enc: (LittleEndian => Unit is Adj + Ctl),param1 : Double[], gates1: GateSequence, param2 : Double[], gates2: GateSequence) : (Qubit[] => Unit is Adj) { - return _endToEndHTcircuit(enc,param1, gates1, param2, gates2, _); - } - - operation HardamardTestPhysical(enc2: (LittleEndian => Unit is Adj + Ctl), param1 : Double[], gates1: GateSequence, param2 : Double[], gates2: GateSequence, nQubits: Int, nMeasurements : Int): Double - { - return 1.0-EstimateFrequencyA(endToEndHTcircuit(enc2,param1,gates1,param2,gates2),measureLastQubit(nQubits), nQubits, nMeasurements); - } - - - - /// # Summary - /// polymorphic classical/quantum gradient estimator - /// - /// # Input - /// ## param - /// circuit parameters - /// - /// ## gates - /// sequence of gates in the circuits - /// - /// ## sg - /// generates quantum encoding of a subject sample (either simulated or true) - /// - /// ## measCount - /// number of true quantum measurements to estimate probabilities. - /// IMPORTANT: measCount==0 implies simulator deployment - /// - /// # Output - /// the gradient - /// - operation EstimateGradient(param : Double[], gates: GateSequence, sg: StateGenerator, nMeasurements : Int) : (Double[]) { - //Synopsis: Suppose (param,gates) define Circ0 - //Suppose (param1,gates1) define Circ1 that implements one-gate derivative of Circ0 - //The expectation derivative is then 2 Re[] = - // Re[] - Re[] - //We observe SEE THEORY that for (Circ1)=(Circ0)' , Re[]==0 - //Thus we are left to compute Re[] = - // 1 - 1/2 < (Z \otimes Id) Circ0 psi - Circ1 psi | (Z \otimes Id) Circ0 psi - Circ1 psi> - //i.e., 1 - HadamardTestResultHack(Circ1,[Z],Circ0) - - - //Now, suppose a gate at which we differentiate is the (Controlled R(\theta))([k0,k1,...,kr],[target]) - //and we want a unitary description of its \theta-derivative. It can be written as - // 1/2 {(Controlled R(\theta'))([k0,k1,...,kr],[target]) - (Controlled Z)([k1,...,kr],[k0])(Controlled R(\theta'))([k0,k1,...,kr],[target])} - let pC = Length(param); - mutable grad = ConstantArray(pC, 0.0); - mutable paramShift = param + [0.0]; - let nQubits = MaxI(NQubitsRequired(gates), sg::NQubits); - - for (gate in gates!) { - set paramShift w/= gate::Index <- (param[gate::Index] + PI()); //Shift the corresponding parameter - // NB: This the *antiderivative* of the bracket - let newDer = 2.0 * HardamardTestPhysical( - sg::Apply, param, gates, paramShift, gates, nQubits + 1, nMeasurements - ) - 1.0; - if (IsEmpty(gate::Span::ControlIndices)) { - //uncontrolled gate - set grad w/= gate::Index <- grad[gate::Index] + newDer; - } else { - //controlled gate - set paramShift w/=gate::Index<-(param[gate::Index]+3.0 * PI()); - //Assumption: any rotation R has the property that R(\theta+2 Pi)=(-1).R(\theta) - // NB: This the *antiderivative* of the bracket - let newDer1 = 2.0 * HardamardTestPhysical( - sg::Apply, param, gates, paramShift, gates, nQubits + 1, - nMeasurements - ) - 1.0; - set grad w/= gate::Index <- (grad[gate::Index] + 0.5* (newDer - newDer1)); - set paramShift w/= gate::Index <-( param[gate::Index] + PI()); //unshift by 2 Pi (for debugging purposes) - } - set paramShift w/= gate::Index <- param[gate::Index]; //unshift this parameter - } - return grad; - - } //GradientHack - - - /// # Summary - /// computes stochastic gradient on one classical sample - /// - /// # Input - /// ## param - /// circuit parameters - /// - /// ## gates - /// sequence of gates in the circuits - /// - /// ## sample - /// sample vector as a raw array - /// - /// ## nMeasurements - /// number of true quantum measurements to estimate probabilities - /// - /// # Output - /// the gradient - /// - operation EstimateGradientFromClassicalSample(tolerance: Double, param : Double[], gates: GateSequence, sample: Double[], nMeasurements : Int) : (Double[]) { - let nQubits = MaxI(FeatureRegisterSize(sample), NQubitsRequired(gates)); - let circEnc = NoisyInputEncoder(tolerance / IntAsDouble(Length(gates!)), sample); - let sg = StateGenerator(nQubits, circEnc); - return EstimateGradient(param, gates, sg, nMeasurements); - } - - //Csharp-frendly adapter for gradient estimation - //'gates' is a array of "flattened" controlled rotation defitions - //each such definition is Int[no.controls+3] in the format [parameter index, Pauli index, target index <,control qubit indices>] - //Pauli index is: 0 for I, 1 for X, 2 for y, 3 for Z - //target index is the index of the target qubit of the rotation - //Sequence of can be empty for uncontroled - operation GradientClassicalSimulationAdapter(tolerance: Double, param : Double[], gates: Int[][], sample: Double[]) : (Double[]) - { - - return EstimateGradientFromClassicalSample(tolerance, param,unFlattenGateSequence(gates),sample,0); - - } - - /// # Summary - /// Get a list of all the classification probabilities. In the from of (prob1,label) pairs. THIS operation is IN DEPRECATION - /// - /// # Input - /// ## samples - /// a container of labeled samples - /// - /// ## sched - /// a schedule to define a subset of samples - /// - /// ## param - /// parameters of the circuits - /// - /// ## gates - /// the sequence of gates in the circuit - /// - /// ## nMeasurements - /// the maximum number of quantum measurements used in the probability estimation - /// - /// # Output - /// TODO - operation ClassificationProbabilitiesClassicalData(samples: LabeledSample[], sched: SamplingSchedule, param: Double[], gates: GateSequence, nMeasurements: Int): - (Double,Int)[] { - mutable N = IsEmpty(samples) - ? NQubitsRequired(gates) - | MaxI(NQubitsRequired(gates), FeatureRegisterSize(_Features(Head(samples)))); - mutable ret = new (Double, Int)[0]; - for (rg in sched!) { - for (ix in rg) { - let sample = samples[ix]; - //agnostic w.r.t. simulator (may still be simulable) - let prob1 = EstimateClassificationProbabilityFromSample(1E-12, param, gates, sample::Features, nMeasurements); - set ret += [(prob1, sample::Label)]; - } - } - - return ret; - } - - operation EstimateClassificationProbabilitiesClassicalDataAdapter(tolerance: Double, samples: Double[][], schedule: Int[][], nQubits: Int, gates: Int[][], param: Double[], measCount: Int): Double[] - { - return EstimateClassificationProbabilitiesClassicalData(tolerance, samples, unFlattenSchedule(schedule), nQubits, unFlattenGateSequence(gates), param, measCount); - } - - /// # Summary - /// tallies hits and misses off a list of probability estimates - /// - /// # Input - /// ## pls - /// a list of estimated probabilities with the corresponding class labels - /// - /// ## bias - /// bias on record - /// - /// # Output - /// (no.hits, no.misses) pair - /// - function TallyHitsMisses(pls: (Double, Int)[], bias: Double) : (Int, Int) { - mutable hits = 0; - mutable misses = 0; - for ((classificationProbability, label) in pls) { - if (label == InferredLabel(bias, classificationProbability)) { - set hits += 1; - } else { - set misses += 1; - } - } - return (hits, misses); - } - - /// # Summary - /// generate a flat list of sample indices where mispredictions occur - /// - /// # Input - /// ## sched - /// a sampling schedule - /// - /// ## pls - /// a list of estimated probabilities with the corresponding class labels - /// - /// ## bias - /// bias on record - /// - /// # Output - /// the list of indices where mispredictions occur - /// - function MissLocations(sched : SamplingSchedule, pls : (Double, Int)[], bias: Double) : Int[] { - mutable ret = new Int[0]; - mutable ir = 0; - - for (rg in sched!) { - for (ix in rg) { - let (prob1, lab) = pls[ir]; - set ir += 1; - if (prob1 + bias > 0.5) { - if (lab < 1) { - set ret += [ix]; - } - } else { - if (lab > 0) { - set ret += [ix]; - } - } - } - } - return ret; - } - - /// # Summary - /// C#-friendly adapter to misclassification tally - /// - /// # Input - /// ## vectors - /// data vectors in flat encoding - /// - /// ## labels - /// array of corresponding class lables - /// - /// ## schedule - /// flat representation of index subset on which the circuit is scored - /// - /// ## param - /// circuit parameters - /// - /// ## gateStructure - /// gate structure in flat representation - /// - /// ## bias - /// prediction bias to be tested - /// - /// ## measCount - /// maximum number of quantum measurements per estimation (measCount==0 implies simulator deployment) - /// - /// # Output - /// the number of misclassifications - /// - operation MisclassificationScoreAdapter(vectors: Double[][], labels: Int[], schedule: Int[][], param: Double[], gateStructure: Int[][], bias: Double, measCount: Int) : Int { - mutable misses = 0; - let samples = unFlattenLabeledSamples(vectors,labels); - let gates = unFlattenGateSequence(gateStructure); - let sched = unFlattenSchedule(schedule); - - let pls = ClassificationProbabilitiesClassicalData(samples,sched,param,gates,measCount); - let biasCurrent = adjustBias(pls, bias, 0.01, 10); - let (h1,m1) = TallyHitsMisses(pls,biasCurrent); - return m1; - } - - /// # Summary - /// Semi-greedily find a bias value that leads to near-minimum misclassification score - /// - /// # Input - /// ## pls - /// a plist of probability estimates and corresponding labels - /// - /// ## bias - /// a fallback value of bias - /// - /// ## tol - /// acceptable tolerance in the bias estimate - /// - /// ## maxIter - /// maximum number of trial bisections - /// - /// # Output - /// the bias estimate - /// - function adjustBias(pls: (Double,Int)[], bias: Double, tol:Double, maxIter: Int) : Double - { - mutable min1 = 1.0; - mutable max0 = 0.0; - for (pl in pls) - { - if (Snd(pl)>0) - { - if (min1 > Fst(pl)) - { - set min1 = Fst(pl); - } - } - else - { - if (max0 < Fst(pl)) - { - set max0 = Fst(pl); - } - } - } - if (max0 <= min1) - { - return 0.5*(1.0-max0-min1); //Gives a perfect classification - } - mutable hBest = 0; - mutable mBest = Length(pls); - mutable bBest = bias; - mutable bLeft = 0.5-max0; - mutable bRight = 0.5-min1; - mutable bestDir = 0; - mutable (hLeft,mLeft) = TallyHitsMisses(pls,bLeft); - if (mLeft < mBest) - { - set bBest = bLeft; - set hBest = hLeft; - set mBest = mLeft; - set bestDir = -1; - } - mutable (hRight, mRight) = TallyHitsMisses(pls,bRight); - - if (mRight < mBest) - { - set bBest = bRight; - set hBest = hRight; - set mBest = mRight; - set bestDir = 1; - } - for (iter in 1..maxIter) - { - if ((bRight - bLeft) 0) //replace the weaker end - { - set bLeft = bMiddle; - set hLeft = hMiddle; - set mLeft = mMiddle; - - if (mMiddle * hBest < hMiddle * mBest) - { - set bBest = bMiddle; - set hBest = hMiddle; - set mBest = mMiddle; - set bestDir = -1; //note that the left end is now better - } - } - else //right end was the weaker end - { - set bRight = bMiddle; - set hRight = hMiddle; - set mRight = mMiddle; - if (mMiddle * hBest < hMiddle * mBest) - { - set bBest = bMiddle; - set hBest = hMiddle; - set mBest = mMiddle; - set bestDir = 1; //note that the right end is now better - } - } - //Done with the left end - } - else - { - if (mMiddle < mRight) - { - //We are better than the right but worse than the left - //Hence the right must be weaker - set bRight = bMiddle; - set hRight = hMiddle; - set mRight = mMiddle; - } - else - { - return bBest; //cannot continue the greedy search - } - } - } //rof iter - return bBest; - } //adjust bias - - /// # Summary - /// Extract a mini batch of samples and wrap the batch as a LabeledSampleContainer - /// - /// # Input - /// ## size - /// desired number of samples in the mini batch - /// - /// ## ixLoc - /// starting index for the batch in the list of locations - /// - /// ## locations - /// list of indices of samples of interest - /// - /// ## samples - /// the container to extract the samples from - /// - /// # Output - /// the mini batched wrapped as a LabeledSampleContainer - /// - /// # Remarks - /// the resulting mini batch can be occasionally shorter than the requested 'size' - /// (when it falls on the tail end of the list of 'locations') - /// - function ExtractMiniBatch(size: Int, ixLoc: Int, locations: Int[], samples: LabeledSample[]): LabeledSample[] { - mutable cnt = Length(locations)-ixLoc; - if (cnt > size) - { - set cnt = size; - } - mutable rgSamples = new LabeledSample[0]; - if (cnt > 0) - { - set rgSamples = new LabeledSample[cnt]; - for (isa in 0..(cnt-1)) - { - set rgSamples w/=isa<- samples[locations[ixLoc+isa]]; - } - } - return rgSamples; - } - - /// # Summary - /// (Randomly) inflate of deflate the source number - operation randomize(src : Double, relativeFuzz : Double) : Double { + operation HardamardTestPhysical(enc2: (LittleEndian => Unit is Adj + Ctl), param1 : Double[], gates1: GateSequence, param2 : Double[], gates2: GateSequence, nQubits: Int, nMeasurements : Int): Double + { + return 1.0-EstimateFrequencyA(endToEndHTcircuit(enc2,param1,gates1,param2,gates2),measureLastQubit(nQubits), nQubits, nMeasurements); + } + + + + /// # Summary + /// polymorphic classical/quantum gradient estimator + /// + /// # Input + /// ## param + /// circuit parameters + /// + /// ## gates + /// sequence of gates in the circuits + /// + /// ## sg + /// generates quantum encoding of a subject sample (either simulated or true) + /// + /// ## measCount + /// number of true quantum measurements to estimate probabilities. + /// IMPORTANT: measCount==0 implies simulator deployment + /// + /// # Output + /// the gradient + /// + operation EstimateGradient(param : Double[], gates: GateSequence, sg: StateGenerator, nMeasurements : Int) : (Double[]) { + //Synopsis: Suppose (param,gates) define Circ0 + //Suppose (param1,gates1) define Circ1 that implements one-gate derivative of Circ0 + //The expectation derivative is then 2 Re[] = + // Re[] - Re[] + //We observe SEE THEORY that for (Circ1)=(Circ0)' , Re[]==0 + //Thus we are left to compute Re[] = + // 1 - 1/2 < (Z \otimes Id) Circ0 psi - Circ1 psi | (Z \otimes Id) Circ0 psi - Circ1 psi> + //i.e., 1 - HadamardTestResultHack(Circ1,[Z],Circ0) + + + //Now, suppose a gate at which we differentiate is the (Controlled R(\theta))([k0,k1,...,kr],[target]) + //and we want a unitary description of its \theta-derivative. It can be written as + // 1/2 {(Controlled R(\theta'))([k0,k1,...,kr],[target]) - (Controlled Z)([k1,...,kr],[k0])(Controlled R(\theta'))([k0,k1,...,kr],[target])} + let pC = Length(param); + mutable grad = ConstantArray(pC, 0.0); + mutable paramShift = param + [0.0]; + let nQubits = MaxI(NQubitsRequired(gates), sg::NQubits); + + for (gate in gates!) { + set paramShift w/= gate::Index <- (param[gate::Index] + PI()); //Shift the corresponding parameter + // NB: This the *antiderivative* of the bracket + let newDer = 2.0 * HardamardTestPhysical( + sg::Apply, param, gates, paramShift, gates, nQubits + 1, nMeasurements + ) - 1.0; + if (IsEmpty(gate::Span::ControlIndices)) { + //uncontrolled gate + set grad w/= gate::Index <- grad[gate::Index] + newDer; + } else { + //controlled gate + set paramShift w/=gate::Index<-(param[gate::Index]+3.0 * PI()); + //Assumption: any rotation R has the property that R(\theta+2 Pi)=(-1).R(\theta) + // NB: This the *antiderivative* of the bracket + let newDer1 = 2.0 * HardamardTestPhysical( + sg::Apply, param, gates, paramShift, gates, nQubits + 1, + nMeasurements + ) - 1.0; + set grad w/= gate::Index <- (grad[gate::Index] + 0.5* (newDer - newDer1)); + set paramShift w/= gate::Index <-( param[gate::Index] + PI()); //unshift by 2 Pi (for debugging purposes) + } + set paramShift w/= gate::Index <- param[gate::Index]; //unshift this parameter + } + return grad; + + } //GradientHack + + + /// # Summary + /// computes stochastic gradient on one classical sample + /// + /// # Input + /// ## param + /// circuit parameters + /// + /// ## gates + /// sequence of gates in the circuits + /// + /// ## sample + /// sample vector as a raw array + /// + /// ## nMeasurements + /// number of true quantum measurements to estimate probabilities + /// + /// # Output + /// the gradient + /// + operation EstimateGradientFromClassicalSample(tolerance: Double, param : Double[], gates: GateSequence, sample: Double[], nMeasurements : Int) : (Double[]) { + let nQubits = MaxI(FeatureRegisterSize(sample), NQubitsRequired(gates)); + let circEnc = NoisyInputEncoder(tolerance / IntAsDouble(Length(gates!)), sample); + let sg = StateGenerator(nQubits, circEnc); + return EstimateGradient(param, gates, sg, nMeasurements); + } + + //Csharp-frendly adapter for gradient estimation + //'gates' is a array of "flattened" controlled rotation defitions + //each such definition is Int[no.controls+3] in the format [parameter index, Pauli index, target index <,control qubit indices>] + //Pauli index is: 0 for I, 1 for X, 2 for y, 3 for Z + //target index is the index of the target qubit of the rotation + //Sequence of can be empty for uncontroled + operation GradientClassicalSimulationAdapter(tolerance: Double, param : Double[], gates: Int[][], sample: Double[]) : (Double[]) + { + + return EstimateGradientFromClassicalSample(tolerance, param,unFlattenGateSequence(gates),sample,0); + + } + + /// # Summary + /// Get a list of all the classification probabilities. In the from of (prob1,label) pairs. THIS operation is IN DEPRECATION + /// + /// # Input + /// ## samples + /// a container of labeled samples + /// + /// ## sched + /// a schedule to define a subset of samples + /// + /// ## param + /// parameters of the circuits + /// + /// ## gates + /// the sequence of gates in the circuit + /// + /// ## nMeasurements + /// the maximum number of quantum measurements used in the probability estimation + /// + /// # Output + /// TODO + operation ClassificationProbabilitiesClassicalData(samples: LabeledSample[], sched: SamplingSchedule, param: Double[], gates: GateSequence, nMeasurements: Int): + (Double,Int)[] { + mutable N = IsEmpty(samples) + ? NQubitsRequired(gates) + | MaxI(NQubitsRequired(gates), FeatureRegisterSize(_Features(Head(samples)))); + mutable ret = new (Double, Int)[0]; + for (rg in sched!) { + for (ix in rg) { + let sample = samples[ix]; + //agnostic w.r.t. simulator (may still be simulable) + let prob1 = EstimateClassificationProbabilityFromSample(1E-12, param, gates, sample::Features, nMeasurements); + set ret += [(prob1, sample::Label)]; + } + } + + return ret; + } + + operation EstimateClassificationProbabilitiesClassicalDataAdapter(tolerance: Double, samples: Double[][], schedule: Int[][], nQubits: Int, gates: Int[][], param: Double[], measCount: Int): Double[] + { + return EstimateClassificationProbabilitiesClassicalData(tolerance, samples, unFlattenSchedule(schedule), nQubits, unFlattenGateSequence(gates), param, measCount); + } + + + /// # Summary + /// generate a flat list of sample indices where mispredictions occur + /// + /// # Input + /// ## sched + /// a sampling schedule + /// + /// ## pls + /// a list of estimated probabilities with the corresponding class labels + /// + /// ## bias + /// bias on record + /// + /// # Output + /// the list of indices where mispredictions occur + /// + function MissLocations(sched : SamplingSchedule, pls : (Double, Int)[], bias: Double) : Int[] { + mutable ret = new Int[0]; + mutable ir = 0; + + for (rg in sched!) { + for (ix in rg) { + let (prob1, lab) = pls[ir]; + set ir += 1; + if (prob1 + bias > 0.5) { + if (lab < 1) { + set ret += [ix]; + } + } else { + if (lab > 0) { + set ret += [ix]; + } + } + } + } + return ret; + } + + /// # Summary + /// C#-friendly adapter to misclassification tally + /// + /// # Input + /// ## vectors + /// data vectors in flat encoding + /// + /// ## labels + /// array of corresponding class lables + /// + /// ## schedule + /// flat representation of index subset on which the circuit is scored + /// + /// ## param + /// circuit parameters + /// + /// ## gateStructure + /// gate structure in flat representation + /// + /// ## bias + /// prediction bias to be tested + /// + /// ## measCount + /// maximum number of quantum measurements per estimation (measCount==0 implies simulator deployment) + /// + /// # Output + /// the number of misclassifications + /// + operation MisclassificationScoreAdapter(vectors: Double[][], labels: Int[], schedule: Int[][], param: Double[], gateStructure: Int[][], bias: Double, measCount: Int) : Int { + mutable misses = 0; + let samples = unFlattenLabeledSamples(vectors,labels); + let gates = unFlattenGateSequence(gateStructure); + let sched = unFlattenSchedule(schedule); + + let pls = ClassificationProbabilitiesClassicalData(samples,sched,param,gates,measCount); + let biasCurrent = _UpdatedBias(pls, bias, 0.01); + let (h1,m1) = TallyHitsMisses(pls,biasCurrent); + return m1; + } + + /// # Summary + /// Extract a mini batch of samples and wrap the batch as a LabeledSampleContainer + /// + /// # Input + /// ## size + /// desired number of samples in the mini batch + /// + /// ## ixLoc + /// starting index for the batch in the list of locations + /// + /// ## locations + /// list of indices of samples of interest + /// + /// ## samples + /// the container to extract the samples from + /// + /// # Output + /// the mini batched wrapped as a LabeledSampleContainer + /// + /// # Remarks + /// the resulting mini batch can be occasionally shorter than the requested 'size' + /// (when it falls on the tail end of the list of 'locations') + /// + function ExtractMiniBatch(size: Int, ixLoc: Int, locations: Int[], samples: LabeledSample[]): LabeledSample[] { + mutable cnt = Length(locations)-ixLoc; + if (cnt > size) + { + set cnt = size; + } + mutable rgSamples = new LabeledSample[0]; + if (cnt > 0) + { + set rgSamples = new LabeledSample[cnt]; + for (isa in 0..(cnt-1)) + { + set rgSamples w/=isa<- samples[locations[ixLoc+isa]]; + } + } + return rgSamples; + } + + /// # Summary + /// (Randomly) inflate of deflate the source number + operation randomize(src : Double, relativeFuzz : Double) : Double { return src * ( 1.0 + relativeFuzz * (Random([0.5, 0.5]) > 0 ? 1.0 | -1.0) ); - } - - - - /// Summary - /// One possible C#-friendly wrap around the StochasticTrainingLoop - /// - operation StochasticTrainingLoopPlainAdapter(vectors: Double[][], labels: Int[], sched: Int[][], schedScore: Int[][], periodScore: Int, - miniBatchSize: Int, param: Double[],gates: Int[][], bias: Double, lrate: Double, maxEpochs: Int, tol: Double, measCount: Int ) : Double[] // - { - let samples = unFlattenLabeledSamples(vectors,labels); - let sch = unFlattenSchedule(sched); - let schScore = unFlattenSchedule(sched); - let gts = unFlattenGateSequence(gates); - let ((h,m),(b,parpar)) = StochasticTrainingLoop(samples, sch, schScore, periodScore, - miniBatchSize, param, gts, bias, lrate, maxEpochs, tol, measCount); - mutable ret = new Double[Length(parpar)+3]; - set ret w/=0<-IntAsDouble (h); - set ret w/=1<-IntAsDouble (m); - set ret w/=2<-b; - for (j in 0..(Length(parpar)-1)) - { - set ret w/=(j+3)<-parpar[j]; - } - return ret; - } + } + + + + /// Summary + /// One possible C#-friendly wrap around the StochasticTrainingLoop + /// + operation StochasticTrainingLoopPlainAdapter(vectors: Double[][], labels: Int[], sched: Int[][], schedScore: Int[][], periodScore: Int, + miniBatchSize: Int, param: Double[],gates: Int[][], bias: Double, lrate: Double, maxEpochs: Int, tol: Double, measCount: Int ) : Double[] // + { + let samples = unFlattenLabeledSamples(vectors,labels); + let sch = unFlattenSchedule(sched); + let schScore = unFlattenSchedule(sched); + let gts = unFlattenGateSequence(gates); + let ((h,m),(b,parpar)) = StochasticTrainingLoop(samples, sch, schScore, periodScore, + miniBatchSize, param, gts, bias, lrate, maxEpochs, tol, measCount); + mutable ret = new Double[Length(parpar)+3]; + set ret w/=0<-IntAsDouble (h); + set ret w/=1<-IntAsDouble (m); + set ret w/=2<-b; + for (j in 0..(Length(parpar)-1)) + { + set ret w/=(j+3)<-parpar[j]; + } + return ret; + } } diff --git a/MachineLearning/src/Runtime/Runtime.csproj b/MachineLearning/src/Runtime/Runtime.csproj index e9e8700740f..5e1fb341f6c 100644 --- a/MachineLearning/src/Runtime/Runtime.csproj +++ b/MachineLearning/src/Runtime/Runtime.csproj @@ -10,7 +10,7 @@ - + diff --git a/MachineLearning/src/Runtime/Training.qs b/MachineLearning/src/Runtime/Training.qs index dc61875bd55..a6072d499da 100644 --- a/MachineLearning/src/Runtime/Training.qs +++ b/MachineLearning/src/Runtime/Training.qs @@ -5,504 +5,442 @@ namespace Microsoft.Quantum.MachineLearning { open Microsoft.Quantum.Convert; open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Canon; - - /// # Summary - /// Returns a bias value that leads to near-minimum misclassification score. - /// - /// # Remarks - /// Note that `probabilities` and `labels` will not in general have the same - /// length, as `labels` is indexed by a training set index while `probabilities` - /// is indexed by the given sampling schedule. - function _UpdatedBias(probabilities: Double[], labels: Int[], sched: SamplingSchedule, bias: Double, tolerance: Double, maxIter: Int) : Double { - mutable min1 = 1.0; - mutable max0 = 0.0; - mutable ipro = 0; - for (rg in sched!) { - for (ix in rg) { - let prob = probabilities[ipro]; - let lab = labels[ix]; - if (lab > 0) { - if (min1 > prob) { - set min1 = prob; - } - } else { - if (max0 < prob) { - set max0 = prob; - } - } - set ipro += 1; - } - } - // Exit early if we can find a perfect classification. - if (max0 <= min1) { - return 0.5 * (1.0 - max0 - min1); - } - mutable mBest = Length(probabilities); - mutable bBest = bias; - mutable bLeft = 0.5 - max0; - mutable bRight = 0.5 - min1; - mutable bestDir = 0; - mutable proposedLabels = InferredLabels(bLeft, probabilities); - mutable mLeft = NMismatches(proposedLabels, labels, sched); - if (mLeft < mBest) { - set bBest = bLeft; - set mBest = mLeft; - set bestDir = -1; - } - set proposedLabels = InferredLabels(bRight, probabilities); - mutable mRight = NMismatches(proposedLabels, labels, sched); - if (mRight < mBest) { - set bBest = bRight; - set mBest = mRight; - set bestDir = 1; - } - - for (iter in 1..maxIter) { - if ((bRight - bLeft) < tolerance) - { - return bBest; - } - let bMiddle = 0.5 * (bLeft+bRight); - set proposedLabels = InferredLabels(bMiddle, probabilities); - let mMiddle = NMismatches(proposedLabels, labels, sched); - - if (mMiddle < mLeft) { - if (bestDir > 0) { //replace the weaker end - set bLeft = bMiddle; - set mLeft = mMiddle; - - if (mMiddle < mBest) { - set bBest = bMiddle; - set mBest = mMiddle; - set bestDir = -1; //note that the left end is now better - } - } else { //right end was the weaker end - set bRight = bMiddle; - set mRight = mMiddle; - if (mMiddle < mBest) { - set bBest = bMiddle; - set mBest = mMiddle; - set bestDir = 1; //note that the right end is now better - } - } - //Done with the left end - } else { - if (mMiddle < mRight) { - // We are better than the right but worse than the left. - // Hence the right must be weaker. - set bRight = bMiddle; - set mRight = mMiddle; - } else { - return bBest; //cannot continue the greedy search - } - } - - } - return bias; - } //recomputeBias - - operation TrainSequentialClassifier( - nQubits: Int, - gates: GateSequence, - parameterSource: Double[][], - samples: LabeledSample[], - trainingSchedule: SamplingSchedule, - validationSchedule: SamplingSchedule, - learningRate: Double, - tolerance: Double, - miniBatchSize: Int, - maxEpochs: Int, - nMeasurements: Int - ) : (Double[], Double) { - mutable retParam = [-1E12]; - mutable retBias = -2.0; //Indicates non-informative start - mutable bestValidation = Length(samples) + 1; - - let features = Mapped(_Features, samples); - let labels = Mapped(_Label, samples); - - let cTechnicalIter = 10; //10 iterations are sufficient for bias adjustment in most cases - for (idxStart in 0..(Length(parameterSource) - 1)) { - Message($"Beginning training at start point #{idxStart}..."); - let ((h, m), (b, parpar)) = StochasticTrainingLoop( - samples, trainingSchedule, trainingSchedule, 1, miniBatchSize, - parameterSource[idxStart], gates, 0.0, learningRate, maxEpochs, - tolerance, nMeasurements - ); - let probsValidation = EstimateClassificationProbabilitiesClassicalData( - tolerance, features, validationSchedule, nQubits, - gates, parpar, nMeasurements - ); - //Estimate bias here! - let localBias = _UpdatedBias( - probsValidation, - labels, - validationSchedule, - 0.0, - tolerance, - cTechnicalIter - ); - let localPL = InferredLabels(localBias, probsValidation); - let localMisses = NMismatches(localPL, labels, validationSchedule); - if (bestValidation > localMisses) { - set bestValidation = localMisses; - set retParam = parpar; - set retBias = localBias; - } - - } - return (retParam, retBias); - } - - /// # Summary - /// Using a flat description of a classification model, find a good local optimum - /// for the model parameters and a related calssification bias - /// - /// # Input - /// ## nQubits - /// the number of qubits used for data encoding - /// - /// ## gates - /// flat characterization of circuit structure. Each element is [parameterIndex, pauliCode, targetQubit\,sequence of control qubits\] - /// - /// ## parameterSource - /// an array of parameter arrays, to be used as SGD starting points - /// - /// ## trainingSet - /// the set of training samples - /// - /// ## trainingLabels - /// the set of training labels - /// - /// ## trainingSchedule - /// defines a subset of training data actually used in the training process - /// - /// ## validatioSchedule - /// defines a subset of training data used for validation and computation of the *bias* - /// - /// ## learningRate - /// initial learning rate for stochastic gradient descent - /// - /// ## tolerance - /// sufficient absolute precision of parameter updates - /// - /// ## learningRate - /// initial learning rate for stochastic gradient descent - /// - /// ## miniBatchSize - /// maximum size of SGD mini batches - /// - /// ## maxEpochs - /// limit to the number of training epochs - /// - /// ## nMeasurenets - /// number of the measurement cycles to be used for estimation of each probability - /// - /// # Output - /// (Array of optimal parameters, optimal validation *bias*) - /// - operation TrainQcccSequential(nQubits: Int, gates: Int[][], parameterSource: Double[][], trainingSet: Double[][], trainingLabels: Int[], trainingSchedule: Int[][], validationSchedule: Int[][], - learningRate: Double, tolerance: Double, miniBatchSize: Int, maxEpochs: Int, nMeasurements: Int) : (Double[],Double) { - let samples = unFlattenLabeledSamples(trainingSet,trainingLabels); - let sch = unFlattenSchedule(trainingSchedule); - let schValidate = unFlattenSchedule(validationSchedule); - let gateSequence = unFlattenGateSequence(gates); - - return TrainSequentialClassifier( - nQubits, gateSequence, parameterSource, samples, - sch, schValidate, learningRate, tolerance, miniBatchSize, - maxEpochs, nMeasurements - ); - } //TrainQcccSequential - - /// # Summary - /// attempts a single parameter update in the direction of mini batch gradient - /// - /// # Input - /// ## miniBatch - /// container of labeled samples in the mini batch - /// - /// ## param - /// circuit parameters - /// - /// ## gates - /// sequence of gates in the circuits - /// - /// ## lrate - /// the learning rate - /// - /// ## measCount - /// number of true quantum measurements to estimate probabilities. - /// - /// # Output - /// (utility, (new)parameters) pair - /// - operation OneStochasticTrainingStep( - tolerance: Double, miniBatch: LabeledSample[], param: Double[], gates: GateSequence, - lrate: Double, measCount: Int - ) : (Double, Double[]) { - mutable upParam = new Double[Length(param)]; - mutable batchGradient = ConstantArray(Length(param), 0.0); - - for (samp in miniBatch) { - mutable err = IntAsDouble(samp::Label); - if (err < 1.0) { - set err = -1.0; //class 0 misclassified to class 1; strive to reduce the probability - } - let grad = EstimateGradientFromClassicalSample(tolerance, param, gates, samp::Features, measCount); - for (ip in 0..(Length(param) - 1)) { - // GradientClassicalSample actually computes antigradient, but err*grad corrects it back to gradient - set batchGradient w/= ip <- (batchGradient[ip] + lrate * err * grad[ip]); - } - - } - for (ip in 0..(Length(param)-1)) { - set upParam w/= ip <- (param[ip] + batchGradient[ip]); - } - return (SquaredNorm(batchGradient), upParam); //TODO:REVIEW: Ok to interpret utility as size of the overall move? - } - - - /// # Summary - /// Perform one epoch of circuit training on a subset of data samples to a quantum simulator - /// - /// # Input - /// ## samples - /// a container of available data samples - /// - /// ## sched - /// a schedule of the data subset for this training loop - /// - /// ## schedScore - /// defines a (possibly different) data subset on which accuracy scoring is performed - /// - /// ## periodScore - /// number of blind gradient steps between scoring points (performance tool, set to 1 for best accuracy) - /// - /// ## miniBatchSize - /// number of samples in a gradient mini batch - /// - /// ## param - /// initial parameter vector - /// - /// ## gates - /// sequence of gates in the circuit - /// - /// ## bias - /// reserved for future use; originally - initial prediction bias - /// - /// ## lrate - /// learning rate - /// - /// ## measCount - /// number of true quantum measurements to estimate probabilities. - /// - operation OneStochasticTrainingEpoch(samples: LabeledSample[], sched: SamplingSchedule, schedScore: SamplingSchedule, periodScore: Int, - miniBatchSize: Int, param: Double[], gates: GateSequence, bias: Double, lrate: Double, tolerance: Double, measCount: Int, - h0: Int, m0: Int): ((Int,Int),(Double,Double[])) - { - let HARDCODEDmaxIter = 10; - let HARDCODEDunderage = 3; //4/26 slack greater than 3 is not recommended - - - mutable hBest = h0; - mutable mBest = m0; - mutable biasBest = bias; - - let pls = ClassificationProbabilitiesClassicalData(samples, schedScore, param, gates, measCount); - let (h2,m2) = TallyHitsMisses(pls,biasBest); - let missLocations = MissLocations(schedScore, pls, biasBest); - - mutable paramBest = param; - mutable paramCurrent = paramBest; - mutable biasCurrent = biasBest; - - //An epoch is just an attempt to update the parameters by learning from misses based on LKG parameters - for (ixLoc in 0..miniBatchSize..(Length(missLocations) - 1)) { - let miniBatch = ExtractMiniBatch(miniBatchSize, ixLoc, missLocations, samples); - let (utility,upParam) = OneStochasticTrainingStep(tolerance, miniBatch, paramCurrent, gates, lrate, measCount); - if (Microsoft.Quantum.Math.AbsD(utility) > 0.0000001) { - //There had been some parameter update - if (utility > 0.0) { //good parameter update - set paramCurrent = upParam; - let plsCurrent = ClassificationProbabilitiesClassicalData(samples, schedScore, paramCurrent, gates, measCount); - set biasCurrent = adjustBias(plsCurrent, bias, tolerance, HARDCODEDmaxIter); - let (h1,m1) = TallyHitsMisses(plsCurrent,biasCurrent); - if (m1 < mBest + HARDCODEDunderage) { - //we allow limited non-greediness - if (m1 < mBest) { - set hBest = h1; - set mBest = m1; - set paramBest = paramCurrent; - set biasBest = biasCurrent; - } - } else { - //otherwise we scrap the parameter update - set paramCurrent = paramBest; - set biasCurrent = biasBest; - } - } - - } - - } - return ((hBest, mBest), (biasBest, paramBest)); - } - - //Make some oblivious gradien descent steps without checking the prediction quality - operation OneUncontrolledStochasticTrainingEpoch(samples: LabeledSample[], sched: SamplingSchedule, schedScore: SamplingSchedule, periodScore: Int, - miniBatchSize: Int, param: Double[], gates: GateSequence, bias: Double, lrate: Double, tolerance: Double, measCount: Int): ((Int,Int),(Double,Double[])) - { - let HARDCODEDmaxIter = 10; //TODO:MUST: tolerance and maxIter cannot stay hardcoded - let pls = ClassificationProbabilitiesClassicalData(samples, schedScore, param, gates, measCount); - mutable biasBest = adjustBias(pls, bias, tolerance, HARDCODEDmaxIter); - let (h0,m0) = TallyHitsMisses(pls,biasBest); // ClassificationScoreSimulated(samples, schedScore, param, gates, bias); //Deprecated - mutable hCur = h0; - mutable mCur = m0; - let missLocations = MissLocations(schedScore, pls, biasBest); - - mutable paramBest = param; - mutable paramCurrent = paramBest; - mutable biasCurrent = biasBest; - - //An epoch is just an attempt to update the parameters by learning from misses based on LKG parameters - for (ixLoc in 0..miniBatchSize..(Length(missLocations) - 1)) { - let miniBatch = ExtractMiniBatch(miniBatchSize,ixLoc,missLocations,samples); - let (utility,upParam) = OneStochasticTrainingStep(tolerance, miniBatch, paramCurrent, gates, lrate, measCount); - if (AbsD(utility) > 0.0000001) { - //There had been some parameter update - if (utility > 0.0) { //good parameter update - set paramCurrent = upParam; - let plsCurrent = ClassificationProbabilitiesClassicalData(samples, schedScore, paramCurrent, gates, measCount); - set biasCurrent = adjustBias(plsCurrent, bias, tolerance, HARDCODEDmaxIter); - let (h1,m1) = TallyHitsMisses(plsCurrent,biasCurrent); - set hCur = h1; - set mCur = m1; - } - - } - - } - return ((hCur, mCur),(biasCurrent,paramCurrent)); - } //OneUncontrolledStochasticTrainingEpoch - - /// # Summary - /// Run a full circuit training loop on a subset of data samples - /// - /// # Input - /// ## samples - /// a container of available data samples - /// - /// ## sched - /// a schedule of the data subset for this training loop - /// - /// ## schedScore - /// defines a (possibly different) data subset on which accuracy scoring is performed - /// - /// ## periodScore - /// number of blind gradient steps between scoring points (performance tool, set to 1 for best accuracy) - /// - /// ## miniBatchSize - /// number of samples in a gradient mini batch - /// - /// ## param - /// initial parameter vector - /// - /// ## gates - /// sequence of gates in the circuit - /// - /// ## bias - /// reserved for future use; originally - initial prediction bias - /// - /// ## lrate - /// learning rate - /// - /// ## maxEpochs - /// maximum number of epochs in this loop - /// - /// ## tol - /// tolerance: acceptable misprediction rate in training - /// - /// ## measCount - /// number of true quantum measurements to estimate probabilities. - /// IMPORTANT: measCount==0 implies simulator deployment - /// - /// # Output - /// ((no.hits,no.misses),(opt.bias,opt.parameters)) - /// - operation StochasticTrainingLoop(samples: LabeledSample[], sched: SamplingSchedule, schedScore: SamplingSchedule, periodScore: Int, - miniBatchSizeInital: Int, param: Double[], gates: GateSequence, bias: Double, lrateInitial: Double, maxEpochs: Int, tol: Double, measCount: Int): ((Int,Int),(Double,Double[])) - { - let HARDCODEDmaxIter = 10; - //const - let manyNoops = 4; - //const - let relFuzz = 0.01; - let HARDCODEDmaxNoops = 2*manyNoops; - mutable pls = ClassificationProbabilitiesClassicalData(samples, schedScore, param, gates, measCount); - mutable biasBest = adjustBias(pls, bias, tol, HARDCODEDmaxIter); - let (h0,m0) = TallyHitsMisses(pls,biasBest); - mutable hBest = h0; - mutable mBest = m0; - mutable paramBest = param; - mutable paramCurrent = param; - mutable biasCurrent = biasBest; - - //reintroducing learning rate heuristics - mutable lrate = lrateInitial; - mutable batchSize = miniBatchSizeInital; - mutable noopCount = 0; - mutable upBias = biasCurrent; - mutable upParam = paramCurrent; - for (ep in 1..maxEpochs) { - let ((h1,m1),(upB,upP)) = OneStochasticTrainingEpoch(samples, sched, schedScore, periodScore, - batchSize, paramCurrent, gates, biasCurrent, lrate, tol, measCount, hBest, mBest); - set upBias = upB; - set upParam = upP; - if (m1 < mBest) - { - set hBest = h1; - set mBest = m1; - set paramBest = upParam; - set biasBest = upBias; - if (IntAsDouble (mBest)/IntAsDouble (mBest+hBest)< tol) //Terminate based on tolerance - { - return ((hBest,mBest),(biasBest,paramBest)); - } - set noopCount = 0; //Reset the counter of consequtive noops - set lrate = lrateInitial; - set batchSize = miniBatchSizeInital; - } - if (NearlyEqualD(biasCurrent,upBias) and _AllNearlyEqualD(paramCurrent,upParam)) - { - set noopCount = noopCount+1; - if (noopCount > manyNoops) - { - if (noopCount > HARDCODEDmaxNoops) - { - return ((hBest,mBest),(biasBest,paramBest)); //Too many non-steps. Continuation makes no sense - } - else - { - set upBias = randomize(upBias, relFuzz); - set upParam = ForEach(randomize(_, relFuzz), upParam); - } - } - set batchSize = noopCount; //batchSize + 1; //Try to fuzz things up with smaller batch count - //and heat up a bit - set lrate = 1.25*lrate; - } - else - { - set noopCount = 0; //Reset the counter of consequtive noops - set lrate = lrateInitial; - set batchSize = miniBatchSizeInital; - } - set paramCurrent = upParam; - set biasCurrent = upBias; - } - - return ((hBest,mBest),(biasBest,paramBest)); - } + open Microsoft.Quantum.Optimization; + + function _MisclassificationRate(probabilities : Double[], labels : Int[], bias : Double) : Double { + let proposedLabels = InferredLabels(bias, probabilities); + return IntAsDouble(NMismatches(proposedLabels, labels)) / IntAsDouble(Length(probabilities)); + } + + /// # Summary + /// Returns a bias value that leads to near-minimum misclassification score. + function _UpdatedBias(labeledProbabilities: (Double, Int)[], bias: Double, tolerance: Double) : Double { + mutable min1 = 1.0; + mutable max0 = 0.0; + + // Find the range of classification probabilities for each class. + for ((probability, label) in labeledProbabilities) { + if (label == 1) { + if (min1 > probability) { + set min1 = probability; + } + } else { + if (max0 < probability) { + set max0 = probability; + } + } + } + + // Exit early if we can find a perfect classification. + if (max0 <= min1) { + return 0.5 * (1.0 - max0 - min1); + } + + // If we can't find a perfect classification, minimize to find + // the best feasible bias. + let optimum = LocalUnivariateMinimum( + _MisclassificationRate(Mapped(Fst, labeledProbabilities), Mapped(Snd, labeledProbabilities), _), + (0.5 - max0, 0.5 - min1), + tolerance + ); + return optimum::Coordinate; + } + + operation TrainSequentialClassifier( + nQubits: Int, + gates: GateSequence, + parameterSource: Double[][], + samples: LabeledSample[], + trainingSchedule: SamplingSchedule, + validationSchedule: SamplingSchedule, + learningRate: Double, + tolerance: Double, + miniBatchSize: Int, + maxEpochs: Int, + nMeasurements: Int + ) : (Double[], Double) { + mutable retParam = [-1E12]; + mutable retBias = -2.0; //Indicates non-informative start + mutable bestValidation = Length(samples) + 1; + + let features = Mapped(_Features, samples); + let labels = Mapped(_Label, samples); + + for (idxStart in 0..(Length(parameterSource) - 1)) { + Message($"Beginning training at start point #{idxStart}..."); + let ((h, m), (b, parpar)) = StochasticTrainingLoop( + samples, trainingSchedule, trainingSchedule, 1, miniBatchSize, + parameterSource[idxStart], gates, 0.0, learningRate, maxEpochs, + tolerance, nMeasurements + ); + let probsValidation = EstimateClassificationProbabilitiesClassicalData( + tolerance, features, validationSchedule, nQubits, + gates, parpar, nMeasurements + ); + // Find the best bias for the new classification parameters. + let localBias = _UpdatedBias( + Zip(probsValidation, Sampled(validationSchedule, labels)), + 0.0, + tolerance + ); + let localPL = InferredLabels(localBias, probsValidation); + let localMisses = NMismatches(localPL, Sampled(validationSchedule, labels)); + if (bestValidation > localMisses) { + set bestValidation = localMisses; + set retParam = parpar; + set retBias = localBias; + } + + } + return (retParam, retBias); + } + + /// # Summary + /// Using a flat description of a classification model, find a good local optimum + /// for the model parameters and a related calssification bias + /// + /// # Input + /// ## nQubits + /// the number of qubits used for data encoding + /// + /// ## gates + /// flat characterization of circuit structure. Each element is [parameterIndex, pauliCode, targetQubit\,sequence of control qubits\] + /// + /// ## parameterSource + /// an array of parameter arrays, to be used as SGD starting points + /// + /// ## trainingSet + /// the set of training samples + /// + /// ## trainingLabels + /// the set of training labels + /// + /// ## trainingSchedule + /// defines a subset of training data actually used in the training process + /// + /// ## validatioSchedule + /// defines a subset of training data used for validation and computation of the *bias* + /// + /// ## learningRate + /// initial learning rate for stochastic gradient descent + /// + /// ## tolerance + /// sufficient absolute precision of parameter updates + /// + /// ## learningRate + /// initial learning rate for stochastic gradient descent + /// + /// ## miniBatchSize + /// maximum size of SGD mini batches + /// + /// ## maxEpochs + /// limit to the number of training epochs + /// + /// ## nMeasurenets + /// number of the measurement cycles to be used for estimation of each probability + /// + /// # Output + /// (Array of optimal parameters, optimal validation *bias*) + /// + operation TrainQcccSequential(nQubits: Int, gates: Int[][], parameterSource: Double[][], trainingSet: Double[][], trainingLabels: Int[], trainingSchedule: Int[][], validationSchedule: Int[][], + learningRate: Double, tolerance: Double, miniBatchSize: Int, maxEpochs: Int, nMeasurements: Int) : (Double[],Double) { + let samples = unFlattenLabeledSamples(trainingSet,trainingLabels); + let sch = unFlattenSchedule(trainingSchedule); + let schValidate = unFlattenSchedule(validationSchedule); + let gateSequence = unFlattenGateSequence(gates); + + return TrainSequentialClassifier( + nQubits, gateSequence, parameterSource, samples, + sch, schValidate, learningRate, tolerance, miniBatchSize, + maxEpochs, nMeasurements + ); + } //TrainQcccSequential + + /// # Summary + /// attempts a single parameter update in the direction of mini batch gradient + /// + /// # Input + /// ## miniBatch + /// container of labeled samples in the mini batch + /// + /// ## param + /// circuit parameters + /// + /// ## gates + /// sequence of gates in the circuits + /// + /// ## lrate + /// the learning rate + /// + /// ## measCount + /// number of true quantum measurements to estimate probabilities. + /// + /// # Output + /// (utility, (new)parameters) pair + /// + operation OneStochasticTrainingStep( + tolerance: Double, miniBatch: LabeledSample[], param: Double[], gates: GateSequence, + lrate: Double, measCount: Int + ) : (Double, Double[]) { + mutable upParam = new Double[Length(param)]; + mutable batchGradient = ConstantArray(Length(param), 0.0); + + for (samp in miniBatch) { + mutable err = IntAsDouble(samp::Label); + if (err < 1.0) { + set err = -1.0; //class 0 misclassified to class 1; strive to reduce the probability + } + let grad = EstimateGradientFromClassicalSample(tolerance, param, gates, samp::Features, measCount); + for (ip in 0..(Length(param) - 1)) { + // GradientClassicalSample actually computes antigradient, but err*grad corrects it back to gradient + set batchGradient w/= ip <- (batchGradient[ip] + lrate * err * grad[ip]); + } + + } + for (ip in 0..(Length(param)-1)) { + set upParam w/= ip <- (param[ip] + batchGradient[ip]); + } + return (SquaredNorm(batchGradient), upParam); //TODO:REVIEW: Ok to interpret utility as size of the overall move? + } + + + /// # Summary + /// Perform one epoch of circuit training on a subset of data samples to a quantum simulator + /// + /// # Input + /// ## samples + /// a container of available data samples + /// + /// ## sched + /// a schedule of the data subset for this training loop + /// + /// ## schedScore + /// defines a (possibly different) data subset on which accuracy scoring is performed + /// + /// ## periodScore + /// number of blind gradient steps between scoring points (performance tool, set to 1 for best accuracy) + /// + /// ## miniBatchSize + /// number of samples in a gradient mini batch + /// + /// ## param + /// initial parameter vector + /// + /// ## gates + /// sequence of gates in the circuit + /// + /// ## bias + /// reserved for future use; originally - initial prediction bias + /// + /// ## lrate + /// learning rate + /// + /// ## measCount + /// number of true quantum measurements to estimate probabilities. + /// + operation OneStochasticTrainingEpoch(samples: LabeledSample[], sched: SamplingSchedule, schedScore: SamplingSchedule, periodScore: Int, + miniBatchSize: Int, param: Double[], gates: GateSequence, bias: Double, lrate: Double, tolerance: Double, measCount: Int, + h0: Int, m0: Int): ((Int,Int),(Double,Double[])) + { + let HARDCODEDunderage = 3; //4/26 slack greater than 3 is not recommended + + + mutable hBest = h0; + mutable mBest = m0; + mutable biasBest = bias; + + let pls = ClassificationProbabilitiesClassicalData(samples, schedScore, param, gates, measCount); + let (h2,m2) = TallyHitsMisses(pls,biasBest); + let missLocations = MissLocations(schedScore, pls, biasBest); + + mutable paramBest = param; + mutable paramCurrent = paramBest; + mutable biasCurrent = biasBest; + + //An epoch is just an attempt to update the parameters by learning from misses based on LKG parameters + for (ixLoc in 0..miniBatchSize..(Length(missLocations) - 1)) { + let miniBatch = ExtractMiniBatch(miniBatchSize, ixLoc, missLocations, samples); + let (utility,upParam) = OneStochasticTrainingStep(tolerance, miniBatch, paramCurrent, gates, lrate, measCount); + if (Microsoft.Quantum.Math.AbsD(utility) > 0.0000001) { + //There had been some parameter update + if (utility > 0.0) { //good parameter update + set paramCurrent = upParam; + let plsCurrent = ClassificationProbabilitiesClassicalData(samples, schedScore, paramCurrent, gates, measCount); + set biasCurrent = _UpdatedBias(plsCurrent, bias, tolerance); + let (h1,m1) = TallyHitsMisses(plsCurrent,biasCurrent); + if (m1 < mBest + HARDCODEDunderage) { + //we allow limited non-greediness + if (m1 < mBest) { + set hBest = h1; + set mBest = m1; + set paramBest = paramCurrent; + set biasBest = biasCurrent; + } + } else { + //otherwise we scrap the parameter update + set paramCurrent = paramBest; + set biasCurrent = biasBest; + } + } + + } + + } + return ((hBest, mBest), (biasBest, paramBest)); + } + + //Make some oblivious gradien descent steps without checking the prediction quality + operation OneUncontrolledStochasticTrainingEpoch(samples: LabeledSample[], sched: SamplingSchedule, schedScore: SamplingSchedule, periodScore: Int, + miniBatchSize: Int, param: Double[], gates: GateSequence, bias: Double, lrate: Double, tolerance: Double, measCount: Int): ((Int,Int),(Double,Double[])) + { + let pls = ClassificationProbabilitiesClassicalData(samples, schedScore, param, gates, measCount); + mutable biasBest = _UpdatedBias(pls, bias, tolerance); + let (h0,m0) = TallyHitsMisses(pls,biasBest); // ClassificationScoreSimulated(samples, schedScore, param, gates, bias); //Deprecated + mutable hCur = h0; + mutable mCur = m0; + let missLocations = MissLocations(schedScore, pls, biasBest); + + mutable paramBest = param; + mutable paramCurrent = paramBest; + mutable biasCurrent = biasBest; + + //An epoch is just an attempt to update the parameters by learning from misses based on LKG parameters + for (ixLoc in 0..miniBatchSize..(Length(missLocations) - 1)) { + let miniBatch = ExtractMiniBatch(miniBatchSize,ixLoc,missLocations,samples); + let (utility,upParam) = OneStochasticTrainingStep(tolerance, miniBatch, paramCurrent, gates, lrate, measCount); + if (AbsD(utility) > 0.0000001) { + //There had been some parameter update + if (utility > 0.0) { //good parameter update + set paramCurrent = upParam; + let plsCurrent = ClassificationProbabilitiesClassicalData(samples, schedScore, paramCurrent, gates, measCount); + set biasCurrent = _UpdatedBias(plsCurrent, bias, tolerance); + let (h1,m1) = TallyHitsMisses(plsCurrent,biasCurrent); + set hCur = h1; + set mCur = m1; + } + + } + + } + return ((hCur, mCur),(biasCurrent,paramCurrent)); + } //OneUncontrolledStochasticTrainingEpoch + + /// # Summary + /// Run a full circuit training loop on a subset of data samples + /// + /// # Input + /// ## samples + /// a container of available data samples + /// + /// ## sched + /// a schedule of the data subset for this training loop + /// + /// ## schedScore + /// defines a (possibly different) data subset on which accuracy scoring is performed + /// + /// ## periodScore + /// number of blind gradient steps between scoring points (performance tool, set to 1 for best accuracy) + /// + /// ## miniBatchSize + /// number of samples in a gradient mini batch + /// + /// ## param + /// initial parameter vector + /// + /// ## gates + /// sequence of gates in the circuit + /// + /// ## bias + /// reserved for future use; originally - initial prediction bias + /// + /// ## lrate + /// learning rate + /// + /// ## maxEpochs + /// maximum number of epochs in this loop + /// + /// ## tol + /// tolerance: acceptable misprediction rate in training + /// + /// ## measCount + /// number of true quantum measurements to estimate probabilities. + /// IMPORTANT: measCount==0 implies simulator deployment + /// + /// # Output + /// ((no.hits,no.misses),(opt.bias,opt.parameters)) + /// + operation StochasticTrainingLoop(samples: LabeledSample[], sched: SamplingSchedule, schedScore: SamplingSchedule, periodScore: Int, + miniBatchSizeInital: Int, param: Double[], gates: GateSequence, bias: Double, lrateInitial: Double, maxEpochs: Int, tol: Double, measCount: Int): ((Int,Int),(Double,Double[])) + { + //const + let manyNoops = 4; + //const + let relFuzz = 0.01; + let HARDCODEDmaxNoops = 2*manyNoops; + mutable pls = ClassificationProbabilitiesClassicalData(samples, schedScore, param, gates, measCount); + mutable biasBest = _UpdatedBias(pls, bias, tol); + let (h0, m0) = TallyHitsMisses(pls,biasBest); + mutable hBest = h0; + mutable mBest = m0; + mutable paramBest = param; + mutable paramCurrent = param; + mutable biasCurrent = biasBest; + + //reintroducing learning rate heuristics + mutable lrate = lrateInitial; + mutable batchSize = miniBatchSizeInital; + mutable noopCount = 0; + mutable upBias = biasCurrent; + mutable upParam = paramCurrent; + for (ep in 1..maxEpochs) { + let ((h1,m1),(upB,upP)) = OneStochasticTrainingEpoch(samples, sched, schedScore, periodScore, + batchSize, paramCurrent, gates, biasCurrent, lrate, tol, measCount, hBest, mBest); + set upBias = upB; + set upParam = upP; + if (m1 < mBest) + { + set hBest = h1; + set mBest = m1; + set paramBest = upParam; + set biasBest = upBias; + if (IntAsDouble (mBest)/IntAsDouble (mBest+hBest)< tol) //Terminate based on tolerance + { + return ((hBest,mBest),(biasBest,paramBest)); + } + set noopCount = 0; //Reset the counter of consequtive noops + set lrate = lrateInitial; + set batchSize = miniBatchSizeInital; + } + if (NearlyEqualD(biasCurrent,upBias) and _AllNearlyEqualD(paramCurrent,upParam)) + { + set noopCount = noopCount+1; + if (noopCount > manyNoops) + { + if (noopCount > HARDCODEDmaxNoops) + { + return ((hBest,mBest),(biasBest,paramBest)); //Too many non-steps. Continuation makes no sense + } + else + { + set upBias = randomize(upBias, relFuzz); + set upParam = ForEach(randomize(_, relFuzz), upParam); + } + } + set batchSize = noopCount; //batchSize + 1; //Try to fuzz things up with smaller batch count + //and heat up a bit + set lrate = 1.25*lrate; + } + else + { + set noopCount = 0; //Reset the counter of consequtive noops + set lrate = lrateInitial; + set batchSize = miniBatchSizeInital; + } + set paramCurrent = upParam; + set biasCurrent = upBias; + } + + return ((hBest,mBest),(biasBest,paramBest)); + } } diff --git a/MachineLearning/src/Runtime/Types.qs b/MachineLearning/src/Runtime/Types.qs index 7b0d668294e..759acbc4094 100644 --- a/MachineLearning/src/Runtime/Types.qs +++ b/MachineLearning/src/Runtime/Types.qs @@ -2,71 +2,92 @@ // Licensed under the MIT License. namespace Microsoft.Quantum.MachineLearning { - open Microsoft.Quantum.Canon; - open Microsoft.Quantum.Arithmetic; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Arithmetic; - /// Qubit span of a multicontrolled single-qubit gate - newtype GateSpan = ( - TargetIndex: Int, - ControlIndices: Int[] - ); + /// Qubit span of a multicontrolled single-qubit gate + newtype GateSpan = ( + TargetIndex: Int, + ControlIndices: Int[] + ); - /// One-parameter controlled rotation gate triplet: - /// (control structure, rotation axis, index of the rotation parameter) - newtype ControlledRotation = ( + /// One-parameter controlled rotation gate triplet: + /// (control structure, rotation axis, index of the rotation parameter) + newtype ControlledRotation = ( Span: GateSpan, Axis: Pauli, Index: Int ); - /// Abstraction for sequence of gates - newtype GateSequence = ControlledRotation[]; + /// Abstraction for sequence of gates + newtype GateSequence = ControlledRotation[]; - /// Abstraction for state preparation - /// Fst(StateGenerator) is the number of qubits - /// Snd(Stategenerator) is a circuit to prepare subject state - newtype StateGenerator = ( + /// Abstraction for state preparation + /// Fst(StateGenerator) is the number of qubits + /// Snd(Stategenerator) is a circuit to prepare subject state + newtype StateGenerator = ( NQubits: Int, Apply: (LittleEndian => Unit is Adj + Ctl) ); - /// Convention: negative Snd(labledSample) signifies the last sample in a batch - newtype LabeledSample = ( + /// Convention: negative Snd(labledSample) signifies the last sample in a batch + newtype LabeledSample = ( Features: Double[], Label: Int ); - // Here, we define a couple private accessor functions for LabeledSample, - // in lieu of having lambda support. These should not be used in external - // code. - function _Features(sample : LabeledSample) : Double[] { return sample::Features; } - function _Label(sample : LabeledSample) : Int { return sample::Label; } + // Here, we define a couple private accessor functions for LabeledSample, + // in lieu of having lambda support. These should not be used in external + // code. + function _Features(sample : LabeledSample) : Double[] { return sample::Features; } + function _Label(sample : LabeledSample) : Int { return sample::Label; } + + /// Abstraction for a two-level range of indices + newtype SamplingSchedule = Range[]; - /// Abstraction for a two-level range of indices - newtype SamplingSchedule = Range[]; + /// # Summary + /// Returns the number of elements in a given sampling schedule. + /// + /// # Input + /// ## schedule + /// A sampling schedule whose length is to be returned. + /// + /// # Output + /// The number of elements in the given sampling schedule. + function ScheduleLength(schedule : SamplingSchedule) : Int { + mutable length = 0; + for (range in schedule!) { + for (index in range) { + set length += 1; + } + } + return length; + } - /// # Summary - /// Returns the number of elements in a given sampling schedule. - /// - /// # Input - /// ## schedule - /// A sampling schedule whose length is to be returned. - /// - /// # Output - /// The number of elements in the given sampling schedule. - function ScheduleLength(schedule : SamplingSchedule) : Int { - mutable length = 0; - for (range in schedule!) { - for (index in range) { - set length += 1; - } - } - return length; - } + /// # Summary + /// Samples a given array, using the given schedule. + /// + /// # Input + /// ## schedule + /// A schedule to use in sampling values. + /// ## values + /// An array of values to be sampled. + /// + /// # Output + /// An array of elements from values, following the given schedule. + function Sampled<'T>(schedule : SamplingSchedule, values : 'T[]) : 'T[] { + mutable sampled = new 'T[0]; + for (range in schedule!) { + for (index in range) { + set sampled += [values[index]]; + } + } + return sampled; + } - newtype ValidationResults = ( - NMisclassifications: Int - ); + newtype ValidationResults = ( + NMisclassifications: Int + ); diff --git a/MachineLearning/src/Runtime/Validation.qs b/MachineLearning/src/Runtime/Validation.qs index bd0f50dff7d..fca2e87397a 100644 --- a/MachineLearning/src/Runtime/Validation.qs +++ b/MachineLearning/src/Runtime/Validation.qs @@ -3,74 +3,96 @@ namespace Microsoft.Quantum.MachineLearning { open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Canon; - function NMismatches(proposed: Int[], actual: Int[], validationSchedule: SamplingSchedule): Int { - mutable count = 0; - mutable ir = 0; - for (rg in validationSchedule!) { - for (ix in rg) { - if (proposed[ir] != actual[ix]) { - set count += 1; - } - set ir += 1; - } - } - return count; - } + function NMismatches(proposed: Int[], actual: Int[]): Int { + mutable count = 0; + for ((proposedLabel, actualLabel) in Zip(proposed, actual)) { + if (proposedLabel != actualLabel) { + set count += 1; + } + } + return count; + } - /// # Summary - /// Using a flat description of a trained classification model, count - /// the number of mispredictions occuring over the validation set - /// - /// # Input - /// ## nQubits - /// the number of qubits used for data encoding - /// - /// ## trainingSet - /// the set of training samples - /// - /// ## trainingLabels - /// the set of training labels - /// - /// ## validatioSchedule - /// defines a subset of training data used for validation and computation of the *bias* - /// - /// ## gates - /// Flat representation of classifier structure. Each element is - /// [parameterIndex, pauliCode, targetQubit, sequence of control qubits] - /// - /// ## parameters - /// an array of candidate parameters - /// - /// ## bias - /// candidate predition bias - /// - /// ## nMeasurenets - /// number of the measurement cycles to be used for estimation of each probability - /// - /// # Output - /// the number of misclassifications - /// - operation CountValidationMisses(tolerance: Double, nQubits: Int, trainingSet: Double[][], trainingLabels: Int[], validationSchedule: Int[][], gates: Int[][], parameters: Double[],bias:Double, nMeasurements: Int) : Int - { - let schValidate = unFlattenSchedule(validationSchedule); - let results = ValidateModel( - tolerance, nQubits, Mapped(LabeledSample, Zip(trainingSet, trainingLabels)), - schValidate, unFlattenGateSequence(gates), - parameters, bias, nMeasurements - ); - return results::NMisclassifications; - } + /// # Summary + /// tallies hits and misses off a list of probability estimates + /// + /// # Input + /// ## pls + /// a list of estimated probabilities with the corresponding class labels + /// + /// ## bias + /// bias on record + /// + /// # Output + /// (no.hits, no.misses) pair + /// + function TallyHitsMisses(pls : (Double, Int)[], bias : Double) : (Int, Int) { + mutable hits = 0; + mutable misses = 0; + for ((classificationProbability, label) in pls) { + if (label == InferredLabel(bias, classificationProbability)) { + set hits += 1; + } else { + set misses += 1; + } + } + return (hits, misses); + } - operation ValidateModel(tolerance: Double, nQubits: Int, samples : LabeledSample[], validationSchedule: SamplingSchedule, gates: GateSequence, parameters: Double[], bias:Double, nMeasurements: Int) : ValidationResults - { - let features = Mapped(_Features, samples); - let labels = Mapped(_Label, samples); - let probsValidation = EstimateClassificationProbabilitiesClassicalData(tolerance, features, validationSchedule, nQubits, gates, parameters, nMeasurements); - let localPL = InferredLabels(bias, probsValidation); - let nMismatches = NMismatches(localPL, labels, validationSchedule); - return ValidationResults( - nMismatches - ); - } + /// # Summary + /// Using a flat description of a trained classification model, count + /// the number of mispredictions occuring over the validation set + /// + /// # Input + /// ## nQubits + /// the number of qubits used for data encoding + /// + /// ## trainingSet + /// the set of training samples + /// + /// ## trainingLabels + /// the set of training labels + /// + /// ## validatioSchedule + /// defines a subset of training data used for validation and computation of the *bias* + /// + /// ## gates + /// Flat representation of classifier structure. Each element is + /// [parameterIndex, pauliCode, targetQubit, sequence of control qubits] + /// + /// ## parameters + /// an array of candidate parameters + /// + /// ## bias + /// candidate predition bias + /// + /// ## nMeasurenets + /// number of the measurement cycles to be used for estimation of each probability + /// + /// # Output + /// the number of misclassifications + /// + operation CountValidationMisses(tolerance: Double, nQubits: Int, trainingSet: Double[][], trainingLabels: Int[], validationSchedule: Int[][], gates: Int[][], parameters: Double[],bias:Double, nMeasurements: Int) : Int + { + let schValidate = unFlattenSchedule(validationSchedule); + let results = ValidateModel( + tolerance, nQubits, Mapped(LabeledSample, Zip(trainingSet, trainingLabels)), + schValidate, unFlattenGateSequence(gates), + parameters, bias, nMeasurements + ); + return results::NMisclassifications; + } + + operation ValidateModel(tolerance: Double, nQubits: Int, samples : LabeledSample[], validationSchedule: SamplingSchedule, gates: GateSequence, parameters: Double[], bias:Double, nMeasurements: Int) : ValidationResults + { + let features = Mapped(_Features, samples); + let labels = Sampled(validationSchedule, Mapped(_Label, samples)); + let probsValidation = EstimateClassificationProbabilitiesClassicalData(tolerance, features, validationSchedule, nQubits, gates, parameters, nMeasurements); + let localPL = InferredLabels(bias, probsValidation); + let nMismatches = NMismatches(localPL, labels); + return ValidationResults( + nMismatches + ); + } } diff --git a/MachineLearning/tests/MachineLearningTests.csproj b/MachineLearning/tests/MachineLearningTests.csproj index d30ed6814b8..40057467b84 100644 --- a/MachineLearning/tests/MachineLearningTests.csproj +++ b/MachineLearning/tests/MachineLearningTests.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/Numerics/src/Numerics.csproj b/Numerics/src/Numerics.csproj index fe72ffe01c3..2ae8d804e74 100644 --- a/Numerics/src/Numerics.csproj +++ b/Numerics/src/Numerics.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 Microsoft.Quantum.Numerics @@ -30,7 +30,7 @@ - + diff --git a/Numerics/tests/NumericsTests.csproj b/Numerics/tests/NumericsTests.csproj index 3f1f62f3317..c729aaa91ba 100644 --- a/Numerics/tests/NumericsTests.csproj +++ b/Numerics/tests/NumericsTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 x64 @@ -15,8 +15,8 @@ - - + + diff --git a/Standard/src/Optimization/Properties/NamespaceInfo.qs b/Standard/src/Optimization/Properties/NamespaceInfo.qs new file mode 100644 index 00000000000..4f2b2e4058e --- /dev/null +++ b/Standard/src/Optimization/Properties/NamespaceInfo.qs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/// # Summary +/// Contains functions and optimizations for finding minima. +namespace Microsoft.Quantum.Optimization { } diff --git a/Standard/src/Optimization/Univariate.qs b/Standard/src/Optimization/Univariate.qs new file mode 100644 index 00000000000..92f238ec056 --- /dev/null +++ b/Standard/src/Optimization/Univariate.qs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Optimization { + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Math; + + /// # Summary + /// Represents the result of optimizing a univariate function. + /// + /// # Input + /// ## Coordinate + /// Input at which an optimium was found. + /// ## Value + /// Value returned by the function at its optimum. + newtype UnivariateOptimizationResult = ( + Coordinate : Double, + Value : Double + ); + + /// # Summary + /// Returns the width of an interval. + function _Width(left : Double, right : Double) : Double { + return right - left; + } + + /// # Summary + /// Given an interval, returns a probe interval that contracts the given + /// interval by a factor of the golden ratio. + function _Probe(left : Double, right : Double) : (Double, Double) { + let goldenRatio = (Sqrt(5.0) + 1.0) / 2.0; + let delta = (_Width(left, right)) / goldenRatio; + return ( + right - delta, left + delta + ); + } + + /// # Summary + /// Returns the midpoint for an interval. + function _Midpoint(left : Double, right : Double) : (Double) { + return (left + right) / 2.0; + } + + /// # Summary + /// Returns the local minimum for a univariate function over a bounded interval, + /// using a golden interval search. + /// + /// # Input + /// ## fn + /// The univariate function to be minimized. + /// ## bounds + /// The interval in which the local minimum is to be found. + /// ## tolerance + /// The accuracy in the function input to be tolerated. + /// The search for local optima will continue until the interval is + /// smaller in width than this tolerance. + /// + /// # Output + /// A local optima of the given function, located within the given bounds. + function LocalUnivariateMinimum( + fn : (Double -> Double), + bounds : (Double, Double), + tolerance : Double + ) : UnivariateOptimizationResult { + + mutable interval = bounds; + mutable probe = _Probe(interval); + while (_Width(probe) > tolerance) { + set interval = + fn(Fst(probe)) < fn(Snd(probe)) + ? (Fst(interval), Snd(probe)) + | (Fst(probe), Snd(interval)); + set probe = _Probe(interval); + } + + let mid = _Midpoint(interval); + return UnivariateOptimizationResult( + mid, fn(mid) + ); + + } + +} diff --git a/Standard/src/Standard.csproj b/Standard/src/Standard.csproj index f32f4c1fdf1..d1e16215f06 100644 --- a/Standard/src/Standard.csproj +++ b/Standard/src/Standard.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 Microsoft.Quantum.Standard @@ -30,7 +30,7 @@ - + diff --git a/Standard/tests/Optimization/UnivariateTests.qs b/Standard/tests/Optimization/UnivariateTests.qs new file mode 100644 index 00000000000..73301bcb343 --- /dev/null +++ b/Standard/tests/Optimization/UnivariateTests.qs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Tests { + open Microsoft.Quantum.Optimization; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Diagnostics; + + function ParabolaCase(minima : Double, x : Double) : Double { + return PowD((x - minima), 2.0); + } + + // @Test("QuantumSimulator") + function MinimizedParabolaTest() : Unit { + let optimum = LocalUnivariateMinimum(ParabolaCase(3.14, _), (-7.0, +12.0), 1e-10); + NearEqualityFactD(optimum::Coordinate, 3.14); + NearEqualityFactD(optimum::Value, 0.0); + } + +} diff --git a/Standard/tests/Standard.Tests.csproj b/Standard/tests/Standard.Tests.csproj index 771ff3c378a..59a589b773b 100644 --- a/Standard/tests/Standard.Tests.csproj +++ b/Standard/tests/Standard.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 x64 @@ -20,8 +20,8 @@ - - + +