"""
# <u> TensorFlow</u>

<center><h2>Introduction</h2></center>
* TensorFlow is an open source software library for numerical computation , particularly well suited and fine-tuned for large-scale Machine Learning .
* Basic principle of Tensorflow : -
   *  In python first define a graph of computation to perform.
   * Then TensorFlow takes that graph and runs it efficiently using optimized C++ code. Most importantly , it is possible to break up the graph into several chunks and run them in parallel across multiple CPUs or GPUs.
* TensorFlow also supports distributed computing.
* TensorFlow can train a network with millions of parameters on a training set composed of billions of instances with millions of features each.
* TensorFlow is developed by the Google Brain team .
* TensorFlow not only runs on Windows , Linux and macOS  , but also on mobile devices , including both iOS and Android .
* It provides a very simple Python API called TF.Learn , compatible with Scikit - Learn .
* Several other high level APIs have been built independently on top of TensorFlow , such as Keras or Pretty Tensor.
* TensorFlow comes with a great visualization tool called as  **TensorBoard** , that allows you to browse through the computation graph , view learning curves and more. 

## <u>Installing TensorFlow</u>
**TensorFlow CPU only** 
* Click on this link  [tensorflowcpuonly](https://www.tensorflow.org/install/)

**TensorFlow GPU**
* Click on this link , there is a video guide for TensorFlow GPU installation [tensorflowgpu](https://www.youtube.com/watch?v=uIm3DMprk7M)

**What are CPUs and GPUs?**

A CPU (central processing unit) is often called the “brain” or the “heart” of a computer. It is required to run the majority of engineering and office software. However, there is a multitude of tasks that can overwhelm a computer’s central processor. That is when using GPU becomes essential for computing.

A GPU (graphics processing unit) is a specialized type of microprocessor, primarily designed for quick image rendering. GPUs appeared as a response to graphically intense applications that put a burden on the CPU and degraded computer performance. They became a way to offload those tasks from CPUs, but modern graphics processors are powerful enough to perform rapid mathematical calculations for many other purposes apart from rendering.


# Creating your first Graph and running it in a session .
* TensorFlow uses a dataflow graph to represent your computation in terms of the dependencies between individual operations. This leads to a low-level programming model in which you first define the dataflow graph, then create a TensorFlow session to run parts of the graph across a set of local and remote devices.
"""
'''Importing tensorflow'''
import tensorflow as tf
"""
** Creating graph  **
"""
'''tf.Varibale(value , name)'''
x = tf.Variable( 2 , name = "x" )
y = tf.Variable( 4 , name = "y" )

'''equation'''
f = x*x*y + y + 2
"""
* In the above cell the codes do not perform any computation . It just creates a computation graph , in fact even the variables are not initialized yet.
* To evaluate this graph , we need to open a **TensorFlow session** and use it to initialize the **variables** and evaluate **f**.
* A TensorFlow **Session** takes care of placing the operations onto devices such as CPUs and GPUs and running them , and it holds all the variable values.
* The following code  creates a **session , initializes the variables , and evaluate , and f then closes the session (which free up resources)**:
"""
'''creating session'''
session = tf.Session()

'''initializing variables
 x = 2 and y = 4 '''
session.run(x.initializer)
session.run(y.initializer)

'''evaluating f 
 f = x*x*y + y + 2'''
result = session.run(f)
print(result)

'''Terminating the session'''
session.close()
"""
**Having  to repeat session.run( ) all the time is a bit cumbersome , but fortunately there is a better way .**
"""
with tf.Session() as session :
    '''initializing variables'''
    x.initializer.run()
    y.initializer.run()
    '''evaluating f'''
    result = f.eval()
    '''session closed automatically'''
    
print(result)
"""
Inside the **with** block , the session i set as default session . Calling **x.initializer.run()**  is equivalent to calling **tf.get_default_session().run(x.initializer)** and similar to **f.eval()** is equivalent to calling **tf.get_default_session().run(f)** . This makes the code easier to read . Moreover  , the session is automatically closed at the end of the block .

Instead of manually running the initializer for every single variable , we can use the **global_variables_initiaizer()** functon . **Note** that it does not actually perform the initialization immediately , but rather creates a node in the graph that will initialize all variables when it is run:
"""
'''initializing a node'''
init = tf.global_variables_initializer()

with tf.Session() as session:
    '''initializing all the variables'''
    init.run()
    '''evaluating f'''
    result = f.eval()
    '''session is closed automatically'''
    
print(result)
"""
Inside **Jupyter** or within a **Python** shell we may prefer to create an **InteractiveSession**. The only differnce from a regular **Session** is that when an **InteractiveSession** is created it automatically sets itself as the default session , so we don't need a **with** block , but we need to close the session manually when we are done with it .
"""
'''Creating an Interactive session.'''
session = tf.InteractiveSession()
'''initializing the global variables'''
init.run()
'''evaluating f'''
result = f.eval()
print(result)

'''terminating session'''
session.close()
"""
**A TensorFlow program is typically split into two parts :**
* First part builds a **computation graph** , this is called as the **construction phase**.
* Second part **runs** it , this is called as the **execution phase**.

**Contruction phase:**
* This phase typically builds a computation graph representing the Machin Learning model and the computations required to training it . 

**Execution phase:**
* This phase generally runs a loop that evaluates a training step repeatedly (for exampe, one step per mini-batch) , gradually improving the model parameters . 

**In the next chapter we will learn how to  Manage  Graphs , Lifecycle of a Node Value  and even do Linear Regression with TensorFlow.** 
"""