Monday, June 20, 2016

tensorflow로 logistic regression 구현하기


아래 내용은 Andrew Ng 의 [coursera](https://www.coursera.org/learn/machine-learning/) 강의 일부와 Fast campus 의 [deep learning](http://www.fastcampus.co.kr/data_camp_deeplearning/) 강의 예제와 내용을 이용한 것이다. Andrew Ng 강의에서의 sigmoid function은 코드에서는 softmax function으로 변경되었고 cost function 역시 코드에서는 cross entropy가 사용되었다.

nbviewer  에서도 아래 내용을 볼 수 있다.


Logistic regression

linear regression은 값에 대한 예상치를 구하는 것에 반해 logistic regression은 classification(분류)을 위한 확률 모델이다 (classification 대신 regression을 쓰는 이유는 단지 역사적인 이유일뿐).
아래 식과 같이 input 값 x vector의 weight를 곱한 다항식을 sigmoid function을 통해서 확률값으로 변경한 것이 logistic regression 에 의한 결과이다.
$$ \begin{align} h_\theta &= g(\theta^Tx) &&\text{hypothesis function} \\ g(x) &= {1 \over 1+e^{-x}} &&\text{sigmoid function (=logistic functon)} \\ \end{align} $$
logistic function의 parameter인 theta값은 아래 overall cost function을 이용하여 구한다.
$$ \begin{align} J(\theta) &= {1 \over m} \sum_{i=1}^m Cost(\ h_\theta(x^{(i)}),\ y^{(i)}) &&\text{overall cost function} \\ Cost(h_\theta(x), y) &= \begin{cases} -log(h_\theta(x)), & \mbox{if }y=1 \\ -log(1-h_\theta(x)), & \mbox{if }y=0 \end{cases} &&\text{cost function}\\ &= -ylog(h_\theta(x)) -(1-y)log(1-h_\theta(x)) \\ \end{align} $$
training 데이터의 overall cost function의 최소값이 되도록 하는 theta값이 logistic regression의 parameter가 된다.
$$ min_\theta J(\theta) $$
overall cost function의 최소값이 되는 theta는 아래 gradient descent 를 이용하여 계산한다.
$$ \begin{align} \theta_j &:= \theta_j - \alpha{\partial\over\partial \theta_j}J(\theta) &&\text{gradient descent} \\ &:= \theta_j -\alpha\sum_{i=1}^m(h_\theta(x^{(i)})-y^{(i)})x_j^{(i)} \end{align} $$
위의 gradient descent 식에 training 데이터를 넣어 theta값을 갱신한다. 갱신된 theta를 이용한 overall cost function 값이 더이상 작아지지 않을 때까지 위를 반복한다 (혹은 derivative 가 0에 가까워지면).

Tensorflow

tensorflow는 구글에서 개발한 deep learning 프로그래밍을 위한 python package이다.
tensorflow를 이용하는 것은 deep learning을 위한 graph(=model)를 구축한는 것이라고 생각하면 될 것 같다. 그래프의 입력값과 같은 데이터의 통로는 tensorflow의 placeholder 라는 것으로 정의하고 데이터를 사용하여 그래프가 트레이닝 되는 동안 변해가는 값, 곧 변수는 tensorflow의 Variable을 사용하여 정의한다. 그래프를 구축하고 실제로 그 그래프를 실행하는 것은 tensorflow 의 session의 run을 사용한다.
In [19]:
%matplotlib inline    # maplotlib의 그림을 notebook 안에 그리기 위한 명령어 (magic이라 함)

import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
In [32]:
mnist = input_data.read_data_sets('data/', one_hot=True)
Extracting data/train-images-idx3-ubyte.gz
Extracting data/train-labels-idx1-ubyte.gz
Extracting data/t10k-images-idx3-ubyte.gz
Extracting data/t10k-labels-idx1-ubyte.gz
In [34]:
print mnist.train.num_examples
55000
notebook 이 실행된 폴더의 서브폴더 data를 생성하고 그 안에 mnist 데이터를 다운받게 된다. mnist는 Mixed National Institute of Standards and Technology의 약어로 이미지 프로세싱 시스템을 트레이닝에 사용되는 손글씨로 쓴 숫자 이미지의 모음이다. one_hot 인자의 true의 의미는 숫자의 label 을 0과 1로 표현하겠다는 의미이다.
In [33]:
trainimg = mnist.train.images 
trainlabel = mnist.train.labels 
testimg = mnist.test.images
testlabel = mnist.test.labels

print type(trainimg), type(trainlabel)
print trainimg.shape, trainlabel.shape
print type(trainimg[0]), trainimg[0].shape
print type(trainlabel[0]), trainlabel[0].shape
print trainlabel[10]
plt.imshow(trainimg[10].reshape((28,28)))
<type 'numpy.ndarray'> <type 'numpy.ndarray'>
(55000, 784) (55000, 10)
<type 'numpy.ndarray'> (784,)
<type 'numpy.ndarray'> (10,)
[ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
Out[33]:
<matplotlib.image.AxesImage at 0x10c624a90>
trainimg 는 55000 행과 784 열로 이루어진 2-dimensional array 이다. 행은 training할 데이터의 수를 의미하며 열은 하나의 데이터를 이루는 숫자의 모음이다. 곧 트레이닝 데이터는 55000개로 이루어 져 있으며 하나의 데이터는 784 (=28*28)의 숫자로 구성되어 있다. trainlabel 역시 2차원 array이며 각 행은 각 데이터의 label을 의미하며 각 데어터의 label은 10개의 0혹은 1 값으로 구성되어 있다.
위의 예처럼 그림이 0을 의미하면 label의 값 중에 0 을 의미하는 첫번째 인덱스의 값만 1이고 나머지는 0이 된다.
In [40]:
learning_rate = 0.01
training_epochs = 50
batch_size = 100
display_step = 10

x = tf.placeholder('float', [None, 784])  # trainimg 혹은 testimg가 들어갈 변수, 데이터가 들어갈 입력변수
y = tf.placeholder('float', [None, 10])  # None 은 입력 데이터의 행 수를 임의로 지정한 것
W = tf.Variable(tf.zeros([784, 10]))  # 그래프가 트레이닝 됨에 따라 변하는 변수
b = tf.Variable(tf.zeros([10]))  # bias 를 나타나내는 변수

actv = tf.nn.softmax(tf.matmul(x, W) + b)
cost = tf.reduce_mean(-tf.reduce_sum(y * tf.log(actv), reduction_indices=1)) # cross entropy
#cost = tf.reduce_mean(tf.pow(actv - y, 2))  # squared loss

optm = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)

pred = tf.equal(tf.argmax(actv, 1), tf.argmax(y, 1))
accr = tf.reduce_mean(tf.cast(pred, 'float'))

init = tf.initialize_all_variables()
epoch 는 모든 training data가 다 사용되는 한번의 turn(=loop)을 뜻하며 batch size는 한번의 turn(=loop)이 되는 동안 사용되어 지는 데이터의 크기(=갯수)를 뜻한다. 예를 들어 55000개의 training data가 있을 경우 batch size 를 100으로 정했으면 550 번의 iteration이 돌아야 한번의 epoch가 완성되는 것이다.
위 코드에서는 sigmoid function 대신 softmax function을 사용하였다.
$$ softmax_i(a) = { e^{a_i} \over \sum e^{a_i}} \ \ \ \ \ \ \ \ \text{softmax} $$
또한 cost function으로 cross entropy 를 사용하였다.
$$ H(p, q) = -\sum_x p(x)\ log\ q(x) \ \ \ \ \ \ \ \ \text{cross entropy} $$
In [42]:
with tf.Session() as sess: # 위의 코드는 그래프를 생성한 것. 실제로 구동은 Session에서 이뤄진다
    sess.run(init)
    
    for epoch in range(training_epochs):
        avg_cost = 0.
        num_batch = int(mnist.train.num_examples / batch_size)
        for i in range(num_batch):
            if 0: # tensorflow 에서 제공하는 mnist데이터 객체의 batch 를 뽑아내는 함수를 이용하는 방법  
                batch_xs, batch_ys = mnist.train.next_batch(batch_size)
            else: # OR manually implement random extraction
                randidx = np.random.randint(trainimg.shape[0], size = batch_size)
                batch_xs = trainimg[randidx, :]
                batch_ys = trainlabel[randidx, :]
                
            sess.run(optm, feed_dict = {x: batch_xs, y: batch_ys}) # optimizer 를 실행. placeholder로 잡아논 변수를 인자로 넣어줌.
            avg_cost += sess.run(cost, feed_dict = {x: batch_xs, y: batch_ys}) / num_batch
        
        if epoch % display_step == 0:
            train_acc = accr.eval({x: batch_xs, y: batch_ys})
            print "Epoch: %03d/%03d cost: %.9f train_acc: %.3f"%(epoch, training_epochs, avg_cost, train_acc)
            
    print "Optimizer finished."
    print "Accuracy:", accr.eval({x: mnist.test.images, y: mnist.test.labels})

print "Done."
Epoch: 000/050 cost: 1.172424991 train_acc: 0.890
Epoch: 010/050 cost: 0.382650341 train_acc: 0.920
Epoch: 020/050 cost: 0.340528482 train_acc: 0.940
Epoch: 030/050 cost: 0.325556715 train_acc: 0.940
Epoch: 040/050 cost: 0.309887736 train_acc: 0.880
Optimizer finished.
Accuracy: 0.9183
Done.

Tuesday, June 14, 2016

chapter 2: Measures of Disease Occurrence

아래 내용은 Staistics for Epidemiology 의 chapter 2 의 내용의 요약이다.


1. Prevalence and incidence
point prevalence 란 질병에 걸릴 가능성이 있는 사람들 중에 특정 시점에서 질병에 걸려 있는 사람의 비율을 뜻한다. 

interval prevalance 란 질병에 걸릴 가능성이 있는 사람들 중에 특정 시간 구간내에 질병에 걸려 있는 사람의 비율을 뜻한다.

incidence proportion (=cumulative incidence proportion) 질병에 걸릴 가능성이 있는 사람들 중에 특정 시간의 구간 안에서 질병 걸린 사람의 비율을 뜻한다. 그렇기 때문에 prevalance와는 달리 조사 시점 시작 전에 질병에 걸려 있던 사람은 제외한다. 

집단의 크기가 100명이라는 가정아래 figure 1 에서 특정 시점 t에서의 point prevalence는 4/100 혹은 4/99 이다. 4/99 인 이유는 case4를 질병에 걸릴 가능성이 있느냐의 여부에 따른다(특정 질병의 경우 질병이 걸리고 회복이 되면 질병에 면역력이 생겨서 다시 걸릴 가능성이 없어진다. 이럴 경우는 계산 대상에서 빠지게 된다). [t0, t1] 시간 구간의 incidence proportion은 4/98 이다 (case4 와 case1은 조사 시작이 되기 전에 이미 질병에 걸려 있었기 때문에 계산에서 제외된다).

incidence와 prevalence의 차이는 incidence는 특정 조사 시간 내에서 병이 시작된 사람들만 포함을 한다는 것이다.

prevalence 는 질병의 발생뿐만 아니라 질병의 지속 시간(duration) 에 의존적이기 때문에 사용에 문제가 있을 수 있다. 

초기 사망이 많은 CHD (coronary heart disease)의 예인 아래 table1 의 통계를 보면 prevalence 사용의 문제점을 확인할 수 있다. 이는 1966년에 30~59 세의 남성의 콜레스테롤과 CHD의 상관성에 대한 연구이다. 이 연구에서는 콜레스테롤 수치를 4단계로 구분하였고 highest와 lowest가 아래 표의 high와 low에 해당한다. 표에서 보듯이 10년기간의 incidence 에서는 콜레스테롤이 높은 집단에서 확연하게 CHD 의 비율이 높은 것이 확인된다 (incidence에서 콜레스테롤 수치는 연구 기간 시작 시점에서의 콜레스테롤 수치이다). 그러나 10년 마지막 시점에서 조사한 prevalence 를 보면 콜레스테롤 높낮이와 CHD의 발병률에 차이가 없음을 확인 할 수 있다(prevalence에서의 콜레스테롤 수치는 연구기간 10년의 마지막 시점에서 측정한 수치이다). 


이와 같이 prevalence 에서 별 차이가 나지 않는 이유는 콜레스테롤에 의한 CHD는 조기 사망이 높아서 일 수 있다. 콜레스테롤 수치가 높아서 CHD가 걸린 사람들은 질병 발생 초반에 사망을 했을 수 있기 때문에 counting이 되지 않았기 때문이다. 또 다른 이유는 CHD에 걸려 살아 남은 사람들이 콜레스테롤 수치를 조절하여 낮췄을 수 있기 때문이다. 

이 예는 prevalence 데이터를 질병과 risk factor의 인과관계의 해석에 사용하였을 경우 발생할 수 있는 위험을 보여준다.

이 글에서 소개되는 통계적인 방법은 prevalence와 incidence 둘 다에서 사용될 수 있으나 이 글은 incidence를 가정한다. 이는 질병과 위험 인자의 인과성을 연구하기 위해서는 당연히 incidence 수치를 사용해야 하기 때문이다.


2. Disease rates
시간 구간이 너무 길고 특정 그룹의 발병이 초기단계에 일어나면 incidence proportion의 사용이 유용하지 않을 수 있다. 

이럴 경우 proportion 대신 rate를 사용하여 시간 구간 동안의 발병의 시간을 조절한다.

incidence rate (=average incidence rate) 는 발병 개체 수 나누기 발병까지의 총 시간으로 나눈 값이다.

아래 figure2 는 5명의 집단에 대한 예이다. o 표시는 사망을 의미하고 x는 disease의 발생을 의미한다.
t =0 시점에서의 point prevalence 는 0 / 5 = 0 이고 t = 5 시점에서의 point prevalence 는 1/2 = 0.5 이다.
t=0 ~ t=5 의 incidence proportion은 3/5 = 0.6 이며
incidence rate 는 3/(5+1+4+3+1) = 3/14 = 0.21 per year 이다.

만약 집단 크기와 연구 기간이 명확하지 않거나 혹은 한사람이 여러번 질병에 걸릴 수 있다면 incidence rate의 단위를 0.21 cases per person-year (per person per year)로 한다.



3. The hazard function
시간 구간 동안 집단이 질환의 발병이 급격하게 변화한다면 이와 같은 현상을 잡아내기 위해서는 시간 구간을 작은 부분 구간으로 나눌 필요가 있다. 집단의 크기가 작으면 발병 횟수 자체가 작아서 이런 접근을 시도 할 수 없지만 집단의 크기가 충분히 크다면 시간 구간을 작은 부분구간으로 나누어 부분 구간별 incidence rate를 계산할 수 있다. 이 부분 구간이 이론적으로 굉장히 작다면 이 부분 구간의 incidence rate를 hazard function, h(f) 라고 한다. figure 3 은 남성의 나이 별 사망률(motality)에 대한 hazard function 의 그림이다. 나이는 x 축으로 motality rate, 곧 hazard function 값은 y 축으로 표현했다. 이 그림에 대한 해석은 N = 1000 이고 시간 t 에서의 hazard function 의 값이 0.005/year 라면 시점 t를 중심으로 한 1년 기간 동안에 질병 발생(여기서는 사망) 횟수가 5 가 되는 것이다. 


시간 구간 [0, T] 에 대해 0<= t <= T 인 시간 t의 hazard function, h(t) 과 시간 구간 [0, t] 의 incidence proportion 간에 아래 식과 같은 직접적인 관계가 있다 (단 질병이 발생한 개체의 경우 더 이상 발병 위험에 노출되지 않는다고 가정한고, 또한 위험에 노출되지 않는 경우는 오직 이 경우 밖에 없다고 가정한다). 
I(t) 는 incidence proportion을 나타내기 때문에 d I (t) / dt 는 incidence proportion 의 시간 t 에서의 기울기를 의미하여 1 - I(t) 는 위험에 노출되어 있는 개체 비율, 곧 질병에 걸릴 수 있는 개체 비율을 의미한다. 

만약에 관심이 있는 것이 질병이 아닌 질병에 의한 사망에 있다면 보통 incidence proportion 대신 1 - incidence proportion 인 survival function 을 사용한다( S(t) = 1 - t(t) ).

아래 figure 4는 figure 3 에 대한 survival function을 나타낸 그림이다. 


hazard function 의 이점은 손쉽게 시간 변화에 따른 발병 정보인 dynamic information을 얻을 수 있다는 것이다.

예를 들면 figure 3 과 figure 4 모두 같은 데이터를 표현한 그림이지만 생의 첫해의 사망 위험과 60대의 사망 위험이 크게 다르지 않다는 정보를 figure 3의 hazard function이 더 쉽게 표현하고 있다 (figure4 에서는 t = 0 과 t = 60 의 y 값 수치가 상당히 차이가 있다). 또한 65 이후 사망률이 크게 올라가기 시작한다는 것을 figure3을 통해 더 쉽게 파악할 수 있다.




이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

Friday, June 10, 2016

ECMAScrtip2015 ( ES2015, ES6 )

아래의 내용은 codeschool의 ES2015를 요약한 것이다. 실제로 강의에서는 더 많은 예와 사용사례를 들지만 필요한 부분만 정리하였다.

let 변수
1. let 변수는 hoisting이 되지 않고 block 영역 내에서만 유효하게 된다.
function showUsers (users) {
  if (users.length >= 2) {
    var messagePlural = 'the plural';
  } else {
    var messageSingular = 'the singular';
  }
  console.log(messageSingular);}

var users = ['a', 'b', 'c'];
showUsers(users);

위 코드를 돌려보면 프로그램이 에러가 발생하지 않고 undefined가 콘솔에 찍히게 된다. 이는 javascript의 hoisting 이라는 특성때문인데 var 가 있는 변수를 프로그램이 시작하기 전에 가장 가까이에 있는 function의 최상단에 변수를 초기화 하기 때문이다. 곧 아래와 같이 작동하게 된다.

function showUsers (users) {
  var messagePlural, messageSingular;
  if (users.length >= 2) {
    messagePlural = 'the plural';
  } else {
    messageSingular = 'the singular';
  }
  console.log(messageSingular);}

var users = ['a', 'b', 'c'];
showUsers(users);

ES2015 에서는 let 이라는 키워드를 이용해서 선언된 변수는 function이 기준이 아닌 block ('{', '}' 으로 둘러쌓여 있는 코드 영역, 곧 if, else, for, while 등) 영역내에서 유의한 변수가 된다. 그래서 block 영역 이외에서 let 변수가 사용되어 지면 ReferenceError 가 발생하게 된다.

보통 for 문을 사용할 때 index에 var변수를 많이 사용하였는데 이는 아래와 같은 문제가 발생하게 된다. 
function changeProfile(profiles) {
  for (var i in profiles) {
    _update('users/' + profiles[i], function () {
      console.log(profiles[i]);
    });
  }}

changeProfile(['a', 'b', 'c'])
// c
// c
// c 가 출력됨

위 코드에서 var i 는 hoisting이 되어 for 문이 실행되기 전에 함수 상단에 선언되어 지고 for 문에 의해 _update의 인자로 주어진 각각의 콜백함수가 실행되기 전에 동일한 i를 갖게 끔 등록되어 진다. 이 후 실행이 되어지기 때문에 i가 갖는 최종값 2를 공유하게 된다.

이럴 경우 for 문에 var 대신 let을 사용하면 위와 같은 문제가 해결된다.

2. let 변수는 재선언을 해서는 안된다.
let 변수에 새로운 값을 할당하는 reassign은 가능하지만 재선언을 하면 TypeError 가 발생

let variable = 'good';
variable = 'best'; // reassigning 은 가능

let variable = 'good';
let variable = 'bad'; // redeclaring 은 불가능



Const 변수

const 변수는 읽기만을 위한 변수이다.
선언시 변수에 값을 할당(정의)을 해야 한다.
reassign이 안된다.
또한 let 변수와 마찬가지로 function 단위가 아닌 block 단위에서 유의한 변수이다.


functions
1. default parameter

javascript 는 parameter를 필요로 하는 함수에 paramter가 없이 호출을 해도 에러를 발생시키지 않는다. 그렇기 때문에 아래 countName1과 같은 방법으로 default paramter를 설정하였으나 ES2015 부터는 countName2 처럼 default paramter를 설정할 수 있다.

function countName1 (userNames) {
  let names = typeof userNames !== 'undefined' ? userNames : [];
  console.log(names.length);}

countName1();


function countName2 (userNames = []) {
  console.log(names.length);}

countName2();


2. named parameter

아래 코드에서 show1 함수의 두번째 인자 options은 object 이다. show1 코드에 options 에는 어떠한 property가 들어가야 하는지 직관적이지 않다. 이는 ES2015에서 show2 처럼 named parameter 를 사용하는 걸로 바꿀 수 있다. 

function show1 (name, options) {
  let color = options.color;
  let size = options.size;
  console.log(color);
  console.log(size);}

function show2 (name, {color, size}) {
  console.log(color); // color is a local variable.
  console.log(size); //}

show2('Tom', {color: 'blue'}) // 'size' will be undefined.
show2('Tom') // this will be error.


function show3 (name, {color, size} = {}) {
  console.log(color); // color is a local variable.
  console.log(size);
}

show2 를 사용하는데 있어 options 인자를 아예 주지 않고 사용하면 TypeError가 발생하게 되는데 이를 방지하기 위해 show3 처럼 default parameter를 지정할 수 있다.


Rest params, Spread op, Arrow Func
1. rest parameters
variadic function 이란 parameter의 수가 유동적인 함수를 의미한다. 이는 보통 아래 코드의 showUser1 처럼 구현한다. 그러나 이는 직관적이지 않고 또한 showUser1에 추가적으로 parameter가 필요할 경우 문제가 생길수 있다. 이를 대체하기 위한 방법으로 ES2015에서는 rest paramter를 사용한다.

function showUsers1 () {
  for (let i in arguments) {
    console.log(arguments[i]);
  }}

showUsers1('user1', 'user2', 'user3');

function showUser2 (...users) {
  for (let i in users) { // users == ['user1', 'user2', 'user3']
    console.log(users[i]);
  }}

showUser2('user1', 'user2', 'user3');

showUser2에서 처럼 인자 앞에 점 3개를 붙이면 이는 rest parameter를 의미하게 되고 넘겨받은 인자들을 array로 받게 된다. rest paramter 는 항상 마지막 인자로 사용되어야 한다.

2. spead operator 
위의 rest parameter 에서는 함수 정의에 사용된 점 3개는 분리된 인자를 하나의 array로 받겠다는 의미가 되고 반대로 함수의 호출에서 넘겨주는 인자에 점 3개를 사용하면 array를 분리된 인자로 변형시키겠다는 의미가 된다. 

function showUser (...users) { // users is an array.
  for (let i in users) {
    console.log(users[i]);
  }}

var users = ['user1', 'user2', 'user3']
showUser(...users);

3. arrow function 
javascript에서 함수는 호출이 되는 scope에 bind 하게 되어 지는데 arrow function 를 이용하면 함수가 정의된 scope에 bind하게 된다.
arrow function 의 형태는 아래와 같다. ES5에서의 보통의 함수 정의에서 function 키워드를 제거하고 "=>"를 붙인 형태로 나타낸다. 아래 코드는 동일한 기능을 한다. parameter가 한개일 때는 괄호를 생략할 수 있다.

// function 1
function (param) {
  statements
}

// arrow function 1
(param) => {
  statements
}

// arrow function 2
param => {
  statements
}

아래의 코드는 동일한 기능을 하는 코드이다. arrow function의 특징 중 한가지는 함수의 내용이 한줄의 표현식 밖에 없을 경우에는 대괄호 "{}" 를 생략 가능하고 이 경우에는 자동적으로 표현식 앞에 암묵적으로 return이 삽입된다. 그렇기 때문에 아래 코드의 arrow function 2와 같이 return을 생략해도 되는 것이다.

// function 1
function (param) {
  return expression
}

// arrow function 1
(param) => {
  return expression
}

// arrow function 2
param => expression

arrow function은 보통의 함수와는 달리 this와 arguments 객체를 갖지 않는다. 그렇기 때문에 lexial this 를 부모 함수와 공유하게 된다.


Objects and strings

Object initializer shorthand
object 의 property 명과 값을 가지고 있는 변수의 식별자가 동일한 경우 이를 축약해서 표현 가능하다. 아래 코드에서 name1 함수와 name2함수의 동일한 형태의 object 를 return하게 된다.

function name1 (first, last) {
  let fullname = first + ' ' + last;
  return {first: first, last: last, fullname: fullname};
}

function name2 (first, last) {
  let fullname = first + ' ' + last;
  return {first, last, fullname};
}

let first = "Good";
let last ="Boy";
let fullname = name1(first, last);

someoneName1 = {first: first, last: last, fullname: fullname};
someoneName2 = {first, last, fullname};

마찬가지로 someoneName1과 someoneName2는 동일한 형태의 object 를 갖는다.

function을 property로 갖을 경우 아래 코드의 makeName2에서 function 키워드를 생략하여 표현할 수 있다.

function makeName1 (first, last) {
  let fullname = first + ' ' + last;
  return {
    first,
    last,
    fullname,
    showName: function () {
      console.log(fullname)
    };
  };
}

function makeName2 (first, last) {
  let fullname = first + ' ' + last;
  return {
    first,
    last,
    fullname,
    showName () {
      console.log(fullname)
    };
  };
}

object destructuring
아래 코드와 같이 object에서 property 값을 받을 경우 {} 괄호를 써서 object 의 property를 풀어서 받을 수 있다. 아래 코드의 맨 아래 표현식에서 보듯이 전체 property가 아닌 특정 property만을 받을 수도 있다. 

function makeName (first, last) {
  let fullname = first + ' ' + last;
  return {first, last, fullname};
}

let someone = makeName('good', 'boy');
let first = someone.first;
let last = someone.last;
let fullname = someone.fullname;

let {first, last, fullname} = makeName('good', 'boy');

let {first, fullname} = makeName('good', 'boy');

template strings
template string은 back-ticks ( `` )에 의해 표현된다. 아래 코드의 fullname은 template string 으로 표현한 문자열이다. 또한 template string 를 이용하면  multiline line text를 쉽게 표현할 수 있다.

let first = 'good';
let last = 'boy';
let fullname = `${first} ${last}`;

let sentence = `
Hi, ${first} ${last}.
Nice weather !
`;

Object.assign
Object.assign 함수를 이용하면 object를 merge할 수 있다. parameter 중 뒷부분의 object값이 앞 object 의 property를 덮어 쓰게 된다. 그리고 첫번째 parameter인 object에 값을 변경하여 return하게 된다. 

let option1 = {first: 'good', last: 'girl'};
let option2 = {last: 'boy'};

let finalOption = Object.assign({}, option1, option2);
// finalOption = {first:'good', last:'boy'}와 동일

이는 함수의 default값을 설정할 경우 유용하게 사용되어 질 수 있다.

Array
array destructuring
아래 코드와 같이 array의 값을 개별 변수에 할당하는 방법이 있다.

let names = ['name1', 'name2', 'name3'];

let name1 = names[0];
let name2 = names[1];
let name3 = names[2];

let [name1, name2, name3] = names;

let [name1, , name3] = names;

위 코드의 마지만 표현식에서와 같이 불필요한 값은 skip할 수 있다.

또한 아래의 코드와 같이 위에서 알아본 rest params를 이용하여 나머지 값들을 하나의 변수에 array로 받을 수 있다.

let names = ['name1', 'name2', 'name3'];

let [name1, ...rest] = names
// rest = ['name2', 'name3'] 와 동일


for …of (array loop)
for of 문을 이용하여 array 의 값에 직접 접근 가능하다.

let names = ['name1', 'name2', 'name3'];

// for1 
for (let index in names){
  console.log(names[index]);
}

//for2
for (let name of names){
  console.log(name);
}

for …of는 plain javascript object(일반 객체) 에서는 사용할 수 없다. for …of는 Symbol.iterator라는 속성에 특별한 함수가 있는 객체에서만 사용가능하다. 

find element
아래 코드와 같이 array의 find함수에 test 함수를 인자로 호출하면 test 함수에서 true 값을 return하는 첫번째 element를 찾을 수 있다.

let users = [
  {name: 'name1', admin: false},
  {name: 'name2', admin: true},
  {name: 'name3', admin: true},
];

let admin = users.find( (user) => user.admin );

// admin = {name: 'name2', admin: true}; 와 동일

Maps
map 은 key/value 쌍의 집합인 자료 구조이다. 물론 object를 map 처럼 사용할 수 있으나 key 값을 string으로 처리하기 때문에 아래와 같은 문제가 생긴다.

let user1 = {name: 'Sam'};
let user2 = {name: 'Bill'};

let userProfile = {};
userProfile[user1] = 'student';  // userProfile['object Object'] = 'student'
userProfile[user2] = 'teacher';  // userProfile['object Object'] = 'teacher'

console.log(userProfile[user1]);  // teacher
console.log(userProfile[user2]);  // teacher

위 문제는 map 자료구조를 통해 해결할 수 있다.

let user1 = {name: 'Sam'};let user2 = {name: 'Bill'};

let userProfile = new Map();
userProfile.set(user1, 'student');
userProfile.set(user2, 'teacher');

console.log(userProfile.get(user1));  // student
console.log(userProfile.get(user2));  // teacher

특히나 runtime시 key 값이 결정되는 자료구조의 경우 map을 사용하는 것이 좋다. 또한 key들의 자료형이 동일하고 마찬가지로 value들의 자료형이 동일할 경우 map을 사용하는 것이 일관성있어서 좋다.

map을 사용할 경우 아래와 같이 for ..of 구문을 사용할 수 있다.

let mapData = new Map();

mapData.set('key1', 'value1');
mapData.set('key2', 'value2');
mapData.set('key3', 'value3');

for (let [key, value] of mapData) {
  console.log(key. value);}

WeakMap
key로 object 만 허용된 map 으로 primitive data type (stirng, number, boolean) 은 key로 사용할 수 없다. weakmap은 for ..of 구문을 사용할 수 없다.
weakmap의 장점은 메모리 효율에 있다. weakmap은 key로 사용하는 object의 garbage collection을 막지 않는다. 곧 weakmap에서 key로 사용되는 object 가 다른 곳에서 사용이 다 되어서 garbage collection이 되어 사라진다 하더라도 weakmap은 여전히 사용가능하다.

Sets
array와는 달리 unqiue한 element만 저장한다. 중복된 element는 대표인 하나의 element만 저장하는 자료 구조이다.

for ..of 구문을 사용할 수 있고 array destructuring이 가능하다.

let setData = new Set();
setData.add('Sam');
setData.add('Mike');
setData.add('Bill');
setData.add('Bill');

console.log(setData.size); // 3

for (let name of setData) {
  console.log(name);}

WeakSet
object만 저장할수 있다. WeakMap과 마찬가지로 gabage collection을 막지 않으며 for ..of 문을 사용할 수 없다.

Class
class syntax는 constructor function을 이용한 prototype-based inheritance의 syntactical sugar로 새로운 형태의 객체가 아닌 기존의 객체를 다른 방식으로 생성하는 것 뿐이다.

아래 코드에서 constoructor function 을 통한 객체 생성과 class 문법을 사용한 객체는 동일하다.

// Constructor function
function Person1 (name, age) {
  this.name = name;
  this.age = age;}

Person.prototype.action = function (something) {
  console.log(something);}

person1 = new Person1('Tom', 12);


// Class syntax
class Person2 {
  constructor (name, age) {  // new 키워드로 객체 생성 시 항상 실행되는 함수
    this.name = name;
    this.age = age;
  }
  action (something) { // instance method
    console.log(something);
  }
}

person2 = new Person2('Tom', 12);

_ (underscore)로 시작하는 이름의 method 는 암묵적으로 객체 내부에서만 사용하는 method를 의미하기 때문에 객체 외부에서는 호출하지 않는것이 일반적이다. 

prototype-based inheritance 는 extends 라는 키워드로 아래와 같은 방식으로 상속받는다.

class Human {
  constructor (name, age) {
    this.name = name;
    this.age = age;
  }
  eat () {
    console.log('eat')
  }}

class Student extends Human {
  constructor (name, age, grade) {
    super(name, age);  // 상속을 받았으면 항상 super를 호출해야 함.
    this.grade = grade;
  }
  study () {
    console.log('study')
  }
  eat() {
    super.eat(); // 부모 클래스의 메소드 호출
    console.log('eat a lot');
  }
  gettingOld () {
    this.age += 1;
  }}

let student1 = new Student('Tom', 14, 2);
student1.eat()
student1.gettingOld();
student1.age  // 15

Module
이전에 ECMAScript 에서 modularization을 하는 방법은 global variable을 사용하는 것이였다(html 파일에 <script> 태그를 이용하여 링크시킴). ECMAScript2015에서도 html에 <script> 를 통해 링크를 시키는 것은 동일하지만 아래 코드의 print-message.js 와 같이 global namespace를 사용하지 않기 때문에 부작용이 일어나지 않는다.

// print-message.js
export default function (message) {  // default 로 export 했기 때문에 임의의 이름으로 import 가능
  console.log(message);
}

// root.js
import print from './print-message';  // .js 확장자 생략 가능
print('good');

export default 로 정의된 module은 그 밖의 함수들은 외부에서 접근 불가능 하다.

// print-message.js
export default function (message) {  // default 로 export 했기 때문에 임의의 이름으로 import 가능
  console.log(message);
}

function print2 (message) { // export default 때문에 이 함수는 외부에서 호출 할 수 없다.
  console.log(message);
}

이럴 경우 default 키워드를 사용하지 말고 외부에서 접근 가능하게 하고자 하는 함수 앞에 export 키워드를 붙인다. 모듈을 호출하는 파일에서는 import 시 함수이름을 모듈에서 정의한 함수 이름과 동일하게 해야 한다.

// print-message.js
export function print1 (message) {
  console.log(message);
;}

export function print2 (message) {
  console.log(message);
}

// root.js
import {print1, print2} from './print-message'; // 호출하는 함수의 이름이 함수 정의에 사용된 이름과 동일해야 함.
print1('good');
print2('best');

// anotherRoot.js
import * as print from './print-message';
print.print1('good');
print.print2('best');

아래와 같은 방법으로 module의 함수들을 export 할 수도 있다.

// print-message.jsfunction print1 (message) {
  console.log(message);}

function print2 (message) {
  console.log(message)}

export {print1, print2};

const 변수와 class 도 위의 function과 같은 방법으로 export 할 수 있다.

Promise
Javascript 에서 서버와의 통신등으로 인해 발생할 수 있는 "main thread 의 block" 상황을 막기 위해서 callback 함수를 인자로 함수를 호출하는 식의 continuation passing style로 구현을 하는 경우가 많다. 이럴 경우 callback 함수에서 또 다른 callback을 던지는 경우 굉장히 복잡한 nested code가 된다. 또한 던져진 callback 함수에서 매번 error을 체크하는 코드를 넣어야 하는 불편함이 있다.

아래와 같이 return을 promise로 받으면 then 메소드를 이용하여 코드를 단순화 할 수 있다

// Define promise function getDataFromServer (name) {
  return new Promise(resolve, reject) {
    let url = `user/${name}`;
    let request = new XMLHttpRequest();
    request.open('GET', url, true);

    request.onload = function () {
      if (request.status >= 200 && request.status <400) {
        resolve(JSON.parse(request.response)); 
      } else { 
        reject(new Error(request.status)); // error 를 던지면 promise의 catch 메소드로 직행 
      }
    };
    request.onerror = function () {
      reject(new Error('error'));
    };
  }
}

// use promise 
getDataFromServer('Sam') 
  .then(someFilter) // promise의 then은 또 다른 promise를 return. 
  .then(doSomething) 
  .catch(function (err) { console.log(err); }); // promise가 error를 던지면 catch 메소드로 직행

Iterators
array, map, set 같은 경우 for ..of 구문을 사용하면 iterator object를 반환하게 된다. 아래 코드에서 보듯이 for ..of 문은 실제로주석에 표시되어 있는 것처럼 각 자료형의 Symbol.iterator 속성의 호출로 iterator 객체가 생겨서 일어나는 것이다.

let names = ['Jack', 'Bill', 'Tom'] ;

for (let name of names) { // let iterator = names[Symbol.iterator]()
  console.log(name);        // let firstRun = iterator.next()  //   firstRun == {value: 'Jack', done: false}
}                                     // let name = firstRun.value
                                      // let secondRun = iterator.next() ...

iterator의 next 메소드를 호출하면 done과 value 속성을 갖는 객체가 return 되고 done이 false 이면 for 문이 계속 돌게 되는 것이다.

plain javascript object의 경우 Symbol.iterator 속성이 없기 때문에 iterator object를 생성할 수가 없다. 그러나 Symbol.iterator를 구현해 주면 iterator 객체를 return 할수 있게 되고 for ..of 구문과 array destructuring에서 사용가능해 진다.

Generators
함수의 이름 앞에 * 기호를 사용하면 generator function이 된다. generator function은 return 대신 yield를 사용할 수 있다 (* 기호는 함수 이름앞에 붙여도 되고 띄어도 상관없다).

function * getNames () {
  yield "Jack";  // {done: false, value: 'Jack'};
  yield "Bill";  // {done: false, value: 'Bill'};
  yield "Tom";  // {done: false, value: 'Tom'};}

for (let name of getNames()) {
  console.log(name);}

let names = [...getNames()];

let [name1, name2] = getNames();

generator 함수는 iterator object와 같은 next 메소드를 갖는 객체를 return 하기 때문에 generator function에 의해 return 된 object는 for ..of, spead operator, destructuring 에서 사용가능하다.

이 genrator를 이용하면 plain javascript object 의 Symbol.iterator 속성의 구현을 쉽게 할 수 있다.




















이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.