Backpropgation

Backpropagation

https://www.youtube.com/watch?v=q0pm3BrIUFo
2010년 가을학기, MIT AI 수업 Patrick Winston 교수님 강의.

지금까지 봤던 Backpropagation 강의 중에서 가장 명쾌하다. 중간즈음부터 보면 됨.

가장 핵심적인 스크린샷 몇 장을 소개한다:







자세한 건 강의를 참고하자.

백프로파게이션 알고리즘의 핵심은, 멀티레이어 뉴럴 네트워크를 “효율적” 으로 학습할 수 있게 해 준다는 것이다. 이전 레이어로 넘어갈 때마다 모든 연산을 다시 계산해야 할 필요가 없고, 이전 레이어에서 계산한 내용을 재활용 할 수 있기 때문에 매유 효율적으로 연산이 가능하다.

'DataScience > Deep Learning' 카테고리의 다른 글

Backpropgation  (0) 2015.12.30
Transfer Learning  (0) 2015.12.26
TensorFlow - (7) word2vec - Implementation  (0) 2015.12.26
TensorFlow - (6) word2vec - Theory  (0) 2015.12.26
TensorFlow - (5) MNIST - CNN  (2) 2015.12.03
TensorFlow - (4) MNIST - Softmax Regression  (0) 2015.12.03

Transfer Learning

Transfer Learning

트랜스퍼 러닝이란 딥러닝을 feature extractor로만 사용하고 그렇게 추출한 피처를 가지고 다른 모델을 학습하는 것을 말한다. 여기서는 (위 링크에서는) CNN 에서의 Transfer learning 에 대해 설명한다.

실제로 CNN을 구축하는 경우 대부분 처음부터 (random initialization) 학습하지는 않는다. 대신에, ImageNet과 같은 대형 데이터셋을 사용해서 pretrain된 ConvNet을 이용한다. 크게 3가지의 시나리오가 있다:

원문에 3가지라고 나와서 그대로 옮김. 앞쪽 레이어만 가지고 fine-tuning 하는 걸 따로 세서 3가지 인 듯.

  • ConvNet as fixed feature extractor. pretrained CNN 에서 마지막 classification layer (보통 softmax layer) 만 제거하면 완전한 feature extractor다. 이렇게 추출한 피처들을 CNN codes 라고 부른다. 알렉스넷 (AlexNet) 의 경우 이 CNN codes 는 4096-D codes다. 이 피처들을 사용해서, 우리의 training set은 linear classifier (e.g. Linear SVM or Softmax) 를 학습하기 위해 사용한다.
  • Fine-tuning the ConvNet. 마지막 classification layer만을 retrain하는 것이 아니라 pretrain된 전체 네트워크를 재조정 (fine-tuning) 하는 것. 상대적으로 사용할 수 있는 데이터가 많을 때 적합하다. 경우에 따라서 앞쪽 레이어 (앞쪽 레이어일수록 더욱 general한 feature를 학습하므로) 는 고정시키고 뒤쪽 레이어만 fine-tuning 하기도 한다.

Pretrained models. 최근의 CNN은 여러개의 GPU를 써도 학습하는 데에 몇주씩 걸린다. 이렇게 학습된 CNN의 weights들이 공유되므로 그걸 사용하자.

When and how to fine-tune? 우리의 새로운 데이터가 큰지/작은지, 새 데이터가 pretrain에 사용된 원래 데이터와 비슷한지/다른지에 따라 4가지 시나리오가 있다.

  1. 새 데이터가 작지만 원래 데이터와 비슷한 경우: CNN codes를 사용해서 linear classifier을 학습.
  2. 새 데이터가 크고 원래 데이터와 비슷한 경우: fine-tuning through the full network.
  3. 새 데이터가 작고 원래 데이터와 매우 다른 경우: 데이터가 작으므로 1번처럼 CNN codes를 사용해서 linear classifier를 학습해야겠지만, 문제는 원래 데이터와 달라서 그러면 안 됨. 대신에 네트워크의 앞쪽 레이어의 activation 값을 사용해서 SVM을 학습하자.
  4. 새 데이터가 크고 원래 데이터와 매우 다른 경우: 데이터가 크므로, 처음부터 CNN을 구축해도 되겠지만, 이러한 경우에도 pretrained model을 사용하는 것이 더 좋다. 데이터가 충분하므로 전체 네트워크를 fine-tuning 하자.

'DataScience > Deep Learning' 카테고리의 다른 글

Backpropgation  (0) 2015.12.30
Transfer Learning  (0) 2015.12.26
TensorFlow - (7) word2vec - Implementation  (0) 2015.12.26
TensorFlow - (6) word2vec - Theory  (0) 2015.12.26
TensorFlow - (5) MNIST - CNN  (2) 2015.12.03
TensorFlow - (4) MNIST - Softmax Regression  (0) 2015.12.03

TensorFlow - (7) word2vec - Implementation

TensorFlow

Vector Representations of Words

word2vec or word embedding.

tensorflow/examples/tutorials/word2vec/word2vec_basic.py
tensorflow/models/embedding/word2vec.py
위 두 코드를 참고하자. 여기서 다루는 내용은 word2vec_basic.py 에 해당한다.

Implementation

원문에서는 TF graph를 생성하는 부분에 대해서, 그리고 트레이닝 하는 과정에 대해서 약간 다루지만 여기서는 주석을 단 코드로 대체하였다. 아래의 코드를 참고하자 (코드가 길기 때문에 제일 아래에 두었다). 원문에서 다루는 그래프 생성은 Step 5, 트레이닝은 Step 6에 해당한다. 또한 이 코드에서는 tSNE를 통한 2차원 시각화까지 제공하니, 원문을 참조하도록 하자.

Analogical Reasoning

word2vec이 주목받을 수 있었던 건 바로 이 유추 (analogical reasoning) 가 가능하기 때문이다. 단어를 유의미한 벡터공간으로 매핑하므로, 단어간의 유사도를 측정하여 king is to queen as father is to ? 따위의 질문에 대답이 가능하다. 조금 더 심플하게 표현하면 king - man + woman = queen 이러한 것이 가능하다는 것이다.

Optimizing the Implementation

하이퍼파라메터 (hyperparameter) 의 선택은 모델의 정확도에 큰 영향을 끼친다. 본 튜토리얼에서는 다루지 않지만, 이를 위해 데이터 subsampling 등 여러 트릭을 사용하여 하이퍼파라메터를 잘 튜닝해야 한다.

이 vanilla implementation 은 텐서플로의 유연성을 잘 보여준다. 예를 들어, object (loss) function 을 tf.nn.nce_loss() 대신 tf.nn.sampled_softmax_loss() 를 사용할 수도 있다. 만약 새로운 아이디어가 있다면, 직접 코드를 작성해도 된다. 텐서플로가 도함수를 계산해 줄 것이다. 이러한 유연성은 머신러닝 모델을 탐색할 때에는 별 의미가 없지만, 모델의 구조를 정한 후 속도를 최적화하고 코드를 개선할 때에는 매우 유용하다. 예를 들어, 우리의 코드에서 데이터를 읽어오는 과정이 상당한 시간을 소비하는데, New Data Formats 을 통해 최적화된 data reader 를 구현할 수 있다. word2vec.py 코드를 참고하자.

더이상 우리의 모델이 I/O bound가 아닌데도, 즉 데이터를 읽어오는 시간을 줄였는데도, 여전히 퍼포먼스를 향상시키고 싶다면 Adding a New Op 에서 설명하는 대로 직접 TensorFlow Ops 를 구현할 수 있다. tensorflow/models/embedding/word2vec_optimized.py 를 참고하자. 이러한 최적화 단계는 C++ 을 써야 할 가능성이 높다.

Conclusion

이 튜토리얼에서는 효율적으로 word embedding 을 학습하는 word2vec 모델에 대해서 다뤘다. word embedding 이 왜 유용한지, 어떻게 효율적으로 학습할 수 있는지 그리고 텐서플로로 어떻게 구현할 수 있는지. 또한 텐서플로가 머신러닝 모델을 탐색하는 초기 실험에서부터 모델 확립 후의 디테일한 최적화까지 유연하게 제공한다는 것을 보았다.

Code

# coding: utf-8
'''
참고: Step 3 가 없음. 원문이 그래서 그렇게 놔두었음.
'''

from __future__ import absolute_import
from __future__ import print_function

import collections
import math
import numpy as np
import os
import random
from six.moves import urllib
from six.moves import xrange  # pylint: disable=redefined-builtin
import tensorflow as tf
import zipfile

# Step 1: Download the data.
# 데이터를 다운로드함. 파일이 이미 있다면 제대로 받아졌는지 (파일 크기가 같은지) 확인.
# 다운로드 받은 후 filename을 리턴함.
print("Step 1: Download the data.")
url = 'http://mattmahoney.net/dc/'

def maybe_download(filename, expected_bytes):
    """Download a file if not present, and make sure it's the right size."""
    if not os.path.exists(filename):
        filename, _ = urllib.request.urlretrieve(url + filename, filename)
    statinfo = os.stat(filename)
    if statinfo.st_size == expected_bytes:
        print('Found and verified', filename)
    else:
        print(statinfo.st_size)
        raise Exception('Failed to verify ' + filename + '. Can you get to it with a browser?')
    return filename

filename = maybe_download('text8.zip', 31344016)


# Read the data into a string.
# file (zipfile) 을 읽어옴
# text8.zip 의 내용은 파일 하나임. 코드를 봐서는 ' '로 구분된 단어들인 듯.
def read_data(filename):
    f = zipfile.ZipFile(filename)
    for name in f.namelist():
        return f.read(name).split()
    f.close()

words = read_data(filename)
print('Data size', len(words))
print('Sample words: ', words[:10])

# Step 2: Build the dictionary and replace rare words with UNK token.
print("\nStep 2: Build the dictionary and replace rare words with UNK token.")
vocabulary_size = 50000

def build_dataset(words):
    """
    vocabulary_size 는 사용할 빈발 단어의 수를 뜻함.
    등장 빈도가 상위 50000개 (vocabulary_size) 안에 들지 못하는 단어들은 전부 UNK로 처리한다.

    :param words: 말 그대로 단어들의 list
    :return data: indices of words including UNK. 즉 words index list.
    :return count: 각 단어들의 등장 빈도를 카운팅한 collections.Counter
    :return dictionary: {"word": "index"}
    :return reverse_dictionary: {"index": "word"}. e.g.) {0: 'UNK', 1: 'the', ...}
    """
    count = [['UNK', -1]]
    count.extend(collections.Counter(words).most_common(vocabulary_size - 1))
    dictionary = dict()
    for word, _ in count:
        dictionary[word] = len(dictionary) # insert index to dictionary (len이 계속 증가하므로 결과적으로 index의 효과)
    data = list()
    unk_count = 0
    for word in words:
        if word in dictionary:
            index = dictionary[word]
        else:
            index = 0  # dictionary['UNK']
            unk_count = unk_count + 1
        data.append(index)
    count[0][1] = unk_count
    reverse_dictionary = dict(zip(dictionary.values(), dictionary.keys()))
    return data, count, dictionary, reverse_dictionary

data, count, dictionary, reverse_dictionary = build_dataset(words)
del words  # Hint to reduce memory.
print('Most common words (+UNK)', count[:5])
print('Sample data: ', data[:10])
print('Sample count: ', count[:10])
print('Sample dict: ', dictionary.items()[:10])
print('Sample reverse dict: ', reverse_dictionary.items()[:10])

data_index = 0


# Step 4: Function to generate a training batch for the skip-gram model.
print("\nStep 4: Function to generate a training batch for the skip-gram model.")
def generate_batch(batch_size, num_skips, skip_window):
    """
    minibatch를 생성하는 함수.
    data_index는 global로 선언되어 여기서는 static의 역할을 함. 즉, 이 함수가 계속 재호출되어도 data_index의 값은 유지된다.

    :param batch_size   : batch_size.
    :param num_skips    : context window 내에서 (target, context) pair를 얼마나 생성할 지.
    :param skip_window  : context window size. skip-gram 모델은 타겟 단어로부터 주변 단어를 예측하는데, skip_window가 그 주변 단어의 범위를 한정한다.
    :return batch       : mini-batch of data.
    :return labels      : labels of mini-batch. [batch_size][1] 의 2d array.
    """
    global data_index
    assert batch_size % num_skips == 0  # num_skips의 배수로 batch가 생성되므로.
    assert num_skips <= 2 * skip_window # num_skips == 2*skip_window 이면 모든 context window의 context에 대해 pair가 생성된다.
    # 즉, 그 이상 커지면 안 됨.

    batch = np.ndarray(shape=(batch_size), dtype=np.int32)
    labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)
    span = 2 * skip_window + 1 # [ skip_window target skip_window ]
    buffer = collections.deque(maxlen=span)
    # Deques are a generalization of stacks and queues.
    # The name is pronounced "deck" and is short for "double-ended queue".
    # 양쪽에 모두 push(append) & pop 을 할 수 있음.

    # buffer = data[data_index:data_index+span] with circling
    for _ in range(span):
        buffer.append(data[data_index])
        data_index = (data_index + 1) % len(data)

    # // 는 나머지 혹은 소수점 아래를 버리는 연산자
    # skip-gram은 타겟 단어로부터 주변의 컨텍스트 단어를 예측하는 모델이다.
    # skip-gram model을 학습하기 전에, words를 (target, context) 형태로 변환해 주어야 한다.
    # 아래 코드는 그 작업을 batch_size 크기로 수행한다.
    for i in range(batch_size // num_skips):
        target = skip_window  # target label at the center of the buffer
        targets_to_avoid = [ skip_window ]
        for j in range(num_skips):
            while target in targets_to_avoid:
                # context window에서 context를 뽑아내는 작업은 랜덤하게 이루어진다.
                # 단, skip_window*2 == num_skips 인 경우, 어차피 모든 context를 다 뽑아내므로 랜덤은 별 의미가 없음. 순서가 랜덤하게 될 뿐.
                target = random.randint(0, span - 1)

            targets_to_avoid.append(target)
            batch[i * num_skips + j] = buffer[skip_window]
            labels[i * num_skips + j, 0] = buffer[target]

        buffer.append(data[data_index])
        data_index = (data_index + 1) % len(data)

    return batch, labels

# batch가 어떻게 구성되는지를 보기 위해 한번 뽑아서 출력:
print("Generating batch ... ")
batch, labels = generate_batch(batch_size=8, num_skips=2, skip_window=1)
print("Sample batches: ", batch[:10])
print("Sample labels: ", labels[:10])
for i in range(8):
    print(batch[i], '->', labels[i, 0])
    print(reverse_dictionary[batch[i]], '->', reverse_dictionary[labels[i, 0]])


# Step 5: Build and train a skip-gram model.
print("\nStep 5: Build and train a skip-gram model.")
batch_size = 128
embedding_size = 128  # Dimension of the embedding vector.
skip_window = 1       # How many words to consider left and right.
num_skips = 2         # How many times to reuse an input to generate a label.

# We pick a random validation set to sample nearest neighbors. Here we limit the
# validation samples to the words that have a low numeric ID, which by
# construction are also the most frequent.
valid_size = 16     # Random set of words to evaluate similarity on.
valid_window = 100  # Only pick dev samples in the head of the distribution.
valid_examples = np.array(random.sample(np.arange(valid_window), valid_size))
# [0 ~ valid_window] 의 numpy array를 만들고 거기서 valid_size 만큼 샘플링함.
# 즉, 여기서는 0~99 사이의 수 중 랜덤하게 16개를 고른 것이 valid_examples 임.
num_sampled = 64    # Number of negative examples to sample.

print("valid_examples: ", valid_examples)

graph = tf.Graph()

with graph.as_default():

    # Input data.
    train_inputs = tf.placeholder(tf.int32, shape=[batch_size])
    train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])
    valid_dataset = tf.constant(valid_examples, dtype=tf.int32)

    # Ops and variables pinned to the CPU because of missing GPU implementation
    # embedding_lookup이 GPU implementation이 구현이 안되어 있어서 CPU로 해야함.
    # default가 GPU라서 명시적으로 CPU라고 지정해줌.
    with tf.device('/cpu:0'):
        # Look up embeddings for inputs.
        # embedding matrix (vectors)
        embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
        # 전체 embedding matrix에서 train_inputs (mini-batch; indices) 이 가리키는 임베딩 벡터만을 추출
        embed = tf.nn.embedding_lookup(embeddings, train_inputs)

        # Construct the variables for the NCE loss
        # NCE loss 는 logistic regression model 을 사용해서 정의된다.
        # 즉, logistic regression 을 위해, vocabulary의 각 단어들에 대해 weight와 bias가 필요함.
        nce_weights = tf.Variable(
            tf.truncated_normal([vocabulary_size, embedding_size],
                                stddev=1.0 / math.sqrt(embedding_size)))
        nce_biases = tf.Variable(tf.zeros([vocabulary_size]))

    # Compute the average NCE loss for the batch.
    # tf.nce_loss automatically draws a new sample of the negative labels each
    # time we evaluate the loss.
    loss = tf.reduce_mean(
        tf.nn.nce_loss(nce_weights, nce_biases, embed, train_labels,
                       num_sampled, vocabulary_size))

    # Construct the SGD optimizer using a learning rate of 1.0.
    optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(loss)

    # Compute the cosine similarity between minibatch examples and all embeddings.
    # minibatch (valid_embeddings) 와 all embeddings 사이의 cosine similarity를 계산한다.
    # 이 과정은 학습이 진행되면서 각 valid_example 들에게 가장 가까운 단어가 어떤 것인지를 보여주기 위함이다 (즉 학습 과정을 보여주기 위함).
    norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))
    normalized_embeddings = embeddings / norm
    valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)
    similarity = tf.matmul(valid_embeddings, normalized_embeddings, transpose_b=True)

# Step 6: Begin training
print("\nStep 6: Begin training")
num_steps = 100001

with tf.Session(graph=graph) as session:
    # We must initialize all variables before we use them.
    tf.initialize_all_variables().run()
    print("Initialized")

    average_loss = 0
    for step in xrange(num_steps):
        batch_inputs, batch_labels = generate_batch(batch_size, num_skips, skip_window)
        feed_dict = {train_inputs : batch_inputs, train_labels : batch_labels}

        # We perform one update step by evaluating the optimizer op (including it
        # in the list of returned values for session.run()
        # feed_dict를 사용해서 placeholder에 데이터를 집어넣고 학습시킴.
        _, loss_val = session.run([optimizer, loss], feed_dict=feed_dict)
        average_loss += loss_val

        if step % 2000 == 0:
            if step > 0:
                average_loss = average_loss / 2000
            # The average loss is an estimate of the loss over the last 2000 batches.
            print("Average loss at step ", step, ": ", average_loss)
            average_loss = 0

        # note that this is expensive (~20% slowdown if computed every 500 steps)
        if step % 10000 == 0:
            sim = similarity.eval()
            for i in xrange(valid_size):
                valid_word = reverse_dictionary[valid_examples[i]]
                top_k = 8 # number of nearest neighbors
                nearest = (-sim[i, :]).argsort()[1:top_k+1]
                log_str = "Nearest to %s:" % valid_word
                for k in xrange(top_k):
                    close_word = reverse_dictionary[nearest[k]]
                    log_str = "%s %s," % (log_str, close_word)
                print(log_str)
    final_embeddings = normalized_embeddings.eval()

# Step 7: Visualize the embeddings.
print("\nStep 7: Visualize the embeddings.")
def plot_with_labels(low_dim_embs, labels, filename='tsne.png'):
    assert low_dim_embs.shape[0] >= len(labels), "More labels than embeddings"
    plt.figure(figsize=(18, 18))  #in inches
    for i, label in enumerate(labels):
        x, y = low_dim_embs[i,:]
        plt.scatter(x, y)
        plt.annotate(label,
                     xy=(x, y),
                     xytext=(5, 2),
                     textcoords='offset points',
                     ha='right',
                     va='bottom')

    plt.savefig(filename)

try:
    # 혹시 여기서 에러가 난다면, scikit-learn 과 matplotlib 을 최신버전으로 업데이트하자.
    from sklearn.manifold import TSNE
    import matplotlib.pyplot as plt

    tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000)
    plot_only = 500

    low_dim_embs = tsne.fit_transform(final_embeddings[:plot_only,:])
    labels = [reverse_dictionary[i] for i in xrange(plot_only)]
    plot_with_labels(low_dim_embs, labels)

except ImportError:
    print("Please install sklearn and matplotlib to visualize embeddings.")

'DataScience > Deep Learning' 카테고리의 다른 글

Backpropgation  (0) 2015.12.30
Transfer Learning  (0) 2015.12.26
TensorFlow - (7) word2vec - Implementation  (0) 2015.12.26
TensorFlow - (6) word2vec - Theory  (0) 2015.12.26
TensorFlow - (5) MNIST - CNN  (2) 2015.12.03
TensorFlow - (4) MNIST - Softmax Regression  (0) 2015.12.03

TensorFlow - (6) word2vec - Theory

TensorFlow

Vector Representations of Words

word2vec or word embedding.

Highlights

  • 왜 단어를 벡터로 나타내야 하는가?
  • 모델의 개념과 어떻게 학습되는가
  • 텐서플로를 통한 간단한 버전의 구현
  • 간단한 버전을 좀 더 복잡하게

기본 버전인 word2vec_basic.py 과 좀 더 진보된 버전인 word2vec.py 를 제공하니 참고하자.

하지만 그 전에, 왜 word embedding을 해야 하는지를 먼저 살펴보자.

Motivation: Why Learn Word Embeddings?

이미지나 오디오 데이터는 dense데이터임. 얼굴인식이나 음성인식 등의 이미지/오디오 데이터를 사용하는 작업은 이 데이터 안에 모든 필요한 정보가 담겨있다 (사람의 경우를 생각해보자 - 이미지만 보고 그 이미지로부터 얼굴을 인식할 수 있다). 반면, NLP에서는 단어를 표현하기 위해 one-hot 벡터를 사용하고, 이 방법은 단어간의 관계를 나타내는데 아무 도움이 안 됨. one-hot 벡터 방식으로 데이터를 나타내는 것은 데이터의 sparsity 문제를 가질 뿐만 아니라 통계적인 모델을 학습할 때 굉장히 많은 데이터가 필요하게 된다 - 예를 들면 ‘cat’에 대해 학습한 내용은 ‘dog’에 대해 학습한 내용에 전혀 영향을 끼치지 못함. 실제로는 서로 충분히 유사한 개체임에도 불구하고. 이러한 문제를 해결하기 위해, 단어의 concept에 대한 feature를 벡터로 나타낸 것이 바로 word embedding이다.

audio images text

Vector space models (VSMs) 는 연속적인 벡터 공간에서 단어를 나타낸다 (임베딩한다) - 비슷한 의미의 단어끼리 모이도록. VSM은 NLP에서 오래도록 다루어졌지만, 거의 모든 방법론들은 같은 컨텍스트의 단어들은 그 의미를 공유한다는 Distributional Hypothesis 에 기반한다.

Distributional Hypothesis는 언어학에서의 의미이론이다. 같은 컨텍스트에서 사용된 단어는 비슷한 의미를 가진다는. 즉 같이 사용된 주변 단어로부터 그 단어를 규정할 수 있다는 것.

이와 다른 접근법은 두가지 카테고리로 나눌 수 있다: count-based methods (e.g. Latent Semantic Analysis) 와 predictive methods (neural probabilistic language models). Count-based method는 거대한 텍스트 코퍼스에서 단어들이 어떤 단어들과 같이 등장하는지를 세고, 이를 작고 dense한 벡터로 압축한다. Predictive model은 이미 학습된 주변 단어들로부터 타겟 단어의 벡터를 예측한다.

랭귀지 모델은 자연어로 된 텍스트 (sequence of words) 에 대해, 단어 시퀀스의 중요한 통계적 분포 특징을 내포한다. 즉, 이 모델은 자연어 텍스트에 대해 통계적 단어 시퀀스 분포를 갖고, 이를 통해 특정 단어 시퀀스 뒤에 어떤 단어가 나올지 확률적 예측을 할 수 있다.
뉴럴 네트워크 랭귀지 모델은 NN에 기반한 랭귀지 모델이다. curse of dimensionality 의 효과를 줄이는 distributed representation을 학습할 수 있다. 러닝 알고리즘의 컨텍스트에서 curse of dimensionality란, 복잡한 함수를 학습할 때 방대한 양의 트레이닝 데이터가 필요한 것을 가리킨다. 인풋 변수의 수가 증가하면 학습을 위한 데이터의 수도 지수적으로 증가한다.

Word2vec은 계산-효율적인 (computationally-efficient) predictive model이다. word2vec은 CBOW (Continuous Bag-of-Words model) 와 Skip-Gram model 이라는 두가지 주요한 특징을 갖는다. 이 두 모델은 알고리즘적으로 유사한데, 단지 CBOW는 컨텍스트 (주변 단어들) 로부터 타겟 단어의 벡터를 예측하고 반대로 skip-gram 은 타겟 단어로부터 주변 컨텍스트 단어들의 벡터를 예측한다. 이 inversion은 이상해보일 수 있는데, 통계학적으로 CBOW는 전체 컨텍스트를 하나의 관찰로 다룸으로써 분산되어 있는 정보를 스무스하게 만드는 효과가 있다. 이는 작은 데이터셋에서 효과적이다. 반면 skip-gram은 모든 타겟 단어 - 컨텍스트 단어 페어 각각을 새로운 관찰로 다루고, 이는 커다란 데이터셋에서 효과적이다. 우리는 skip-gram 모델에 집중할것이다.

Scaling up with Noise-Contrastive Training

Maximum-Likelihood Estimation (MLE)

들어가기 전에 MLE를 먼저 살펴보자. 이 개념이 매번 헷갈려서 정리도 한번 했었는데 아직도 헷갈림. -_-

MLE는 주어진 데이터의 통계모델의 파라메터를 추정하는 방법론이다. 예를 들어, 성장한 여성 펭귄의 키 분포를 알고 싶다고 하자. 모든 펭귄의 키를 다 잴수는 없다. 키 분포가 정규분포를 따른다고 가정하자. 그러면 이때 평균과 분산을 알면 전체 분포를 알 수 있다. 이를 어떻게 알 수 있을까? MLE는 여기서 일부 데이터를 기반으로 모집단의 파라메터, 즉 평균과 분산을 추정한다 - 측정한 데이터가 나올 확률이 가장 높은 모집단을 추정하는 방식으로.

일반적으로, 고정된 수의 데이터와 통계 모델에 기반해서, MLE는 likelihood function을 최대화하는 파라메터를 선택한다. 직관적으로, 관찰된 데이터로부터 선택된 모델의 “agreement” 를 최대화한다. 그리고 결과 분포에서 주어진 데이터의 확률을 최대화한다.

likelihood function (or simply likelihood) : 통계모델의 파라메터의 함수. “probability” 와 비슷하게 쓰이지만, 통계학적으로 “결과” 냐 “파라메터” 냐의 차이가 있다. Probability는 정해진 파라메터를 기반으로 결과 함수를 설명할 때 쓰인다 - e.g. 동전을 10번 튀겼고, 공평한 동전이라면, 이 때 항상 앞면이 나올 probability는 얼마일까? 반면 likelihood는 주어진 결과를 기반으로 파라메터의 함수를 설명할때 쓰인다 - e.g. 동전을 10번 튀겼고, 10번 다 앞면이 나왔을 때, 이 동전이 공평할 likelihood는 얼마일까?

Return to Scaling up with Noise-Contrastive Training

Neural probabilistic language model은 이전 단어들 (for “history”) 가 주어졌을 때 다음 단어 (for “target”) 의 확률을 추정하는 MLE를 통해 학습된다. 이 과정은 softmax function에 기반한다:

여기서 는 타겟 단어 와 컨텍스트 의 공존 가능성 (compatibility) 를 계산한다 - 보통 dot product를 쓴다. 이 모델을 학습하기 위해 트레이닝 셋에 대해, log-likelihood를 최대화한다:

근데 가 probability (posterior) 아닌가? likelihood면 여야 할 것 같은데…

이 방법은 적절하게 normalized된 probabilistic language model 을 학습하지만, 문제는 이 방법은 너무 비싸다. 다음에 어떤 단어가 나올지를 예측하기 위해 모든 단어들에 대해 확률을 전부 계산하고 노멀라이즈 해야 한다. 그리고 이 과정을 모든 training step마다 반복해야 한다.

softmax-nplm

반면, word2vec의 feature learning 에서는 full probabilistic model을 학습할 필요가 없다. CBOW와 skip-gram 모델이 binary classification object (logistic regression) 을 사용해서 학습하는 대신, 같은 컨텍스트에서 개의 가상의 (noise) 단어 로부터 타겟 단어 를 구별한다. 아래는 CBOW에 대한 그림이다. skip-gram은 단지 방향만 반대로 하면 된다:

CBOW

잘 이해는 안 가지만, 이미지를 참고하면, 원래는 모든 가능한 단어 에 대해 확률을 구해봐야 했지만 CBOW 혹은 skip-gram 에서는 k개의 imaginary (noise) 단어 에 대해서만 테스트하여 학습 속도를 향상시킨다.

수학적으로, 이 예제에 대해, 다음 objective 를 최대화 하는 것을 목표로 한다:

는, 임베딩 벡터 를 학습하면서, 데이터셋 에서 컨텍스트 하에서 단어 가 나올 확률을 계산하는 binary logistic regression probability 모델이다. 실제 학습에서는, noise distribution으로부터 k contrastive words를 샘플링 (drawing) 함으로써 기대값 (expectation) 을 추정한다. (즉, Monte Carlo average 를 계산한다)

Monte Carlo average (integration):
몬테카를로 적분 (integration) 은 랜덤을 이용해서 적분하는 방법이다. 랜덤하게 난수를 발생시키고, 해당 난수가 적분 범위 안에 들어가는 확률을 계산하여 이를 통해 적분한다. 도형의 넓이를 계산한다고 생각해 보면 쉽게 이해할 수 있음.

여기서 임베딩 벡터 는 word embedding에서 바로 그 임베딩 벡터를 말함.

위의 목적 함수 (objective) 는 real word에 높은 확률을 할당하고 noise word에 낮은 확률을 할당했을 때 최대화된다. 기술적으로, 이는 Negative Sampling 이라 불린다. 이 함수는 위 소프트맥스 함수 () 를 근사하지만 훨씩 더 적은 계산량을 가지고, 이는 훨씬 빠른 학습속도를 제공한다. 우리는 정확하게는 이와 거의 유사한 noise-contrastive estimation (NCE) 를 사용한다. 이는 TensorFlow 에서 tf.nn.nce_loss()라는 함수로 제공하므로 편리하게 사용할 수 있다.

The Skip-gram Model

the quick brown fox jumped over the lazy dog

라는 데이터셋을 생각해보자. ‘context’ 라는 것은 다양하게 정의될 수 있지만, syntactic contexts는 보통 타겟 단어의 주변 단어를 가리킨다. 일단, ‘context’ 가 타겟 단어의 좌우 1칸을 가리킨다고 해 보자:

([the, brown], quick), ([quick, fox], brown), ([brown, jumped], fox), ...

살펴보면 알겠지만 이는 (context, target) 쌍이다. skip-gram은 타겟 단어로부터 컨텍스트 단어를 예측한다. 즉, 우리는 ‘quick’ 으로부터 ‘the’ 와 ‘brown’ 을 예측하고, ‘brown’ 으로부터 ‘quick’ 과 ‘fox’ 를 예측해야 한다. 자, 그러면 데이터셋을 (input, output) 으로 구성하면 이렇게 된다:

(quick, the), (quick, brown), (brown, quick), (brown, fox), ...

object function 은 데이터셋 전체에 대한 함수이지만, 우리는 학습을 위해 online 혹은 minibatch learning 을 사용한다. 이를 자세히 살펴보자. 보통 minibatch 에서 batch_size 는 16에서 512 사이다.

위 트레이닝 셋에서 제일 첫 번째 케이스로 트레이닝 스텝 를 생각해보자. 우리의 목표는 quick 으로부터 the 를 예측하는 것이다. 먼저 noisy (contrastive) example 의 수를 나타내는 num_noise 를 선택해야 한다. noisy example 은 noise distribution 을 따르며, 이 분포는 일반적으로 unigram distribution 이다. 간단하게 하기 위해 num_noise=1 이라 하고 noisy example 로는 sheep 을 사용하자. 그러면 loss function을 계산할 수 있다:

unigram distribution 라는 것은 전체 데이터셋에서 각 단어의 unigram으로 생성한 확률분포를 의미하는 듯. sheep 이 위 데이터셋에 없다는 것이 이상한데, 일단 위 예제는 데이터셋의 일부라고 생각해보자.

이 과정의 목표는 임베딩 파라메터 를 업데이트하여 object function 을 최적화 (여기서는 최대화) 하는 것이다. 이를 위해, 임베딩 파라메터 에 대해 loss의 gradient를 계산한다. 여기서는 를 계산한다 - TensorFlow는 이를 위한 함수를 제공한다. 이후 이 gradient의 방향으로 임베딩 파라메터를 약간 업데이트한다. 이 과정을 전체 데이터셋에 대해 반복하면, 임베딩 벡터는 점차 실제 단어의 위치로 이동한다 - real words와 noise words가 분리될때까지.

이 학습 과정을 t-SNE dimensionality reduction technique 같은 방법을 사용해서 2차원 혹은 3차원 공간으로 차원축소하여 시각화 할 수 있다. 이 과정을 살펴보면, 우리가 원하는 대로 단어의 의미를 잘 추출하여 벡터공간에 임베딩하는 것을 확인할 수 있다:

word2vec visualization

즉, 이 벡터들은 기존의 NLP prediction task에서 훌륭한 특성으로 사용될 수 있다 - POS tagging or named entity recognition 등. Collobert et al. 또는 Turian et al. 을 참고하자.

'DataScience > Deep Learning' 카테고리의 다른 글

Transfer Learning  (0) 2015.12.26
TensorFlow - (7) word2vec - Implementation  (0) 2015.12.26
TensorFlow - (6) word2vec - Theory  (0) 2015.12.26
TensorFlow - (5) MNIST - CNN  (2) 2015.12.03
TensorFlow - (4) MNIST - Softmax Regression  (0) 2015.12.03
TensorFlow - (3) Basic Usage  (0) 2015.12.03

TensorFlow - (5) MNIST - CNN

TensorFlow

MNIST tutorial.

Deep MNIST for Experts

(4) MNIST - Softmax Regression 의 결과인 91%의 정확도는 충분하지 않다. 약간 수정해서 작은 CNN을 만들어 보자. state-of-art는 당연히 아니지만, 99.2%의 정확도가 나온다.

Weight Initialization

모델을 만들기 위해, 많은 weight와 bias를 만들어야 할 필요가 있다. gradient가 0이 나오는 평형 상태를 방지하고 평형 상태를 깨기 위해 (symmetry breaking) 적은 양의 노이즈로 초기화해야 한다. 우리는 ReLU를 쓸 것이므로, “dead neuron” 을 피하기 위해 약간 양의 초기 bias로 초기화하는 것도 괜찮다. 이를 하기 전에 두가지 편리한 함수를 만들어두자.

symmetry breaking 이란 물리학 개념으로 작은 떨림이 시스템을 결정하는 것이라고 함

# truncated normal distribution에 기반해서 랜덤한 값으로 초기화
def weight_variable(shape):
    # tf.truncated_normal:
    # Outputs random values from a truncated normal distribution.
    # values whose magnitude is more than 2 standard deviations from the mean are dropped and re-picked.
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

# 0.1로 초기화
def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

Convolution and Pooling

여기서 사용하는 옵션들은 전부 기초적인 CNN임. convolution과 pooling에서도 마찬가지.

# convolution & max pooling
# vanila version of CNN
# x (아래 함수들에서) : A 4-D `Tensor` with shape `[batch, height, width, channels]`
def conv2d(x, W):
    # stride = 1, zero padding은 input과 output의 size가 같도록.
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

First Convolutional Layer

# [5, 5, 1, 32]: 5x5 convolution patch, 1 input channel, 32 output channel.
# MNIST의 pixel은 0/1로 표현되는 1개의 벡터이므로 1 input channel임.
# CIFAR-10 같이 color인 경우에는 RGB 3개의 벡터로 표현되므로 3 input channel일 것이다.
# Shape을 아래와 같이 넣으면 넣은 그대로 5x5x1x32의 텐서를 생성함.
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
# 최종적으로, 32개의 output channel에 대해 각각 5x5의 convolution patch (filter) weight 와 1개의 bias 를 갖게 됨.

# x는 [None, 784] (위 placeholder에서 선언). 이건 [batch, 28*28] 이다.
# x_image는 [batch, 28, 28, 1] 이 됨. -1은 batch size를 유지하는 것이고 1은 color channel.
x_image = tf.reshape(x, [-1,28,28,1])

# 이제, x_image를 weight tensor와 convolve하고 bias를 더한 뒤 ReLU를 적용하자. 그리고 마지막으론 max pooling.
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

Second Convolutional Layer

# channels (features) : 32 => 64
# 5x5x32x64 짜리 weights.
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])

h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

Densely Connected Layer

2x2의 max pooling을 두번 거쳤으니 이제 우리가 갖고있는 이미지의 크기는 7x7이다. 이제 전체 이미지에 연결된 1024개의 뉴런으로 구성된 fully-connected layer를 추가하자. 이전 레이어, 즉 풀링 레이어2 (h_pool2) 의 텐서를 batch of vector로 변환하고, weight matrix를 곱하고, bias를 더하고, ReLU를 적용하자.

# Densely connected layer
# 7*7*64는 h_pool2의 output (7*7의 reduced image * 64개의 채널). 1024는 fc layer의 뉴런 수.
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])

h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) # -1은 batch size를 유지하는 것.
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

Dropout

오버피팅을 최소화하기 위해 readout layer 이전에 dropout을 적용하자. 텐서플로의 tf.nn.dropout은 자동으로 scaling하고 masking하므로 추가적인 작업이 필요 없다.

scaling과 masking은 드롭아웃이 노드를 선택해서 제거하고 (masking) 그로 인해 전체 노드의 수가 줄어드는 것 (scaling) 을 의미하는 듯.

# keen_prob은 dropout을 적용할지 말지에 대한 확률임. 이를 이용해서 training 동안만 드롭아웃을 적용하고 testing 때는 적용하지 않는다.
# training & evaluation 코드를 보니 keen_prob = 1.0일때 dropout off 인 듯.
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

Readout Layer

Readout layer는 그냥 소프트맥스 레이어임. 그냥 softmax 적용하면 됨.

W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

Train and Evaluate the Model

트레이닝과 평가는 Softmax Regression에서 했던 것과 동일하다. 단, optimizer를 steepest gradient descent 대신에 ADAM 을 사용한다. 또한 dropout 확률인 keen_prob가 feed_dict에 추가된다.

cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
sess.run(tf.initialize_all_variables())
for i in range(20000):
    batch = mnist.train.next_batch(50)
    if i%100 == 0:
        train_accuracy = accuracy.eval(feed_dict={x:batch[0], y_: batch[1], keep_prob: 1.0})
        print "step %d, training accuracy %g" % (i, train_accuracy)
    train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

print "test accuracy %g" % accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})

20000번을 돌리면 최종적으로 99.2%가 나온다고 한다. 거기까진 못 돌려봤고 Softmax regression을 돌린 횟수인 1000번 까지의 결과는 다음과 같다. 참고로, 소프트맥스 리그레션을 20000번 돌려도 정확도는 92%다.

step 0, training accuracy 0.12
step 100, training accuracy 0.8
step 200, training accuracy 0.92
step 300, training accuracy 0.88
step 400, training accuracy 0.96
step 500, training accuracy 0.9
step 600, training accuracy 1
step 700, training accuracy 0.96
step 800, training accuracy 0.86
step 900, training accuracy 1
step 1000, training accuracy 0.96

'DataScience > Deep Learning' 카테고리의 다른 글

TensorFlow - (7) word2vec - Implementation  (0) 2015.12.26
TensorFlow - (6) word2vec - Theory  (0) 2015.12.26
TensorFlow - (5) MNIST - CNN  (2) 2015.12.03
TensorFlow - (4) MNIST - Softmax Regression  (0) 2015.12.03
TensorFlow - (3) Basic Usage  (0) 2015.12.03
TensorFlow - (2) Install  (0) 2015.12.03

TensorFlow - (4) MNIST - Softmax Regression

TensorFlow

MNIST tutorial.

Softmax Regression

내가 softmax regression (multinomial logstic regression) 에 대해 자세히 몰라서 정리해본다. MNIST 데이터 사용.

데이터의 클래스 라벨들에 대해 확률을 예측하고 싶다면 소프트맥스가 그것을 할 수 있다. 소프트맥스는 두 스텝으로 구성된다: evidence를 계산하고, 이를 probability로 변환하고.

evidence를 계산하기 위해 픽셀 강도 (pixel intensities) 의 가중합 (weighted sum) 을 계산한다. 만약 높은 강도를 갖는 픽셀이 그 이미지에 불리한 증거 (evidence) 라면 weight는 음수가 되고, 그 픽셀이 유리한 증거라면 양수가 된다.

자세히 이해는 안가지만, 클래스 라벨 즉 여기서는 0~9 이미지를 비교해서 0에서만 나타나는 픽셀 특징을 갖고 있으면 0에 대해 양수가 되고 그 반대면 음수가 되고 이런 식인듯.

음수 가중치는 빨갛게 표시하고 양수 가중치는 파랗게 표시하면 다음과 같다:


또한 인풋 데이터에 독립적인 어떤 가중치가 있을 수 있다. 이를 bias라 한다. 최종적으로 인풋 $x$가 주어졌을 때 클래스 $i$에 대한 evidence는:

$$\text{evidence}_i = \sum_j W_{i,~ j} x_j + b_i$$

자 이제 이렇게 계산한 evidence를 softmax 함수를 통해 확률로 변환할 수 있다.

$$y = \text{softmax}(\text{evidence})$$

softmax는 “activation” 혹은 “link” 를 제공한다. linear 한 값 (여기서는 evidence) 을 우리가 원하는 형태 (여기서는 10가지 클래스 라벨에 대한 확률분포) 로 변환해준다. 즉 evidence를 확률로 변환하는 과정이라고 이해할 수 있다. 이는 다음과 같이 정의된다:

$$\text{softmax}(x) = \text{normalize}(\exp(x))$$

식을 풀면 이렇게 된다:

$$\text{softmax}(x)_i = \frac{\exp(x_i)}{\sum_j \exp(x_j)}$$

그러나 첫 식으로 이해하는게 더 도움이 될 것이다 - exp(input) 을 normalize 하는 것. exp는 결과의 차이를 부각시키는 효과를 갖고, normalize는 결과를 확률분포로 변환하기 위함이다.

원문에는 위 식에 대한 설명이 주저리 적혀 있는데, 내가 보기엔 매우 심플하게 softmax는 간극을 벌리고 그 결과를 확률화한다. 즉, 클래스 라벨에 대해 각각 evidence를 계산한 다음, 이 evidence가 큰 클래스 라벨의 확률을 그 evidence 이상으로 크게 만들어 주는 것. 어찌보면 당연할 수 있는데, 10개의 클래스 중 9개의 evidence가 1이고 1개만 2라고 한다면 그 클래스가 정답일 확률은 2/11 보다는 훨씬 클 것이다.

이 과정을 다이어그램으로 나타내면 다음과 같다.



이는 결국 이렇게 쓸 수 있다:

$$y = \text{softmax}(Wx + b)$$

Deep MNIST for Experts

위 링크에서 Softmax Regression 부분.

Load MNIST Data

MNIST 데이터셋은 60000개의 트레이닝 데이터와 10000개의 테스트 데이터로 구성된다. 각 이미지는 28x28x1 (0/1의 흑백 채널) 이며 0~9의 class label을 갖는다.

# coding=utf-8
import tensorflow as tf
import input_data


# download data
# mnist is traning/validation/test set as Numpy array
# Also it provides a function for iterating through data minibatches
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

Start TensorFlow InteractiveSession

# InteractiveSession을 쓰지 않으면 세션에 그래프를 올리기 전에 그래프를 전부 완성해야 함.
# 다른말로 하면 계산하기 전에 그래프를 완성해야 함. InteractiveSession을 쓰면 그때그때 계산이 가능함.
sess = tf.InteractiveSession()

Computation graph

파이썬에서 연산을 빠르게 하기 위해 numpy와 같은 라이브러리들은 파이썬 바깥 (아마도 C) 에서 연산을 수행하게 한다.
그러나 이런 노력에도 불구하고 이러한 switching 자체가 오버헤드임. 특히 GPU나 분산처리할 때 심각함.
이러한 문제를 해결하기 위해 텐서플로는 연산 하나하나를 C로 수행하는게 아니라 연산 전체를 통째로 C로 수행함.
이러한 접근방식은 Theano나 Torch와 유사
파이썬의 역할은 전체 컴퓨테이션 그래프를 구축하고, 어느 부분이 실행되어야 하는지를 명시하는 일이다.

즉 그래프를 만들고 run()을 통해 원하는 부분을 실행하면 해당 부분이 Session에 올려져 실행이 되는데 이 작업이 통째로 외부에서 실행되는듯.
그렇기 때문에 원래는 InteractiveSession을 쓰면 안 되는거임. InteractiveSession을 쓰면 속도가 상당히 느려지리라 짐작함.

본문에서는 파이썬 바깥 혹은 파이썬으로부터 독립적으로 연산한다고 표현하므로 정확히 C인지는 잘 모르겠음. 참고로 여기서 C 라고 하면 C++도 포함.

Softmax Regression Model

Placeholder

# 1-layer NN => Softmax. 1-layer란 Input layer와 Output layer만 있는 것이 1-layer임.
# placeholder는 실행할때 우리가 값을 넣을 수 있음
x = tf.placeholder("float", shape=[None, 784])  # x는 input image. 784 = 28*28, 이미지를 핌 (flatten). 흑백 이미지이므로 각 픽셀은 0/1
y_ = tf.placeholder("float", shape=[None, 10])  # _y는 class label. mnist가 0~9까지의 이미지이므로 10개. one-hot 벡터.

Variables

# Variables: Weights & Bias
# Variable은 말 그대로 변수에 해당한다.
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

# 변수는 사용하기 전에 초기화해줘야 한다. 선언시에 정해준 값으로 (여기서는 0) 초기화된다.
sess.run(tf.initialize_all_variables())  # 모든 변수 초기화

Predicted Class and Cost Function

# softmax 함수는 이미 구현되어 있으므로 한줄에 짤 수 있음.
# tf.nn.softmax는 소프트맥스 함수만을 말하고 이 과정 전체가 소프트맥스 리그레션이다.
y = tf.nn.softmax(tf.matmul(x, W) + b) # Wx+b = Output nodes의 액티베이션 값. 즉 액티베이션 값을 소프트맥스 함수에 넣음.

# Cost function: 트레이닝 과정에서 최소화해야 하는 값.
# cross-entropy between the target and the model's prediction.
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
# reduce_sum은 텐서의 합을 구함. reduce는 텐서를 축소한다는 개념인 듯.
"""
# Example)
# 'x' is [[1, 1, 1]]
#         [1, 1, 1]]
tf.reduce_sum(x) ==> 6
tf.reduce_sum(x, 0) ==> [2, 2, 2]
tf.reduce_sum(x, 1) ==> [3, 3]
tf.reduce_sum(x, 1, keep_dims=True) ==> [[3], [3]]
tf.reduce_sum(x, [0, 1]) ==> 6
"""

Train the Model

# 지금까지, 우리의 모델과 코스트 펑션을 정의했음.
# 즉 다시 말해서 텐서플로는 우리의 전체 컴퓨테이션 그래프를 알고 있음.
# 이에 기반하여 자동으로 미분하여 (differentiation) gradient를 계산할 수 있음.
# 텐서플로는 다양한 최적화 알고리즘을 제공함. http://www.tensorflow.org/api_docs/python/train.html#optimizers
# 여기서는 steepest gradient descent를 사용.
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
# 이 한 줄은 그래프에 하나의 op를 추가한 것. 이 op는 gradient를 계산하고, 파라메터 업데이트 스텝을 계산하고, 그 결과를 파라메터에 적용한다.
# 즉 위 스텝을 반복하면 점점 학습이 됨.

# 50 크기의 mini-batch로 1000번 학습을 함.
for i in range(1000):
    batch = mnist.train.next_batch(50) # mini-batch (50)
    # batch[0]은 x, batch[1]은 y로 구성됨. [data, class_label] 구조.
    # batch[0 or 1][0~49] 가 각각의 데이터.
    # batch_xs, batch_ys = mnist.train.next_batch(50) 형태로 받는 것이 더 직관적.
    train_step.run(feed_dict={x: batch[0], y_:batch[1]})
    y_eval = y.eval(feed_dict={x:batch[0]})

Evaluate the Model

# argmax는 tensor에서 max값을 찾는 함수다. 파라메터는 tensor, axis (dimension) 임. axis개념은 numpy와도 같고 위의 reduce_sum과도 같음.
# reduce_mean도 마찬가지.
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))  # 예측한 라벨과 정답 라벨을 비교하고
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))  # 비교한 결과의 평균을 낸다
print accuracy.eval(feed_dict={x: mnist.test.images, y_:mnist.test.labels})  # 실행

# => 0.9092

# 참고: Tensor.eval(), Operation.run(). (in InteractiveSession)

More

더 자세한 건 아래 문서들을 참고하자.
http://ufldl.stanford.edu/tutorial/supervised/SoftmaxRegression/
http://neuralnetworksanddeeplearning.com/chap3.html#softmax (원문 추천)

'DataScience > Deep Learning' 카테고리의 다른 글

TensorFlow - (6) word2vec - Theory  (0) 2015.12.26
TensorFlow - (5) MNIST - CNN  (2) 2015.12.03
TensorFlow - (4) MNIST - Softmax Regression  (0) 2015.12.03
TensorFlow - (3) Basic Usage  (0) 2015.12.03
TensorFlow - (2) Install  (0) 2015.12.03
TensorFlow - (1) Intro  (1) 2015.12.03

TensorFlow - (3) Basic Usage

TensorFlow

Basic Usage

텐서플로를 쓰기 위해서는 먼저 이해해야 한다.

  • 컴퓨테이션을 그래프로 나타낸다
  • 그래프는 Sessions 위에서 실행한다
  • 데이터는 tensor로 나타낸다
  • 상태를 Variables 와 함께 유지한다
  • 데이터를 쓰기 (연산하기) 위해 feeds와 fetches를 사용한다

지금은 이해가 잘 안 되는데, 다 읽고 나면 이해가 됨.

Overview

텐서플로는 컴퓨테이션을 그래프로 나타내는 시스템이다. 노드는 op (operation) 라고 불린다. op는 0개 이상의 Tensor를 받고, 0개 이상의 텐서를 생산한다. 텐서는 다차원의 어레이 (multi-dimensional array) 다. 예를 들어, 이미지의 mini-batch를 [batch, height, width, channels]의 4D floating point array로 표현할 수 있다.

gradient descent 를 계산할 때, full-batch 라고 하면 전체 데이터를 다 사용해서 gradient를 구하는 것이고, mini-batch는 일부분만을 사용하고 on-line 은 한 샘플만을 사용한다. 즉 full-batch는 기본 gradient descent이고 mini-batch와 online은 SGD (stochastic gradient descent) 이다. 참고로 full-batch, mini-batch, online learning 은 이것을 말하는 것이다.

텐서플로 그래프는 컴퓨테이션의 설명이다. 무언가를 컴퓨팅하기 위해서는, 그래프가 Session에 올려져야 (launch) 한다. 세션은 그래프의 ops (operations) 를 Devices (CPU or GPU) 에 올리고, op를 실행하기 위한 메소드를 제공한다. 그러면 이 메소드는 ops에 의해 생성된 텐서를 리턴하는데 이는 numpy의 ndarray 이고 C and C++의 tensorflow::Tensor 이다.

The computation graph

텐서플로는 그래프를 조립하는 construction phase와 그래프의 ops를 실행하기 위해 세션을 사용하는 execution phase로 구성된다.

예를 들면, NN을 학습하는 그래프는 construction phase에서 생성되고, 이것이 반복적으로 실행되면서 실질적으로 트레이닝 ops가 실행되는 것은 execution phase에서 일어난다.

텐서플로는 C, C++, Python에서 사용할 수 있지만 그래프를 조립하는 작업은 파이썬에서 훨씬 쉽게 가능함. 반면 세션 라이브러리는 세 언어에서 동일하게 사용가능함.

Building the graph

import tensorflow as tf

matrix1 = tf.constant([[3., 3.]])
matrix2 = tf.constant([[2.],[2.]])

product = tf.matmul(matrix1, matrix2)

이렇게 쓰면, 바로 계산되는 것이 아니라, ops constructor가 default graph에 nodes를 추가한다. 대부분의 경우에 이 디폴트 그래프로 전체 플로우를 나타낼 수 있다. 여러개의 그래프가 필요할 경우 Graph class 도큐먼트를 참조하자.

이제, 디폴트 그래프는 3개의 노드를 가진다: 2개의 constant() ops 와 하나의 matmul() op. 실제로 결과를 얻기 위해서는 이 그래프를 세션에 올려야 한다.

Launching the graph in a session

sess = tf.Session()

# The output of the op is returned in 'result' as a numpy `ndarray` object.
result = sess.run(product)
print result
# ==> [[ 12.]]

sess.close()

세션에서 모든 연산은 패러렐하게 작동한다. 세션 constructor에 파라메터를 넘기지 않았기 때문에 디폴트 그래프로 작동한다. 세션 API는 Session class 도큐먼트를 참고하자.

작업이 끝나면 리소스를 해방하기 위해 close()가 필요하다. 파이썬에서 이러한 구조는 with 를 통해 간단하게 쓸 수 있다:

with tf.Session() as sess:
  result = sess.run(product)
  print result

텐서플로는 그래프를 패러렐하게 작동시킨다. 이는 자동으로 작동하지만 원한다면 명시적으로 작동시킬수도 있다.

with tf.Session() as sess:
  with tf.device("/gpu:1"):
    matrix1 = tf.constant([[3., 3.]])
    matrix2 = tf.constant([[2.],[2.]])
    product = tf.matmul(matrix1, matrix2)
    ...

위 예제는 두번째 gpu를 작동시킨다. 물론 있을 때 얘기. Using GPU 참고.

Interactive Usage

이와 같이, 텐서플로에는 그래프를 만들고 이를 세션에 올려서 Session.run() 을 통해 실행시킨다. 이러한 과정은 IPython 등의 interactive python environment에서는 불편할 수 있으므로 이를 위한 환경을 제공한다 - InteractiveSession, Tensor.eval(), Operation.run()

import tensorflow as tf
sess = tf.InteractiveSession()

x = tf.Variable([1.0, 2.0])
a = tf.constant([3.0, 3.0])

x.initializer.run()

sub = tf.sub(x, a)
print sub.eval()
# ==> [-2. -1.]

자세히는 모르겠지만 Variable로 선언된 x는 initializer를 거쳐야 초기화가 되는 듯 하며, sess변수는 어디에서도 쓰이지 않지만 InteractiveSession() 을 생성하지 않으면 에러가 난다. run() 과 eval() 에서 자동적으로 참조가 되는 듯.

Tensors

텐서플로에서 모든 데이터는 텐서로 나타낸다. 컴퓨테이션 그래프에서 노드 (오퍼레이션) 들을 이동할 수 있는 것은 오직 텐서뿐이다. 텐서는 n차원의 어레이나 리스트로 생각할 수 있다. 텐서는 고정된 (static) type, rank, shape을 가지고 있다. Rank, Shape, and Type 참조. Rank는 차원 수를 의미한다.

Variables

변수는 그래프의 실행 사이에 상태를 유지한다. Variables 참고. 간단한 카운터 예제를 보자:

# Create a Variable, that will be initialized to the scalar value 0.
state = tf.Variable(0, name="counter")
# Variable을 생성하는 것 또한 노드 (op) 다.

# Create an Op to add one to `state`.
one = tf.constant(1)
new_value = tf.add(state, one)
update = tf.assign(state, new_value)
# 이 작업은 그래프에서 3개의 노드로 구성된다. 이후 sess.run(update) 가 되면 재귀적으로 세 단계가 실행된다.

# Variables must be initialized by running an `init` Op after having launched the graph.  We first have to add the `init` Op to the graph.
init_op = tf.initialize_all_variables()
# 변수 (Variable) 는 `init` Op 를 통해 초기화되어야 한다. 즉 변수를 쓰기 이전에 `init`를 실행시켜야 하고 그러려면 일단 그래프에 올려야 한다.

# Launch the graph and run the ops.
with tf.Session() as sess:
  # Run the 'init' op
  sess.run(init_op)
  # Print the initial value of 'state'
  print sess.run(state)
  # sess.run(state)는 state 변수를 생성하는 생성노드를 실행시킨다.

  # Run the op that updates 'state' and print 'state'.
  for _ in range(3):
    sess.run(update)
    # sess.run(update)는 state를 update하는 노드를 실행시킨ㄴ다.
    print sess.run(state)

# output:

# 0
# 1
# 2
# 3

개인적으로 느끼기에 텐서플로의 활용은 함수형 언어 그리고 memoization (메모리 펑션) 을 닮았다. 연산 과정이 각각의 연산 (op) 들로 쪼개지고, 이 연산들이 각각 노드가 된다. 결과를 나타내는 마지막 노드를 실행시키면 그 노드는 차례차례 필요한 노드를 호출하여 재귀적으로 모든 연산을 실행시킨다. 예를 들면 위 과정에서 sess.run(update)는 tf.assign(state, new_value) 라는 op인데, 이 op는 다시 new_value 노드를 호출하고 이는 tf.add(state, one) 이라는 op이다. 그리고 이는 다시 one 노드로 가서 tf.constant(1) 이라는 op를 호출할 것이다. 반면, state는 Variable이기 때문에 초기 생성자 tf.Variable(0, name=”counter”) 까지 올라가지 않고 state에 저장된 값을 사용한다.

Fetches

연산의 결과를 fetch 하는 (가져오는) 함수는, 이미 위에서도 많이 썼지만, run()이다. 지금까지는 싱글 노드만을 fetch했지만 여러개도 할 수 있다.

input1 = tf.constant(3.0)
input2 = tf.constant(2.0)
input3 = tf.constant(5.0)
intermed = tf.add(input2, input3)
mul = tf.mul(input1, intermed)

with tf.Session() as sess:
  result = sess.run([mul, intermed])
  print result

# output:
# [array([ 21.], dtype=float32), array([ 7.], dtype=float32)]

All the ops needed to produce the values of the requested tensors are run once (not once per requested tensor).
영어 문장이 100% 이해가 안 되는데, 같은 인풋 (텐서) 에 대해서 op는 한번만 계산한다는 의미인 듯 하다.

Feeds

fetch가 값을 가져오는 개념이었다면, feed는 값을 넣는 개념이다. 지금까지 값을 넣기 위해 ConstantVariable을 사용했다. 이 외에 텐서를 직접적으로 그래프의 op에 넣는 방법도 있다.

# input1 = tf.placeholder(tf.types.float32)
# input1 = tf.constant(1) # error! 
input1 = tf.constant(1.)
input2 = tf.placeholder(tf.types.float32)
output = tf.mul(input1, input2)

with tf.Session() as sess:
  print sess.run([output], feed_dict={input1:[7.], input2:[2.]})

# output:
# [array([ 14.], dtype=float32)]

위와 같이 run()에서 feed를 넣어주면 넣은 tensor가 임시로 op (위 예제에서는 tf.placeholder()) 를 대체한다. 일반적으로 “feed” 를 하기 위해서 명시적으로 op를 생성하고자 할 때 tf.placeholder()를 사용한다. 위 예제 맨 위의 주석과 같이, constant와 같이 다른 op 를 대체할 수 있으나 type은 같아야 한다 (tf.constant(1) 을 하면 에러가 난다).

placeholder()의 경우 feed를 넣지 않으면 에러가 난다.

MNIST fully-connected feed tutorial (source code) 는 larger-scale feed example이다.

'DataScience > Deep Learning' 카테고리의 다른 글

TensorFlow - (5) MNIST - CNN  (2) 2015.12.03
TensorFlow - (4) MNIST - Softmax Regression  (0) 2015.12.03
TensorFlow - (3) Basic Usage  (0) 2015.12.03
TensorFlow - (2) Install  (0) 2015.12.03
TensorFlow - (1) Intro  (1) 2015.12.03
Autoencoder vs RBM (+ vs CNN)  (1) 2015.11.29

TensorFlow - (2) Install

TensorFlow

나중에 linux-gpu ver 도 설치할텐데 그때 추가.

Install

Mac

맥에서는 cpu버전밖에 지원을 하지 않는다.
여러가지 설치법이 있으나, 나는 그냥 기본으로 시스템에 설치하기로 했다. virtualenv는 사실상 아카데믹한 환경에서는 별로 필요하지 않다.

cpu버전은 홈페이지에 소개되어 있는 대로 그냥 깔면 되는데, 간단한 트러블슈팅이 있었다:

  • 아직까지 python 2만 지원한다. 이는 리눅스도 마찬가지로 텐서플로 자체가 파이썬 2만 지원함. 파이썬 3용도 개발중이라고 함.
  • 내 맥북의 경우 python 2와 python 3이 같이 깔려있다. 문제는 pip임. pip를 python 2용 pip와 python 3용 pip로 분리해야 한다.
$ pip2.7 -V
pip 1.5.6 from /Library/Python/2.7/site-packages/pip-1.5.6-py2.7.egg (python 2.7)

'DataScience > Deep Learning' 카테고리의 다른 글

TensorFlow - (4) MNIST - Softmax Regression  (0) 2015.12.03
TensorFlow - (3) Basic Usage  (0) 2015.12.03
TensorFlow - (2) Install  (0) 2015.12.03
TensorFlow - (1) Intro  (1) 2015.12.03
Autoencoder vs RBM (+ vs CNN)  (1) 2015.11.29
Deep Learning을 위해 어떤 GPU를 써야 할까?  (0) 2015.11.19

TensorFlow - (1) Intro

TensorFlow

텐서플로는 굉장히 흥미로운 내부 구조를 가지고 있다. Theano나 Torch도 마찬가지의 구조를 차용한다고 하는데, 나는 텐서플로에서 처음 접했다. 프로그래밍 언어 패러다임 중에 Stream Programming 이라는 것이 있다. 이 패러다임은 프로그램을 flow graph 형태로 구조화하고, 각 노드는 데이터를 받아 처리하여 출력한다. 데이터는 엣지를 따라 흐른다. 이 패러다임에 따라 구조화된 프로그램은 유저가 특별히 신경쓰지 않아도 내부적으로 (자동적으로) 패러렐이 가능하다.

parallel stream graph

대표적인 스트림 프로그래밍 언어로는 StreamIt 이 있다. 텐서플로는 파이썬 인터페이스를 제공하지만, 내부적인 실행 매커니즘은 이 스트림 프로그래밍에 기반한다. 기존에 파이썬이 제공하던 imperative programming 과는 다르게, 코드 한줄 한줄이 그 즉시 실행되지 않고 컴퓨테이션 그래프를 생성하며, 나중에 그래프를 다 생성한 후에 이 그래프를 통째로 실행한다. 그러면 내부적으로 패러렐하게 실행된다.

정확하게는, 코드 한줄한줄은 당연히 그 때 실행된다. 단지 그 실행이 연산을 하는 것이 아니라 그래프에 노드를 추가하는 작업이라서 실제로 연산이 되지는 않는다는 것이다. 더 자세한 건 Basic Usage를 참고하자.

Introduction

Google + open-source = TensorFlow 를 요약번역한 것. 별 내용은 없다.

What is TensorFlow?

머신러닝 라이브러리. 마이크로소프트의 Azure ML 같은 클라우드 기반 머신러닝 서비스가 아님. “라이브러리” 다.

What is flow graph?

flow graph

이러한, 말 그대로 flow graph인데, 텐서플로우에는 이걸 자동으로 그려주는 TensorBoard 라는 모듈이 있음.

What about tensors?

텐서플로우에서 데이터는 텐서로 표현되고, 텐서는 다차원의 다이나믹 사이즈 데이터 어레이다 (multidimensional and dynamically sized data array). 이 텐서가 플로우 그래프에서 흐르게 (flow) 되므로 tensor flow가 이 라이브러리의 이름인 것.

What is cool about TensorFlow?

  1. 표현의 유연성 - 알고리즘을 데이터 플로우 그래프로 나타낼 수 있으면 텐서플로우로 구현할 수 있다.
  2. CPU와 GPU 를 모두 사용하며 parallel & asynchronous 컴퓨팅을 지원한다.
  3. 연구(리서치)와 실무(프로덕션) 모두에서 사용할 수 있다.
  4. 자동 미분 (auto-differentiation). 텐서플로우는 도함수 (derivative) 를 자동으로 계산하고 이는 매우 편리하다. 특히 네가 gradient-based 알고리즘을 사용한다면 - SGD 같은거.
  5. 파이썬 인터페이스!

Licencse

Apache 2.0 - 연구와 상업적 목적으로 전부 프리하게 쓸 수 있음.

Etc

본문에 보면 튜토리얼 및 예제도 소개하고 있음. 특히, 텐서플로우의 scikit-learn 식 인터페이스를 제공하는 Scikit Flow 는 흥미롭다! 또한 Theano와의 비교도 간략하게 적혀 있으니 참고하자.

'DataScience > Deep Learning' 카테고리의 다른 글

TensorFlow - (3) Basic Usage  (0) 2015.12.03
TensorFlow - (2) Install  (0) 2015.12.03
TensorFlow - (1) Intro  (1) 2015.12.03
Autoencoder vs RBM (+ vs CNN)  (1) 2015.11.29
Deep Learning을 위해 어떤 GPU를 써야 할까?  (0) 2015.11.19
Recent Advances in Deep Learning  (0) 2015.11.02

Autoencoder vs RBM (+ vs CNN)

Autoencoder vs RBM (+ vs CNN)

http://stats.stackexchange.com/questions/114385/what-is-the-difference-between-convolutional-neural-networks-restricted-boltzma

딥러닝은 크게 unsupervised pretraining 페이즈와 supervised fine-tuning 페이즈로 나눌 수 있다. 여기서 대표적인 pretraining 모델이 오토인코더와 RBM이다.

오토인코더와 RBM은 둘 다 unsupervised pretraining 기법이다. 즉, 클래스 라벨로부터 backpropagation 하는 것이 아니라 인풋을 아웃풋으로 하여 인풋을 reconstruction 할 수 있는 hidden units를 찾아내는 과정이다. 이 히든 유닛들은 데이터로부터 뽑아낸 feature라고 할 수 있으며 이 과정을 data-specific kernel이라고도 할 수 있다.

차이가 있다면 RBM은 에너지 모델을 차용하고 그 에너지 모델은 볼츠만 분포 (boltzmann distribution) 에 기반하므로 이는 generative model이다. 반면, 오토인코더는 deterministic (discriminant) 모델이다.

오토인코더는 피처를 축소하는 심플한 개념이고, RBM은 확률분포에 기반하여 visible 변수들과 hidden 변수들간에 어떤 상관관계가 있고 어떻게 상호작용하는지를 파악하는 개념이다. 즉, 인풋 (visible) 변수와 히든 변수의 joint probability distribution을 학습하고자 하는 것이다.

오토인코더가 직관적일 뿐 아니라 구현하기도 더 쉽고 파라메터가 더 적어서 튜닝하기가 쉽다. 대신 RBM은 generative 모델이기 때문에 오토인코더와는 달리 찾아낸 확률분포로부터 새로운 데이터를 생성할 수 있다. 파라메터가 많은만큼 더욱 유연하다.

CNN 경우에도 결국 유사한 개념이나, spatial한 개념이 들어간다. CNN도 오토인코더처럼 discriminant한 함수를 학습한다.

이 글의 주가 되는 위 링크에서는 CNN을 unsupervised pretraining인 오토인코더나 RBM과 같은 맥락에서 취급하지만, 실제로 CNN은 대부분 directly supervised training이다. 이는, Stacked RBM과 Stacked autoencoder가 각각 2006년, 2007년에 소개되었는데, vanishing gradient 문제를 해결한 ReLU가 2009년에 등장하면서 그리고 데이터의 양이 증가하면서 점차 unsupervised pretraining의 중요성이 감소하였고, CNN은 1989년부터 있던 개념이지만 deep structure는 2012년에 등장하였는데 이 시점에서는 이미 충분한 데이터가 확보되어 거의 대부분의 CNN이 pretraining 없이 바로 supervised learning을 통해 학습하게 되었다. 물론, 다양한 CNN이 존재하고 개중에는 supervised pretraining을 하는 CNN도 있다. Pre-training in deep convolutional neural network? 참고.

Dimensionality reduction

차원축소에서 가장 유명한 개념은 PCA다. PCA는 “component” 라고 불리는 “internal axis” 들을 찾아내고, 그 중 중요한 몇가지만을 추출함으로써 차원축소를 수행한다. 오토인코터와 RBM도 동일한 개념이다. 잘 학습하고 나면, 데이터에서 노이즈를 제거하고, 히든 유닛은 오브젝트의 어떤 중요한 숨겨진 피처를 학습하게 된다 - 영화의 장르라던가, 이미지에서 눈썹의 모양이라던가.

Deep architecture

그렇다면 PCA와의 차이점은 무엇인가? PCA는 오직 데이터의 linear transformation 만이 가능하다. 아무리 PCA를 여러번 적용해도 마찬가지다. 반면, 오토인코더와 RBM은 non-linear한 특성을 학습할 수 있고 따라서 더욱 복잡한 관계를 학습할 수 있다. 더욱이 이것들은 stacked 될 수 있고, 쌓일수록 더욱 강력해진다.

face recog

위와 같이 레이어가 쌓일수록 더욱 추상화된 피처를 학습한다. 위로 갈수록 더 큰 공간상의 피처를 나타내는데, 이는 위에서 사용한 딥러닝 모델이 공간적인 특성을 사용하는 CNN이기 때문이다.

Classification

위에서 언급한 모든 딥러닝 모델들은 전부 classification으로 직접적으로 쓰이진 않는다. 대신, low-level representation을 high-level로 변환하는 방법을 학습하기 위한 pretraining으로 사용한다. 그렇게 프리트레이닝을 통해 추상화된 하이레벨 피처를 학습한 이후 그 피처를 기반으로 SVM이나 logistic regression (softmax) 등의 classifier를 학습한다. 위의 이미지 예시의 경우에도 제일 아래에 최종적으로 학습한 피처로 classification을 수행하는 classification layer (혹은 component) 가 하나 추가되어야 한다. pretraining 이후 최종적으로 classifier를 학습하는 과정을 fine-tuning이라 한다. 이 때 최종적으로 추가된 classifier layer만이 학습되는 것이 아니라 전체 네트워크가 조정된다 (그런 것 같다).

Furthermore

'DataScience > Deep Learning' 카테고리의 다른 글

TensorFlow - (2) Install  (0) 2015.12.03
TensorFlow - (1) Intro  (1) 2015.12.03
Autoencoder vs RBM (+ vs CNN)  (1) 2015.11.29
Deep Learning을 위해 어떤 GPU를 써야 할까?  (0) 2015.11.19
Recent Advances in Deep Learning  (0) 2015.11.02
DL4J vs. Torch vs. Theano vs. Caffe  (1) 2015.08.11