# Load Module
import numpy as np
from sklearn import datasets
from sklearn import metrics
from sklearn import model_selection
import tensorflow as tf

# Load dataset.
iris = datasets.load_iris() # 총 150개의 붓꽃 사진과 class load
x_train, x_test, y_train, y_test = model_selection.train_test_split(iris.data, iris.target, test_size=0.2, random_state=42)
print('train and test ready')

x_train[:10]
# 각 열은 꽃받침 길이, 꽃받침 너비, 꽃잎 길이, 꽃잎 너비

y_train[:10] # 0,1,2는 꽃의 종 의미

# 10, 20, 10 단위로 각각 3층 DNN 생성
feature_columns = tf.contrib.learn.infer_real_valued_columns_from_input(x_train) # list feature column
classifier = tf.contrib.learn.DNNClassifier(feature_columns=feature_columns, hidden_units=[10, 20, 10], n_classes=3)

# Train.
classifier.fit(x_train, y_train, steps=200)
predictions = list(classifier.predict(x_test, as_iterable=True))

# Score with sklearn.
score = metrics.accuracy_score(y_test, predictions)
print('Accuracy: {0:f}'.format(score))

new_samples = np.array(
    [[6.4, 3.2, 4.5, 1.5], [5.8, 3.1, 5.0, 1.7]], dtype=float)
y = list(classifier.predict(new_samples, as_iterable=True))

print('Predictions: {}'.format(str(y)))

"""
Explanation: Lecture 1. Introduction to Tensorflow
Deep Learning Library
Theano, Torch, Tensorflow가 가장 많이 사용되는 lib
|     Software     | Creator                                            |Opensource  | Interface   |
|------------------|----------------------------------------------------|------------|-------------|
|Apache SINGA   | Apache Incubator         |Yes  | Python, C++, Java   |
|Caffe   | Berkeley Vision and Learning Center         |Yes  | Python, MATLAB   |
|Deeplearning4j   | Skymind engineering team; Deeplearning4j community; originally Adam Gibson           |Yes  | Java, Scala, Clojure, Python (Keras)   |
|Dlib   | Davis King         |Yes  | C++   |
|Keras   | François Chollet         |Yes  | Python   |
|Microsoft Cognitive Toolkit   | Microsoft Research         |Yes  | Python, C++, Command line, BrainScript (.NET on roadmap)   |
|MXNet  | Distributed (Deep) Machine Learning Community         |Yes  | C++, Python, Julia, Matlab, JavaScript, Go, R, Scala, Perl   |
|Neural Designer   | Artelnics         |No  | Graphical user interface   |
|OpenNN   | Artelnics         |Yes  | C++   |
|TensorFlow   | Google Brain team         |Yes  | Python (Keras), C/C++, Java, Go, R   |
|Theano   | Université de Montréal         |Yes  | Python   |
|Torch   | Ronan Collobert, Koray Kavukcuoglu, Clement Farabet         |Yes  | Lua, LuaJIT, C, utility library for C++/OpenCL   |
|Wolfram Mathematica   | Wolfram Research         |Yes  | Command line, Java, C++   |
Why TensorFlow?

Python API
Portability: 단일 API를 사용하여 데스크톱, 서버 또는 모바일 장치의 하나 이상의 CPU 또는 GPU에서 계산 가능
Flexibility(유연성): from Raspberry Pi, Android, Windows, iOS, Linux to server farms
Visualization (TensorBoard)
Checkpoints (for managing experiments)



Getting started with one-liner Tensorflow
1. TF Learn(tf.contrib.learn)
End of explanation
"""


input = ...
with tf.name_scope('conv1_1') as scope:
    kernel = tf.Variable(tf.truncated_normal([3, 3, 64, 128], dtype=tf.float32,
                                           stddev=1e-1), name='weights')
    conv = tf.nn.conv2d(input, kernel, [1, 1, 1, 1], padding='SAME')
    biases = tf.Variable(tf.constant(0.0, shape=[128], dtype=tf.float32),
                       trainable=True, name='biases')
    bias = tf.nn.bias_add(conv, biases)
    conv1 = tf.nn.relu(bias, name=scope)

"""
Explanation: 2. TF-slim(tf.contrib.slim)

Contrib 중 하나의 library로, 상위 수준의 개념(argument scoping, layer, variable)으로 모델을 짧고 쉽게 정의할 수 있게 만듦
많이 사용되는 regularizer를 사용하여 모델을 단순하게 함. VGG, AlexNet과 같이 많이 쓰이는 모델을 개발 해놓음

without TF-Slim
End of explanation
"""


input = ...
net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')

"""
Explanation: with TF-Slim
End of explanation
"""


import tensorflow as tf

a = tf.add(2, 3)

a = tf.add(3, 5)

print (a)

"""
Explanation: Data Flow Graph
End of explanation
"""


sess = tf.Session()
sess.run(a)

a = tf.add(3, 5)

with tf.Session() as sess:
    print (sess.run(a))

"""
Explanation: How to get the value of a?

Session을 시작해줘야 operation이 작동함
End of explanation
"""


x = 2
y = 3

op1 = tf.add(x, y)
op2 = tf.multiply(x, y)
useless = tf.multiply(x, op1)
op3 = tf.pow(op2, op1)

with tf.Session() as sess:
    op3 = sess.run(op3)

x = 2
y = 3

op1 = tf.add(x, y)
op2 = tf.multiply(x, y)
useless = tf.multiply(x, op1)
op3 = tf.pow(op2, op1)

with tf.Session() as sess:
    op3, not_useless = sess.run([op3, useless])

# Creates a graph.

with tf.device("/cpu:0"): # 연산장치 선택 가능
    a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape = [2,3], name='a')
    b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape = [3,2], name='b')
    c = tf.matmul(a, b) 
    
# Creates a session with log_device_placement set to True.
sess = tf.Session(config=tf.ConfigProto(log_device_placement=True)) # tf.ConfigProto(log_device_placement=True) cpu와 gpu연산이 모두 가능할 때 gpu선택

# Runs the op.
print (sess.run(a))
print (sess.run(b))
print (sess.run(c))

"""
Explanation: More graphs
End of explanation
"""


a = tf.constant(2)
b = tf.constant(3)
x = tf.add(a, b)
with tf.Session() as sess:
    writer = tf.summary.FileWriter('./graphs', sess.graph) # 텐서보드에서 볼 수 있는 그래프 저장
    print (sess.run(x))

    # close the writer when you’re done using it
writer.close()

"""
Explanation: Why graphs

계산 저장 가능
계산을 작은 단위로 나누어 자동차별화를 용이하게 해줌
분산 컴퓨팅을 가능케하고 CPU, GPU 혹은 여러 장치를 한번에 사용할 수 있게 해줌
많은 기계학습 모델들이 이미 그래프를 통해 가르쳐지고 시각화되고 있기 때문

Lecture 2: Tensorflow Ops
1. Fun with TensorBoard
텐서보드 활성화를 위해 그래프의 학습루프가 실행되기 전 코드 삽입

writer = tf.summary.FileWriter(logs_dir, sess.graph) 
tf.train.FileWriter -> 에러
tf.train.SummaryWriter -> 에러
이벤트의 로그가 지정된 폴더에 저장됨
End of explanation
"""


# constant of 1d tensor (vector)
a = tf.constant([2, 2], name="vector")

# constant of 2x2 tensor (matrix)
b = tf.constant([[0, 1], [2, 3]], name="b")

with tf.Session() as sess:
    print(sess.run(a))
    print(sess.run(b))

"""
Explanation: 텐서보드 실행법

터미널에서 
python [yourprogram.py]
tensorboard --logdir="./graphs"

http://localhost:6006/ 로 이동<br><br/>


jupyter 터미널에서도 실행 가능  

윈도우버전 jupyter 실행 불가

2. Constant types

상수유형 공식문서 https://www.tensorflow.org/api_docs/python/constant_op/
constant 함수로 상수, 스칼라, 텐서값 등을 생성할 수 있음

tf.constant(value, dtype=None, shape=None, name='const', verify=False)
End of explanation
"""


with tf.Session() as sess:
    print (sess.run(tf.zeros([2, 3], tf.int32))) # [[0, 0, 0], [0, 0, 0]]

import numpy as np

np.zeros((2,3), dtype=np.int32)

"""
Explanation: 텐서의 원소로 특정한 값을 생성할 수 있음
유사함수: numpy.zeros, numpy.zeros_like, numpy.ones, numpy.ones_like

tf.zero(shape, dtype=tf.float32, name=None)

모든 원소가 0인 텐서쉐입을 만드는 함수
End of explanation
"""


input_tensor = [[0, 1], [2, 3], [4, 5]]

with tf.Session() as sess:
    print (sess.run(tf.zeros_like(input_tensor)))  # [[0, 0], [0, 0], [0, 0]]

np.zeros_like(input_tensor)

"""
Explanation: tf.zeros_like(input, dtype=None, name=None, opitmize=True)

모든 원소를 0으로 만드는 함수
End of explanation
"""


with tf.Session() as sess:
    print(sess.run(tf.ones([2, 3], tf.int32))) # [[1, 1, 1], [1, 1, 1]]

np.ones([2,3], dtype=np.int32)

"""
Explanation: tf.one(shape, dtype=tf.float32, name=None)

모든 원소가 1인 텐서쉐입을 만드는 함수
End of explanation
"""


input_tensor = [[0, 1], [2, 3], [4, 5]]
with tf.Session() as sess:
    print(sess.run(tf.ones_like(input_tensor))) # [[1, 1], [1, 1], [1, 1]]

np.ones_like(input_tensor)

"""
Explanation: tf.ones_like(input_tensor, dtype=None, name=None, optimize=True)

모든 원소를 1로 만드는 함수
End of explanation
"""


with tf.Session() as sess:
    print(sess.run(tf.fill([2, 3], 8))) # [[8, 8, 8], [8, 8, 8]]

"""
Explanation: tf.fill(dims, value, name=None)

텐서를 한가지 스칼라값으로 채움
End of explanation
"""


with tf.Session() as sess:
    print(sess.run(tf.linspace(10.0, 13.0, 4, name="linspace"))) # [10.0 11.0 12.0 13.0]

"""
Explanation: tf.linspace(start, stop, num, name=None)

특정 구간에서 균등하게 증가(개수만큼)하는 수열 생성
End of explanation
"""


with tf.Session() as sess:
    print(sess.run(tf.range(3, 18, 3))) # [3, 6, 9, 12, 15]

"""
Explanation: tf.range(start, limit=None, delta=1, dtype=None, name='range')

등차수열 생성
End of explanation
"""


for _ in range(4):# OK
    a

for _ in tf.range(4): # TypeError("'Tensor' object is not iterable.")
    a

"""
Explanation: 텐서는 반복문에 사용할 수 없음
End of explanation
"""


with tf.Session() as sess:
    print(sess.run(tf.random_normal(shape = [2,3])))

with tf.Session() as sess:
    print(sess.run(tf.truncated_normal(shape = [2,3])))

with tf.Session() as sess:
    print(sess.run(tf.multinomial(tf.random_normal(shape = [2,3]),5)))

with tf.Session() as sess:
    print(sess.run(tf.random_gamma(shape = [2,3], alpha = 1)))

a = tf.constant([[2,1], [3,2], [7,3]])

with tf.Session() as sess:
    print(sess.run(tf.random_shuffle(a)))

with tf.Session() as sess:
    print(sess.run(tf.random_crop(a, [2,1])))

"""
Explanation: 특정 분포에서 난수를 생성할 수 있음

tf.random_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
정규분포로부터의 난수 생성<br><br/>


tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None,name=None) 
절단정규분포로부터의 난수 생성<br><br/>


tf.random_uniform(shape, minval=0, maxval=None, dtype=tf.float32, seed=None,name=None)
균등분포로부터의 난수 생성<br><br/>


tf.multinomial(logits, num_samples, seed=None, name=None)
다항분포로부터의 난수 생성<br><br/>


tf.random_gamma(shape, alpha, beta=None, dtype=tf.float32, seed=None, name=None)
감마분포로부터의 난수 생성<br><br/>


tf.random_shuffle(value, seed=None, name=None)
값의 첫번째 차원을 기준으로 랜덤하게 섞어줌<br><br/>


tf.random_crop(value, size, seed=None, name=None)
텐서를 주어진 value만큼 랜덤하게 잘라냄
End of explanation
"""


a = tf.constant([3, 6])
b = tf.constant([2, 2])

with tf.Session() as sess:
    print(sess.run(tf.add(a, b))) # >> [5 8], 2개의 input을 받아 덧셈

with tf.Session() as sess:
    print(sess.run(tf.add_n([a, b, b]))) # >> [7 10]. 모든 input을 덧셈

with tf.Session() as sess:
    print(sess.run(tf.multiply(a, b))) # >> [6 12] because mul is element wise

# matmul: 2차원이상의 텐서간의 곱
with tf.Session() as sess:
    print(sess.run(tf.matmul(tf.reshape(a, shape=[1, 2]), 
          tf.reshape(b, shape=[2, 1]))))

with tf.Session() as sess:
    print(sess.run(tf.div(a, b))) # >> [1 3], 나눗셈 실행

with tf.Session() as sess:
    print(sess.run(tf.mod(a, b))) # >> [1 0], 나머지 반환

"""
Explanation: 3. Math Operations
End of explanation
"""


# 0차원 상수텐서 - 스칼라
t_0 = 19

with tf.Session() as sess:
    print(sess.run(tf.zeros_like(t_0))) # ==> 0
    print(sess.run(tf.ones_like(t_0))) # ==> 1

# 1차원 텐서 - 벡터
t_1 = [b"apple", b"peach", b"grape"]
with tf.Session() as sess:
    print(sess.run(tf.zeros_like(t_1))) # ==> ['' '' '']
    print(sess.run(tf.ones_like(t_1))) # ==> TypeError: Expected string, got 1 of type 'int' instead.

# 2차원 텐서 - 메트릭스
t_2 = [[True, False, False],
       [False, False, True],
       [False, True, False]]

with tf.Session() as sess:
    print(sess.run(tf.zeros_like(t_2))) # ==> 2x2 tensor, 모든 원소값 False
    print(sess.run(tf.ones_like(t_2))) # ==> 2x2 tensor, 모든 원소값 True

"""
Explanation: 4. Data Types
### Python Native Types : 불린, 숫자, 스트링
  - 단일 값은 0차원 텐서 (스칼라)
  - 값의 리스트는 1차원 텐서 (벡터)
  - 리스트의 리스트는 2차원 텐서 (매트릭스)
End of explanation
"""


my_const = tf.constant([1.0, 2.0], name="my_const")
print (tf.get_default_graph().as_graph_def())

"""
Explanation: TensorFlow Native Types

텐서플로우는 Numpy 처럼 tf.int32, tf.float32 독자적인 데이터 타입을 사용함
https://www.tensorflow.org/versions/r0.11/resources/dims_types

5. Variables

변수는 할당될수 있고 변경 될수 있음
상수는 그래프에 값이 저장되어 있어 그래프를 로딩할때 함께 로딩됨
변수는 그래프와 별도로 저장됨(파라미터 서버에 살고 있음)
End of explanation
"""


# a를 스칼라 값으로 생성
a = tf.Variable(2, name="scalar")

# b를 벡터로 생성
b = tf.Variable([2, 3], name="vector")

# c를 2x2 matrix로 생성
c = tf.Variable([[0, 1], [2, 3]], name="matrix")

# W를 0으로 채워진 784x10 tensor로 생성
W = tf.Variable(tf.zeros([784,10]))

"""
Explanation: Declare variables

tf.Variable 함수로 생성
End of explanation
"""


init = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)

"""
Explanation: 변수를 사용하기 전에는 항상 변수를 초기화해야함
tf.global_variables_initializer() 함수는 모든 변수를 초기화해줌
End of explanation
"""


init_ab = tf.variables_initializer([a, b], name = "init_ab")
with tf.Session() as sess:
    sess.run(init)

"""
Explanation: To initialize only as subset of varuables
tf.variables_initializer()
End of explanation
"""


W = tf.Variable(tf.truncated_normal([700, 10]))

with tf.Session() as sess:
    sess.run(W.initializer)
    print (W)

"""
Explanation: Evaluate values of variables

그냥 프린트하면 텐서와 유형, 쉐입만 볼수 있음
End of explanation
"""


with tf.Session() as sess:
    sess.run(W.initializer)
    print (W.eval())

"""
Explanation: eval()함수를 사용하면 값까지 볼수 있음
End of explanation
"""


W = tf.Variable(10)
W.assign(100) # 100이 W에 할당되지 않음

with tf.Session() as sess:
    sess.run(W.initializer)
    print (W.eval()) # >> 10

W = tf.Variable(10)
assign_op = W.assign(100) # assign이 W를 initialize시킴

with tf.Session() as sess:
    sess.run(assign_op)
    print (W.eval()) # >> 100

# 값이 2인 변수 a 생성
a = tf.Variable(2, name="scalar")

# a_times_two에 a * 2 할당
a_times_two = a.assign(a * 2)

init = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init) # a_times_two가 a에 따라 바뀌기 때문에 반드시 a를 initialize시켜줘야 함
    sess.run(a_times_two) # >> 4
#     sess.run(a_times_two) # >> 8
#     sess.run(a_times_two) # >> 16
    print (a_times_two.eval())

W = tf.Variable(10)

with tf.Session() as sess:
    sess.run(W.initializer) # assign_add와 assign_sub는 assign과는 다르게 variable을 initialize시켜주지 않음
    print(sess.run(W.assign_add(10)))
    print(sess.run(W.assign_sub(2)))

"""
Explanation: Assign values to variables

tf.Variable.assign()함수를 사용함
End of explanation
"""


W = tf.Variable(10)

sess1 = tf.Session()
sess2 = tf.Session()

sess1.run(W.initializer)
sess2.run(W.initializer)

print(sess1.run(W.assign_add(10))) # ==> 20
print(sess2.run(W.assign_sub(2))) # ==> 8

print(sess1.run(W.assign_add(100))) # ==> 120
print(sess2.run(W.assign_sub(50))) # ==> -42

sess1.close()
sess2.close()

"""
Explanation: Tensorflow session은 각각 유지됨
각각의 session은 그래프에서 정의된 변수들의 현재값을 각각 가질 수 있다
End of explanation
"""


W = tf.Variable(tf.truncated_normal([700, 10]))
U = tf.Variable(W * 2)

"""
Explanation: 다른 변수를 사용해서 변수를 만들수 있음
End of explanation
"""


sess = tf.InteractiveSession()
a = tf.constant(5.0)
b = tf.constant(6.0)
c = a * b

print(c.eval())
sess.close()

"""
Explanation: 6. InteractiveSession

인터렉티브 세션은 그것 자체로 디폴트세션으로 작동함
따로 run()을 선언하지 않아도 실행됨
End of explanation
"""


# your graph g have 5 ops: a, b, c, d, e
with g.control_dependencies([a, b, c]):
    d = ....
    e = ....

"""
Explanation: 7. Control Dependencies

두개 이상의 오퍼레이션이 존재할 때, 오퍼레이션의 실행 순서 지정
tf.Graph.control_dependencies(control_inputs) 함수사용
End of explanation
"""


# create a placeholder of type float 32-bit, shape is a vector of 3 elements
a = tf.placeholder(tf.float32, shape=[3])

# create a constant of type float 32-bit, shape is a vector of 3 elements
b = tf.constant([5, 5, 5], tf.float32)

# use the placeholder as you would a constant or a variable
c = a + b # Short for tf.add(a, b)

with tf.Session() as sess:
    # feed [1, 2, 3] to placeholder a via the dict {a: [1, 2, 3]}
    # fetch value of c
    writer = tf.summary.FileWriter('./my_graph', sess.graph)
#     print(sess.run(c)) # ==> Error
    print(sess.run(c, {a: [1, 2, 3]}))

"""
Explanation: 8. Placeholders and feed_dict

우리는 값을 알지 못하는 상태에서 그래프 먼저 그려야한다!
그래프를 그리고 나중에 데이터를 공급해주기 위해 placeholder사용함
 ### tf.placeholder(dtype, shape=None, name=None)
End of explanation
"""


# create Operations, Tensors, etc (using the default graph)
a = tf.add(2, 5)
b = tf.multiply(a, 3)

# start up a `Session` using the default graph
sess = tf.Session()

# define a dictionary that says to replace the value of `a` with 15
replace_dict = {a: 15}

# Run the session, passing in `replace_dict` as the value to `feed_dict`
sess.run(b, feed_dict=replace_dict) # returns 45

"""
Explanation: 꼭 placeholder가 아니여도 feed 가능

tf.Graph.is_feedable(tensor)

위의 함수를 사용하여 feed가능 여부를 확인할 수 있음
End of explanation
"""


# Normal loading
x = tf.Variable(10, name='x')
y = tf.Variable(20, name='y')
z = tf.add(x, y)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    writer = tf.summary.FileWriter('./my_graph/l2', sess.graph)
    for _ in range(10):
        sess.run(z)
    
writer.close()

# Lazy loading
x = tf.Variable(10, name='x')
y = tf.Variable(20, name='y')

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    writer = tf.summary.FileWriter('./my_graph/l2', sess.graph)
    for _ in range(10):
        sess.run(tf.add(x, y)) # someone decides to be clever to save one line of code
writer.close()

"""
Explanation: 9. The trap of lazy loading

속도를 조금 더 빠르게 해주는 팁
연산을 최대한 뒤로 미루는 것
End of explanation
"""


