# Notebook from PayneLab/cptac_demos
Path: usecase5_enrichment_analysis.ipynb

# Use Case 5: Gene Set Enrichment Analysis_____no_output_____## Step 1: Importing packages and setting up your notebook.

We start the notebook by importing the standard packages for data science. These are useful for analyzing data stored in dataframes and for plotting the results._____no_output_____

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import gseapy as gp
from gseapy.plot import barplot, dotplot_____no_output_____

Our cancer data and a variety of accessory functions are provided in the cptac package._____no_output_____

import cptac
en = cptac.Endometrial()

## Step 2: Joining dataframes

For this example we will be separating the protein abudance based on whether the sample is tumor or non-tumor tissue. This information is stored in the "Proteomics_Tumor_Normal" column in the clinical dataframe. We first join the clinical information into the proteomics dataframe utilizing the en.join_metadata_to_omics function._____no_output_____

tumorProt = en.join_metadata_to_omics(metadata_df_name="clinical", omics_df_name="proteomics",
                                      metadata_cols='Proteomics_Tumor_Normal')_____no_output_____

## Step 3: Organizing the data

We then separate the proteomics into two groups, based on whether the patient is categorized as "Tumor" or "Normal". If you look at the values in the tumorProt dataframe, you'll see that "Normal" actually includes several different types of normal tissue--adjacent normal, normal tissue from other individuals called 'enriched normal', etc. We make a single value out of this in the code below calling everything besides "Tumor" a "Normal" sample._____no_output_____

#Retrieve boolean array of true values
tumor_bool = tumorProt['Proteomics_Tumor_Normal'] == "Tumor"
normal_bool = tumorProt['Proteomics_Tumor_Normal'] != "Tumor"
#Use boolean array to select for appropriate patients
tumor = tumorProt[tumor_bool]
normal = tumorProt[normal_bool]_____no_output_____

## Step 4: Perform statistical tests

Next, we find the genes that are upregulated in each partition using Welch's t-test (a variation on the two sample t-test due to different variances between the two groups) to compare the "Tumor" individuals with the "Normal" individuals for each gene._____no_output_____

#Create array variables to hold the significant genes for each partition
tumor_genes = []
normal_genes = []
#Grab the genes of interest, ignoring the MSI column in the dataframe
genes = tumor.columns[1:]
#Correct alpha level for multiple testing by dividing the standard .05 by the number of genes to be analyzed
threshold = .05 / len(genes)
#Perform Welch's t-test(different variances) on each gene between the two groups
for gene in genes:
    tumor_gene_abundance = tumor[gene]
    normal_gene_abundance = normal[gene]
    pvalue = stats.ttest_ind(tumor_gene_abundance, normal_gene_abundance, equal_var=False,nan_policy='omit').pvalue
    #If the P-value is significant, determine which partition is more highly expressed
    if pvalue < threshold:
        if tumor_gene_abundance.mean() > normal_gene_abundance.mean():
            tumor_genes.append(gene.split("_")[0])
        elif normal_gene_abundance.mean() > tumor_gene_abundance.mean():
            normal_genes.append(gene.split("_")[0])
#Optional check of number of genes in each partition
print("Proteomics Tumor Genes:", len(tumor_genes))
print("Proteomics Normal Genes:", len(normal_genes))Proteomics Tumor Genes: 2154
Proteomics Normal Genes: 2237

## Step 5: Gene set enrichment analysis

We then use the genes that are up-regulated in these partitions to perform a Gene Set Enrichment Analysis using the gp.enrichr() function (gp being the specified abbreviation for the imported gseapy package)._____no_output_____

tumor_enr = gp.enrichr(gene_list = tumor_genes, description='Tumor_partition', gene_sets='KEGG_2016',
                       outdir='test/enrichr_kegg')
normal_enr = gp.enrichr(gene_list = normal_genes, description='Normal_partition', gene_sets='KEGG_2016',
                        outdir='test/enrichr_kegg')_____no_output_____

We can view the data as a table by using the obj.res2d command (obj being the variable name specified in the previous step). The Gene Set Enrichment Analysis returns a list of pathways the genes provided are involved in, based on a significant adjusted P-value._____no_output_____

tumor_enr.res2d.head()_____no_output_____normal_enr.res2d.head()_____no_output_____

## Step 6: Plot the p-values

We can better visualize these tables and the significant pathways detected through the gseapy imported barplot() function, which takes an enrichr table and a title as parameters._____no_output_____

barplot(tumor_enr.res2d,title="Proteomics Tumor KEGG_2016")
plt.show()_____no_output_____barplot(normal_enr.res2d,title="Normal KEGG_2016")
plt.show()_____no_output_____

## Discussion

When comparing these barplots, we can observe the "Tumor" group has one pathway expressed at a very significant adjusted P-value, while the other pathways are expressed at a less significant P-value. The single pathway is the Ribosome pathway, which could be attributed to the "Tumor" genes disrupting ribosome activity. In comparison, the "Normal" group has a higher count of high significant P-values._____no_output_____


# Notebook from hongkuntian/Udacity-Computer-Vision-Nanodegree
Path: P2_Automatic_Image_Captioning/2_Training.ipynb

# Computer Vision Nanodegree

## Project: Image Captioning

---

In this notebook, you will train your CNN-RNN model.

You are welcome and encouraged to try out many different architectures and hyperparameters when searching for a good model.

This does have the potential to make the project quite messy!  Before submitting your project, make sure that you clean up:
- the code you write in this notebook.  The notebook should describe how to train a single CNN-RNN architecture, corresponding to your final choice of hyperparameters.  You should structure the notebook so that the reviewer can replicate your results by running the code in this notebook.
- the output of the code cell in **Step 2**.  The output should show the output obtained when training the model from scratch.

This notebook **will be graded**.

Feel free to use the links below to navigate the notebook:
- [Step 1](#step1): Training Setup
- [Step 2](#step2): Train your Model
- [Step 3](#step3): (Optional) Validate your Model_____no_output_____
## Step 1: Training Setup

In this step of the notebook, you will customize the training of your CNN-RNN model by specifying hyperparameters and setting other options that are important to the training procedure.  The values you set now will be used when training your model in **Step 2** below.

You should only amend blocks of code that are preceded by a `TODO` statement.  **Any code blocks that are not preceded by a `TODO` statement should not be modified**.

### Task #1

Begin by setting the following variables:
- `batch_size` - the batch size of each training batch.  It is the number of image-caption pairs used to amend the model weights in each training step.
- `vocab_threshold` - the minimum word count threshold.  Note that a larger threshold will result in a smaller vocabulary, whereas a smaller threshold will include rarer words and result in a larger vocabulary.
- `vocab_from_file` - a Boolean that decides whether to load the vocabulary from file.
- `embed_size` - the dimensionality of the image and word embeddings.
- `hidden_size` - the number of features in the hidden state of the RNN decoder.
- `num_epochs` - the number of epochs to train the model.  We recommend that you set `num_epochs=3`, but feel free to increase or decrease this number as you wish.  [This paper](https://arxiv.org/pdf/1502.03044.pdf) trained a captioning model on a single state-of-the-art GPU for 3 days, but you'll soon see that you can get reasonable results in a matter of a few hours!  (_But of course, if you want your model to compete with current research, you will have to train for much longer._)
- `save_every` - determines how often to save the model weights.  We recommend that you set `save_every=1`, to save the model weights after each epoch.  This way, after the `i`th epoch, the encoder and decoder weights will be saved in the `models/` folder as `encoder-i.pkl` and `decoder-i.pkl`, respectively.
- `print_every` - determines how often to print the batch loss to the Jupyter notebook while training.  Note that you **will not** observe a monotonic decrease in the loss function while training - this is perfectly fine and completely expected!  You are encouraged to keep this at its default value of `100` to avoid clogging the notebook, but feel free to change it.
- `log_file` - the name of the text file containing - for every step - how the loss and perplexity evolved during training.

If you're not sure where to begin to set some of the values above, you can peruse [this paper](https://arxiv.org/pdf/1502.03044.pdf) and [this paper](https://arxiv.org/pdf/1411.4555.pdf) for useful guidance!  **To avoid spending too long on this notebook**, you are encouraged to consult these suggested research papers to obtain a strong initial guess for which hyperparameters are likely to work best.  Then, train a single model, and proceed to the next notebook (**3_Inference.ipynb**).  If you are unhappy with your performance, you can return to this notebook to tweak the hyperparameters (and/or the architecture in **model.py**) and re-train your model.

### Question 1

**Question:** Describe your CNN-RNN architecture in detail.  With this architecture in mind, how did you select the values of the variables in Task 1?  If you consulted a research paper detailing a successful implementation of an image captioning model, please provide the reference.

**Answer:**

My Sequence to Sequence model consists of a CNN as the encoder, and a RNN as the decoder.

The CNN encoder consists of a pretrained model named **ResNet-50**, short for Residual Network 50 [layers], connected to a Fully Connected Linear layer to perform the embeddings.

The RNN decoder consists of one LSTM layer connected to a Fully Connected layer. The paper [Show and Tell](https://arxiv.org/pdf/1411.4555.pdf) presents this architecture. As suggested, I am opting to replicate their architecture here.

Here are my choices of hyperparameters:

- batch_size = **128**
- vocab_threshold = **5**
- vocab_from_file = **False**
- embed_size = **512**
- hidden_size = **512**

I've referred to [Show and Tell](https://arxiv.org/pdf/1411.4555.pdf) to select vocab_size, embed_size, and hidden_size as it seemed to be a good starting point since their methods and choices of hyperparameters have worked well. As for the batch_size, I landed on 128 after also trying several other options (32, 64). The training time was reduced as the batch_size increased but the accuracy somewhat suffered as it increased as well. I found 128 to be a good balance of training time and accuracy achieved.

### (Optional) Task #2

Note that we have provided a recommended image transform `transform_train` for pre-processing the training images, but you are welcome (and encouraged!) to modify it as you wish.  When modifying this transform, keep in mind that:
- the images in the dataset have varying heights and widths, and
- if using a pre-trained model, you must perform the corresponding appropriate normalization.

### Question 2

**Question:** How did you select the transform in `transform_train`?  If you left the transform at its provided value, why do you think that it is a good choice for your CNN architecture?

**Answer:**

I have opted to leave the transform at its provided value since ResNet-50 accepts an image of size of (224,224,3). I considered adding some additional augmentation (e.g. RandomRotation) but deemed it not particularly beneficial to rotate the image and crop out perhaps pertinent information from the image.

### Task #3

Next, you will specify a Python list containing the learnable parameters of the model.  For instance, if you decide to make all weights in the decoder trainable, but only want to train the weights in the embedding layer of the encoder, then you should set `params` to something like:
```
params = list(decoder.parameters()) + list(encoder.embed.parameters())
```

### Question 3

**Question:** How did you select the trainable parameters of your architecture?  Why do you think this is a good choice?

**Answer:**

I selected the trainable parameters as suggested.

ResNet-50 is a pre-trained model, therefore it would be counter intuitive to retrain the model. Instead, to adapt ResNet-50 to our particular needs, I chose to train the subsequent Fully Connected layer following ResNet-50 to perform the desired embedding.

The RNN decoder is completely untrained, therefore all of the decoder's parameters must be trained.

### Task #4

Finally, you will select an [optimizer](http://pytorch.org/docs/master/optim.html#torch.optim.Optimizer).

### Question 4

**Question:** How did you select the optimizer used to train your model?

**Answer:**

I chose to use **Adam** optimizer because its efficiency exceeds that of SGD. Additionally, it is known to produce more consistent results while also performing much faster than SGD in most cases._____no_output_____

import torch
import torch.nn as nn
from torchvision import transforms
import sys
sys.path.append('/opt/cocoapi/PythonAPI')
from pycocotools.coco import COCO
from data_loader import get_loader
from model import EncoderCNN, DecoderRNN
import math
import nltk

## TODO #1: Select appropriate values for the Python variables below.
batch_size = 128          # batch size
vocab_threshold = 5        # minimum word count threshold
vocab_from_file = False     # if True, load existing vocab file
embed_size = 512           # dimensionality of image and word embeddings
hidden_size = 512          # number of features in hidden state of the RNN decoder
num_epochs = 3             # number of training epochs
save_every = 1             # determines frequency of saving model weights
print_every = 100          # determines window for printing average loss
log_file = 'training_log.txt'       # name of file with saved training loss and perplexity

# (Optional) TODO #2: Amend the image transform below.
transform_train = transforms.Compose([
    transforms.Resize(256),                          # smaller edge of image resized to 256
    transforms.RandomCrop(224),                      # get 224x224 crop from random location
    transforms.RandomHorizontalFlip(),               # horizontally flip image with probability=0.5
    transforms.ToTensor(),                           # convert the PIL Image to a tensor
    transforms.Normalize((0.485, 0.456, 0.406),      # normalize image for pre-trained model
                         (0.229, 0.224, 0.225))])

# Build data loader.
data_loader = get_loader(transform=transform_train,
                         mode='train',
                         batch_size=batch_size,
                         vocab_threshold=vocab_threshold,
                         vocab_from_file=vocab_from_file)

# The size of the vocabulary.
vocab_size = len(data_loader.dataset.vocab)

# Initialize the encoder and decoder.
encoder = EncoderCNN(embed_size)
decoder = DecoderRNN(embed_size, hidden_size, vocab_size)

# Move models to GPU if CUDA is available.
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
encoder.to(device)
decoder.to(device)

# Define the loss function.
criterion = nn.CrossEntropyLoss().cuda() if torch.cuda.is_available() else nn.CrossEntropyLoss()

# TODO #3: Specify the learnable parameters of the model.
params = list(encoder.embed.parameters()) + list(decoder.parameters())

# TODO #4: Define the optimizer.
optimizer = torch.optim.Adam(params, lr=0.001)

# Set the total number of training steps per epoch.
total_step = math.ceil(len(data_loader.dataset.caption_lengths) / data_loader.batch_sampler.batch_size)loading annotations into memory...
Done (t=0.89s)
creating index...
index created!
[0/414113] Tokenizing captions...
[100000/414113] Tokenizing captions...
[200000/414113] Tokenizing captions...
[300000/414113] Tokenizing captions...
[400000/414113] Tokenizing captions...
loading annotations into memory...
Done (t=0.93s)
creating index...

## Step 2: Train your Model

Once you have executed the code cell in **Step 1**, the training procedure below should run without issue.

It is completely fine to leave the code cell below as-is without modifications to train your model.  However, if you would like to modify the code used to train the model below, you must ensure that your changes are easily parsed by your reviewer.  In other words, make sure to provide appropriate comments to describe how your code works!

You may find it useful to load saved weights to resume training.  In that case, note the names of the files containing the encoder and decoder weights that you'd like to load (`encoder_file` and `decoder_file`).  Then you can load the weights by using the lines below:

```python
# Load pre-trained weights before resuming training.
encoder.load_state_dict(torch.load(os.path.join('./models', encoder_file)))
decoder.load_state_dict(torch.load(os.path.join('./models', decoder_file)))
```

While trying out parameters, make sure to take extensive notes and record the settings that you used in your various training runs.  In particular, you don't want to encounter a situation where you've trained a model for several hours but can't remember what settings you used :).

### A Note on Tuning Hyperparameters

To figure out how well your model is doing, you can look at how the training loss and perplexity evolve during training - and for the purposes of this project, you are encouraged to amend the hyperparameters based on this information.

However, this will not tell you if your model is overfitting to the training data, and, unfortunately, overfitting is a problem that is commonly encountered when training image captioning models.

For this project, you need not worry about overfitting. **This project does not have strict requirements regarding the performance of your model**, and you just need to demonstrate that your model has learned **_something_** when you generate captions on the test data.  For now, we strongly encourage you to train your model for the suggested 3 epochs without worrying about performance; then, you should immediately transition to the next notebook in the sequence (**3_Inference.ipynb**) to see how your model performs on the test data.  If your model needs to be changed, you can come back to this notebook, amend hyperparameters (if necessary), and re-train the model.

That said, if you would like to go above and beyond in this project, you can read about some approaches to minimizing overfitting in section 4.3.1 of [this paper](http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=7505636).  In the next (optional) step of this notebook, we provide some guidance for assessing the performance on the validation dataset._____no_output_____

import torch.utils.data as data
import numpy as np
import os
import requests
import time

from workspace_utils import active_session

# Open the training log file.
f = open(log_file, 'w')

old_time = time.time()
response = requests.request("GET",
                            "http://metadata.google.internal/computeMetadata/v1/instance/attributes/keep_alive_token",
                            headers={"Metadata-Flavor":"Google"})

with active_session():
    for epoch in range(1, num_epochs+1):

        for i_step in range(1, total_step+1):

            if time.time() - old_time > 60:
                old_time = time.time()
                requests.request("POST",
                                 "https://nebula.udacity.com/api/v1/remote/keep-alive",
                                 headers={'Authorization': "STAR " + response.text})

            # Randomly sample a caption length, and sample indices with that length.
            indices = data_loader.dataset.get_train_indices()
            # Create and assign a batch sampler to retrieve a batch with the sampled indices.
            new_sampler = data.sampler.SubsetRandomSampler(indices=indices)
            data_loader.batch_sampler.sampler = new_sampler

            # Obtain the batch.
            images, captions = next(iter(data_loader))

            # Move batch of images and captions to GPU if CUDA is available.
            images = images.to(device)
            captions = captions.to(device)

            # Zero the gradients.
            decoder.zero_grad()
            encoder.zero_grad()

            # Pass the inputs through the CNN-RNN model.
            features = encoder(images)
            outputs = decoder(features, captions)

            # Calculate the batch loss.
            loss = criterion(outputs.view(-1, vocab_size), captions.view(-1))

            # Backward pass.
            loss.backward()

            # Update the parameters in the optimizer.
            optimizer.step()

            # Get training statistics.
            stats = 'Epoch [%d/%d], Step [%d/%d], Loss: %.4f, Perplexity: %5.4f' % (epoch, num_epochs, i_step, total_step, loss.item(), np.exp(loss.item()))

            # Print training statistics (on same line).
            print('\r' + stats, end="")
            sys.stdout.flush()

            # Print training statistics to file.
            f.write(stats + '\n')
            f.flush()

            # Print training statistics (on different line).
            if i_step % print_every == 0:
                print('\r' + stats)

        # Save the weights.
        if epoch % save_every == 0:
            torch.save(decoder.state_dict(), os.path.join('./models', 'decoder-%d.pkl' % epoch))
            torch.save(encoder.state_dict(), os.path.join('./models', 'encoder-%d.pkl' % epoch))

# Close the training log file.
f.close()Epoch [1/3], Step [100/3236], Loss: 4.8313, Perplexity: 125.3751
Epoch [1/3], Step [200/3236], Loss: 3.3040, Perplexity: 27.2203
Epoch [1/3], Step [300/3236], Loss: 3.0024, Perplexity: 20.1339
Epoch [1/3], Step [400/3236], Loss: 2.9030, Perplexity: 18.2286
Epoch [1/3], Step [500/3236], Loss: 2.9748, Perplexity: 19.5851
Epoch [1/3], Step [600/3236], Loss: 3.0737, Perplexity: 21.6219
Epoch [1/3], Step [700/3236], Loss: 2.5681, Perplexity: 13.0406
Epoch [1/3], Step [800/3236], Loss: 2.8132, Perplexity: 16.6629
Epoch [1/3], Step [900/3236], Loss: 2.6215, Perplexity: 13.7563
Epoch [1/3], Step [1000/3236], Loss: 2.6472, Perplexity: 14.1141
Epoch [1/3], Step [1100/3236], Loss: 2.5676, Perplexity: 13.0343
Epoch [1/3], Step [1200/3236], Loss: 2.3838, Perplexity: 10.8460
Epoch [1/3], Step [1300/3236], Loss: 2.4533, Perplexity: 11.6266
Epoch [1/3], Step [1400/3236], Loss: 2.4193, Perplexity: 11.23745
Epoch [1/3], Step [1500/3236], Loss: 2.3335, Perplexity: 10.3137
Epoch [1/3], Step [1600/3236], Loss: 2.4599, Perplexity: 11.7040
Epoch [1/3], Step [1700/3236], Loss: 2.3480, Perplexity: 10.4648
Epoch [1/3], Step [1800/3236], Loss: 2.6956, Perplexity: 14.8140
Epoch [1/3], Step [1900/3236], Loss: 2.3120, Perplexity: 10.0945
Epoch [1/3], Step [2000/3236], Loss: 2.4591, Perplexity: 11.6947
Epoch [1/3], Step [2100/3236], Loss: 2.3623, Perplexity: 10.6149
Epoch [1/3], Step [2200/3236], Loss: 2.4258, Perplexity: 11.3107
Epoch [1/3], Step [2300/3236], Loss: 2.2751, Perplexity: 9.72908
Epoch [1/3], Step [2400/3236], Loss: 2.2298, Perplexity: 9.29845
Epoch [1/3], Step [2500/3236], Loss: 2.2289, Perplexity: 9.28971
Epoch [1/3], Step [2600/3236], Loss: 2.2403, Perplexity: 9.39587
Epoch [1/3], Step [2700/3236], Loss: 2.1319, Perplexity: 8.43052
Epoch [1/3], Step [2800/3236], Loss: 2.3997, Perplexity: 11.0200
Epoch [1/3], Step [2900/3236], Loss: 2.1152, Perplexity: 8.29081
Epoch [1/3], Step [3000/3236], Loss: 2.6103, Perplexity: 13.6036
Epoch [1/3], Step [3100/3236], Loss: 2.0993, Perplexity: 8.16026
Epoch [1/3], Step [3200/3236], Loss: 2.2174, Perplexity: 9.18389
Epoch [2/3], Step [100/3236], Loss: 2.1569, Perplexity: 8.644146
Epoch [2/3], Step [200/3236], Loss: 2.2202, Perplexity: 9.20933
Epoch [2/3], Step [300/3236], Loss: 2.2575, Perplexity: 9.55905
Epoch [2/3], Step [400/3236], Loss: 2.1296, Perplexity: 8.41185
Epoch [2/3], Step [500/3236], Loss: 2.0938, Perplexity: 8.11609
Epoch [2/3], Step [600/3236], Loss: 1.9913, Perplexity: 7.32514
Epoch [2/3], Step [700/3236], Loss: 2.6396, Perplexity: 14.0072
Epoch [2/3], Step [800/3236], Loss: 2.1529, Perplexity: 8.60987
Epoch [2/3], Step [900/3236], Loss: 2.1472, Perplexity: 8.56126
Epoch [2/3], Step [1000/3236], Loss: 2.1286, Perplexity: 8.4027
Epoch [2/3], Step [1100/3236], Loss: 2.2037, Perplexity: 9.05880
Epoch [2/3], Step [1200/3236], Loss: 2.1088, Perplexity: 8.23832
Epoch [2/3], Step [1300/3236], Loss: 1.9001, Perplexity: 6.68680
Epoch [2/3], Step [1400/3236], Loss: 2.0897, Perplexity: 8.08234
Epoch [2/3], Step [1500/3236], Loss: 2.0292, Perplexity: 7.60780
Epoch [2/3], Step [1600/3236], Loss: 2.0659, Perplexity: 7.89232
Epoch [2/3], Step [1700/3236], Loss: 2.0523, Perplexity: 7.78553
Epoch [2/3], Step [1800/3236], Loss: 2.0221, Perplexity: 7.55407
Epoch [2/3], Step [1900/3236], Loss: 2.0978, Perplexity: 8.14833
Epoch [2/3], Step [2000/3236], Loss: 2.0958, Perplexity: 8.13234
Epoch [2/3], Step [2100/3236], Loss: 2.0711, Perplexity: 7.93386
Epoch [2/3], Step [2200/3236], Loss: 2.1714, Perplexity: 8.77045
Epoch [2/3], Step [2300/3236], Loss: 2.2696, Perplexity: 9.67556
Epoch [2/3], Step [2400/3236], Loss: 1.9952, Perplexity: 7.35394
Epoch [2/3], Step [2500/3236], Loss: 2.0872, Perplexity: 8.06231
Epoch [2/3], Step [2600/3236], Loss: 1.9942, Perplexity: 7.34656
Epoch [2/3], Step [2700/3236], Loss: 1.9811, Perplexity: 7.25116
Epoch [2/3], Step [2800/3236], Loss: 2.2537, Perplexity: 9.52281
Epoch [2/3], Step [2900/3236], Loss: 1.9705, Perplexity: 7.17449
Epoch [2/3], Step [3000/3236], Loss: 2.0048, Perplexity: 7.42491
Epoch [2/3], Step [3071/3236], Loss: 2.0730, Perplexity: 7.94896

Note: The model stopped training midway for this iteration, but the performance is nevertheless adequate for the latest saved model, and so I used that in the next notebook._____no_output_____
## Step 3: (Optional) Validate your Model

To assess potential overfitting, one approach is to assess performance on a validation set.  If you decide to do this **optional** task, you are required to first complete all of the steps in the next notebook in the sequence (**3_Inference.ipynb**); as part of that notebook, you will write and test code (specifically, the `sample` method in the `DecoderRNN` class) that uses your RNN decoder to generate captions.  That code will prove incredibly useful here.

If you decide to validate your model, please do not edit the data loader in **data_loader.py**.  Instead, create a new file named **data_loader_val.py** containing the code for obtaining the data loader for the validation data.  You can access:
- the validation images at filepath `'/opt/cocoapi/images/train2014/'`, and
- the validation image caption annotation file at filepath `'/opt/cocoapi/annotations/captions_val2014.json'`.

The suggested approach to validating your model involves creating a json file such as [this one](https://github.com/cocodataset/cocoapi/blob/master/results/captions_val2014_fakecap_results.json) containing your model's predicted captions for the validation images.  Then, you can write your own script or use one that you [find online](https://github.com/tylin/coco-caption) to calculate the BLEU score of your model.  You can read more about the BLEU score, along with other evaluation metrics (such as TEOR and Cider) in section 4.1 of [this paper](https://arxiv.org/pdf/1411.4555.pdf).  For more information about how to use the annotation file, check out the [website](http://cocodataset.org/#download) for the COCO dataset._____no_output_____

# (Optional) TODO: Validate your model._____no_output_____


# Notebook from baidu/Quanlse
Path: Tutorial/EN/tutorial-single-qubit.ipynb

# Single-Qubit Gate

*Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*_____no_output_____## Outline
This tutorial introduces how to generate and optimize pulses for single-qubit gates by Quanlse. The outline of this tutorial is as follows:
- Introduction
- Preparation
- Construct Hamiltonian
- Pulse optimization for Hadamard gate
- Pulse optimization for X gate
- Pulse optimization for Z gate
- Pulse optimization for arbitrary single-qubit gate
- Summary_____no_output_____## Introduction

  In quantum computing, we call an action upon qubit(s) a **quantum gate**. In superconducting quantum circuits, a quantum gate is implemented by applying external microwave pulses and magnetic flux. A single-qubit gate, the focus of this section, can be written as the symbol $U$, which is a $2\times2$ unitary matrix in linear algebra. As shown in the graph below, a single-qubit gate can also be visualized by a vector ($|\psi\rangle$) transformation on what we call a Bloch Sphere. In particular, $|\psi\rangle=\cos(\theta/2)|0\rangle+e^{i\phi}\sin(\theta/2)|1\rangle$ represents the superposition of two quantum states, $|0\rangle$ and $|1\rangle$. Via a single-qubit gate, we can transform a quantum state on the Bloch Sphere \[1\].
_____no_output_____![bloch sphere](figures/sphere.png)_____no_output_____The following are six commonly used single-qubit gates as well as their operators and matrix representations:_____no_output_____| Gate   |      Operation on Bloch Sphere      |  Operator | Matrix Form |
|----------|:-------------:|:-:|:-:|
| $X$ |  Rotate $\pi$ about the $x$ axis | $\hat{\sigma}_x$ | $\left(\begin{array}{cc} 0 & 1\\1 & 0\end{array}\right)$ |
| $Y$ |  Rotate $\pi$ about the $y$ axis | $\hat{\sigma}_y$ |$\left(\begin{array}{cc} 0 & -i\\i & 0\end{array}\right)$ |
| $Z$ |  Rotate $\pi$ about the $z$ axis | $\hat{\sigma}_z$ |$\left(\begin{array}{cc} 1 & 0\\0 & -1\end{array}\right)$ |
| $S$ |  Rotate $\pi\over 2$ about the $z$ axis |  |$\left(\begin{array}{cc} 1 & 0\\0 & e^{i\frac{\pi}{2}}\end{array}\right)$ |
| $T$ |  Rotate $\pi\over 4$ about the $z$ axis |  |$\left(\begin{array}{cc} 1 & 0\\0 & e^{i\frac{\pi}{4}}\end{array}\right)$ |
| $H$ | First rotate $\pi$ about the $x$ axis, then rotate $\pi\over 2$ about the $z$ axis |  |$\frac{1}{\sqrt{2}}\left(\begin{array}{cc} 1 & 1\\1 & -1\end{array}\right)$ |_____no_output_____**Single-qubit gate in Quanlse**

Please note that in Quanlse, any arbitrary operation on a single qubit is implemented by the following equation (this representation has a global phase in front and no $R_x$ component):
$$
U(\theta, \phi, \lambda) = e^{i(\phi/2+\lambda/2)} R_z(\phi) R_y(\theta) R_z(\lambda) =
\begin{bmatrix}
    \cos(\theta/2) & - e^{i\lambda} \sin(\theta/2) \\
    e^{i\phi} \sin(\theta/2) & e^{i(\phi + \lambda)} \cos(\theta/2)
\end{bmatrix} ,
$$
where the $e^{i(\phi/2+\lambda/2)}$ is the global phase of the transformation.

Please refer to our [API documentation](https://quanlse.baidu.com/api/) for more information regarding other types of gates Quanlse supports._____no_output_____To get the physical intuition of the implementation of single-qubit gates, we will now introduce the two means of implementing single-qubit gates on superconducting qubit:
- **Microwave control** uses capacitive coupling between a resonator and the superconducting qubit (X, Y channel).
- **Frequency tuning** uses a local magnetic field (Z channel) for flux-tunable qubits.

The graph below delineates the X/Y/Z channels of a superconducting qubit:

![X/Y/Z controls for single superconducting qubit](figures/hardware_qubit_control.png)

**Experimental microwave pulse implementation**

To experimentally implement microwave control (X, Y control), we need a local oscillator (LO) to produce a high frequency ($\omega_{\rm LO}$) sinusoidal component combined with a low frequency ($\omega_{\rm AWG}$) arbitrary waveform generator (AWG) to produce a pulse envelope in the form of a Gaussian or tangential function, etc., such that $\omega_{d}=\omega_{\rm LO}\pm\omega_{\rm AWG}$.

**Experimental flux pulse implementation**

The conventional way of implementing flux control (Z control) is to replace the single Josephson junction with a DC-SQUID, which is a loop consisting of two Josephson junctions. Qubit frequency can be tuned by applying an external magnetic flux perpendicular to the loop._____no_output_____## Preparation
After you have successfully installed Quanlse, you could run the Quanlse program below following this tutorial. To run this particular tutorial, you would need to import the following packages from Quanlse and other commonly-used Python libraries:_____no_output_____

# Import numpy and math
from numpy import round
from math import pi

# Import the Hamiltonian module
from Quanlse.QHamiltonian import QHamiltonian as QHam

# Import simulator interface on Quanlse Cloud Service
from Quanlse.remoteOptimizer import remoteOptimize1Qubit

# Import related packages
from Quanlse.Utils.Functions import project
from Quanlse.QOperator import duff
from Quanlse.QOperation import FixedGate_____no_output_____

## Construct Hamiltonian

Now, we switch our focus to implementing a single-qubit gate using Quanlse. In this example, we will model a system formed by a three-level transmon. Ideally, the non-equidistant energy levels in a transmon allow qubit states to be individually addressed through a frequency-selective driving field. However, the fact that the transmon qubit is weakly anharmonic suggests that the finite bandwidth of the driving field can lead to transitions out of the computational states. This effect is known as **leakage**. In our model, leakage into state $|2\rangle$ - the first level above the computational space - is taken into account by treating the superconducting qubit as a simplified three-level system. The Hamiltonian in the rotating frame can be written as \[2\]:

 $$
 \hat{H}=\alpha_q\lvert2\rangle\langle 2\lvert+\frac{\Omega^x(t)}{2}\left[ \hat{a}^\dagger + \hat{a} \right] + \frac{\Omega^y(t)}{2} i \left[\hat{a}^\dagger - \hat{a}\right]+\Omega^z(t)\hat{a}^{\dagger}\hat{a},
 $$

where $\alpha_q$ is the anharmonicity; $\Omega^x(t)$ is the amplitude of the driving pulse in the X channel; $\Omega^y(t)$ is the amplitude of the driving pulse in the Y channel; and $\Omega^z(t)$ is the amplitude of the flux pulses in the Z channel. The two ladder operators are respectively $\hat{a}^\dagger = |1\rangle\langle 0| + \sqrt{2}|2\rangle\langle 1|$ and $\hat{a} = |0\rangle\langle 1| + \sqrt{2}|1\rangle\langle 2|$._____no_output_____Quanlse can be used to realize arbitrary single-qubit gates. While Quanlse supports various waveforms' definitions. Here, we take the Gaussian pulse function as an example. The Gaussian pulse function takes the form:

$$
A^{x}(t)=A^{x} e^{-((t-\tau^{x})/2\sigma^{x})^2},
$$

$$
A^{y}(t)=A^{y} e^{-((t-\tau^{y})/2\sigma^{y})^2} .
$$

In these equations above, $A^{x}, A^{y}, \tau^{x}, \tau^{y}, \sigma^{x}, \sigma^{y}$ are the parameters to be optimized.
Unlike microwave control, the flux channel's input takes the form of a square wave, i.e. $
A^{z}(t) = A^{z}$, where $A^{z}$ is the parameter to be optimized._____no_output_____Now, we need to construct the above Hamiltonian using Quanlse. We start by defining some of the basic parameters needed for constructing a Hamiltonian object: the sampling period, the number of qubits in the system as well as the system's energy levels to consider:_____no_output_____

# Sampling period
dt = 0.2

# Number of qubits
qubits = 1

# System energy level
level = 3_____no_output_____

Then, we define the anharmonicity of the qubit:_____no_output_____

# Define anharmonicity
qubitArgs = {
    "qubit_anharm": - 0.33 * (2 * pi),  # Anharmonicity of the qubit
}_____no_output_____

We create the Hamiltonian object using the initilizer `Qham()`. When initializing, we specify the qubit number, system's energy level number and the sampling period of the AWG(arbitrary wave generator). Then, the users would need to add the anharmonicity term using the function `addDrift()` which takes three arguments: the corresponding operator, the qubit index, and the amplitude of the anharmonicity._____no_output_____

# Create the Hamiltonian.
ham = QHam(qubits, level, dt)

# Add the drift term(s).
ham.addDrift(duff, 0, coef=qubitArgs["qubit_anharm"] / 2)_____no_output_____

## Pulse optimization for Hadamard gate_____no_output_____With the system Hamiltonian created, we can now generate and optimize the pulses to a selected single-qubit gate with desired infidelity (we choose the Hadamard gate as an example) using the following function: `remoteOptimize1Qubit()`. The optimization usually takes a long time to process on local devices, however, we provide a cloud service that could speed up this process significantly. To use Quanlse Cloud Service, the users need to acquire a token from http://quantum-hub.baidu.com and use the following command to submit the job onto Quanlse's server. For this specific example, we can write:_____no_output_____

# Import Define class and set the token
# Please visit http://quantum-hub.baidu.com
from Quanlse import Define
Define.hubToken = ''

# Run the optimization
gateJob, infidelity = remoteOptimize1Qubit(ham, FixedGate.H.getMatrix(), depth=4, targetInfid=0.0001)_____no_output_____

In the optimization above, the gate infidelity for performance assessment is defined as ${\rm infid} = 1 - \frac{1}{d}\left|{\rm Tr}[U^\dagger_{\rm goal}P(U)]\right|$, where $U_{\rm goal}$ is the unitary matrix of the target single-qubit gate and $d$ is the dimension of $U_{\rm goal}$; $U$ is the unitary evolution of the system. Note that $P(U)$ in particular describes the evolution projected to the computational subspace.

The function `remoteOptimize1Qubit()` takes in a Hamiltonian object (`ham`), a target unitary matrix (`FixedGate.H.getMatrix()`), the maximum pulse circuit's depth (also maximum pulse number), and a target infidelity. While this demo generates a decent fidelity for the Hadamard gate, we encourage the users to try varying these parameters to get the optimal result.

The `plot()` function allows us to visualize the pulse generated. We can also obtain the projected evolution:_____no_output_____

# Print infidelity and the waveforms
print(f"minimum infidelity: {infidelity}")
gateJob.plot(dark='True')

# Print the evolution process.
result = ham.simulate(job=gateJob)
projectedEvolution = project(result.result[0]["unitary"], qubits, level, 2)
print("Projected evolution:\n", round(projectedEvolution, 2))_____no_output_____

`plotWaves()`'s function arguments include a Hamiltonian object (`ham`), the channels to print(`["q0-ctrlx", "q0-ctrly", "q0-ctrlz"]`) and an optional bool parameter `dark`, which enables a dark-themed mode. Moreover, the user can use the `color` parameter to specify colors for individual pulses (the colors will repeat if there are more pulses than colors). Apart from the pulse visualization, we can also obtain the projected evolution shown above.

The following are demonstrations for the X and Z gates, using the exact same system Hamiltonian as above._____no_output_____## Pulse optimization for X gate_____no_output_____The following code generates and optimizes the pulses for an X gate. Here, we specify the pulse numbers on the X and Y channels by setting the parameter: `xyzPulses=[1, 1, 0]`._____no_output_____

# Run the optimization
gateJob, infidelity = remoteOptimize1Qubit(ham, FixedGate.X.getMatrix(), depth=4, targetInfid=0.0001)

# Print infidelity and the waveforms
print(f"minimum infidelity: {infidelity}")
gateJob.plot(dark='True')

# Print the evolution process.
result = ham.simulate(job=gateJob)
projectedEvolution = project(result.result[0]["unitary"], qubits, level, 2)
print("Projected evolution:\n", round(projectedEvolution, 2))_____no_output_____

## Pulse optimization for Z gate_____no_output_____The following code generates and optimizes the pulses for a Z gate._____no_output_____

# Run the optimization
gateJob, infidelity = remoteOptimize1Qubit(ham, FixedGate.Z.getMatrix(), depth=4, targetInfid=0.0001)

# Print infidelity and the waveforms
print(f"minimum infidelity: {infidelity}")
gateJob.plot(dark='True')

# Print the evolution process.
result = ham.simulate(job=gateJob)
projectedEvolution = project(result.result[0]["unitary"], qubits, level, 2)
print("Projected evolution:\n", round(projectedEvolution, 2))_____no_output_____

## Pulse optimization for arbitrary single-qubit gate_____no_output_____The following code generates and optimizes the pulses for an arbitrary gate, `U(theta=-1.231, phi=1.231, lamda=-1.231)`. Note that the user would need to import `U` from `RotationGate`._____no_output_____

from Quanlse.QOperation.RotationGate import U

# Define a U3 gate
aGate = U(theta=-1.231, phi=1.231, lamda=-1.231)

# Run the optimization
gateJob, infidelity = remoteOptimize1Qubit(ham, aGate.getMatrix(), depth=4, targetInfid=0.0001)

# Print infidelity and the waveforms
print(f"minimum infidelity: {infidelity}")
gateJob.plot(dark='True')

# Print the evolution process.
result = ham.simulate(job=gateJob)
projectedEvolution = project(result.result[0]["unitary"], qubits, level, 2)
print("Projected evolution:\n", round(projectedEvolution, 2))_____no_output_____

## Summary

This tutorial introduces the complete process of generating and optimizing pulses for any single-qubit gate using Quanlse. The users could follow this link [tutorial-single-qubit-gate.ipynb](https://github.com/baidu/Quanlse/blob/main/Tutorial/EN/tutorial-single-qubit.ipynb) to the GitHub page of this Jupyter Notebook document and obtain the relevant code for themselves. The users are encouraged to try parameter values different from this tutorial to obtain the optimal result._____no_output_____## References
\[1\] [Nielsen, Michael A., and Isaac L. Chuang. Quantum Computation and Quantum Information: 10th Anniversary Edition. Cambridge University Press, 2010.](https://doi.org/10.1017/CBO9780511976667)

\[2\] [Wilhelm, Frank K., et al. "An introduction into optimal control for quantum technologies." *arXiv preprint arXiv:2003.10132* (2020).](https://arxiv.org/abs/2003.10132)_____no_output_____


# Notebook from chcomin/disciplina-visao-computacional-01-2019
Path: Lecture 12 -Neural networks/Gradient descent.ipynb

# Least squares regression using gradient descent

We will apply a least squares regression using gradient descent. The idea is to find the optimal parameters of a function

$
y = f(x) = ax+b
$

that best adjusts to a dataset._____no_output_____### Generate the data_____no_output_____

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(2)

N = 50

x_data = np.random.rand(N)
y_data = 3.4*x_data+1.3+np.random.rand(N)

plt.figure()
plt.plot(x_data, y_data, 'o')_____no_output_____

### Propose some initial value for parameters $a$ and $b$_____no_output_____

slope = -7 #np.random.rand()
intercept = 15 #np.random.rand()
x_fit = np.linspace(min(x_data), max(x_data), 100)
y_fit = slope*x_fit + intercept

plt.figure()
plt.plot(x_data, y_data, 'o')
plt.plot(x_fit, y_fit)
plt.legend(('Data', 'Fit'))_____no_output_____

### Define the function that provides the error of the linear regression

The function is given by

$
L=\sqrt{\frac{1}{N}\sum_{i=1}^{N}(y_d(i)-y_e(i))^2}
$

where $y_d(i)$ is the $y$ value obtained for object $i$ and $y_e(i)$ is given by

$
y_e(i) = ax_d(i)+b
$

where $x_d(i)$ is the $x$ value measured for object $i$._____no_output_____

def get_error(x_data, y_data, intercept, slope):

    num_points = x_data.size
    error = 0
    for idx in range(num_points):
        error += (slope*x_data[idx]+intercept - y_data[idx])**2

    return np.sqrt(error/num_points)

print(get_error(x_data, y_data, intercept, slope))8.906238775493145

### Local error surface

We will analyse the local values of the error around the current parameters_____no_output_____

def get_local_error(x_data, y_data, intercept, slope,
                    range_intercept=1, range_slope=1, size=200):
    '''Calculate the regression error around the current intercept and slope.'''

    loc_slope = np.linspace(slope-range_slope, slope+range_slope, size)
    loc_intercept = np.linspace(intercept-range_intercept, intercept+range_intercept, size)
    local_error = np.zeros((size, size))
    for row, sl in enumerate(loc_slope):
        for col, ipt in enumerate(loc_intercept):
            local_error[row, col] = get_error(x_data, y_data, ipt, sl)  # Invert y because origin is at lower left

    return local_error, loc_intercept, loc_slope

def plot_local_error(x_data, y_data, intercept, slope, vmin=8, vmax=10):

    local_error, X, Y = get_local_error(x_data, y_data, intercept, slope)

    plt.figure()
    plt.subplot(111, aspect='equal')
    plt.pcolormesh(X, Y, local_error, vmin=vmin, vmax=vmax)
    plt.plot([intercept], [slope], 'ro')
    cbar = plt.colorbar()
    cbar.set_label('Error')
    plt.xlabel('Intercept')
    plt.ylabel('Slope')

plot_local_error(x_data, y_data, intercept, slope)_____no_output_____

### Gradient computation and visualization

We can compute the gradient using two methods:

1\. Using finite differences

We calculate

$
L(a,b) = \sqrt{\frac{1}{N}\sum_{i=1}^{N}(y_d(i)-ax_d(i)-b)^2}
$

Define some small change to one of the parameters and calculate the new error

$
L(a+\Delta a,b) = \sqrt{\frac{1}{N}\sum_{i=1}^{N}(y_d(i)-(a+\Delta a)x_d(i)-b)^2}
$

The derivative of $L$ with respect to $a$ is then approximated as

$
\large
\frac{\partial{L}}{\partial{a}} \approx \frac{L(a+\Delta a,b)-L(a,b)}{\Delta a}
$

For instance, we could use $\Delta a=0.001$. The same process is done to parameter $b$.

2\. Using the known derivative

We do not need to calculate the gradient numerically, because we can find the exact expression for the derivatives:

$
\frac{\partial{L}}{\partial{a}} = -\left[N\sum_{i=1}^N e(i)^2\right]^{-1/2}\sum_{i=1}^N e(i)x_d(i)
$

$
\frac{\partial{L}}{\partial{b}} = -\left[N\sum_{i=1}^N e(i)^2\right]^{-1/2}\sum_{i=1}^N e(i)
$

where $e(i)=y_d(i)-ax_d(i)-b$.

But we will use the finite difference method for calculating the gradient.
_____no_output_____

def calculate_gradient(x_data, y_data, intercept, slope, dintercept=0.01, dslope=0.01):
    '''Calculate the gradient of the error function using finite differences'''

    error = get_error(x_data, y_data, intercept, slope)
    error_dintercept = get_error(x_data, y_data, intercept+dintercept, slope)
    error_dslope = get_error(x_data, y_data, intercept, slope+dslope)

    derror_dslope = (error_dslope-error)/dslope
    derror_dintercept = (error_dintercept-error)/dintercept
    gradient = np.array((derror_dintercept, derror_dslope))
    return gradient

def calculate_gradient_analytical(x_data, y_data, intercept, slope, dintercept=0.01, dslope=0.01):
    '''Calculate the gradient of the error function using the analytic functions shown above'''

    num_points = x_data.size
    squared_error = 0
    error = 0
    error_x_data = 0
    for idx in range(num_points):
        e = y_data[idx] - slope*x_data[idx]-intercept
        squared_error += e**2
        error_x_data += e*x_data[idx]
        error += e
    derrordslope = -(num_points*squared_error)**(-0.5)*error_x_data
    derrordintercept = -(num_points*squared_error)**(-0.5)*error

    gradient = np.array((derrordintercept, derrordslope))
    return gradient

gradient = calculate_gradient(x_data, y_data, intercept, slope)
gradient_normalized = 0.2*gradient

plot_local_error(x_data, y_data, intercept, slope)
plt.arrow(intercept, slope, gradient_normalized[0], gradient_normalized[1], width=0.05)
_____no_output_____# We want to move in the negative direction of the gradient
plot_local_error(x_data, y_data, intercept, slope)
plt.arrow(intercept, slope, -gradient_normalized[0], -gradient_normalized[1], width=0.05)_____no_output_____

### Do one gradient descent step_____no_output_____

learning_rate = 0.4
position = np.array((intercept, slope))

# Gradient descent step
new_position = position - learning_rate*gradient

new_intercept, new_slope = new_position
plot_local_error(x_data, y_data, intercept, slope)
plt.plot([new_intercept], [new_slope], c='cyan', marker='o')_____no_output_____

### Visualize the new local error surface_____no_output_____

plot_local_error(x_data, y_data, new_intercept, new_slope)_____no_output_____

### Do many gradient descent steps, storing the parameters obtained for each step_____no_output_____

%%time

num_iterations = 170
learning_rate = 0.5

position = np.array((intercept, slope))
position_values = np.zeros((num_iterations, 2))
position_values[0] = position
for i in range(1, num_iterations):
    gradient = calculate_gradient(x_data, y_data, position[0], position[1])
    position = position - learning_rate*gradient
    position_values[i] = positionCPU times: user 8.01 ms, sys: 3.98 ms, total: 12 ms
Wall time: 10.6 ms

### Visualize the evolution of the intercept and slope parameters_____no_output_____

from IPython import display

local_error, X, Y = get_local_error(x_data, y_data, intercept, slope, range_intercept=20, range_slope=25)

fig = plt.figure()
ax = fig.add_subplot(111)
plt.pcolormesh(X, Y, local_error)
cbar = plt.colorbar()
cbar.set_label('Error')
plt.xlabel('Intercept')
plt.ylabel('Slope')
for i in range(0, num_iterations, 5):
    ax.plot([position_values[i][0]], [position_values[i][1]], 'o', ms=3)
    ax.set_title('Iteration %d'%(i), size=18)
    display.display(fig)
    display.clear_output(wait=True)
_____no_output_____fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(0, num_iterations, 5):
    est_intercept, est_slope = position_values[i]
    y_fit = est_slope*x_fit + est_intercept

    ax.clear()
    ax.plot(x_data, y_data, 'o')
    ax.plot(x_fit, y_fit)
    ax.set_title('Iteration %d'%(i), size=18)
    ax.set_xlim((0,1))
    ax.set_ylim((0,15))
    display.display(fig)
    display.clear_output(wait=True)     _____no_output_____


# Notebook from Hiroya-W/quantum-native-dojo
Path: notebooks/7.1_quantum_phase_estimation_detailed.ipynb

## 7-1. 量子位相推定アルゴリズム詳説：水素分子への応用を例として
この節では、量子位相推定アルゴリズム (Quantum Phase Estimation, QPE) の復習をするとともに、QPEを用いて量子多体系のハミルトニアン（水素分子）の固有値を求めてみる。その過程で、QPEを実際の問題に応用する際の流れやテクニックを詳しく学んでいく。_____no_output_____###  位相推定の復習
[2-4. 位相推定アルゴリズム（入門編）](2.4_phase_estimation_beginner.ipynb)で既に学んだように、QPEは、適当なユニタリ行列 $U$ が与えられたときにその固有値 $e^{i \lambda}$ をビット列として取り出すアルゴリズムである。
QPEをサブルーチンとして用いることで、素因数分解や量子多体系のエネルギー計算といった（ユニタリ行列の固有値問題に帰着できる）多くの問題を、古典コンピュータと比べて非常に高速に解けるようになることが期待されている。

具体的にQPEの変換を思い出してみよう。$U$ の $i$番目の固有値 $e^{i \lambda_i}$ に対応する固有ベクトルを $| u_i \rangle$ とする ($U | u_i \rangle = e^{i \lambda_i} | u_i \rangle$)。この時、制御ユニタリ演算 $\Lambda (U)$ と量子フーリエ変換を用いて、QPEは次の変換を実現する。

$$
| u_i \rangle | 0 \rangle{}^{\otimes{t}} \xrightarrow{\text{QPE}} | u_i \rangle | \tilde{\lambda_i} \rangle.
$$

ここで、 $| 0 \rangle{}^{\otimes{t}}$ は $t$ 個の補助量子ビットであり、$\tilde{\lambda_i}=j_1j_2 \ldots j_t$ は $\lambda_i$ を $t$ 桁目まで2進展開したビット列

$$
\frac{\lambda_i}{2 \pi} = \frac{j_1}{2} + \frac{j_2}{2^2} + \ldots + \frac{j_t}{2^t} = 0.j_1 j_2 \ldots j_t
$$

である。 (  $(t+1)$ 桁目以降は省略した)

このQPEを実用的な問題に対して実行するには、誤り訂正機能のある量子コンピュータが必要であると考えられている (long-term アルゴリズム)。主な理由としては、

- 補助ビットの数
- 制御ユニタリゲート $\Lambda (U^{2^{k-1}})$ ($k =1, ..., t$) の複雑さ

といった点が挙げられる。まず補助ビットの数についてだが、例えば現在の古典コンピュータで使われているような倍精度実数の精度で $\lambda_i$ を求めるには50個以上の補助ビットが必要になる 。
そして、そのような多数の補助ビット（つまり大きな $t$ ）を用いる場合、制御ユニタリゲート $\Lambda (U^{2^{k-1}})$ ($k =1, ..., t$) を $U$ の非常に大きなべき乗に対して用意する必要が生じる。
このような制御ゲートを用意する難しさは $U$ としてどのような行列を考えるかに依存するが、一般には簡単でない：
後で見るように、QPEの応用が最も期待されている問題の一つである「エルミート行列 $H$ の固有値問題」の場合は、$U$ を $H$ による時間発展演算子、つまり$U = e^{-iH\tau}$ ($\tau$は定数) とおくので、$U$ の大きなべきは $H$ による（精度の逆数に対して指数関数的に）長時間の時間発展をシミュレーションすることを意味する。これは非常に難しい。_____no_output_____### 反復的位相推定
実は、$U$ の固有値のみに興味があり固有状態が必要ないのであれば、QPEの補助ビットを減らすことができる。
これは反復的位相推定法 (Iterative Quantum Phase Estimation (IQPE), https://journals.aps.org/pra/abstract/10.1103/PhysRevA.76.030306) と呼ばれており、すでに[2-4節](2.4_phase_estimation_beginner.ipynb)の「はじめに：アダマールテストを改良する」の箇所で紹介した方法と等価なのだが、ここに改めて記しておく。

IQPEは固有値を2進展開したビット列を、各桁ごとに決定論的に求める方法である。必要な補助ビットは1つで、1イテレーションごとに固有値の2進小数表示 $0.j_1...j_t$ の1つの桁の値 ($j_k$) を求める。
手順は以下の通りである ($k = t, t-1, \ldots, 1$とイテレーションする)：

#### $k = t$ のとき
1. 補助ビットにアダマールゲートをかける

$$
| 0 \rangle | u_i \rangle \xrightarrow{H} \frac{1}{\sqrt{2}} \left( |0\rangle + |1\rangle \right) | u_i \rangle
$$

2. 補助ビットに $\Lambda (U^{2^{t-1}})$ をかける

$$
\frac{1}{\sqrt{2}} \left( |0\rangle + |1\rangle \right) | u_i \rangle  \xrightarrow{\Lambda (U^{2^{t-1}})} \frac{1}{\sqrt{2}} \left(| 0 \rangle + e^{i \pi j_t} | 1 \rangle \right) | u_i \rangle
$$

3. 補助ビットにアダマールゲートをかけて測定する

$$
\frac{1}{\sqrt{2}} \left(| 0 \rangle + e^{i \pi j_t} | 1 \rangle \right) | u_i \rangle  \xrightarrow{H} \frac{1}{2} [ (1 + e^{i \pi j_t})| 0 \rangle + (1 - e^{i \pi j_t}) | 1 \rangle]   |u_i \rangle =  | j_t \rangle | u_i \rangle  \xrightarrow{\textrm{Measure}} j_t
$$

4. 測定結果 $j_t$ を、蓄積位相 $\Phi(t)$ に反映させる: $\Phi(t) = \pi \cdot \frac{j_t}{2} = \pi 0. j_t$

#### $k = t-1, t-2, \ldots, 1$ のとき
1. 補助ビットにアダマールゲートをかける

$$
| 0 \rangle | u_i \rangle \xrightarrow{H} \frac{1}{\sqrt{2}} \left( |0\rangle + |1\rangle \right) | u_i \rangle
$$

2. 補助ビットにZ回転ゲート $R_Z ( \Phi(k+1) )$ (ここで $R_Z(\theta)=e^{i\theta/2 Z}, \Phi(k+1) = \pi 0.j_{k+1} j_{k+2} \ldots j_{t}$となっている) をかける

$$
\frac{1}{\sqrt{2}} \left( |0\rangle + |1\rangle \right) | u_i \rangle \xrightarrow{R_Z} \frac{e^{i\Phi(k+1)/2}}{\sqrt{2}} (| 0 \rangle + e^{- i \pi 0.j_{k+1} j_{k+2} \ldots j_{t}} | 1 \rangle) | u_i \rangle
$$

3. 補助ビットに $\Lambda (U^{2^{k-1}})$ をかける

$$
\frac{e^{i\Phi(k+1)/2}}{\sqrt{2}} (| 0 \rangle + e^{- i \pi 0.j_{k+1} j_{k+2} \ldots j_{t}} | 1 \rangle) | u_i \rangle \xrightarrow{\Lambda (U^{2^{k-1}})} \frac{e^{i\Phi(k+1)/2}}{\sqrt{2}} (| 0 \rangle + e^{i \pi j_k} | 1 \rangle) | u_i \rangle
$$

4. 補助ビットにアダマールゲートをかけて測定する

$$
\frac{e^{i\Phi(k+1)/2}}{\sqrt{2}} (| 0 \rangle + e^{i \pi j_k} | 1 \rangle) | u_i \rangle  \xrightarrow{H} \frac{e^{i\Phi(k+1)/2}}{2}  [(1 + e^{i \pi j_k})| 0 \rangle + (1 - e^{i \pi j_k}) | 1 \rangle] | u_i \rangle = e^{i\Phi(k+1)/2} | j_k \rangle | u_i \rangle  \xrightarrow{\textrm{Measure}} j_k
$$

5. 測定結果 $j_k$ を 蓄積位相 $\Phi(k)$ に反映させる:

$$
\Phi(k) = \frac{\Phi(k+1)}{2} + \pi \cdot \frac{j_k}{2} = \pi 0.j_{k} j_{k+1} \ldots j_{t}
$$

#### 全ての$j_k$ ($k = t, t-1, \ldots, 1$) を測定した後
所望の位相

$$
\lambda_i = 2 \Phi(1) = 2 \pi 0.j_1.j_2 \ldots j_{t}
$$

が得られる。_____no_output_____### 例： 量子位相推定アルゴリズムを用いた水素分子ハミルトニアンの基底状態エネルギーの計算_____no_output_____上記の反復的位相推定アルゴリズムを用いて、実際に水素分子の基底状態エネルギーを求めてみよう（以下の内容は論文[1] を参考としている）。
[4-1節](4.1_quantum_simulation.ipynb) や [6-1節](6.1_openfermion_basics.ipynb) で学んだように、水素分子の基底状態エネルギーとは、水素分子を表すハミルトニアン $H$ というエルミート行列の最小固有値のことである。
QPEやIQPEでは、ハミルトニアン $H$ の固有値問題を、その**時間発展演算子** $U = e^{−iH \tau}$ **の固有値を求める**ことで解いている。
ここで$\tau$は何らかの定数であり、$H$ の最小固有値と最大固有値を $E_{\text{min}}, E_{\text{max}}$ とした時に、$[ E_{\text{min}}, E_{\text{max}}]$ が $[0, 2\pi]$ に収まるようにとっておく（必要であれば、定数シフトも加える）。QPE・IQPEでは $U$ の固有値の位相には $2\pi$ の不定性があるが、こうしておけば $U$ の固有値から $H$ の固有値が一意に復元できるからだ。

基底エネルギー計算に必要なステップは以下の通りである:

0. （ハミルトニアンのサイズを対称性などを用いて削減する）
1. ハミルトニアンの時間発展演算子 $U=e^{-iH\tau}$ を精度よく近似する
2. 制御時間発展演算子を量子コンピュータで容易に実行可能なゲートセットに分解し実装する
3. 基底状態と十分重なりのある初期状態を準備する
4. IQPEでエネルギー固有値を測定する

以下、順を追って手法の詳細な説明と実装例を示す。_____no_output_____#### 0. （ハミルトニアンのサイズを対称性などを用いて削減する）
このステップは、実際にIQPEをシミュレータや実機で動かすときに、なるべくリソースを減らすために必要な工程であり、理論上必須ではない。
詳しくは文献 [1] に譲るが、水素分子の第二量子化されたハミルトニアン (STO-6G 基底) を Bravyi-Kitaev 変換で qubit 系にmapすると、そのままでは 4qubit  のハミルトニアン = $16 \times 16$ 行列になる。しかし、**ハミルトニアンの持つ対称性**（電子数保存則など）を用いてハミルトニアンを部分対角化して考えると、基底状態を求める際には 2 qubit で良いことがわかり、さらにそのハミルトニアンは以下のような6種類の項しか持たない。

$$
H = \sum_i g_i H_i = g_0 I + g_1 Z_0 + g_2 Z_1 + g_3 Z_0 Z_1 + g_4 Y_0 Y_1 + g_5 X_0 X_1
$$

ここで係数 $g_i$ は実数で、その値は水素分子の原子間距離（配置）に依存する。
化学に馴染みのない読者は、とにかく水素分子に対応した上記のエルミート行列が存在し、それの最小固有値を求めることが重要であるとだけ理解しておけば問題ない。_____no_output_____#### 1. ハミルトニアンの時間発展演算子 $U=e^{-iH\tau}$ を精度よく近似する
IQPEで用いる制御ユニタリ演算 $\Lambda (U^{2^k})$ を実装するため、まずは時間発展演算子 $U = e^{−iH \tau}$ を量子回路に実装する。
まず、定数項 $g_0 I$ と$g_3 Z_0 Z_1$ 項がハミルトニアンの他の全ての項と交換することに注意すると、$U$ は次のようになる。

$$
e^{−iH \tau} = \exp \left[−i \tau \sum_i g_i H_i \right] = \exp[−i \tau g_0 I] \exp[−i \tau g_3 Z_0 Z_1] \exp[−i \tau H_{\textrm{eff}}]
$$

ここで、$H_{\text{eff}}$ は

$$
H_{\text{eff}} = g_1 Z_0 + g_2 Z_1 + g_4 Y_0 Y_1 + g_5 X_0 X_1
$$

である。$g_0 I$ と$g_3 Z_0 Z_1$ 部分の固有値への寄与は後から簡単に加算することができるので、以下では $H_{\textrm{eff}}$ の固有値を $U_{\text{eff}}  := e^{−i H_{\text{eff}} \, \tau}$ のIQPEを用いて求めることを考える。

$U_{\text{eff}}$ をトロッター展開 ([4-2節](4.2_trotter_decomposition.ipynb)) すると、

$$
U_{\text{eff}} = \exp \left[−i \tau \sum_{i=1,2,4,5} g_i H_i \right] \approx U_{\text{Trot}}^{(N)} (\tau) := \left( \prod_{i=1,2,4,5} \exp[-i g_i H_i \tau/N] \right)^N
$$

となる。 $U_{\text{Trot}}^{(N)}$ に現れる積の各項は multi-qubit Pauli rotation gate、つまりパウリ行列の積の指数関数 $\exp(i\theta P)$ の形をしているので、簡単な量子ゲートの積として実装することが容易になっている。これで $U_{\textrm{eff}}$ を近似的に量子回路上へ実装することができた。_____no_output_____この近似のエラーについて調べてみよう。解析的にざっくりと評価すると

$$
\left| U_{\textrm{eff}} - U_{\textrm{Trot}}^{(N)} (\tau) \right| \sim \frac{\tau^2}{2N} \sum_{i > j} | [H_i, H_j] |
$$

となる[2]。この表式によると、欲しい精度 $\epsilon$ に対して $N \sim \tau^2/\epsilon$ 程度の大きさの $N$ をとれば良い事が分かる。
今回取り扱う系は $4 \times 4$ という小さい行列で表されるので、$H_{\textrm{eff}}$ を実際に厳密に対角化してみてその最小固有値 $E_{\textrm{min}}$ を求め、$U_{\textrm{Trot}}^{(N)}$ の固有値 $e^{i \lambda_{\textrm{Trot}}\tau}$ の $\lambda_{\textrm{Trot}}$ と比較してみよう。_____no_output_____まずは $H_{\textrm{eff}}$ の対角化を行う。エネルギーの単位は[ハートリー(Ha)](https://ja.wikipedia.org/wiki/ハートリー)というものが使われている。_____no_output_____

from functools import reduce
import numpy as np
from numpy.linalg import matrix_power, eig
from scipy.sparse.linalg import eigsh
from openfermion.ops import QubitOperator
from openfermion.linalg import get_sparse_operator #エラーが出る場合は openfermion を version 1.0.0 以上にしてみてください
from qulacs import QuantumState, Observable, QuantumCircuit
import matplotlib.pyplot as plt

## Google Colaboratory / (Linux or Mac)のjupyter notebook 環境の場合にのみ実行してください。
## Qulacsのエラーが正常に出力されるようになります。
!pip3 install wurlitzer
%load_ext wurlitzer_____no_output_____def hamiltonian_eff():
    """
    distance = 0.70 A
    removed 'I' and 'Z0 Z1' terms, which add up to -1.31916027
    """
    n_qubits = 2
    g_list = [0.3593, 0.0896, -0.4826, 0.0896] ## taken from table 1 of paper [1]
    pauli_strings = ['Z0', 'Y0 Y1', 'Z1', 'X0 X1']
    hamiltonian = QubitOperator()
    for g, h in zip(g_list, pauli_strings):
        hamiltonian += g * QubitOperator(h)
    sparse_matrix = get_sparse_operator(hamiltonian, n_qubits=n_qubits)
    vals, vecs = eigsh(sparse_matrix, k=1, which='SA') ## only smallest eigenvalue and eigenvector are calculated
    return sparse_matrix, vals_____no_output______, eigs = hamiltonian_eff()
exact_eigenvalue = eigs[0]
print('exact_eigenvalue: {:.10f} Ha'.format(exact_eigenvalue)) ## print eigenvalue up to 10 digits after decimal pointexact_eigenvalue: -0.8607602744 Ha

次に $U_{\textrm{Trot}}^{(N)}$ を対角化してみる。後のステップでは $U_{\textrm{Trot}}^{(N)}$ を量子回路として具体的に実装するが、ここでは $H_i^2 = I$ (恒等行列) の時

$$
\left( \prod_{i=1,2,4,5} \exp[-i g_i H_i \tau/N] \right)^N = \left( \prod_{i=1,2,4,5} \left( \cos(g_i\tau/N) I -i \sin(g_i\tau/N) H_i \right) \right)^N
$$

となる性質を用いて計算してしまう。
そして、$N = 1, 3, \ldots, 9$ において $U_{\textrm{Trot}}^{(N)}$ の固有値 $e^{-i \lambda_{\textrm{Trot}}\tau}$ の $\lambda_{\textrm{Trot}}$ を求め、 $E_{\textrm{min}}$ と比較する。_____no_output_____

def order_n_trotter_approx(t, n_trotter_steps):
    """
    ordering: 'Z0', 'Y0 Y1', 'Z1', 'X0 X1'
    Returns:
        sparse_matrix: trotterized [exp(iHt/n)]^n
        args: list of phases of each eigenvalue, exp(i*phase)
    """
    n_qubits = 2
    g_list = [0.3593, 0.0896, -0.4826, 0.0896]
    pauli_strings = ['Z0', 'Y0 Y1', 'Z1', 'X0 X1']
    terms = []
    for g, h in zip(g_list, pauli_strings):
        arg = g * t / n_trotter_steps
        qop = complex(np.cos(arg), 0) * QubitOperator('') - complex(0, np.sin(arg)) * QubitOperator(h)
        terms += [get_sparse_operator(qop, n_qubits=n_qubits)]
    sparse_matrix = reduce(np.dot, terms)
    matrix = matrix_power(sparse_matrix.toarray(), n_trotter_steps)  ## this is U_{trot}^{(N)}
    vals, vecs = eig(matrix)  ## e^{i lambda_{trot} }
    args = np.angle(vals) ## returns value in [-pi, pi] -> we don't modify output since we know exact value is around -0.86
    return sparse_matrix, sorted(args) ## we return sorted values_____no_output_____tau = 0.640 ## taken from table 1 of paper [1]
print('N, E_trot, |exact_eig - E_trot|')
for n in range(1, 10, 2):
    _, phases = order_n_trotter_approx(tau, n)
    e_trotter = phases[0]/tau
    print( f"{n}, {e_trotter:.10f}, {abs(exact_eigenvalue - e_trotter):.3e}" ) N, E_trot, |exact_eig - E_trot|
1, -0.8602760326, 4.842e-04
3, -0.8607068561, 5.342e-05
5, -0.8607410548, 1.922e-05
7, -0.8607504700, 9.804e-06
9, -0.8607543437, 5.931e-06

お分かり頂けただろうか？ 次数 $N$ が増えるごとに近似精度が上がっており、真のエネルギー固有値を chemical accuracy ( $1.6 × 10^{−3}$ Ha) と呼ばれる化学計算で必要な精度で近似するには $N = 1$ で今回は十分であることが分かる。_____no_output_____#### 2. 制御時間発展演算子を量子コンピュータで容易に実行可能なゲートセットに分解し実装する
量子コンピュータ上で制御時間発展演算子 $\Lambda \left( \left( U_{\textrm{Trot}}^{(N)} \right)^{2^k} \right)$ を実行するためには、これを簡単な量子ゲートに分解する必要がある。
今回の例では、 $U_{\textrm{Trot}}^{(N)}$ に含まれる

- $\Lambda(R_Z(\theta))$
- $\Lambda(R_{XX}(\theta))$
- $\Lambda(R_{YY}(\theta))$

という制御回転ゲートを分解できれば良い。ここで $R_Z(\theta) = e^{i\theta/2 Z_x}$ は $x=0,1$ 番目のqubitに作用する $Z$ 方向の回転ゲート、$R_{XX}(\theta) = e^{i\theta/2 X_0 X_1}, R_{YY}(\theta) = e^{i\theta/2 Y_0 Y_1}$ も回転ゲートである。

まず、 $\Lambda(R_Z(\theta))$ は 制御ビット $| c \rangle$ と標的ビット $| t \rangle$ にかかると、

$$
\Lambda(R_Z(\theta)) | c \rangle | t \rangle = | c \rangle (R_Z(\theta))^c | t \rangle
$$

を満たすゲートである $(c=0,1)$ 。 $\textrm{CNOT} | c \rangle | t \rangle = | c \rangle X^c | t \rangle$、 $XZX = -Z$ が成り立つことに注意すると、

$$
\textrm{CNOT} \left(I \otimes R_Z(-\theta/2) \right) \textrm{CNOT} \left(I \otimes R_Z(\theta/2) \right) | c \rangle | t \rangle
= | c \rangle　X^c　R_Z(-\theta/2) X^c R_Z(\theta/2) | t \rangle
= | c \rangle \left(R_Z(\theta)\right)^c | t \rangle
$$

と式変形できるから、

$$
\Lambda(R_Z(\theta)) = \textrm{CNOT} \left(I \otimes R_Z(-\theta/2) \right) \textrm{CNOT} \left(I \otimes R_Z(\theta/2) \right)
$$

が言える。つまり、制御回転ゲート $\Lambda(R_Z(\theta))$ が CNOTゲートと $Z$ 回転ゲートという基本的なゲートを使って実装できた。

さらに、

$$
\textrm{CNOT}_{0,1} \left(I_0 \otimes Z_1 \right) \textrm{CNOT}_{0,1} = Z_0 \otimes Z_1
$$

の性質を用いると、$\Lambda(R_{ZZ}(\theta))$ が実現できる。
そして、$H Z H = X$　や $SH Z HS^{\dagger} = Y$ を用いると $\Lambda(R_{XX}(\theta))$、$\Lambda(R_{YY}(\theta))$ がそれぞれ実現できる。詳細は以下の実装も参照してほしい。

以下のコードでは、 Qulacs で制御時間発展演算子 $\Lambda \left( \left( U_{\textrm{Trot}}^{(N)} \right)^{2^k} \right)$ の量子回路を実装し、IQPEで実行すべき回路を作っている。回路図は以下のようである。

![IQPE回路](figs/7/IQPE.png)_____no_output_____

def IQPE_circuit(g_list, tau, kickback_phase, k, n_trotter_step=1):
    n_qubits = 3 ## 2 for system, 1 for ancillary
    a_idx = 2 ## ancilla index
    phi = -(tau / n_trotter_step) * g_list ## coefficient for Pauli
    circuit = QuantumCircuit(n_qubits)
    ## Apply Hadamard to ancilla
    circuit.add_H_gate(a_idx)
    ## Apply kickback phase rotation to ancilla bit
    circuit.add_RZ_gate(a_idx, kickback_phase)
    ## controlled time evolution (Apply controll-e^{-iH*tau} for 2^{k-1} times
    for _ in range( 2 ** (k-1) ):
        for _ in range(n_trotter_step):
            # CU(Z0) i.e. controlled exp(i phi[0]*Z_0)
            circuit.add_RZ_gate(0, phi[0]) ## note that qulacs' RZ is defined as RZ(theta) = e^{i*theta/2*Z}
            circuit.add_CNOT_gate(a_idx, 0)
            circuit.add_RZ_gate(0, -phi[0])
            circuit.add_CNOT_gate(a_idx, 0)

            # CU(Y0 Y1)
            circuit.add_Sdag_gate(0)
            circuit.add_Sdag_gate(1)
            circuit.add_H_gate(0)
            circuit.add_H_gate(1)
            circuit.add_CNOT_gate(0, 1)
            circuit.add_RZ_gate(1, phi[1])
            circuit.add_CNOT_gate(a_idx, 1)
            circuit.add_RZ_gate(1, -phi[1])
            circuit.add_CNOT_gate(a_idx, 1)
            circuit.add_CNOT_gate(0, 1)
            circuit.add_H_gate(0)
            circuit.add_H_gate(1)
            circuit.add_S_gate(0)
            circuit.add_S_gate(1)

            # CU(Z1)
            circuit.add_RZ_gate(1, phi[2])
            circuit.add_CNOT_gate(a_idx, 1)
            circuit.add_RZ_gate(1, -phi[2])
            circuit.add_CNOT_gate(a_idx, 1)

            # CU(X0 X1)
            circuit.add_H_gate(0)
            circuit.add_H_gate(1)
            circuit.add_CNOT_gate(0, 1)
            circuit.add_RZ_gate(1, phi[3])
            circuit.add_CNOT_gate(a_idx, 1)
            circuit.add_RZ_gate(1, -phi[3])
            circuit.add_CNOT_gate(a_idx, 1)
            circuit.add_CNOT_gate(0, 1)
            circuit.add_H_gate(0)
            circuit.add_H_gate(1)

    ## Apply Hadamard to ancilla
    circuit.add_H_gate(a_idx)
    return circuit_____no_output_____

#### 3. 基底状態と十分重なりのある初期状態を準備する
これまでのQPE・IQPEの説明では、簡単のために $U$ が作用する状態はその固有状態であることを仮定してきた。実は、入力状態が「固有値を知りたい状態に十分近い（重なりがある）状態」であっても、十分高い精度でその固有値を求めることができる。

$|n \rangle$ をハミルトニアンの固有状態、それに対応する固有値を $E_n$ 、参照状態を $|\phi \rangle = \sum_n c_n |n \rangle$ とすると、QPE・IQPEの回路の作用は（$\tau=-1$とした）

$$
e^{iH } |0\ldots 0 \rangle  |\phi \rangle \xrightarrow{QPE} \sum_n c_n |\tilde{E_n} \rangle  |n \rangle
$$

となる。ここで例えば1番目の補助ビット $j_1$ の測定が行われると、0が観測される確率は $\sum_{n \, \textrm{s.t.} \tilde{E_n}^{(1)}=0} |c_n|^2$ 、つまり固有値の2進小数展開の1桁目が0であるような固有値$E_n$についての重みの和に比例する。よって、もし $\{|c_n|^2\}_n$ の値が所望の状態についてのみ十分大きければ、$j_1$ の測定を繰り返して0か1か多く観測された方を採用すれば、所望の固有値が得られることが言える。（もう少し厳密な取り扱いは、Nielsen-Chuangの `Exercise 5.8` も参照）

今回の水素分子の基底エネルギーを求める問題の場合、 Hartree-Fock (HF) 状態 $|\phi \rangle = |01 \rangle$ が十分に基底状態に近い為、これを参照状態とする。（注：今回の問題では、HF状態が $|01\rangle$ になっている[1]）_____no_output_____#### 4. IQPEでエネルギー固有値を測定する
それでは、IQPEを実行してみよう。

以下の実装では、補助ビットなど特定の量子ビットのみを測定しその結果を用いる際に便利な `state.get_marginal_probability(bit_list)` を使っている。これは量子状態 `state` の特定の量子ビットが特定のビット値を持っている確率を、波動関数の振幅から計算する関数である。
例えば 補助ビット (`index=2`) が `0` 状態 (0、1番目の量子ビットに関しては測定しない) である
確率は、`get_marginal_probability([2, 2, 0])` で得られる (`2` は測定しない事を表している)。_____no_output_____

from qulacs.circuit import QuantumCircuitOptimizer

def iterative_phase_estimation(g_list, tau, n_itter, init_state, n_trotter_step=1, kickback_phase=0.0):
    for k in reversed(range(1, n_itter+1)): ## run from n_itter to 1
        psi = init_state.copy()
        circuit = IQPE_circuit(np.array(g_list), tau, kickback_phase, k, n_trotter_step=n_trotter_step)
        ## 実行時間短縮のため回路の最適化を行う
        opt = QuantumCircuitOptimizer()
        max_block_size = 4
        opt.optimize(circuit, max_block_size)
        ## execute circuit
        circuit.update_quantum_state(psi)
        # partial trace
        p0 = psi.get_marginal_probability([2, 2, 0])
        p1 = psi.get_marginal_probability([2, 2, 1])
        # update kickback phase
        #print(f"k={k:2d}, p0={p0:.3f}, p1={p1:.3f}")
        kth_digit = 1 if (p0 < p1) else 0
        kickback_phase = 0.5 * kickback_phase + np.pi * 0.5 * kth_digit
    return 2 * kickback_phase_____no_output_____

それでは、位相を何桁まで測定すれば良いか ($0.j_1 \ldots j_t$ の $t$ をどこまで大きくとるべきか) というベンチマークを取りつつ、IQPEを実行する。
化学で基底状態エネルギーの計算精度の一つの目安とされる chemical accuracy ($1.6 \times 10^{-3}$ Ha) の精度が必要な場合、イテレーションの回数を理論的に見積もると[3]

$$
t = - \log_2 (1.6 \times 10^{-3}) + \log_2 \left(2 + \frac{1}{2 \times 0.5} \right) \approx 10.87
$$

となる。
つまり $t = 11$ 程度にとれば十分そうであるが、実際に計算してみよう。_____no_output_____

n_qubits = 3 # 2 for electron configurations and 1 for ancilla
g_list = [0.3593, 0.0896, -0.4826, 0.0896]
# pauli_strings = ['Z 0', 'Y 0 Y 1', 'Z 1', 'X 0 X 1']
hf_state = QuantumState(n_qubits)
hf_state.set_computational_basis(0b001) # |0>|01>
tau = 0.640

e_trotter = -0.8602760325707504 ## exact one of U_{Trot}^{(N)}
print(f"e_trotter={e_trotter:.10f}")

result_list = []
for n_itter in range(1, 12+1): # precission in digit
    iqpe_phase = iterative_phase_estimation(g_list, tau, n_itter, hf_state, n_trotter_step=1, kickback_phase=0.0)
    e_iqpe = - iqpe_phase/tau ## U=exp(-iH*tau) so the IQPE picks up eigenvalue of -H*tau
    print(f"n_itter={n_itter:2d}, e_iqpe={e_iqpe:10f}, error={np.abs(e_iqpe-e_trotter):.5e}")
    result_list.append([n_itter, e_iqpe])

#print('e_iqpe = {} Ha, |e_iqpe-e_trotter| = {} Ha'.format(e_iqpe, abs(e_iqpe-e_trotter)))e_trotter=-0.8602760326
n_itter= 1, e_iqpe= -0.000000, error=8.60276e-01
n_itter= 2, e_iqpe= -0.000000, error=8.60276e-01
n_itter= 3, e_iqpe= -1.227185, error=3.66909e-01
n_itter= 4, e_iqpe= -0.613592, error=2.46684e-01
n_itter= 5, e_iqpe= -0.920388, error=6.01124e-02
n_itter= 6, e_iqpe= -0.920388, error=6.01124e-02
n_itter= 7, e_iqpe= -0.843689, error=1.65866e-02
n_itter= 8, e_iqpe= -0.843689, error=1.65866e-02
n_itter= 9, e_iqpe= -0.862864, error=2.58816e-03
n_itter=10, e_iqpe= -0.862864, error=2.58816e-03
n_itter=11, e_iqpe= -0.858071, error=2.20553e-03
n_itter=12, e_iqpe= -0.860467, error=1.91316e-04
## 結果のプロット
result_array = np.array(result_list)
plt.xlabel("# of digit", fontsize=15)
plt.ylabel("Error", fontsize=15)
plt.semilogy(result_array[:,0], np.abs(result_array[:,1] - e_trotter), "bo-")
plt.xlim(0,13)
plt.fill_between([0,13], 1.6e-3, color = "lightgrey") ## fill the chemical accuracy region_____no_output_____

お分かり頂けただろうか？ 予想通り `n_itter = 12` でようやく chemical accuracy に到達した。

ここで1点注意が必要なのは、ここで紹介したサンプルコードでは(制御)時間発展演算子 $\Lambda \left( \left( U_{\textrm{Trot}}^{(N)} \right)^{2^k} \right)$ のゲートの深さが $t$ に関して指数的に増大している事である。つまり、精度を上げるのに指数関数的に多くのゲートを用いなければならない。ここでは単純なトロッター分解に基づく方法を紹介したが、他の方法を使って時間発展演算子を効率的に実装する研究も数多くなされている。興味を持たれた読者は文献[4][5][6]を参照されたい。_____no_output_____### 参考文献
[1] P. J. J. O’Malley _et al._ , “Scalable Quantum Simulation of Molecular Energies“, [PHYSICAL REVIEW X 6, 031007 (2016)](https://journals.aps.org/prx/abstract/10.1103/PhysRevX.6.031007)
[2] S. Lloyd, “Universal Quantum Simulators“, [Science 273, Issue 5278, pp.1073-1078](https://science.sciencemag.org/content/273/5278/1073)
[3] M. Nielsen and I. Chuang, “Quantum Computation and Quantum Information“ , Sec. 5.2
[4] D. W. Berry, G. Ahokas, R. Cleve, B. C. Sanders, “Efficient quantum algorithms for simulating sparse Hamiltonians“,
[Communications in Mathematical Physics 270, 359 (2007)](https://link.springer.com/article/10.1007/s00220-006-0150-x)
[5] D. W. Berry and  A. M. Childs,  “Black-box Hamiltonian simulation and unitary implementation“, [Quantum Information and Computation 12, 29 (2012)](http://www.rintonpress.com/journals/qiconline.html#v12n12)
[6] D. W. Berry, A. M. Childs, R. Cleve, R. Kothari, and R. D. Somma,  “Simulating Hamiltonian dynamics with a truncated Taylor series“,
[Phys. Rev. Lett. 114, 090502 (2015)](https://dx.doi.org/10.1103/PhysRevLett.114.090502)_____no_output_____


# Notebook from pengfei99/TensorFlowDataValidation
Path: notebooks/TFDV_example2.ipynb

# Use Tensorflow data validation

## Install the package
The official pypi page can be found here: https://pypi.org/project/tensorflow-data-validation/
Note, It only works in python 3.7 and python 3.8

```shell
pip install tensorflow-data-validation
```

There are alos bugs in the package depenedencies management, the above pip install will install an outdated numpy, pandas package. But it requries a newer version. So after running the above pip install, you need to run

```shell
pip install numpy --upgrade
pip install pandas --upgrade
```_____no_output_____

import tensorflow as tf
import tensorflow_data_validation as tfdv
import os
import tempfile, urllib, zipfile

print('TF version:', tf.__version__)
print('TFDV version:', tfdv.version.__version__)TF version: 2.8.0
TFDV version: 1.6.0
# make a temp dir
BASE_DIR = tempfile.mkdtemp()

# Download the zip file from GCP and unzip it
zip, headers = urllib.request.urlretrieve('https://storage.googleapis.com/artifacts.tfx-oss-public.appspot.com/datasets/chicago_data.zip')
zipfile.ZipFile(zip).extractall(BASE_DIR)
zipfile.ZipFile(zip).close()

# check the downloaded file
print("Here's what we downloaded:")
!ls -R {BASE_DIR}

# setup file path for further usage
DATA_DIR = os.path.join(BASE_DIR, 'data')
OUTPUT_DIR = os.path.join(BASE_DIR, 'chicago_taxi_output')
TRAIN_DATA = os.path.join(DATA_DIR, 'train', 'data.csv')
EVAL_DATA = os.path.join(DATA_DIR, 'eval', 'data.csv')
SERVING_DATA = os.path.join(DATA_DIR, 'serving', 'data.csv')Here's what we downloaded:
/tmp/tmpchvbcdga:
data

/tmp/tmpchvbcdga/data:
eval  serving  train

/tmp/tmpchvbcdga/data/eval:
data.csv

/tmp/tmpchvbcdga/data/serving:
data.csv

/tmp/tmpchvbcdga/data/train:
data.csv

## Step1 Data profiling

First we'll use [tfdv.generate_statistics_from_csv](https://www.tensorflow.org/tfx/data_validation/api_docs/python/tfdv/generate_statistics_from_csv) to understand our training data.

TFDV can compute descriptive [statistics](https://github.com/tensorflow/metadata/blob/v0.6.0/tensorflow_metadata/proto/v0/statistics.proto) that provide a quick overview of the data in terms of the features that are present and the shapes of their value distributions.

Internally, TFDV uses [Apache Beam](https://beam.apache.org/)'s data-parallel processing framework to scale the computation of statistics over large datasets. For applications that wish to integrate deeper with TFDV (e.g., attach statistics generation at the end of a data-generation pipeline), the API also exposes a Beam PTransform for statistics generation._____no_output_____

train_stats = tfdv.generate_statistics_from_csv(data_location=TRAIN_DATA)WARNING:apache_beam.runners.interactive.interactive_environment:Dependencies required for Interactive Beam PCollection visualization are not available, please use: `pip install apache-beam[interactive]` to install necessary dependencies to enable all data visualization features.

Now let's use [tfdv.visualize_statistics](https://www.tensorflow.org/tfx/data_validation/api_docs/python/tfdv/visualize_statistics), which uses [Facets](https://pair-code.github.io/facets/) to create a succinct visualization of our training data:

* Notice that numeric features and catagorical features are visualized separately, and that charts are displayed showing the distributions for each feature.
* Notice that features with missing or zero values display a percentage in red as a visual indicator that there may be issues with examples in those features.  The percentage is the percentage of examples that have missing or zero values for that feature.
* Notice that there are no examples with values for `pickup_census_tract`.  This is an opportunity for dimensionality reduction!
* Try clicking "expand" above the charts to change the display
* Try hovering over bars in the charts to display bucket ranges and counts
* Try switching between the log and linear scales, and notice how the log scale reveals much more detail about the `payment_type` categorical feature
* Try selecting "quantiles" from the "Chart to show" menu, and hover over the markers to show the quantile percentages_____no_output_____

tfdv.visualize_statistics(train_stats)_____no_output_____

## Step2: Infer a schema

Now let's use [tfdv.infer_schema](https://www.tensorflow.org/tfx/data_validation/api_docs/python/tfdv/infer_schema) to create a schema for our data.  A schema defines constraints for the data that are relevant for ML. Example constraints include the data type of each feature, whether it's numerical or categorical, or the frequency of its presence in the data. For categorical features the schema also defines the domain - the list of acceptable values.  Since writing a schema can be a tedious task, especially for datasets with lots of features, TFDV provides a method to generate an initial version of the schema based on the descriptive statistics.

Getting the schema right is important because the rest of our production pipeline will be relying on the schema that TFDV generates to be correct.  The schema also provides documentation for the data, and so is useful when different developers work on the same data.  Let's use [tfdv.display_schema](https://www.tensorflow.org/tfx/data_validation/api_docs/python/tfdv/display_schema) to display the inferred schema so that we can review it._____no_output_____

schema = tfdv.infer_schema(statistics=train_stats)
tfdv.display_schema(schema=schema)_____no_output_____

## Check evaluation data for errors

So far we've only been looking at the training data.  It's important that our evaluation data is consistent with our training data, including that it uses the same schema.  It's also important that the evaluation data includes examples of roughly the same ranges of values for our numerical features as our training data, so that our coverage of the loss surface during evaluation is roughly the same as during training.  The same is true for categorical features.  Otherwise, we may have training issues that are not identified during evaluation, because we didn't evaluate part of our loss surface.

* Notice that each feature now includes statistics for both the training and evaluation datasets.
* Notice that the charts now have both the training and evaluation datasets overlaid, making it easy to compare them.
* Notice that the charts now include a percentages view, which can be combined with log or the default linear scales.
* Notice that the mean and median for `trip_miles` are different for the training versus the evaluation datasets.  Will that cause problems?
* Wow, the max `tips` is very different for the training versus the evaluation datasets.  Will that cause problems?
* Click expand on the Numeric Features chart, and select the log scale.  Review the `trip_seconds` feature, and notice the difference in the max.  Will evaluation miss parts of the loss surface?_____no_output_____

# Compute stats for evaluation data
eval_stats = tfdv.generate_statistics_from_csv(data_location=EVAL_DATA)WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.
# Compare evaluation data with training data
tfdv.visualize_statistics(lhs_statistics=eval_stats, rhs_statistics=train_stats,
                          lhs_name='EVAL_DATASET', rhs_name='TRAIN_DATASET')_____no_output_____

## Step 4. Check for evaluation anomalies

Does our evaluation dataset match the schema from our training dataset?  This is especially important for categorical features, where we want to identify the range of acceptable values.

Key Point: What would happen if we tried to evaluate using data with categorical feature values that were not in our training dataset?  What about numeric features that are outside the ranges in our training dataset?_____no_output_____

# Check eval data for errors by validating the eval data stats using the previously inferred schema.
anomalies = tfdv.validate_statistics(statistics=eval_stats, schema=schema)
tfdv.display_anomalies(anomalies)_____no_output_____

## Fix evaluation anomalies in the schema

Oops!  It looks like we have some new values for `company` in our evaluation data, that we didn't have in our training data.  We also have a new value for `payment_type`.  These should be considered anomalies, but what we decide to do about them depends on our domain knowledge of the data.  If an anomaly truly indicates a data error, then the underlying data should be fixed.  Otherwise, we can simply update the schema to include the values in the eval dataset.

Key Point: How would our evaluation results be affected if we did not fix these problems?

Unless we change our evaluation dataset we can't fix everything, but we can fix things in the schema that we're comfortable accepting.  That includes relaxing our view of what is and what is not an anomaly for particular features, as well as updating our schema to include missing values for categorical features.  TFDV has enabled us to discover what we need to fix.

Let's make those fixes now, and then review one more time._____no_output_____

# Relax the minimum fraction of values that must come from the domain for feature company.
company = tfdv.get_feature(schema, 'company')
company.distribution_constraints.min_domain_mass = 0.9

# Add new value to the domain of feature payment_type.
payment_type_domain = tfdv.get_domain(schema, 'payment_type')
payment_type_domain.value.append('Prcard')

# Validate eval stats after updating the schema
updated_anomalies = tfdv.validate_statistics(statistics=eval_stats, schema=schema)
tfdv.display_anomalies(updated_anomalies)_____no_output_____

We also split off a 'serving' dataset for this example, so we should check that too.  By default all datasets in a pipeline should use the same schema, but there are often exceptions. For example, in supervised learning we need to include labels in our dataset, but when we serve the model for inference the labels will not be included. In some cases introducing slight schema variations is necessary.

**Environments** can be used to express such requirements. In particular, features in schema can be associated with a set of environments using `default_environment`, `in_environment` and `not_in_environment`.

For example, in this dataset the `tips` feature is included as the label for training, but it's missing in the serving data. Without environment specified, it will show up as an anomaly._____no_output_____

serving_stats = tfdv.generate_statistics_from_csv(SERVING_DATA)
serving_anomalies = tfdv.validate_statistics(serving_stats, schema)

tfdv.display_anomalies(serving_anomalies)WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.

We'll deal with the `tips` feature below.  We also have an INT value in our trip seconds, where our schema expected a FLOAT. By making us aware of that difference, TFDV helps uncover inconsistencies in the way the data is generated for training and serving. It's very easy to be unaware of problems like that until model performance suffers, sometimes catastrophically. It may or may not be a significant issue, but in any case this should be cause for further investigation.

In this case, we can safely convert INT values to FLOATs, so we want to tell TFDV to use our schema to infer the type.  Let's do that now._____no_output_____

options = tfdv.StatsOptions(schema=schema, infer_type_from_schema=True)
serving_stats = tfdv.generate_statistics_from_csv(SERVING_DATA, stats_options=options)
serving_anomalies = tfdv.validate_statistics(serving_stats, schema)

tfdv.display_anomalies(serving_anomalies)WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.

Now we just have the `tips` feature (which is our label) showing up as an anomaly ('Column dropped').  Of course we don't expect to have labels in our serving data, so let's tell TFDV to ignore that._____no_output_____

# All features are by default in both TRAINING and SERVING environments.
schema.default_environment.append('TRAINING')
schema.default_environment.append('SERVING')

# Specify that 'tips' feature is not in SERVING environment.
tfdv.get_feature(schema, 'tips').not_in_environment.append('SERVING')

serving_anomalies_with_env = tfdv.validate_statistics(serving_stats, schema, environment='SERVING')

tfdv.display_anomalies(serving_anomalies_with_env)_____no_output_____

In addition to checking whether a dataset conforms to the expectations set in the schema, TFDV also provides functionalities to detect drift and skew.  TFDV performs this check by comparing the statistics of the different datasets based on the drift/skew comparators specified in the schema.

### Drift

Drift detection is supported for categorical features and between consecutive spans of data (i.e., between span N and span N+1), such as between different days of training data.  We express drift in terms of [L-infinity distance](https://en.wikipedia.org/wiki/Chebyshev_distance), and you can set the threshold distance so that you receive warnings when the drift is higher than is acceptable.  Setting the correct distance is typically an iterative process requiring domain knowledge and experimentation._____no_output_____### Skew

TFDV can detect three different kinds of skew in your data - schema skew, feature skew, and distribution skew.

#### Schema Skew

Schema skew occurs when the training and serving data do not conform to the same schema. Both training and serving data are expected to adhere to the same schema. Any expected deviations between the two (such as the label feature being only present in the training data but not in serving) should be specified through environments field in the schema.

#### Feature Skew

Feature skew occurs when the feature values that a model trains on are different from the feature values that it sees at serving time. For example, this can happen when:

* A data source that provides some feature values is modified between training and serving time
* There is different logic for generating features between training and serving. For example, if you apply some transformation only in one of the two code paths.

#### Distribution Skew

Distribution skew occurs when the distribution of the training dataset is significantly different from the distribution of the serving dataset. One of the key causes for distribution skew is using different code or different data sources to generate the training dataset. Another reason is a faulty sampling mechanism that chooses a non-representative subsample of the serving data to train on._____no_output_____

# Add skew comparator for 'payment_type' feature.
payment_type = tfdv.get_feature(schema, 'payment_type')
payment_type.skew_comparator.infinity_norm.threshold = 0.01

# Add drift comparator for 'company' feature.
company=tfdv.get_feature(schema, 'company')
company.drift_comparator.infinity_norm.threshold = 0.001

skew_anomalies = tfdv.validate_statistics(train_stats, schema,
                                          previous_statistics=eval_stats,
                                          serving_statistics=serving_stats)

tfdv.display_anomalies(skew_anomalies)_____no_output_____

## Freeze the schema

Now that the schema has been reviewed and curated, we will store it in a file to reflect its "frozen" state._____no_output_____

from tensorflow.python.lib.io import file_io
from google.protobuf import text_format

file_io.recursive_create_dir(OUTPUT_DIR)
schema_file = os.path.join(OUTPUT_DIR, 'schema.pbtxt')
tfdv.write_schema_text(schema, schema_file)

!cat {schema_file}feature {
  name: "pickup_community_area"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "fare"
  type: FLOAT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "trip_start_month"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "trip_start_hour"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "trip_start_day"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "trip_start_timestamp"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "pickup_latitude"
  type: FLOAT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "pickup_longitude"
  type: FLOAT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "dropoff_latitude"
  value_count {
    min: 1
    max: 1
  }
  type: FLOAT
  presence {
    min_count: 1
  }
}
feature {
  name: "dropoff_longitude"
  value_count {
    min: 1
    max: 1
  }
  type: FLOAT
  presence {
    min_count: 1
  }
}
feature {
  name: "trip_miles"
  type: FLOAT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "pickup_census_tract"
  type: BYTES
  presence {
    min_count: 0
  }
}
feature {
  name: "dropoff_census_tract"
  value_count {
    min: 1
    max: 1
  }
  type: INT
  presence {
    min_count: 1
  }
}
feature {
  name: "payment_type"
  type: BYTES
  domain: "payment_type"
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  skew_comparator {
    infinity_norm {
      threshold: 0.01
    }
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "company"
  value_count {
    min: 1
    max: 1
  }
  type: BYTES
  domain: "company"
  presence {
    min_count: 1
  }
  distribution_constraints {
    min_domain_mass: 0.9
  }
  drift_comparator {
    infinity_norm {
      threshold: 0.001
    }
  }
}
feature {
  name: "trip_seconds"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "dropoff_community_area"
  value_count {
    min: 1
    max: 1
  }
  type: INT
  presence {
    min_count: 1
  }
}
feature {
  name: "tips"
  type: FLOAT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  not_in_environment: "SERVING"
  shape {
    dim {
      size: 1
    }
  }
}
string_domain {
  name: "payment_type"
  value: "Cash"
  value: "Credit Card"
  value: "Dispute"
  value: "No Charge"
  value: "Pcard"
  value: "Unknown"
  value: "Prcard"
}
string_domain {
  name: "company"
  value: "0118 - 42111 Godfrey S.Awir"
  value: "0694 - 59280 Chinesco Trans Inc"
  value: "1085 - 72312 N and W Cab Co"
  value: "2733 - 74600 Benny Jona"
  value: "2809 - 95474 C & D Cab Co Inc."
  value: "3011 - 66308 JBL Cab Inc."
  value: "3152 - 97284 Crystal Abernathy"
  value: "3201 - C&D Cab Co Inc"
  value: "3201 - CID Cab Co Inc"
  value: "3253 - 91138 Gaither Cab Co."
  value: "3385 - 23210 Eman Cab"
  value: "3623 - 72222 Arrington Enterprises"
  value: "3897 - Ilie Malec"
  value: "4053 - Adwar H. Nikola"
  value: "4197 - 41842 Royal Star"
  value: "4615 - 83503 Tyrone Henderson"
  value: "4615 - Tyrone Henderson"
  value: "4623 - Jay Kim"
  value: "5006 - 39261 Salifu Bawa"
  value: "5006 - Salifu Bawa"
  value: "5074 - 54002 Ahzmi Inc"
  value: "5074 - Ahzmi Inc"
  value: "5129 - 87128"
  value: "5129 - 98755 Mengisti Taxi"
  value: "5129 - Mengisti Taxi"
  value: "5724 - KYVI Cab Inc"
  value: "585 - Valley Cab Co"
  value: "5864 - 73614 Thomas Owusu"
  value: "5864 - Thomas Owusu"
  value: "5874 - 73628 Sergey Cab Corp."
  value: "5997 - 65283 AW Services Inc."
  value: "5997 - AW Services Inc."
  value: "6488 - 83287 Zuha Taxi"
  value: "6743 - Luhak Corp"
  value: "Blue Ribbon Taxi Association Inc."
  value: "C & D Cab Co Inc"
  value: "Chicago Elite Cab Corp."
  value: "Chicago Elite Cab Corp. (Chicago Carriag"
  value: "Chicago Medallion Leasing INC"
  value: "Chicago Medallion Management"
  value: "Choice Taxi Association"
  value: "Dispatch Taxi Affiliation"
  value: "KOAM Taxi Association"
  value: "Northwest Management LLC"
  value: "Taxi Affiliation Services"
  value: "Top Cab Affiliation"
}
default_environment: "TRAINING"
default_environment: "SERVING"

## When to use TFDV

It's easy to think of TFDV as only applying to the start of your training pipeline, as we did here, but in fact it has many uses.  Here's a few more:

* Validating new data for inference to make sure that we haven't suddenly started receiving bad features
* Validating new data for inference to make sure that our model has trained on that part of the decision surface
* Validating our data after we've transformed it and done feature engineering (probably using [TensorFlow Transform](https://www.tensorflow.org/tfx/transform/)) to make sure we haven't done something wrong_____no_output_____