문서 분석 시스템은 말 그대로 문서를 분석해서 유용한 정보를 얻어내는 시스템입니다. 문서의 내용을 분석해서 분류하거나, 문서의 주제를 추출하거나, 문법 분석을 해서 문장 구조를 파악하거나, 고유명사를 추출하는 등의 용도로 사용합니다. 문서 데이터는 다른 데이터보다 구하기 쉽고 종류도 굉장히 많습니다. 내용에 관계없이 대량의 텍스트가 필요하다면 웹페이지, 잘 정리된 백과사전 자료는 위키백과, 사용자 간의 사회활동 정보는 트위터, 상품에 대한 평가는 쇼핑몰 댓글에서 얻을 수 있습니다. 이렇게 많은 문서 데이터가 존재하는 이유는 인테넷에서의 소통이 대부분 텍스트를 통해 이루어지기 때문입니다. 따라서 문서의 내용을 분석하여 결과를 활요하면 경쟁에서 우위를 확보하거나 통찰력을 얻을 수 있기 때문에 최근 들어 쇼핑몰, 인터넷방송사, 정부기관, 포털사이트 등 다양한 분야에서 문서 분석 시스템을 사용합니다.
하지만 3.1.1절 '텍스트 데이터'에서 말했듯이 문서 분석에서 좋은 성능을 얻기란 생각보다 쉽지 않습니다. 문서의 의미라는 것은 중의적일 때도 있고, 단어가 배열된 순서에 따라 달라지며, 의미가 미묘하게 다른 단어들이 많아서 각각을 구분하기도 어렵기 때문입니다.
이런 어려움을 이겨내고 텍스트의 복잡한 의미를 파악하는 방법에 대해 알아보도록 하겠습니다.(실제 구현은 11장에서 다룹니다.) 이장에서 다루는 내용은 다음과 같습니다.
- 문서 분류 시스템 만들기
- 토픽 모델링
- 문법 분석
- 단어 임베딩
5.1 문서 분류 시스템 만들기
문서 분로(document classification, document categorization)는 말 그대로 문서를 분류하는 것이 목적입니다. 예를 들어 문서를 항목별로 분류하기(도서관에서 책 분류하기), 제품 리뷰로 상품에 대한 긍정/부정 의견 판단하기(sentiment analysis), 문서 내용을 보고 사람과 봇 중 누가 생성했는지 판단하기 등을 들 수 있습니다. 이 절에서는 일상에서쉽게 접할 수 있는 스팸 필터를 머신러닝을 이용해서 만들어 보겠습니다(실제 코드는 11장에서 다룹니다)
5.1.1 문서 분류에 많이 사용하는 피처
문서는 어떤 기준으로 분류할까요? '문서 내요을 분석해서 분류해야지'라고 생각했다면 반은 맞춘 겁니다. 문서에서는 내용, 글쓴이, 서식, 문서 구조, 문서 길이 등 여러가지 피처를 얻을 수 있는데, 그중 문서 내용이 가장 기본이 됩니다.
5.1.1.1 단어 빈도 피처
문서 내용을 완벽하게 이해하는 머신러닝 시스템은 아직 존재하지 않습니다. 그래서 간단한 모델을 이용하여 약간은 부정확하지만 적당한 수준의 시스템을 만든 후 많은 문서로 학습시켜 조금씩 더 나은 좋은 결과를 만들어 내는 방식이 주로 사용됩니다.
이러한 간단한 모델에 사용되는 피처의 예로는 단어 빈도(word frequency, term frequency)를 들 수 있습니다. 이 피처는 문서에서 단어가 얼마나 자주 나오는지 측정해서 문서 내용을 팡가하는 방법입니다. 예를 들어 문서에 축구와 관련된 단어가 자주 등장한다면 그 문서를 축구와 관련된 문서라고 여기는 것입니다. 단어 빈도는 단어가 문서에서 얼마나 나오는지 계산한 후 문서 전체 단어 숫자로 나누어 그 비율을 계산합니ㅏㄷ. 물론 단어가 단순히 몇 번 출현하는지 세는 단어 카운팅(word counting)방식도 많이 사용하지만, 이 방식은 한 문서가 길고(즉, 많은 단어로 이루어져 있고) 다른 문서가 짧은 경우(즉, 적은 단어로 이루어져 있을 경우)공정한 비교가 어렵기 때문에 횟수보다는 빈도를 더 많이 사용합니다. 때로는 단어의 존재 여부만 체크하는 단어 출연(word occurrence)도 유용합니다(단순히 문서를 긍정 또는 부정으로 평가할 때 의외로 좋은 성능을 보여 줍니다).
엄밀하게 정의하면 단어 빈도 피처는 다음과 같습니다.
i 번째 단어 빈도 피쳐 = i 번째 단어가 문서에 나타난 횟수/문서의 총 단어수
즉, 단어 빈도 피처는 가능한 모든 단어(보통 단어집vocabulary이라고 부릅니다)의 개수만큼의 피처를 가지며 각각의 값은 단어가 문서에 나타난 횟수와 문서의 총 단어 수로 결정됩니다.
5.1.1.2 TF-IDF
단어 빈도 피처의 한가지 문제점은 단어 하나하나의 중요도가 동일하기 때문에 빈도수가 높은 단어를 상대적으로 중요하다고 오인할 수 있습니다. TF-IDF(Term-Frequency Inverse-Document Frequency)단어를 이용하면 단어의 희귀도를 고려해서 피처를 생성할 수 있습니다.
단어의 중요도는 어떻게 결정할까요? 다음과 같은 문서가 있다고 합시다.
11살 생일을 몇칠 앞둔 어느 날 해리에게 초록색 잉크로 쓰여진 한 통의 편지가 배달된다. 그 편지의 내용은 다름 아닌 해리의 11살 생일을 맞이하여 전설적인 '호그와트 마법학교'에서 보낸입학 안내장이었다..(이하 생략)
이 문서에서 가장 많이 나오는 단어는 '해리'이므로 해리가 가장 주용한 단어인 것 처럼 보입니다. 하지만 해리포터 시리즈의 각 편을 다룬 기사를 구분하는 데 과연 해리라는 단어가 유용할까요?
예를 들어 해리포터 시리즈를 다룬 기사 3편에 대해 각각 단어 수를 세어봤더니 [표5-2]와 같은 결과를 얻었다고 합시다.
표5-2 기사에 따라 다르게 등장하는 단어 수에 대한 예
기사1 기사2 기사3
해리 132 98 103
호그와트 90 95 55
지팡이 0 120 55
성물 0 0 49
세 기사 모두에서 '해리'라는 단어가 가장 많이 등장하므로, 이 세 기사를 서로 구분하는데 해리는 별로 중요하지 않습니다. 반면 '성물'은 가사 3과 나머지 기사를 구분하는데 쓰일 수 있습니다. 이런 식으로 각 단어가 문서(여기서는 기사)를 구분하는데 얼마나 중요한지 고려하는 피처가 바로 TF-IDF입니다. 여기서 TF는 앞서 설명한 단어 빈도(Term-Frequency)를 뜻하고 IDF는 역문서 빈도(Inverse Document Frequency)를 뜻합니다.
TF-IDF=단어 빈도 * 역문서 빈도
단어 빈도는 5.1.1.1절 '단어 빈도 피처'에서 설명했듯이 단어가 나온 횟스를 문서 전체에 나온 단어 수로 나눈 값입니다. 역문서 빈도는 다음과 같습니다.
가능한 모든 단어 중 I번째 IDF피처 = log( 전체 문서수/i번째 단어가 나타나는 무서 수)
[표 5-2]에서 성물은 한번 나왔기 때문에 IDF가 log(3/1)= 0.477, 해리는 모든 문서에서 나왔기 때문에 log(3/3)=0.000이 됩니다. 많은 문서에 흔하게 등장하는 단어의 IDF가 낮다는 것을 알 수 있습니다. 각 단어의 IDF 는 다음표와 같습니다.
IDF 피처값
해리 log(3/3) = 0.000
호그와트 log(3/3) = 0.000
지팡이 log(3/2) = 0.176
성물 log(3/1) = 0.477
그렇다면 TF도 계산해서실제로 TF-IDF를 구해보도록 합시다. 문서 1에서 해리의 TF는 132/(132+90) = 0.595고 IDF는 0이므로 TF-IDF는 0입니다. 마찬가지로 호그와트의 TF-IDF도 0입니다. 지팡이와 성물의 TF는 0이므로 문서1의 TF-IDF 피처는 [0,0,0,0]입니다. 문서 3의 TF피처가 [0.393, 0.210, 0.210, 0.187]이므로 각각에 IDF를 곱한 TF-IDF는 [0,0,0.210*0.176, 0.198 * 0.477]= [0,0,0.037, 0.0094]입니다.
TF-IDF 피처는 이렇게 중요한 단어에 강조를 주어 문서를 구분하는데 도움을 줍니다.
전체적으로 자주 나오지 않는 단어의 로그값이 너무 작아서 IDF가 예외적으로 커지는 것을 막기 위해 IDF에 약간 변형을 주어 사용하기도 합니다. 바로 log를 취하기 전에 1을 더해서 다음과 같이계산하는 방법입니다.
스무딩된 IDF피처 = log(1 + 전체문서수/i번째 단어가 나타나는 문서수)
5.1.1.3 토픽 또는 군집에 기반한 피처
지금까지는 단순희 단어 출현 횟수를 세거나 그 중요도를 고려해서 피처를 정하는 방법을 알아 보았습니다. 단어외에 더 큰 의미 단위를 이용해보면 어떻까요? 예를 들어 단어들을 군집한 후 각 문서에 나타나는 단어가 어떤 군집에 해당되는지, 혹은 각 문서가 어떤 토픽으로 구성되는지 파악한 다음 그 토픽들이 가지는 중요도를 계산해서 피처를 만드는 방법을 생각해 볼 수 있습니다.
여기서는 이러한 방법의 문서에서 피처를 만들 수 있다는 정도만 언급하겠습니다. 자세한 내용은 5.2절'토픽 모델링'에서 다룹니다.
5.1.2 피처를 이용해서 실제로 분류하기
5.1.1절 '문서 분류에서 많이 사용하는 피처'에서는 문서에서 피처를 뽑아보았으며, 그를 통해 문서를 어떤 고정된 길이의 숫자로 변환시켰는데, 여기서는 그것을 이용해서 문서를 어떻게 분류할 수 있는지 이양기 해보겠습니다.
문서를 피처로 변환한 후에는 피처를 분휴할 수 있는 먼신러닝 기법을 사요하면 됩니다. 이 절에서는 대표적인 문서 분류 기법인 로지스틱 희귀와 SVM에 대해 살펴보겠습니다.(물론 결정 트리등 거의 모든 분류 깁버을 사요할 수 있습니다.)
5.1.2.1 로지스틱 회귀
로지스틱 회귀는 2.1.2절 '간단한 모델'에서 잠깐 소개햇던 선형 회귀 모델에 로지스틱 함수를 적용해서 분류를 하는 기법입니다. 이 모델은 간단해서 구현이 쉬우며 좋은 성능을 내기 때문에 자주 사용됩니다. 로지스틱 회귀는 선형 회귀와 밀접한 연관관계를 가지고 있습니다.
선형 회귀는 2.1.2절 '간단한 모델'에서도 잠깐 소개를 했었느데요, 주어진 입력과 출력 사이에 선형 관계가 존재한다는 가정을 합니다. 주어진 피처(xi)에 따라 다음과같이 정의됩니다.
선형 회귀는 간단하지만 학습이 빠르고 모델이 단순함에도 불구하고 좋은 성능을 보이므로굉장히 널리 쓰입니다. 하지만 주어진 입력에 따라 연속적인 산술값을 출력하는 회귀 기법이기 때문에 분류를 할 때는 약간 변형시켜 사용해야 합니다. 이 변형을 위해 로지스틱 함수를 이용합니다.
로지스틱 함수는 선형 회귀의 연속적인 숫자값을 확률값으로 변환합니다. 이를 이용해서 결과가 어떤 분류에 해당하는지와 해당 하지 않는 지 알 수 있습니다.(이항 분류라고 합니다.) 옐르 들어 입력 데이터의 로지스틱 회귀 값이 0.5보다 크면 분류 1에 속하고, 0.5보다 작으면 분류 0에 속한다는 것을 알 수 있습니다.
좀 더 복잡한 로지스틱 회귀에는 다항 로지스틱 회귀가 있습니ㅏㄷ. 이를 소프트맥스 회귀라고 부릅니다.(딥러닝에서 스프트맥스 회귀라는 표현을 더 많이 사용합니다.) 다항 로지스틱 회귀는 다음과같이 정의되며, 이항 로지스틱 회귀와 굉장히 비슷하다는 것을 알 수 있습니다.
수식이 조금 복잡하게 보일텐데,간단히 설명하면 모든 가능한 항목에 대해 선형 회귀를 수행한 다음 소프트맥스 함수에 통과시킨 겁니다. 소프트맥스 함수는 로지틱스 함수의 확장판으로 생각하면 되는데, 여러 개의 선형 회귀 값을 가지고 각각을 0 부터 1까지 변환시킨다고 생각하면 됩니다.
예를 들면 항목 A에 대한 파라미터 (Wa)가 벡터로 [3, -1]이고, 항목 B에 대한 파라미터(Wb)가[5,2], 항목 C에 대한 파라미터(Wc)가 [-1,10]인 예를 생각해 봅시다. 각각의 항목에 대한 W와 입력의 벡터곱에 exp를 취한 값으로 해당 항목의 확률을 계산합니다. 그러므로 만일 입력(x)이 [1,1]이면 다음과 같이 계산됩니다.
- 항목 A: exp(3*1 + -1*1)= 7.4
- 항목 B: exp(5*1 + 2*1) = 1096.6
- 항목 C: exp(-1*1 + 10 *1) = 8103.1
각각의 항목에 대한 확률은 각각의 값을 전체 합(7.4 + 1096.6 + 8103.1 = 9707.1)으로 나눈 값입니다.
- 항목 A가 맞을 확률: 7.4/9707.1 = 0.0008
- 항목 B가 맞을 확률: 1096.6/9707.1 = 0.1191
- 항목 C가 맞을 확률: 8103.1/9707.1 = 0.8801
이렇게 문서를 피처로 변환시켜서 그것을 입력으로 계산하면 각 항목에 속할 확률값이 나옵니다. 그중 가장 높은 확률을 가지는 항목이 문서의 분류값이 됩니다.
5.1.2.2 SVM
SVM은 문서 분류 문제에 사용하는 표준적인 분류 모델입니다. 로지스틱 회귀가 확률을 로지스틱 함수를 사용해서 모델링한다면 SVM은 확률을 직접 계산하지 않고 데이터가 어떤 '경계선'을 넘지 않는지 검사하는 분류합니다. 결과적으로 SVM은 확률값이 아닌 분류값을 출력합니다.
'경계선'에 대해 좀 더 이야기 하겠습니다(경계선의 머신러닝 공식 용어는 결정 경계선(decision boundary입니다). 데이터가 피처값에 의해 어떤 공간에 분포하고 있다고 합시다. 여기서 한 데이터에 피처 값이 두개 주어졌을 때를 생각해 봅시다.
5.1.2.3 그 외에 만히 쓰이는 분류 방법
로지스틱 회귀와 SVM이 문서 분류에 굉장히 널리 사용되지만, 결정 트리나 그레디언트 부스티드 트리 등의 모델도 많이 사용됩니다. 일단 5.1.1절 '문서 분류에서 만힝 사용하는 피처'에서다룬 것처럼 문서를 피처로 변환합니다. 그리고 나서 앞서 소개한 몇 가지 유명한 머신러닝 모델중 성능이 잘 나오는 것을 고르면 됩니다.
문서에서 좋은 피처를 뽑는 것과 좋은 모델을 찾는 것 중 어떤 것이 더 중요한지 궁금할 것입니다. 사실 정답은 없지만(정확하게는 데이터에 따라 다릅니다). 우선 단어 빈도나 TF-IDF피처로 시작해서 몇 가지 머신러닝 기법을 시도해 보는 것이 좋을 것 같고, 분류가 잘되지 않는 문서를 직접 보면서 어떤 피처가 분류에 도움이 될지 생각해 보는 것이 좋을 것 같습니다.
5.2 토픽 모델링
토픽 모델링은 문서에 존재하는 토픽을 추출하는 기법입니다. 토픽은 문서의 주제를 뜻하는데, 문서 분석의 경우 어떤 단어가 특정하게 분포하는 것을 토픽이라고 합니다. 옐르 들어 스포츠에 대한 단어가 다른 단어보다 많이 나오면 그 문서의 토픽은 스포츠와 관련되었다고 볼 수 있고, 연예인에 대한 단어가 많이 나오면 연예인과관련되었다고 볼수 있습니ㅏㄷ.
토픽 모델링은 문서가 토픽들로부터 생성된 단어로 이루어진다고 가정합니다.
- 문서에는 여러 토픽이 각각의비중을 가지고 존재합니다.
- 토픽마다 단어의 분포가 달라집니다.
- 문서를 작성할 때 토픽의 비중에 따라 토픽을 고라가면서 단어를 뽑습니다.
문서가 토픽에 기반하여 생성된다고 가정하면 문서 내용을 단순히 단어가 아니라 토픽이라는 큰 의미 단위를 통해 파악할 수 있습니다. 예를 들어 어떤 문서는 스포츠 토픽과 연예인 토픽을 7:3으로 썪었고, 또 다른 문서는 과학과 기술을 8:2로 썩였다고 판단할 수 있는 거죠. 그리고 일반적으로 토픽에 따라서 단어가 나오는 비율만 달라질 뿐 단어의 순서는 고려하지 않습니다.
이렇게 토픽을 가정하고 이를 문서를 분석하는 머신러닝 기법을 토픽 모델링이라고 합니다. 이 절에서는 토픽 모델링에 가장 유명한 LDA에 관해 알아보도록 하겠습니다.
5.2.1 LDA
LDA는 확률모델을 통해 문서의 토픽과 토픽에 분포하는 단어들을 분석합니다. 이전에도 말했듯이 LDA는 잠재 디리클레 할당이라는 뜻인데, 문서 안의 토픽 하나하나 디리클레 분포를 따르며, 문서를 그런 토픽에 할당한다는 의미에서 이렇게 부릅니다.
LDA에서는 하나하나의 문서가 다음 과정을 거쳐 생성된다고 가정합니다.
1. 문서에 단어가 몇 개 있는지 결정합니다.
2. 문서마다 토픽이 어떻게 분포되어 있는지, 토픽마다 단어 분포가 어떤지 디리클레 분포(Dirichlet distribution)에 따라 정의합니다.
3. 문서의 제일 첫 단어부터 끝 단어까지 다음 과정을 거칩니다.
a. 현재 위치의 단어가 어떤 토픽과 관계있는지 정합니다.
b. a에서 정한 현재위치의 토픽에 따라 단어의 분포를 결정하고 그 중에서 가장 높은 확률의 단어를 선택합니다.
간단히 요약하면, 문서가 어떤 토픽을 가질 확률, 각 위치의 단어가 어떤 토픽에 해당할지의확률, 그 토픽에 따라 단어가 어떤 확률로 생성될지 정의해서 문서를 활률 모델로 설명한 것으로 볼수 있습니다.
이제부터 LDA가 어떤 일을 하는지 구체적인 예제를 살펴보겠습니다. 다음은 LDA 논문에 수록된 내용으로, LDA를 이용해서 AP통신 기사에서 토픽을 추출한 것입니다.
이렇게 LDA를 통해 각 토픽이 어떤 단어들로 이루어져 있는지, 문서가 어떤 토픽을 가지는지 알 수 있습니다. 추출한 토픽 정보를 이용해서 5.1.1.3 절 '토픽 또는 군집에 기반한 피처'에서 설명한 것처럼 분류에 사용될 피처를 만들기도 하고, 문서 분석을위해 토픽 자체를 사용하기도 합니다. 또한 이러한 토픽을 이용해서 문서를 요약하거나 군집화하기도 합니다.
여기서는 토픽 모델링을 문서에 적용했는데, 문서 데이터와 비슷한 양상을 보이는 데이터가 있다면 토픽 모델링을 적용해서 내용을 간추리는데 도움이 될 수도 있습니다. 예를 들어 한 사용자의 쇼핑 정보를 문서로 간주하고 구매 항목을 단어로 취급하면 여러 상품을 토픽처럼 묶어서 분석할 수 있습니다.
5.3 문법 분석
문법분석은 문서의 문법 구조를 분석해서 내용을 파악하는 기법입니다. 문법을 분석해서 파악하기 때문에 단순히 단어 수만 세서 파악할 때보다 내용을 좀 더 정확하게 파악할 수 있습니다. 같은 단어를 사용하더라도 문법 구조에 따라 의미가 정반대가 되기도 하고, 대명사가 지칭하는 단어가 달라질 수 있기 때문입니다.
물론 앞서 잠시 이야기 했듯이 문서를 하나하나 완벽하게 분석하는 ㅣㅇㄹ은 어렵습니다. 문법 분석은 아직 완전히 풀린 문제로 보기 어렵고, 현존하는 기술로는 아직 단어 중심의 분석만큼 큰 규모의 분석을 하기 어렵습니다. 그렇기 때문에 문서의 뜻을 광의적으로 파악하는 문제보다 문장 하나의 뜻을 세부적으로 파악하는 것이 결정적인 역할을 하는 경우에만 적용합니다. 옐르 들어 신문기사를 분류하는 문제보다는 채팅 메시지를 보고 그것이 긍정인지 부정인지 파악하는 것을 생각해 볼 수 있습니다.
이 절에서는 두 가지 대표적인 문법 분석방법에 대해 알아보겠습니다. 첫 번째는 문서 내의 각 단어의 품사를 파악하는 품사 태깅(POS :Part of Speech Tagging)문제고, 두 번째는 각 단어가 어떤 의미 단위에 속하는지(옐르 들어 장소인지 사람인지)파악하느 고유명사 추출(NER: Named Entity Recognition)입니다.
5.3.1 품사 태깅
품사 태깅 문제는 주어진 문서에서 각 단어의 품사(주어, 목적어, 형용사 등)를 파악하는 것입니다. 보통 문서 전체에서 품사를 한번에 파악하기보다는 각 문장의 품사를 파악하는 것이 일반적입니다. 다음 문장을 읽으면서 단어들의 품사를 어떻게 결정하느지 생각해 봅시다.
The brown fox jumps over the lazy dog.
위 문장은 'The'로 시작합니다. 이 단어는 항상 관사로 상요되므로 뒤에 오는 단어가 명사임을 알 수 잇습니다. 그다음에 나온 단어는 'brown'인데, 이 단어는 형용사나 명사로 사용되며, 다음에 명사가 나왔기 때문에 이 명사를 수식하는 형용사로 쓰였다는 것을 아 수 있습니다. 그 다음에 'fox'라는 명사가 나오면서 이 명사절은 끝납니다. 문장 구조에서는 명사절 뒤에 동사가 나옵니다. 여기서 'jumps'가 그 역할을 하고 있습니다. 이런 식으로 문장을 끝까지 처리하면 다음과 같은 문법 트리를 얻게 됩니다.
(주어(명사구 The brown fox)
(동사 jumps
(전치사 over
(명사구 the lazy dog))))
POS 태깅 문제를 푸는 과정을 살펴보면 현재 단어와 이전 단어에 따라 품사가 결정 된다는 것을 알 수 있습니다. 이런 방식의 문제를 2.1.4.1절 '순차 모델'에서 잠깐 다루었는데, 바로 순차 모델링(sequential modeling)입니다. 품사 태깅에 순차 모델을 이용하면 문자으이 첫 단어부터 마지막 단어까지 단어 하나하나가 입력으로 들어가고, 모델의 상태를 순차적으로 업데이트해가며 문장 구조를 분석합니다.
이런 순차 모델링의 가장 대표적인 기법으로는 CRF와 최근 많이 쓰이는 딥러닝 기법인 RNN과 그중에서도 유며안 LSTM(Long Short-Term Memory)이 있습니다. 이어서 RNN과 LSTM을 간단히 소개하겠습니다.
5.3.1.1 RNN
2.1.4.1절 '순차 모델'에서 순차 모델을 설명하면서 잠깐 RNN의 정의에 대해 살펴봤습니다. 여기서는 그 정의에 좀 더 살을 붙여보겠습니다.
RNN을 사용한 POS 태깅의 경우 앞서 설명했듯이 순서를 가지는 데이터(여기서는 문장에서의단어들)를 앞에서부터 하나씩 읽어나가면서 품사 예측을 수행합니다. x는 문장의 단어의 각 단어들의 피처가 됩니다. 간단하게 전체 단어집에서 명 몇번째 단어에 해당하는지 생각할 수 있겠고요, 복잡하게는 사전에서 이 단어에 어떠한 뜻이 있는지 혹은 어떤 품사들을 가질 수 있는지에 대한 정보를 담을 수 있습니다. 출력 y는 모델이 예측한 품사입니다. h는 문장의 첨음부터 단어를 하나씩 보면서 전에 어떤 단어들이 모델의 입력으로 들어왔는지 기억하는 부분입니다. 즉,각 단어를 하나씩 보면서 바로 직전의 상태(ht-1)와 현재 단어(xt)를 가지고 현재 상태(ht)를 업데이트 합니다.
순서에 입각한 RNN의 학습은2장에서 예로 든 순서가 없는 모델보다 약간 더 복잡합니다. 순서가 없는 경우에는 입력 x와 출력y의 한 쌍이 다른 입출력과 독립적이기 때문에 각각의입출력에 대해 최적화를 했었는데, RNN의 경우에는 현재 단어와 지단 단어가 h를 통해 연결되기 때문에 한 문장 안의단어들은 함께 연관되어 학습되어야 합니다.(문장끼리는 보통 독립적이라고 생각합니다.) 이 학습을 위해서는 보통 경사하강법을 시간에 따라 거슬러 올라가면서 계산하는 방법(Backpropagation Though Time)을 많이 사용하며, 보통 이 방법은 라이브러리에 구현되어 있기 때문에 직접 작성하기 보다는 구현된 라이브러리를 활용하면 됩니다.
학습 방법에 대해 굳이 설명을 한 이유는 RNN이 가지고 있는 한계점에 대해 설명을 하고 싶었기 때문입니다. RNN은 문장이길어져서 단어 간의 연관관계가 멀리 떨어져 있을 때 제대로 모델링하지 못하는 문제가 있습니다. 이론적으로는 내부에 존재하는 표현형(h)과 연관관계가 멀리 떨어져 있더라도 제대로 모델링할 수 있어야 합니다. 하지만 앞서 언급한 '시간에 따라 거슬러 올라가며 계산하는 방법'으로 멀리 떨어져 있는 단어의 정보가 제대로 전달되지 않는 문제가 있습니다. 이 문제를 소멸하는 1차 미분값(vanishing gradient)이라고 부릅니다. 나열된 순서를 거슬러 올라가며 1차 미분값을 계산하다보면 그 값이 점점 작아져서 정보가 잘 전달되지 않습니다. 그렇기 때문에 문장이 길어지면 연관관계를 잘 모델링 하지 못해서 원하는 성능을 얻지 못하는 문제가 있게 되는 것입니다.
이 문제를 해결하고자 등장한 것이 LSTM(long Short-Term Memory)입니다. 물론 LSTM 모델이 앞서 설명한 기본적인 RNN보다 훨씬 더 복잡하기 때문에 학습이 어려워서 항상 잘 동작하는 것은 아니지만 멀리 떨어진 연관관계를 모델링하는 데는 탁월한 성능을 보여줍니다.
지금까지는 간단한 RNN모델만 이야기 했는데, [그림 5-6]처럼 직전 표현형만 고려하는 것을 넘어 두 단계 이전 표현형을 고려하는 등의 시도가 있습니다. 간단한 RNN 구조를 층층이 쌓아서 더 복잡한 연관관계를 학습하려는 시도인데, 보통 딥러닝이라고 불리는 모델은 이러한 복잡한 구조를 많이 사용합니다. 2.1.4 절 '구조가 있는 모델'에서 설명한 것처럼 구조가 복잡할수록 데이터가 많아야 제대로 동작합니다.
5.3.1.2 LSTM
LSTM은 RNN 모델 중 하나로 RNN기본 모델의 단점을 해결하고자 고안되었습니다. 가장 기본적인 차이점은 전에 가지고 있던 정보를 계속 가지고 갈지 혹은 잊을지 판단하는 신경망 구조가 들어 있다는 것입니다.
[그림 5-7]을 보면 [그림 5-6]과 비슷하게 문장의 흐름(xt-1, xt, xt+1)에 따라 각 단어가 입력으로 들어가고 각각에 해당하는 출력(yt-1, yt, yt+1)이 있습니다. 하지만 [그림 5-6]과는 다르게 상태를 저장하는 부분에 복잡한 박스가 있습니다.
이 박스는 입력과 현재까지의상태를 이용해서 내부에 가지고 있던 정보를 업데이트할지 잊을지 판단합니다. 문장을 앞에서부터 보다가 내용이 어느정도 정리된 것 같다고 판단되면 지금까지의 상태 정보를 잊고 새로 입력된 단어가 별로 유용하지 않으면 지난 단계까지의 상태를 많이 기억하고 업데이트를 조금만 합니다. 그리고 이 상태를 C(셀cell)라고 불리는 곳에 저장합니다.
이러한 선택적인 업데이트와 기억방식은 박스의 특별한 구조 때문인데, 자세한 동작 방식은 다음과 같습니다. 앞서 설명한 것처럼 C는 상탯값, W는 모델의매개변수를 뜻합니다.
1. 맨 왼쪽 첫번째 구조는 망각 게이트(forget gate)라 불리는데, 지금까지 기억했던 상태와 현재 입력에 파라미터를 곱해서 상태를 잊을지 말지 정합니다.
ft = Q(Wforget[Ct-1, Xt])
2. 그다음 구조는 입력 게이트(input gate)인데, 상태와 현재 입력을 가지고 얼마나 기억할지 정합니다.
it = Q(Winput[Ct-1, xt])
3. 그리고 그다음 상태를 앞에서 설명한 RNN과 비슷하게 추측합니다.
Ct =tanh(Wcell[Ct-1, Xt])
4. 이렇게 3가지를 계산하고 나면 망각을 얼마나 하고(항목 1) 업데이트를 얼마나 할지(항목2) 어떤 값을 업데이트할지(항목 3) 정해서 업데이트 합니다.
Ct = ft*Ct-1 + itCi
5. 실제 출력은 현재 상태(Ct)와 입력을 이용해서 결정합니다.
y = Q(Wy[Ct, xt])
수식 때문에 햇갈린다면 한글로 된 설명 부분만 이해해도 됩니다. 기본 RNN과 비슷한게, 내부에 어떤 상태가 있고(C, RNN의 h와 동일하지만 LSTM에서는 주로 셀이라고 부릅니다), 전 상태와 현재 입력에 따라 현재 상태를 얼마나 기억할지, 얼마나 업데이트할지, 무엇을 업데이트 할지 파라미터로 정합니다.
LSTM학습은 파라미터들이 어떻게 기억하는지, 출력을 어떻게 내는지에 대한 손실함수를 만들고(보통 교차 엔트로피를 많이 사용합니다) 경사하강법을 응용한 방법으로 학습을 수행합니다. 이걸 직접 구현하기는 복잡하므로 라리브러리에 주어진 기본 구현을 사용하면 됩니다.
[그림 5-7]은 광장히 단순화한 모델이며, 실제 사용할때는 구조가 훨씬 더 복잡해집니다. 옐르 들어 망각 게이트나 입력만 고려하는 것이 아니라 전 단계의 출력값까지 교려하거나, 아니면 몇 단계 전의 입력을 고려하기도 합니다. 또한 입력과 출력 사이에 LSTM박스를 하나만 두는것이 아니라 박스를 층층이 쌓아서 더 복잡한 학습에 사용합니다(보통 딥러링은 이렇게 복잡한 구조를 지칭합니다.) 반면 LSTM의 망각과 입력 게이트를 묶어서 망각하는 만큼 기억을 하는 GRU(Gated Recurrent Unit)같은 약간 더 간단한 모델도 있습니다.
이렇게 LSTM을 이용하면 RNN보다 더 복잡하고 멀리 떨어진 연관관계를 모델링할 수있습니다.LSTM은 POS 태깅 문제에도 사용되지만, 음성 데이터처럼 시간에 따라 변화하는 데이터를 분석하느데도 많이 사용됩니다.
5.3.2 고유명사 추출(NER)
구글 어시스턴트, 시리, 아마존 에코 등 대화형 봇이 유행입니다. 이런 봇들이 대화에서 장소나 시간 정보를 발견하면 일정을 만들어주는 머신러닝 시스템을 만든다고 생각해봅시다. 그렇다면 문장에서 어떤 부분이 장소고 어떤 부분이 시간인지 추측해야겠지요? 이런일을 가능하게 해주는 것이 바로 NER(Named-Entitiy Recognition)입니다. NER은 텍스트에서 인물, 조직, 장소, 일정 등을찾아내는 문법 분석 방법입니다. 책이나 웹 페이지에서 이름, 장소, 주소, 제품명을 인식하는 데 사용할 수 있습니다.
NER은 POS 태깅 문제와 몇 가지 비슷한 성질을 가집니다. 첫 번째로 품사 문제 때처럼 사전정보를 사용하면 많은 도움이 됩니다(유명한 사람의 이름이나 장소 같은 고유명사가 특히 중요합니다.) 두번째로 품사 문제를 풀 때와 마찬가지로 앞뒤에 나열된 단어에 따라 영향을 받습니다. 예를 들어'정문 앞에서 만나자'라는 부분이 있다고 합시다. 이때 '에서 만나자'라는 표현은 장소와 연관성이 있기 때문에 '정문 앞'에 해당하는 단어가 장소사전에 등록되어 있지 않더라도 장소일 확률이 높습니다.
이 절에서는 앞서 설명한 RNN이나 LSTM모델을 이용해서 어떻게 NER문제를 푸는지 알아보겠습니다. 또한 참고로 머신러닝 기법을 사용하지 않고서도 어떻게 NER문제를 풀 수 있는지에 대해서도 살펴보겠습니다.
5.3.2.1 순차 모델을 사용한 NER(RNN/LSTM)
NER도 머신러니의 순차 모델을 많이 사용합니다. 예를 들어 LSTM과 같은 순차 모델에 입력으로 NER 문제에 도움이 되는 장소사전이나 품사를 넣고, 출력값으로 NER 항목들을 주어서 학습하는 방식입니다. NER 항목은 필요에 따라 여러가지를 생각할 수 있지만 일반적으로 '장소, 일정, 인물, 해당 항목 없음' 이런 식으로 분류를 만들어 학습을 진행하면 됩니다.
다음 절에서 설명하겠지만 이 방식의 가장 큰 문제는 문장 하나하나마다 순차 모델을 돌려야 하므로 속도가 그렇게 빠르지는 않아서 문서를 대규모로 분석하기에는 시간이 많이 소요된다는 것입니다.
5.3.2.2 머신러닝은 아니지만 NER에 사용되는 기법
NER은POS태깅 문제와 약간 다르게 큰 규모의문서 분석에서도 종종 사용됩니다. 옐르 들어 소셜 네트워크나 포털사이트에서 사용자가 신문 기사를 포스팅하면 기사에 나오는 장소나 인물에 자동으로 링크를 만들어 주는 시스템을 생각할 수 있습니다.
물론 향휴에는 머신러닝의 발전으로 RNN이나 LSTM도 이런 큰 규모의 문제를 잘 처리할 거라 생각합니다. 하지만 머신러닝을 이용하지 않고 정규표현식을 이용해서 처리하는 방법도 있다는 점을 알려드립니고 싶습니다. 예를 들어 \d\d\d\d-\d\d\d\d-\d\d\d\d와 같이 숫자 3개, 4개,4개가 '-'로 연결되어있다면 전화번호일 확류이 굉장히 높습니다. 앞서 말씀드린 인물사전, 장소사전과 적절한 정규표현식을 사용하면 순차 모델을 사용하는 것보다 정확도나 발견 확률이 떨어지지만 빠르게 대규모 분석이 가능합니다.
5.4 단어 임베딩 학습-word2vec
컴퓨터로 단어의 의미를 나타내는 방법은 크게 이산표현(discrete representation)과 분산표현(distributed representation)이 있습니다.
쉽게 설명하자면 이산표현은 그 단어 자체를 이요하며 의미를 나타내는 것입니다. 옐르 들어 고양이라는 단어가 있다면 사전을 이용하여 단어를 정의하는 것이죠, 따라서 이산표현에서 단어 하나의 의미는 말뭉치에 든 모든 단어 수를 벡터 크기로 하는 원-핫 인코딩 벡터(3.3.2절'카테고리 데이터 표준화'참조)로 표현합니다.
이와 달리 분산 표현은 한 단어의 주변 단어를 이용해서 의미를 표현합니다. 즉, 분산 표현에서는 고양이는 '귀엽다', '야옹거린다'라는 주변 단어로 의미가 정해집니다. 이러한 분산 표현은 사전에 정의되지 않은 새로운 단어도 표현할 수 있으므로, 더 많은 단어를 분석에 이용할 수있습니다. 분산 표현을 이용하면 단어 하나는 실숫값으로 이루어진 벡터가 되는데, 이를 단어 임베딩(word embedding)이라고 합니다.
이산 표현과분산 표현의 예는 다음과 같습니다.
- 이산 표현
고양이 = [0,0,0,....1,0,0](원-핫 인코딩 벡터. 하나의 항만 1이 됩니다.)
여기서 벡터의 길이는 말뭉치 안의 단어(어휘집의 크기) 수가 됩니다.
- 분산 표현
고양이= [1.281, -2.321, 4.509,....3.212]
여기서 벡터의 길이는 사용자가 지정한 길이가 됩니다.
word2vec은 뉴럴넷을 이용하여 분산 표현을 학습하는 모델입니다. 이러한 시도는 오랫동안 있어왔지만 2013년에 소개죈 word2vec이 계산이 빠르고 간단해서 널리 쓰이므로, 이 책에서는 word2vec에 대해 자세히 살펴보겠습니다.
word2vec은 단어가 주어지면 그 단어와 주변 단어가 같이 일어날 확률을 구합니다.
word2vec의 목적 함수로는 스킵-그램과 COW가 있습니다.
- 스킵-그램(skip-gram):단어 하나(xt)를 받아서 그 주변에 같이 나타날 확률이 높은 단어들(context)을 구합니다. 즉, p(context|xt)를 구합니다.
- COW(continuous bag or words): 주변 단어들(context)을 받아서 그 단어들과 같이 나타날 확률이 높은 단어(xt)를 구합니다. 즉p(xt|context)를 계산합니다.
그림으로 나타내면 다음과 같습니다.
word2vec 은 한 단어wc가 주어졌을 때 그 주변에 단어가 존재하면 0, 그렇지 않으면 1로 하는 원-핫 인코딩을 생성해서 학습합니다. 예를 들어 '귀여/운 태비 고양이/가 고등어 통조림 앞/에서 야옹/하고 울/었/다'라는 문장을 앞뒤 두 단어를 이용하여 word2vec을 학습할때, 고양이를 중앙 단어로 입력받으면 (고양이, 태비)는 1, (고양이, 울)은 0이 되겠죠.
이 책에서는 의미적인 유사성을 얻기 위해 자주 사용하는 스킴-그램의 목적 합수를 살펴보겠습니다.
현재의 단어를 wt, 학습에 사용할 위도우 크기(주변 단어의 수+1)를 2m+1이라고할때, 스킴-그램의 목적은 다음의확률을 최대로 하는 파라미터의 집합 Q를 찾는 것입니다.
2018년 3월 9일 금요일
2018년 3월 6일 화요일
CHAPTER 4 구매 이력 데이터를 이용한 사용자 그룹 만들기
이 장에서는 사용자 그룹을 만드는 방법을 알아보겠습니다. 온라인 상거래 사이트는 사용자를 그룹으로 나누어 캠페인, 세일 등의 마케팅 전략을 세웁니다. 예를 들어 디비디와 블루레이를 판매하는 사이트에 20대 남성이 많을 경우, 20대 남성이 좋아할 만한 영화를 적극 프로모션하는 식 입니다.'20대 남성'과 같은 성별 및 연령 정보 말고도 구매 이력을 이용하여 그룹을 만들 수도 있습니다. 예를 들어 마블 슈퍼 히어로 영화를 구매하는 마블팬 그룹 같은 것 말이죠. 그러려면 제일 먼저 사용자으 ㅣ구매 이력을 살펴볼 필요가 있습니다. [표 4-1]은 가사의 사용자 구매 이력 데이터 입니다.
표 4-1 사용자 구매 이력 데이터
피처 피처값
사용자 ID abdc
구매일시 2017/04/01
구매상품영 [Blu-ray]캡틴 아메리카:시빌 워 2D + 3D Combo (2Disc)
구매상품 가격 30000
구매상품 수 1
구매상품 카테고리 DVD/블루레이
이 데이터를 이요해서 사용자의 구매 패턴을 정의해 봅시다. 일반회원/VIP(구매 횟수 및 구매 총액), 정기구매회원/단발성회원(구매횟수), 가전제품구매자/생활용품구매자(구매상품 카테고리), 저가 상품구매자(구매상품 가격의 평균), 마블팬(구매상품명) 등 하나의 데이터셋으로 무수한 패턴을 정의할 수 있습니다. 2장에서 설명한 대로 모델을 만들어 문제를 풀 경우 각 패턴에 대해 일일이 모데을 만들어야 합니다. 여기에서 사용자의 성별, 연령, 주소 등 사용자 정보까지 더해지면 계산이 매우 복잡해집니다. 어쩌면 우리가 상상도 할 수 없는 패턴이 존재할 수있습니다. 이와 같이 '사용자 그룹 만들기'는 수학적 모델을 만들기 어려운 대표적인 무제입니다. 이 문제를 푸는 방법으로 군집화에 대해 알아보겠습니다.
4.1 군집화
데이터 특징을 이요한 그룹핑 문제에서는 특정 패턴을 정의하여 모델을 만드는 방법보다 비슷한 데이터를 한데 모으는 방법이 더 효율적입니다. 이 방법을 군집화라고 합니다. 이 방법은 미리 그룹을 정의하지 않기 때문에 비지도학습에 속합니다. 크게 중심 기반 군집화, 계층적 군집화, 밀도 기반 군집화로 나눌 수 있습니다. 다음은 각 방법에 대한 간단한 소개입니다.
-중심 기반 군집화(prototype-based clustering): 클러스터를 크러스터 중심점으로 정의하는 기법입니다.
- 계층적 군집화(hierarchical clustering): 클러스터의 크기에 따라 클러스터의 계층을 정의하고 계층의 상하위를 이용하는 기법입니다.
- 밀도 기반 군집화(density-based clustering): 클러스터를 데이터가 높은 밀도로 모여 있는 공간으로 보는 기법입니다.
군집화의 핵심 아이디어는 비슷한 데이터를 한데 묶는다는 겁니다. 그렇다면 비슷하다는 것, 즉 유사도가 높다는것은 무슨 의미일까요? 데이터를 이루는 피처값이 비슷하다는 겁니다. 사용자 구매이력을 생각해보면, 비슷한 상품을 구매하는 사용자를 한데 묶을 수 있습니다. 또한 비슷한 연령대 사용자를 한데 묶을 수도 있습니다.
예를 들면 X는 사용자 1, Y는 사용자 2를 의미하고, 데이터를 X를 X={x1, x2, x3, x4,...xm}으로, 데이터 Y를 Y={y1, y2, y3....ym}으로 정의합니다. 여기서 m은 피처 수입니다. [표 4-1]을 기준으로 예를 들어보면 x1과 y1은 구매상품 개수, x2와 y2는 구매상품명 등으로 생각할 수 있습니다. 그럼 각 피처의 차이의 총합 가 적을 수록 비슷한 사용자라고 할 수 있습니다.
하지만 단순히 차이를 전부 한 피처에 대해 +가 나오고, 다른 한 피처에 대해 - 가 나오면 두 데이터 사이의 피처값이 많이 다름에도 불구하고 전체 합이 0이 될 수 있습니다. 따라서 단순한 차이 대신 그 차이를 제곱한 값을 더하여 얻어진 값에 루트를 사용합니다. 수학적 정의는 다음과 같습니다.
식4-1 유클리드 거리(민코스키 거리의 특수한 경우)
d(X, Y) = 루트(합((xi-yi)제곱))) = ||X-Y||
그러고 보니 이 식은 두 점사이의 최단 거리인 유클리드 거리(Euclidean distance)를 구하는 식과 같습니다. 일반적으로 유클리드 거리를 구하는 식은 계산의 편의를 위해 위 식을 제고(제곱유크릴드 거리)하여 사용합니다.
여기서 궁금한 점이 생깁니다. 가격 2만원과 1뭔의 차이인 100000이, 연령 25세와 25세의 차이인 1과 같다고 할 수 있을까요? 또 연령의 차이를 뺄셈으로 구하는 것 이해가 되는데, 상품명의 차이는 어떻게 구할 까요? 성별의 차이는 수학적으로 표현할 수 있을까요? 이에 대해서는 3.3절 '데이터 표준화'에서 살펴본 데이터 표준화 방법을 적ㅇ둉하면 됩니다. 또한 [식 4-1]에서 차이값을 제곱하지 않고 그냥 절댓값을 취하면 어떻게 될까요? 이에 대해서는 4.5절 '유사도 계산'에서 자세히 살펴보겠습니다.
4.2 K-중심 군집화
이 절에서는 중심기반 군집화의 대표적인 예인 K-중심 군집화( K-centroid clustering, K-medoid clustering) 에 대해 알아보겠습니다. 중심 기반 군집화는 클러스터 중심점을 정한 후 클러스터 중심점에 가까운 데이터들을 모아가며 클러스터를 확장하는 방법으므로 군집화 초기에 몇 개의 중심점을 어떻게 배치하는가 중요합니다. K-중심 군집화는 초기에 K개의 중심점을 랜덤으로 선택하여 군집화를 시작합니다.
중심 기반 군집화에서는 클러스터 수 K와 함께 클러스터 중심을 어떻게 구하느냐를 정할 필요가 있습니다. 여러 데이터를 요약하는 방법으로 평균(mean), 대푯값(median), 최빈값(mode)이 있다는 것은 아실 겁니다(중학교 때 변량, 도수 등을 배운 것 기억하시죠?). 클러스터 안의 데이터의 평균값을 중심으로 사용할 경우 K-평균 군집화, 대푯값을 사용할 경우 K-대푯값 군집화( K-medians clustering),최빈값을 사용할 경우 K-최빈값 군집화(K-modes clustering)라고 합니다. 3가지 방법 모두 중심점을 구르는 방법이 다를 뿐 전체 군집화 과정은 동일합니다.
K-주심 군집화는 다음 절차를 거칩니다.
1. 임의로 뽑은 K개의 데이터를 중심으로 하는 클러스터를 만듭니다(각 클러스터가 데이터 하나씩만 가지고 있습니다.)
2. 각 데이터와 클러스터 중심 간의 거리를 계산합니다.
3. 데이터와 클러스터 중심 간의 거리가 가장 짧은 클러스터에 데이터를 할당하고 이 데이터를 포함하여 클러스터 중심을 업데이트 합니다.
4. 과정 2와 3을 반복하는데, 과정3에서 더 이상 중심의 위치가 변하지 않거나, 업데이트 정후 중심점의 차이가 사용자가 지정한 허용 오차 이내이거나, 사용자가 지정한 횟수만큼 반복했으면 종료합니다.
과정 3에서 가장 가까운 클러스터에 데이터를 할당하고 있습니다. K-중심 군집화에서 클러스터는 클러스터 중심으로 정의되므로 결국 데이터와 클러스터 중심 사이의 거리가 가장 짧게 되는 클러스터를 찾는 것입니다.[식4-2]를 이용하여 거리를 구할 수 있습니다.
식4-2 클러스터 안의 데이터와 클러스터 중심 간의 거리
||x-uc||2
x:데이터
uc: 클러스터 c의 중심점
군집화는 각 데이터에 대해 [식4-2]의 값을 최소화하는 것이 목적입니다. 그런데[식4-2]를 자세히 보면 2.2.1절 '산술 손실함수'에서 살펴본 제곱 손실함수와 같은 형태임을 알 수 있습니다.
loss(f) = (y - y')2
f : 모델
y : 데이터로부터 주어진 출력
y':모델에 데이터로부터 주어진 입력을 넣어서 계산한 값
따라서 분산 분석과 같은 제곱 손실함수에 대한 이론을 k-중심 군집화에도 적용할 수 있습니다.
과정 4에서 업데이트가 없으면 군집화를 종료한다고 했는데, 이는 각 데이터가 속한 ㅡㄹ러스터가 변하지 않는 것, 즉 수렴한다는 것을 의미합니다. 하지만 수렴되기까지 상당한 시간이 걸리므로 실제로는 이전 단계의 총제봅합과 새로 얻은 총제곱합의 차이가 허용오차보다 작거나, 클러스터 중심 업데이트의 횟수가 사용자가 지정한 반복 횟수 이상이면 군집화를 종료합니다.
클러스터 수 k, 허용 오차, 반복 횟수 모두 사용자가 지정하는 파라미터이므로 군집화 결과를 보면서 바꾸어 가며 시도해야 데이터를 잘 표현하는 그룹을 만들 수 있습니ㅏㄷ. 이에 대해서는 10장에서 실제 구현을 통해 설명하겠습니다.
4.3 계층적 군집화
계층적 군집화에서의 계층은 클러스터의 계층을 의미합니다. 최상위 계층의 클러스터느 모든 데이터를 포함하는 하나의 클러스터입니다. 최하위 계층의 클러스터는 단 하나의 데이터만을 포함하므로 클러스터 수가 데이터 수와 같게 됩니다.
계층적 군집화에는 하향식인 분할적 군집화와 상향식인 집괴적 군집화가 있습니다. 사용자가 클러스터 수 k를 명시적으로 지정하는 k-중심 군집화와는 달리 계층적 군집화는 클러스터 수를 지정할 필요가 없습니다.
계층적 군집화는 각 단계마다 한 쌍의 클러스터를 비교하는데, 하향식인 분할적 군집화는 각 계층의 클러스터들을 둘로 쪼개어 하위 계층으로 진행하고, 상향식인 집괴적 군집화는 각 계층의 클러스터들 중에 가장 가까운 두 개를 하나로 합쳐 상위 계층의 클러스터를 만들어 갑니다.
같은 계통 트리를 만들어서 클러스터의 생성 과정을 시각화하기 쉽다는 것이 계층적 군집화의 장점입니다.
계층적 군집화에서 주의할 점은 늘 한쌍의 클러스터만 비교한다는 겁니다. 예를 들어 데이터에 실제 존재하는 클러스터가 3개라고 하더라도 계층적 군집화는 2n개의클러스터만 생성할 수 있으므로 데이터를 잘 표현할 수 없습니다. 군집화 전에 클러스터 수가 2n개가 아니라는 점을 알고 있다면 계층적 군집화보다는 k-중심 군집활르 하는 것이 좋습니다.
k-중심 군집화에서는 k 값에 따라 군집화의 성능이 달라지는 것과 마찬가지로, 계층적 군집화에서는 클러스터를 어느 정도로 분할 혹은 병합하는가에 따라 군집화의 성능이 달라집니다. 계통 트리를 통한 클러스터 생성 과정, 클러스터 안의 데이터 분석 등을 통해 적절한 클러스터를 찾을 수 있습니다.
집괴적 군집화는 다음 순서로 진행됩니다.
1. 하나의 데이터를 하나의 클러스터로 지정합니다.
2. 과정 1의 클러스터들에 대해 가장 유사도가 높음 클러스터 둘을 하나로 합칩니다.
3. 과정 2에서 생성된 클러스터들에 대해 다시 같은 과정을 반복합니다.
클러스터 둘을 합칠 때 복수의 데이터로 이루어진 클러스터 간의 유사도를 측정할 필요가 있습니다. 대표적인 방법으로 단일 연결법(single linkage), 완전 연결법(complete linkage), 평균 연결법(averae linkage)이 있습니다.
- 단일 연결법: 두 클러스터에 속하는 데이터들의 거리 중에 가장 짧은 거리를 클러스터 사이의 거리로 간주합니다.
- 완전 연결법: 두 클러스터에 속하는 데이터들의 거리 중에 가장 먼 거리를 클러스터 사이의 거리로 간주합니다.
- 평균 연결법: 두 클러스터에 속하는 데이터들의 거리 평균을 클러스터 사이의 거리로 간주합니다.
그럼 어떤 방법이 가장 나을까요? 알핏 생각하면 클러스터 안의 모든 데이터를 고려하는 평균 연결법이 가장 나을 것 같기도 합니다. 하지만 평균 연결법을 사용할 경우 한 쌍의 클러스터 내의 모든 데이터의거리를 구하므로 다른 두 방식보다 계산량이 늘어난다는 단점이 있습니다. 데이터에 따라서는 평균 연결법 혹은 단일 연결법이 시간이 덜 걸리고 비슷하게 좋은 클러스터를 만들 수도 있습니다.
분할적 군집화의 대표적인 예인 DIANA(Divisive Analysis,분할 분석)는 다음과 같은 과정으로 클러스터를 생성합니다.
1. 현 단계에서 가장 큰 클러스터를 선택합니다(가장 처음에는 전체 데이터를 하나의 클러스터 안에 있다고 간주합니다).
2. 과정 1의 클러스터에서 가장 이질적인 데이터를 찾습니다. 예를 들어 클러스터 안의 데이터 평균에서 가장 동떨어진 데이터를 찾습니다. 이로써 클러스터를 평균점과 이질적인 점을 중심으로 하는 두 클러스터로 나눌수 있게 됩니다.
3. 과정 2에서 구한 두 점(평균점, 이질적인 점)과 각 데이터의 거리를 계산하여 거리가 짧은 점을 중심으로 하는 클러스터로 데이터를 할당합니다.
4. 한 클러스터가 데이터 하나만을 가질 때까지 과정 1, 2, 3을 반복합니다.
분할적 군집화도 앞에서 사용한 연결 방법을 통해 거리를 측정합니다. 하지만 모든 데이터를 작은 클러스터로 나눈다는 점에서 k-중심 군집화와 굉장히 비슷한데다가, 클러스터 수의 지정이 자유로운 k-중심 군집화와는 달리 클러스터 수가 2n개라는 제약이 있어 많이 상요하지 않습니다.
4.4 밀도 기반 군집화
밀도 기반 군집환느 데이터 밀도가 높아지는 방향으로 데이터를 군집화하는 방식입니다. 대표적인 밀도 기반 군집화 알고리즘에는 평균이동 군집화와 DBSCAN(density-based spatial clustering of application with noise, 디비크캔, 노이즈를 가지느 애플리케이션의 밀도 기반 공간 군집화)이 있습니다.
평균이동 군집화는 다음과 같은 과정으로 진행됩니다.
1. 임의의 초기 중심점을 지정하거나 탐색 반경을 지정합니다(초기 중심점을 지정하지 않으면 탐색 반경에 따라 초기 중심점이 결정됩니다.)
2. 지정한 줌심점으로부터 지정한 반경을 가지는 원 안의 밀도를 계산한 후 데이터의 밀도가 가장 높은 곳을 중심점으로 재지정합니다. 데이터의 밀도가 높다는 것은 데이터가 많이 몰려 있다는 뜻입니다. 일반적으로 데이터가 정규분포(가우스분포)를 따른다고 가정하여 평균 주변에 데이터가 가장 많이 몰려 있다고보고, 반경 안의 평균점을 새로운 중심점으로 지정합니다.
3. 더 이상 중심점이 이동하지 않을 때까지, 즉 중심점이 가장 밀도가 높은 곳으로옮겨질 때까지 단계2를 반복합니다.
4. 단계 3에서 얻은 마지막 중심점이 클러스터의 중심점이 되고, 중심점의 이동 경로에 있던 데이터들을 그 클러스터에 할당합니다.
평균이동은 중심점을 갱신한다는 점에서 중심 기반 군집화로 분류되기도합니다.
DBSCAN은 반경의 밀도가 아니라 클러스터 중심의 데이터 수와 가장자리의 데이터 수의 차이를 이용합니다. DBSCAN에서 데이터는 다음과같이 나누어집니ㅏㄷ.
- 중심 포인트(core point):반경e 안에 일정 개수 이상의 데이터가 존재하는 데이터
- 경계 포인트(border point): 반경 e안에 있는 데이터 수는 중심 포인트보다 적지만, 중심 포인트로부터 반경 e안에 존재하는 데이터
- 노이즈 포인트(noise point): 중심 포인트도 경계 포인트도 아닌 데이터
데이터셋 안의 모든 데이터를 군집화하는 k-중심 군집화나 계층적 군집화와는 달리 밀도 기반 군집화는 예외 데이터를 정의하고 군집화 과정에서 그것을 제외시킵니다. 또한 클러스터 중시과 데이터 사이의 거리를 이용하는 k-중심 군집화와 계층적 군집화의 경우 중심으로부터의 같은 거리 안에 있는 모든 점이 같은 클러스터에 속하게 되므로 클러스터가 원형이 될 수빡에 없습니다. 이와 달리 밀도 기반 군집화는 클러스터 모양에 제한이 없습니다.
4.5 유사도계산
이 절에서는 유사도를 측정하는 대표적인 척도인 '거리'에 대해 자세히 살펴보겠습니다. 유사도가 클수록 거리가 줄어들기 때문에 거리가 가깝다는 것은 유사도가 크다는 것이고 거리가 멀다는 것은 유사도가 작다는 겁니다. 이절에서는 다음과 같은 거리를 다룹니다.
- 민코스키 거리: 벡터 공간 안의 두 점 사이의 거리
- 마할라노비스 거리: 점들의 분포를 고려한 거리
4.5.1 민코스키 거리
4.1절'군집화'에서 데이터를 X를 X={x1,x2, x3....xn},데이터Y를 Y={y1, y2, y3...yn}, 피처수를 m이라고 했을 때의 유크릴드 거리를 정의했습니다. 그때의 [식 4-1]은 민코스키 거르의 특수한 경우입니다. 민코스키 거리는 다음과 같이 정의됩니다.
식 4-3 민코스키 거리
d(x,y)= p루트(총함(i=1~m)|xi-yi|p
여기서 p가 1일 때는 맨해튼 거리(Manhattan distance혹은 L1 거리), 2일 때는 유크릴드 거리(Euclidean distance 혹은 L2거리)라고합니다.
맨해튼 거리는 맨해튼의 도로가 격자 모양으로 되어 있어서 한 빌딩에서 다른 빌딩까지의 이동 거리가 두 좌푯값의 차의 절댓값을 합한 값이 되기 때문에 붙여진 이름입니다.
그럼 언제 맨해튼 거리를 쓰고 언제 유클리드 거리를 싸야 할까요? 맨해튼 거리는 거리 계산 시 제약이 있을때 유용합니다. 예를 들어 체스 케임의 경우 각각의 말은 체스 판 위를 자유롭게 움직이는 게 아니라 움직일 수 있는 방향이 정해져 있습ㄴ디ㅏ. 비숏과비숍이 이동할 수 있는 칸을 별표로 표시했습니다. 체스 판의 한칸 (a1,b1)에서 다른칸(a2, b2)으로 비숍을 이동시킬 때 비숍의 디오거리는 루트 | a2 -a1| + | b2 - b1|이 됩니다. 반면 유클리드 거리는 두점 사이의 최소거리르 제약 없이 구할 때 사용합니다.
4.5.2 마할라노비스 거리
마할라노비스 거리(Mahalanobis distance)는 두 점사이의 거리를 계산할 때 데이터의 분포를 고려하는 거리입니다.
공분산이란 두 변수가 얼마나 연관성이 있는지 나타내는 값입니다. 공분산의 값이 1이면 한 변수가 증가할 때 다른 변수도 증가하는 것이고, -1이면 한 변수가 증가할 대 다른 변수가 감소하는 것입니다. 0이면 두 변수 간에 아무런 상관관계가 없습니다. X와 Y가 k개의 실수로 이루어진 벡터고, ux가 X값의 평균, uy가 Y값의 평균일 때, 공분산은 다음과 같이 계산합니다.
Cov(X,Y) = E[(X-ux)(Y-uy)]
X = {x1, x2, x3...,xk}
Y = {y1, y2, y3...yk}
ux = X값의 평균
uy = Y값의 평균
공분산을 고려한다는 것은 X값의 증감과 Y값의 증감의 관계를 거리 계산에 넣는 것입니다. 즉, 키가 평균에서 표준편차의 2배로 멀어지는 것은 몸무게가 평균에서 표준편차의 1배로 멀어지는 것과 같다는 정보를 이용합니다.
마할라노비스 거리는 거리 계산에 위에서 설명한 데이터의 분포 및 공분산을 사용합니다. 수식으로 표현하면 다음과 같습니다.
d(x,y) = 루트((xi-yi)TS-1(xi-yi))
표 4-1 사용자 구매 이력 데이터
피처 피처값
사용자 ID abdc
구매일시 2017/04/01
구매상품영 [Blu-ray]캡틴 아메리카:시빌 워 2D + 3D Combo (2Disc)
구매상품 가격 30000
구매상품 수 1
구매상품 카테고리 DVD/블루레이
이 데이터를 이요해서 사용자의 구매 패턴을 정의해 봅시다. 일반회원/VIP(구매 횟수 및 구매 총액), 정기구매회원/단발성회원(구매횟수), 가전제품구매자/생활용품구매자(구매상품 카테고리), 저가 상품구매자(구매상품 가격의 평균), 마블팬(구매상품명) 등 하나의 데이터셋으로 무수한 패턴을 정의할 수 있습니다. 2장에서 설명한 대로 모델을 만들어 문제를 풀 경우 각 패턴에 대해 일일이 모데을 만들어야 합니다. 여기에서 사용자의 성별, 연령, 주소 등 사용자 정보까지 더해지면 계산이 매우 복잡해집니다. 어쩌면 우리가 상상도 할 수 없는 패턴이 존재할 수있습니다. 이와 같이 '사용자 그룹 만들기'는 수학적 모델을 만들기 어려운 대표적인 무제입니다. 이 문제를 푸는 방법으로 군집화에 대해 알아보겠습니다.
4.1 군집화
데이터 특징을 이요한 그룹핑 문제에서는 특정 패턴을 정의하여 모델을 만드는 방법보다 비슷한 데이터를 한데 모으는 방법이 더 효율적입니다. 이 방법을 군집화라고 합니다. 이 방법은 미리 그룹을 정의하지 않기 때문에 비지도학습에 속합니다. 크게 중심 기반 군집화, 계층적 군집화, 밀도 기반 군집화로 나눌 수 있습니다. 다음은 각 방법에 대한 간단한 소개입니다.
-중심 기반 군집화(prototype-based clustering): 클러스터를 크러스터 중심점으로 정의하는 기법입니다.
- 계층적 군집화(hierarchical clustering): 클러스터의 크기에 따라 클러스터의 계층을 정의하고 계층의 상하위를 이용하는 기법입니다.
- 밀도 기반 군집화(density-based clustering): 클러스터를 데이터가 높은 밀도로 모여 있는 공간으로 보는 기법입니다.
군집화의 핵심 아이디어는 비슷한 데이터를 한데 묶는다는 겁니다. 그렇다면 비슷하다는 것, 즉 유사도가 높다는것은 무슨 의미일까요? 데이터를 이루는 피처값이 비슷하다는 겁니다. 사용자 구매이력을 생각해보면, 비슷한 상품을 구매하는 사용자를 한데 묶을 수 있습니다. 또한 비슷한 연령대 사용자를 한데 묶을 수도 있습니다.
예를 들면 X는 사용자 1, Y는 사용자 2를 의미하고, 데이터를 X를 X={x1, x2, x3, x4,...xm}으로, 데이터 Y를 Y={y1, y2, y3....ym}으로 정의합니다. 여기서 m은 피처 수입니다. [표 4-1]을 기준으로 예를 들어보면 x1과 y1은 구매상품 개수, x2와 y2는 구매상품명 등으로 생각할 수 있습니다. 그럼 각 피처의 차이의 총합 가 적을 수록 비슷한 사용자라고 할 수 있습니다.
하지만 단순히 차이를 전부 한 피처에 대해 +가 나오고, 다른 한 피처에 대해 - 가 나오면 두 데이터 사이의 피처값이 많이 다름에도 불구하고 전체 합이 0이 될 수 있습니다. 따라서 단순한 차이 대신 그 차이를 제곱한 값을 더하여 얻어진 값에 루트를 사용합니다. 수학적 정의는 다음과 같습니다.
식4-1 유클리드 거리(민코스키 거리의 특수한 경우)
d(X, Y) = 루트(합((xi-yi)제곱))) = ||X-Y||
그러고 보니 이 식은 두 점사이의 최단 거리인 유클리드 거리(Euclidean distance)를 구하는 식과 같습니다. 일반적으로 유클리드 거리를 구하는 식은 계산의 편의를 위해 위 식을 제고(제곱유크릴드 거리)하여 사용합니다.
여기서 궁금한 점이 생깁니다. 가격 2만원과 1뭔의 차이인 100000이, 연령 25세와 25세의 차이인 1과 같다고 할 수 있을까요? 또 연령의 차이를 뺄셈으로 구하는 것 이해가 되는데, 상품명의 차이는 어떻게 구할 까요? 성별의 차이는 수학적으로 표현할 수 있을까요? 이에 대해서는 3.3절 '데이터 표준화'에서 살펴본 데이터 표준화 방법을 적ㅇ둉하면 됩니다. 또한 [식 4-1]에서 차이값을 제곱하지 않고 그냥 절댓값을 취하면 어떻게 될까요? 이에 대해서는 4.5절 '유사도 계산'에서 자세히 살펴보겠습니다.
4.2 K-중심 군집화
이 절에서는 중심기반 군집화의 대표적인 예인 K-중심 군집화( K-centroid clustering, K-medoid clustering) 에 대해 알아보겠습니다. 중심 기반 군집화는 클러스터 중심점을 정한 후 클러스터 중심점에 가까운 데이터들을 모아가며 클러스터를 확장하는 방법으므로 군집화 초기에 몇 개의 중심점을 어떻게 배치하는가 중요합니다. K-중심 군집화는 초기에 K개의 중심점을 랜덤으로 선택하여 군집화를 시작합니다.
중심 기반 군집화에서는 클러스터 수 K와 함께 클러스터 중심을 어떻게 구하느냐를 정할 필요가 있습니다. 여러 데이터를 요약하는 방법으로 평균(mean), 대푯값(median), 최빈값(mode)이 있다는 것은 아실 겁니다(중학교 때 변량, 도수 등을 배운 것 기억하시죠?). 클러스터 안의 데이터의 평균값을 중심으로 사용할 경우 K-평균 군집화, 대푯값을 사용할 경우 K-대푯값 군집화( K-medians clustering),최빈값을 사용할 경우 K-최빈값 군집화(K-modes clustering)라고 합니다. 3가지 방법 모두 중심점을 구르는 방법이 다를 뿐 전체 군집화 과정은 동일합니다.
K-주심 군집화는 다음 절차를 거칩니다.
1. 임의로 뽑은 K개의 데이터를 중심으로 하는 클러스터를 만듭니다(각 클러스터가 데이터 하나씩만 가지고 있습니다.)
2. 각 데이터와 클러스터 중심 간의 거리를 계산합니다.
3. 데이터와 클러스터 중심 간의 거리가 가장 짧은 클러스터에 데이터를 할당하고 이 데이터를 포함하여 클러스터 중심을 업데이트 합니다.
4. 과정 2와 3을 반복하는데, 과정3에서 더 이상 중심의 위치가 변하지 않거나, 업데이트 정후 중심점의 차이가 사용자가 지정한 허용 오차 이내이거나, 사용자가 지정한 횟수만큼 반복했으면 종료합니다.
과정 3에서 가장 가까운 클러스터에 데이터를 할당하고 있습니다. K-중심 군집화에서 클러스터는 클러스터 중심으로 정의되므로 결국 데이터와 클러스터 중심 사이의 거리가 가장 짧게 되는 클러스터를 찾는 것입니다.[식4-2]를 이용하여 거리를 구할 수 있습니다.
식4-2 클러스터 안의 데이터와 클러스터 중심 간의 거리
||x-uc||2
x:데이터
uc: 클러스터 c의 중심점
군집화는 각 데이터에 대해 [식4-2]의 값을 최소화하는 것이 목적입니다. 그런데[식4-2]를 자세히 보면 2.2.1절 '산술 손실함수'에서 살펴본 제곱 손실함수와 같은 형태임을 알 수 있습니다.
loss(f) = (y - y')2
f : 모델
y : 데이터로부터 주어진 출력
y':모델에 데이터로부터 주어진 입력을 넣어서 계산한 값
따라서 분산 분석과 같은 제곱 손실함수에 대한 이론을 k-중심 군집화에도 적용할 수 있습니다.
과정 4에서 업데이트가 없으면 군집화를 종료한다고 했는데, 이는 각 데이터가 속한 ㅡㄹ러스터가 변하지 않는 것, 즉 수렴한다는 것을 의미합니다. 하지만 수렴되기까지 상당한 시간이 걸리므로 실제로는 이전 단계의 총제봅합과 새로 얻은 총제곱합의 차이가 허용오차보다 작거나, 클러스터 중심 업데이트의 횟수가 사용자가 지정한 반복 횟수 이상이면 군집화를 종료합니다.
클러스터 수 k, 허용 오차, 반복 횟수 모두 사용자가 지정하는 파라미터이므로 군집화 결과를 보면서 바꾸어 가며 시도해야 데이터를 잘 표현하는 그룹을 만들 수 있습니ㅏㄷ. 이에 대해서는 10장에서 실제 구현을 통해 설명하겠습니다.
4.3 계층적 군집화
계층적 군집화에서의 계층은 클러스터의 계층을 의미합니다. 최상위 계층의 클러스터느 모든 데이터를 포함하는 하나의 클러스터입니다. 최하위 계층의 클러스터는 단 하나의 데이터만을 포함하므로 클러스터 수가 데이터 수와 같게 됩니다.
계층적 군집화에는 하향식인 분할적 군집화와 상향식인 집괴적 군집화가 있습니다. 사용자가 클러스터 수 k를 명시적으로 지정하는 k-중심 군집화와는 달리 계층적 군집화는 클러스터 수를 지정할 필요가 없습니다.
계층적 군집화는 각 단계마다 한 쌍의 클러스터를 비교하는데, 하향식인 분할적 군집화는 각 계층의 클러스터들을 둘로 쪼개어 하위 계층으로 진행하고, 상향식인 집괴적 군집화는 각 계층의 클러스터들 중에 가장 가까운 두 개를 하나로 합쳐 상위 계층의 클러스터를 만들어 갑니다.
같은 계통 트리를 만들어서 클러스터의 생성 과정을 시각화하기 쉽다는 것이 계층적 군집화의 장점입니다.
계층적 군집화에서 주의할 점은 늘 한쌍의 클러스터만 비교한다는 겁니다. 예를 들어 데이터에 실제 존재하는 클러스터가 3개라고 하더라도 계층적 군집화는 2n개의클러스터만 생성할 수 있으므로 데이터를 잘 표현할 수 없습니다. 군집화 전에 클러스터 수가 2n개가 아니라는 점을 알고 있다면 계층적 군집화보다는 k-중심 군집활르 하는 것이 좋습니다.
k-중심 군집화에서는 k 값에 따라 군집화의 성능이 달라지는 것과 마찬가지로, 계층적 군집화에서는 클러스터를 어느 정도로 분할 혹은 병합하는가에 따라 군집화의 성능이 달라집니다. 계통 트리를 통한 클러스터 생성 과정, 클러스터 안의 데이터 분석 등을 통해 적절한 클러스터를 찾을 수 있습니다.
집괴적 군집화는 다음 순서로 진행됩니다.
1. 하나의 데이터를 하나의 클러스터로 지정합니다.
2. 과정 1의 클러스터들에 대해 가장 유사도가 높음 클러스터 둘을 하나로 합칩니다.
3. 과정 2에서 생성된 클러스터들에 대해 다시 같은 과정을 반복합니다.
클러스터 둘을 합칠 때 복수의 데이터로 이루어진 클러스터 간의 유사도를 측정할 필요가 있습니다. 대표적인 방법으로 단일 연결법(single linkage), 완전 연결법(complete linkage), 평균 연결법(averae linkage)이 있습니다.
- 단일 연결법: 두 클러스터에 속하는 데이터들의 거리 중에 가장 짧은 거리를 클러스터 사이의 거리로 간주합니다.
- 완전 연결법: 두 클러스터에 속하는 데이터들의 거리 중에 가장 먼 거리를 클러스터 사이의 거리로 간주합니다.
- 평균 연결법: 두 클러스터에 속하는 데이터들의 거리 평균을 클러스터 사이의 거리로 간주합니다.
그럼 어떤 방법이 가장 나을까요? 알핏 생각하면 클러스터 안의 모든 데이터를 고려하는 평균 연결법이 가장 나을 것 같기도 합니다. 하지만 평균 연결법을 사용할 경우 한 쌍의 클러스터 내의 모든 데이터의거리를 구하므로 다른 두 방식보다 계산량이 늘어난다는 단점이 있습니다. 데이터에 따라서는 평균 연결법 혹은 단일 연결법이 시간이 덜 걸리고 비슷하게 좋은 클러스터를 만들 수도 있습니다.
분할적 군집화의 대표적인 예인 DIANA(Divisive Analysis,분할 분석)는 다음과 같은 과정으로 클러스터를 생성합니다.
1. 현 단계에서 가장 큰 클러스터를 선택합니다(가장 처음에는 전체 데이터를 하나의 클러스터 안에 있다고 간주합니다).
2. 과정 1의 클러스터에서 가장 이질적인 데이터를 찾습니다. 예를 들어 클러스터 안의 데이터 평균에서 가장 동떨어진 데이터를 찾습니다. 이로써 클러스터를 평균점과 이질적인 점을 중심으로 하는 두 클러스터로 나눌수 있게 됩니다.
3. 과정 2에서 구한 두 점(평균점, 이질적인 점)과 각 데이터의 거리를 계산하여 거리가 짧은 점을 중심으로 하는 클러스터로 데이터를 할당합니다.
4. 한 클러스터가 데이터 하나만을 가질 때까지 과정 1, 2, 3을 반복합니다.
분할적 군집화도 앞에서 사용한 연결 방법을 통해 거리를 측정합니다. 하지만 모든 데이터를 작은 클러스터로 나눈다는 점에서 k-중심 군집화와 굉장히 비슷한데다가, 클러스터 수의 지정이 자유로운 k-중심 군집화와는 달리 클러스터 수가 2n개라는 제약이 있어 많이 상요하지 않습니다.
4.4 밀도 기반 군집화
밀도 기반 군집환느 데이터 밀도가 높아지는 방향으로 데이터를 군집화하는 방식입니다. 대표적인 밀도 기반 군집화 알고리즘에는 평균이동 군집화와 DBSCAN(density-based spatial clustering of application with noise, 디비크캔, 노이즈를 가지느 애플리케이션의 밀도 기반 공간 군집화)이 있습니다.
평균이동 군집화는 다음과 같은 과정으로 진행됩니다.
1. 임의의 초기 중심점을 지정하거나 탐색 반경을 지정합니다(초기 중심점을 지정하지 않으면 탐색 반경에 따라 초기 중심점이 결정됩니다.)
2. 지정한 줌심점으로부터 지정한 반경을 가지는 원 안의 밀도를 계산한 후 데이터의 밀도가 가장 높은 곳을 중심점으로 재지정합니다. 데이터의 밀도가 높다는 것은 데이터가 많이 몰려 있다는 뜻입니다. 일반적으로 데이터가 정규분포(가우스분포)를 따른다고 가정하여 평균 주변에 데이터가 가장 많이 몰려 있다고보고, 반경 안의 평균점을 새로운 중심점으로 지정합니다.
3. 더 이상 중심점이 이동하지 않을 때까지, 즉 중심점이 가장 밀도가 높은 곳으로옮겨질 때까지 단계2를 반복합니다.
4. 단계 3에서 얻은 마지막 중심점이 클러스터의 중심점이 되고, 중심점의 이동 경로에 있던 데이터들을 그 클러스터에 할당합니다.
평균이동은 중심점을 갱신한다는 점에서 중심 기반 군집화로 분류되기도합니다.
DBSCAN은 반경의 밀도가 아니라 클러스터 중심의 데이터 수와 가장자리의 데이터 수의 차이를 이용합니다. DBSCAN에서 데이터는 다음과같이 나누어집니ㅏㄷ.
- 중심 포인트(core point):반경e 안에 일정 개수 이상의 데이터가 존재하는 데이터
- 경계 포인트(border point): 반경 e안에 있는 데이터 수는 중심 포인트보다 적지만, 중심 포인트로부터 반경 e안에 존재하는 데이터
- 노이즈 포인트(noise point): 중심 포인트도 경계 포인트도 아닌 데이터
데이터셋 안의 모든 데이터를 군집화하는 k-중심 군집화나 계층적 군집화와는 달리 밀도 기반 군집화는 예외 데이터를 정의하고 군집화 과정에서 그것을 제외시킵니다. 또한 클러스터 중시과 데이터 사이의 거리를 이용하는 k-중심 군집화와 계층적 군집화의 경우 중심으로부터의 같은 거리 안에 있는 모든 점이 같은 클러스터에 속하게 되므로 클러스터가 원형이 될 수빡에 없습니다. 이와 달리 밀도 기반 군집화는 클러스터 모양에 제한이 없습니다.
4.5 유사도계산
이 절에서는 유사도를 측정하는 대표적인 척도인 '거리'에 대해 자세히 살펴보겠습니다. 유사도가 클수록 거리가 줄어들기 때문에 거리가 가깝다는 것은 유사도가 크다는 것이고 거리가 멀다는 것은 유사도가 작다는 겁니다. 이절에서는 다음과 같은 거리를 다룹니다.
- 민코스키 거리: 벡터 공간 안의 두 점 사이의 거리
- 마할라노비스 거리: 점들의 분포를 고려한 거리
4.5.1 민코스키 거리
4.1절'군집화'에서 데이터를 X를 X={x1,x2, x3....xn},데이터Y를 Y={y1, y2, y3...yn}, 피처수를 m이라고 했을 때의 유크릴드 거리를 정의했습니다. 그때의 [식 4-1]은 민코스키 거르의 특수한 경우입니다. 민코스키 거리는 다음과 같이 정의됩니다.
식 4-3 민코스키 거리
d(x,y)= p루트(총함(i=1~m)|xi-yi|p
여기서 p가 1일 때는 맨해튼 거리(Manhattan distance혹은 L1 거리), 2일 때는 유크릴드 거리(Euclidean distance 혹은 L2거리)라고합니다.
맨해튼 거리는 맨해튼의 도로가 격자 모양으로 되어 있어서 한 빌딩에서 다른 빌딩까지의 이동 거리가 두 좌푯값의 차의 절댓값을 합한 값이 되기 때문에 붙여진 이름입니다.
그럼 언제 맨해튼 거리를 쓰고 언제 유클리드 거리를 싸야 할까요? 맨해튼 거리는 거리 계산 시 제약이 있을때 유용합니다. 예를 들어 체스 케임의 경우 각각의 말은 체스 판 위를 자유롭게 움직이는 게 아니라 움직일 수 있는 방향이 정해져 있습ㄴ디ㅏ. 비숏과비숍이 이동할 수 있는 칸을 별표로 표시했습니다. 체스 판의 한칸 (a1,b1)에서 다른칸(a2, b2)으로 비숍을 이동시킬 때 비숍의 디오거리는 루트 | a2 -a1| + | b2 - b1|이 됩니다. 반면 유클리드 거리는 두점 사이의 최소거리르 제약 없이 구할 때 사용합니다.
4.5.2 마할라노비스 거리
마할라노비스 거리(Mahalanobis distance)는 두 점사이의 거리를 계산할 때 데이터의 분포를 고려하는 거리입니다.
공분산이란 두 변수가 얼마나 연관성이 있는지 나타내는 값입니다. 공분산의 값이 1이면 한 변수가 증가할 때 다른 변수도 증가하는 것이고, -1이면 한 변수가 증가할 대 다른 변수가 감소하는 것입니다. 0이면 두 변수 간에 아무런 상관관계가 없습니다. X와 Y가 k개의 실수로 이루어진 벡터고, ux가 X값의 평균, uy가 Y값의 평균일 때, 공분산은 다음과 같이 계산합니다.
Cov(X,Y) = E[(X-ux)(Y-uy)]
X = {x1, x2, x3...,xk}
Y = {y1, y2, y3...yk}
ux = X값의 평균
uy = Y값의 평균
공분산을 고려한다는 것은 X값의 증감과 Y값의 증감의 관계를 거리 계산에 넣는 것입니다. 즉, 키가 평균에서 표준편차의 2배로 멀어지는 것은 몸무게가 평균에서 표준편차의 1배로 멀어지는 것과 같다는 정보를 이용합니다.
마할라노비스 거리는 거리 계산에 위에서 설명한 데이터의 분포 및 공분산을 사용합니다. 수식으로 표현하면 다음과 같습니다.
d(x,y) = 루트((xi-yi)TS-1(xi-yi))
2018년 3월 3일 토요일
CHAPTER3 데이터와 문제
데이터는 머신러닝에서 정말 중요합니다. 머신러닝은 데이터를 이용해서 문제를 해결하기 때문에 데이터의 중요성은 몇 번이고 강조해도 부족하지 않습니다. 머신러닝으로 문제를 풀 때는 데이터형, 품질, 양에 따라 고려해야 할 점이 달라집니다. '풀고자 하는 문제 유형' 역시 중요합니다. 어떤 문제를 풀지에 따라 고민거리가 달라지기 때문입니다. 이 장에서는 데이터와 풀고자 하는 문제 유형에 대해 알아봅시다.
- 데이터형에 따라 머신러닝에서 고려해야 할 점
- 데이터양과 품질이 달라질 때 고려해야 할 점
- 데이터에 치우침이 있을때 표준화하여 효율적으로 학습하는 방법
- 머신러닝 문제 휴형과 다양한 기법
3.1 데이터 형
데이터에도 유형이 있을까요? 있다면 머신러닝과 데이터형 간에는 어떤 연관이 있을까요? 이 절에서는 데이터형별 특징을 살펴보고 각각의 특징에 따라 어떤 머신러닝 기법을 사용하는지 살펴보겠습니다. 2장에서 설명한 바와 같이 머신러닝의 모델을 결정한느 것은 데이터에 대한 믿음(가정)입니다. 만일 우리가 다루는 데이터가 어떤 유형인지 알고 있다면 당연히 모델을 선택하는 일도 쉬워질 겁니다. 그리고 거의 대부분의 경우 데이터양이나 품질 때문에 생기는 고민에 비해 데이터형에 의한 고민은 쉽게 해결할 수 있습니다.
3.1.1 텍스트 데이터
텍스트 데이터는 다른 데이터에 비해 쉽게 구할 수 있습니다. 예를 들어워키피디아 덤프 파일이라든가 전자책 등 다양한 유형의 공개 텍스트 데이터가 있습니다. 또한 데이터의 의미 단위가 개별 단어나 연속하는 단어로 이루어져 있으므로 다른 데이터에 비해 의미 단위를 족각내기도 쉽습니다. 하지만 단위 데이터를 쉽게 조각내기 힘든 이미지 데이터나 음성 데이터와 달리 의미를 파악하려면 단어 간의 관계를 유추하거나 문법을 분석해야 합니다. 또한 의미 단위인 단어의 유형이 너무 다양하다는 문제도 있습니다. 텍스트 분석이 언뜻 보면 쉬워보이지만 이러한 문제들 때문에 사실은 굉장히 까다롭습니다.
특히 행렬을 이용해서 문서의 단어 분포를 표현하는 경우에는 존재하는 단어에 비해 문서에 나타나는 단어가 적으므로 행렬의 원소 대부분의 0인 상황이 많이 발생합니다. 이런 경우 데이터가 굉장히 희박(sparse)하다고 표현하며, 이러한 희박한 데이터를 이용하여 머신러닝을 하면 모델 학습이 잘못되어 실전 성능이 제대로 나오지 않게 됩니다. 그렇기 때문에 텍스트를 모델링하려면 이러한 상황을 해결해야 합니다.
또한 텍스트 데이터는 순서가 중요합니다. 순서에 따른 의미 변화를 모델링하기 위해서는 순차 모델 머신러닝 기법을 사용해야 합니다.
3.1.2 수치 데이터
수치 데이터는 금융, 생산 공정, 관측 장치 등으로 부터 얻을 수 있습니다. 많은 양을 쉽게 얻을 수 있지만 항상 노이즈가 섞여 있습니다. 노이즈가 생기는 이유는 다양하지만 일반적으로는 센서 노이즈와 현상의 무작위성이 원인입니다. 그렇기 때문에 수치 데이터 분석에는 노이즈에 강한 머신러닝 기법을 사용합니다.
수치 데이터는 대부분 수수자 피처를 가지기 때문에 텍스트에 비해 데이터 밀도가 높습니다. 그렇기 때문에 텍스트 데이터와는 다른 방식으로 접근해야 합니다.
텍스트 데이터와 비슷하게 데이터의 순서가 중요한 수치 데이터도 있습니다. 예를 들면 주식 데이터를 생각해 볼 수 있는데, 굉장이 노이즈가 많기 때문에 노이즈에 강한 숫차 모델, 예를 들면 칼만 필터(Kalman Filter)와 같은 모델을 사용합니다.
3.1.3 이미지 데이터
이미지 테이터는 최근 들어 주목받고 있습니다. 스마트폰, 디지털 카메라, 사진 및 동영상 공유 서비스가 발전함에 의해 사용할 수 있는 데이터양이 급격하게 늘어났고, 이는 데이터양이 많을 수록 성능이 향상되는 딥러닝을 이용한 모델이 전에 없던 성능을 낼 수 있게 해주었기 때문입니다.
이미지 데이터의 가장 큰 특징은 값(색상, 채도, 명도 등)과 위치에 따라 점의 의미가 달라진다는 겁니다.
이미지 데이터는 각 구역을 픽셀로 쪼개어 표현하게 되는데, 주변 픽셀에 따라 한 픽셀이 갖는 값의 의미가 달라집니다. 예를 들어 검은 구역이 있을 경우, 주변이 모두 검은 구역이면 사과의 가운데, 오른쪽 옆이 흰구역이면 사과의 가장자리가 되는 식이죠. 이렇게 픽셀 데이터가 근접한 픽셀 데이터와 강한 연관 관계를 가지는 것이 이미지 데이터의 가장 큰 특징입니다.
이러한 근접 연관성을 효과적으로 모델링하는 데는 '그래프 모델'에서 다룬 구조 모델 등을 사용합니다. 예를 덜어 대표적인 딥러닝 기법인 CNN(Convolutional Neural Network)은 한 픽셀과 그 주변 픽셀의 값을 동시에 고려하여 피처를 생성합니다.
비디오 데이터도 머신러인에 사용됩니다.예를 들어 비디오를 이용한 머신러닝을 할 경우, 비디오를 연속되는 이미지로 분할하여 사용하게 됩니다. 이때 연속된 이미지가 어떤 장면을 나타내는지 학습하기 위해 앞서 설명한 구조 모델을 확장시켜 화면의 위치 근접도와 시간 근접도를 복합적으로 사용하는기법이 적합합니다
3.1.4 음성 데이터
음성 데이터는 수치 데이터와 많은 부분에서 비슷합니다. 노이즈가 많으며 특히 시간에 따른 변화가 중요하기 때문에 시간에 따른 변화를 모델링할 수 있는 머신러닝 기법이 사용됩니다. 하지만 몇 가지 주목할 점이 있습니다.
음성 데이터는 소리에 관한 데이터이므로 일반적으로 소리의 높낮이나음색을 신호 처리 기법으로 전처리한 후에 사용하게 됩니다. 때표적인 기법으로 스펙트로그램(spectrogram)이나 퓨리에(Fourier)피처 등이 있습니다. 이러한 신호 처리 기법을 적용하여 얻은 피처와원래 신호를 조합하여 머시너닝을 하는 경우가 많습니다.
또한 데이터를 수집할 때 보통 한 가지 소리만 깔끔하게 얻는 경우는 많지 않습니다. 그래서 녹음된 소리를 여러 가지 소리로 나누는 머신러닝 기법을 사용한 이후에 앞서 언급한 기법을 일반적으로 사용합니다.
3.1.5 복합 데이터
복합 데이터는 다양한 유형의 데이터가 썩여 있는 것을 말합니다. 단순하게 생각하면 데이터의 특성에 따라 각기 다른 머신러닝 기법을 적용하여 분석을 할 수도 있지만, 연관 관계를 이용해서 여러 기법을 한번에 학습시키는 방법도 생각할 수 있습니다.
예를 들어 비디오(이미지 데이터, 음성 데이터)와 자막(텍스트 데이터)이 동시에 입력으로 들어오는 경우를 생각할 수 있습니다. 시간대별 이미지, 음성, 자막이 연관 관계를 가지고 있기 때문에 이러한 연관 관계를 한번에 학습할 수 있는 기법을 사용하면 각각을 따로 처리하는 것보다 더 좋은 결과를 얻을 수 있습니다. 최근에 각광받는 방법으로는 이미지 분류와 자막분류를 하는 신경망 모델을 동시에 중간 레이어를 공유해서 학습합니다. 그 결과로 이미지를 입력하면 그 이미지에 무엇이 있는지 텍스트로 설명할 수 있습니다.
3.2 데이터양과 품질
데이터양과 품질은 유형만큼이나 머신러닝 기법의 선택과 적용에 큰 영향을 끼칩니다. 이미 2장에서 간단히 설명했지만 여기서는 머시러닝을 실제로 적용할 때의 고려사항을 자세히 살펴보겠습니다.
3.2.1 데이터양과 머신러닝의 연관성
모든 머신러닝 기법은 데이터가 적으면 유용한 학습 결과를 얻기 힘듭니다. 그렇기 때문에 데이터를 많이 모으는 것은 괸장히 중요합니다. 하지만 데이터가 많더라도 특정 항목(레이블)에 대한 데이터가 적으면 해당 부분에 대해서는 학습이 제대로 되지 않을 수 있습니다. 이 문제는 이미지 태깅처럼 분류 항목 수가 굉장히 많은 경우에 비번히 발생합니다. 구체적으로 다음 세 경우를 들 수 있습니다.
데이터의 레이블을 얻기 굉장히 어려운 경우
사람이 일일이 예측 결과(예를 들면 이미지 태깅이나 문법 파싱)를 제공하는 경우에는 큰 비용이 들기 때문에 데이터의 레이블을 얻기 어렵습니다. 이런 경우에는 학습이 잘되지 않은 부분을 머신러닝 시스템이 사람에게 질문하는 방식으로 학습 성능을 높이는 액티브 러닝(active learning)기법을 사용하는 것도 좋습니다.
데이터양은 많지만 특정 레이블의 데이터가 부족한 경우
데이터가 전체적으로는 많지만 레이블 유형이 다양해서 어떤 레이블은 굉장히 적은 수의 데이터만 가지는 경우입니다. 사람이 레이블을 태깅하는 경우에는 특정 레이블로의 쏠림이 더 많이 발생해서 이 현상이 더 심각해지는 경우가 많습니다. 균형이 맞지 않게 되면 손실 함수는 데이터가 많은 레이블에 집중하고 나머지를 무시하기 때문에 균형잡힌 성능을 보여주지 못합니다.
이를 해결하려면 데이터양이 많은 레이블에서 임의로 데이터를 누락시켜(과소표집) 데이터의 균형을 맞추거나, 데이터가 적은 경우에도 잘 동작하는 원샷러닝(one-shot learning, 또는원샷학습)등의 기법을 사용할 수 있습니다. 데이터를 한 번만 보고 학습이 가능하다고 해서 원샷러닝이라 부릅니다.
데이터양은 많지만 특정 경우의 데이터가 아예 없는 경우
데이터가 들어오는데 새로운 레이블이 끊임없이 생성되는 경웅에는 제로샷러닝(zero-shot learning, 또는 제로샷학습)을 사용합니다. 대표적인 제로샷러닝 기법으로 다른 형식의 데이터를 이용해서 유사도를 판별하여 새로운 레이블을 학습 데이터 없이 예측하는 방법이 잇습니다. 예를 들어 비디오와 자막이 입력으로 주어지는 경우 자막의 유사도를 판단하여 이미지의 내용을 추측하는 방식을 사용할 수 있습니다.
3.2.2 데이터 품질과 머신러닝의 연관성
데이터양뿐만 아니라 품질도 머신러닝 기법 선택에 여향을 미칩니다. 데이터 품질은 노이즈양에 가장 크게 여향을 받습니다. 유용한 피처와 함께 레이블이 주어졌을 때 레이블의 무작위성에 따라 머신러닝 기법이 달라집니다. 데이터 품질에 따른 고려 사항은 다음과 같습니다.
관측 데이터에 노이즈가 많은 경우
무작위성이 높아 노이즈가 많은 경우는 수치 데이터에서 굉장히 비번하게 발생합니다. 데이터 양이 적으면 성능에 더 큰여향을 주는데, 이를 해결하려면 '간단한 모델'을 사용합니다. 간단한 모델은 가정이 가합니다. 이 모델은 유연하지 않고 노이즈에 강하기 때문에 이 경우에 적합한 모델이라고 볼 수 있습니다. 노이즈 자체를 가정에 두는 칼만 필터 같은 모델도 많이 이용됩니다.
레이블이 일정하지 않은 경우
크라우드 소싱이나 여러 사람이 수작업으로 레이블링하는 경우에는 작업자의 데이터나 레이블에 대한 해석 차이로 레이블이 통일되지 않을 수 있습니다. 예를 들어 단어가 긍정적인지 부정적인지 수치로 나타내는 작업을 여럿이서 한다고 가정합시다. A는 전반적으로 점수를 낮게 주고 B는 높게 주었다고 합시다. 이런 경우에는 평가자로 인한 편차를 줄이는 전처리 작업이 필요합니다. 수치값이라면 레이블의 평균과 분산을 구해서 맞춰주는 경우가 일반적이고, 여러 항목에서 고르는 작업의 경우에는 몇 명 이상이 동일한 항목을 고른 경우에만 그 값을 활용하는 방법이 있습니다. 즉, 전처리 작업을 한 후 에 머신러닝을 적용합니다.
3.3 데이터 표준화
데이터 척도 및 단위를 변환하여 데이터를 비교하기 쉽게 만드는 방법을 데이터 표준화(data normalization)라고 합니다. 데이터형에 따라서 데이터 표준화의 방식이 달라집니다. 여기서 다룰 데이터형에 따른 표준화 방법은 담음과 같습니다.
- 수치 데이터 표준화: 단위가 다른 수치 데이터를 비교하기 위해 데이터르 ㄹ변환하는 기법입니다.(예를 들면 키와몸무게_
- 카테고리 데이터 표준화: 숫자로 표현할 수 없는 항목을 수치 데이터로 변환하는 방버입니다.(예를 들면 성별)
- 서수 데이터 표준화: 순서 혹은 크기가 있는 데이터의 카테고리 차이를 나타내기 위해 데이터를 변환하는 기법입니다.(예를 들면 영화 별점, 설문지 응답등)
3.3.1 수치 데이터 표준화
키와 몸무게, 구매상품 수와 구매 가격 등 단위가 다른 두 수치 데이터를 직접 비교하는 것은 의미가 없습니다. 이럴 때는 각 데이터를 평균이 0, 표준편차 1인 데이터로 바꾼후 비교하면 효과적입니다. 이런 식으로 데이터를 변환하는 방법을 z-점수 표준화(z-score standardization)라고 합니다.
z-점수 표준화 외에 데이터를 변환하는 방법으로 척도화와 벡터 정규화가 있습니다. 척도화(scaling)는 피처의 최댓값 및 최솟값을 이용하여 피처 값의 범위를 조정하는 방법입니다. x를 원래 데이터의 피처값, x1를 변환된 피처값으로 표기할 때의 척도화를 식으로 나타내면 다음과 같습니ㅏㄷ.
척도화
x1 = (x - min(x)) /(max(x)-min(x))
벡터 정규화(vector normaliztion)는 한 데이터 가지는 피처 벡터의 노름(norm)이 1이 되게 하는 방법입니다. 노름은 벡터의 크기라고 볼수 있으며, 벡터 x의 노름은 ||x||로 표시하고 다음 식으료 계산합니다.
||x|| = 루트(xx1 + xx2 + xx3 .... xxn)
x1 = x / ||x||
즉, 벡터 정규화는 벡터의 각 요소를 벡터의 총 크기로 나누는 것이라고 볼 수 있습니다. 이렇게 하면 크기가 다른 벡터들의 요소를 비교할 수 있게 됩니다.
예를 들어 구매 항목 피처 벡터가 디비디 카테고리 구매 수와 간식 카테고리 구매수로 이루어진 경우을 생각해 봅시다. 사용자가 A가 디비디10개와 감자침 5개를 샀고, 사용자 B가 디비디 6개와 감자칩 3개를 샀다고 했을 때, 구매한 상품의 총 수는 다르지만 두 사용자 모두 디비디를 감자칩의 2배수로 구매했다는 점에서 두 사용자는 비슷하다고 볼 수 있습니다. 이 경우 두 사용자의 벡터 크기는 다음과 같이 구합니다.
사용자 A 의 피처 벡터 크기 루트(10*10 + 5*5) = 11
사용자 B 의 피처 벡터 크기 루트(6*6 + 3*3) = 7
각 피처 요소를 나누면(즉, 벡터 정규화를 하면) 사용자 A의 피처 벡터는 (10/11, 5/11), 사용자 B의 피처 벡터는 (6/7, 3/7)로 비슷한 값이 됩니다. 이 두 벡터로 유클리드 거리를 계산하면 사용자 A와 B의 유사도가 높게 나올 겁니다.
데이터형과 데이터 값 및 학습에 사용하는 모델에 따라 데이터 표준화 방법을 바꾸는 것이 성능에 도움이 됩니다. 예를 들어 선형 희귀의 경우 z-점수 표준화를 하는 편이 성능도 좋고 이해하기 쉬운 결과가 나옵니다. 길이가 다른 문서 안의 단어 빈도를 피처 벡터로 바꾼 뒤 유사도를 계산할 경우 피처 벡터를 벡터 정규화한 후 계산해야 정확한 비교를 할 수 있습니다.
3.3.2 카테고리 데이터 표준화
상품 카테고리의 차이는 어떻게 계산해야 할까요? 상품명, 상품 카테고리, 성별과 같은 비수치 데이터 사이의 유사도 계산에는 원-핫 인코딩(혹은 더미코딩)방식을 이용합니다. 원-핫 이코딩이란 카테고리 데이터 값을 피처로 만든 후 1 또는 0으로 지정하는 방법입니다. 예를 들어 '성별:나성, 성별:여성' 대신 남성은 0, 여성은 1로 수치화하여 '성별:0, 성별:1' 로 표기하는 겁니다.
3.3.3 서수 데이터 표준화
카테고리 데이터이면서 카테고리에 순서가 있는 데이터형을 서수(ordinal)데이터라고 합니다.
서수는 설문조사 답변(예를 들면 '전혀 그렇지 않다, 그렇지 않다, 어느 쪽도 아니다, 맞다, 매우 맞다')이나 영화의 별점을 생각하면 쉽습니다. 담변이나 별점을 1 부터 5의 숫자로 표시하기는 하지만 가능한 별점이 카테고리로 정해져 있으므로 이는 수치 데이터가 아닙니다. 예를 들어 가능한 별이 1~5점인 경우와 1~10점인 경우, 별점 3점이 가지는 의미는 매우 다릅니다.
하지만 각 카테고리가 동등하지 않다는 점에서 카테고리 데이터도 아닙니다. 따라서 다순히 수치 데이터로 취급하여 평균과 분산을 이용해서 표준화하거나 원-핫 인코딩으로 변환하는 것은 적절하지 않습니다. 이러한 경웅에는 일반적으로 다음 식을 이용하여 0과 1사이의 값으로 표준화 합니다.
서수형 데이터 표준화
(t -1 /2 )/M, i = 1,2,...,M
t: 카테고리
M: 가능한 카테고리
옐르 들어 별점 5점 만점에 별점 3인경우에는 표준화한 값이 (3-1)/5 = 0.5가 됩니다. 별점 3점이 1점과 5점의 한가운데임을 생각하면 0.5로 표시하는게 적절한 것 같습니다. 별점 10점 만점에 별점 3인 경우에는 (3 - 12)/10 = 0.25로 다른 값이 나오게 됩니다.
0부터 1사이로 척도화하는 방법도 생각할 수 있지만, 이 경우 별점 1을 수치 0 으로 바꾸게 되므로 데이터의 의미가 약간 달라집니다. 별점 1과 수치가 존재하지 않음을 의미하는 0은 다르니까요.
3.4 문제 유형
지금까지 데이터양과 품질에 따른 고려 사항을 살펴봤습니다. 이 절에서는 문제 유형에 따른 고려 사항을 살펴보겠습니다. 문제 휴형은 크게 희귀, 분류, 군집화, 표현형학습으로 구분할 수 있습니다. 문제 휴형을 잘못 선택하면 원하는 답을 얻지 못할 수 있기 때문에 문제를 구별하기는 기준은 꼭 숙지해둬야 합니다.
3.4.1 회귀문제
회귀는 머신러닝 문제 중에서도 가장 기본입니다. 회귀는 간단히 말해 입력을 받아서 가장 적합한 숫자값을 예측하는 문제입니다. 옐르 들어 주어진 상황 정보를 종합해서 주식값이 얼마가 될지 예측하는 겁니다.
회귀를 가장 기본적인 문제로 보는 이유는 출력된 결과를 해석하는 방식에 따라 여러가지 문제를 풀 수 있기 때문입니다. 예를 들어 출력된 숫자를 확률로도 특정 항목 번호로도 해석할 수 있습니다. 그렇기 때문에 어떤 문제를 풀더라도 결귀에는 회귀문제로 환원되는 경우가 많습니다.
회귀문제에 사용되는 머신러닝 기법은 다른 머신러닝 기법의 기초가되는 경우가 많습니다. 회귀문제를 푸는 기법으로는 통계학에서도 널리 쓰이는 선형회귀, 가우시안 프로세스 회귀, 칼만 필터가 유명합니다.
3.4.2 분류문제
분류는 회귀만큼이나 기본적인 머신러닝 문제입니다. 회귀 기법으로 분류 문제를 풀 수도 있지만, 보통은 분류에 해당하는 손실함수를 직접 최적화해서 푸는 방법을 많이 사용합니다. 예를 들어 인터넷 신문기사의 단어들을 가지고 이 기사가 어떤 분류 항목에 해당하는지 선택하는 경우들 들 수 있습니다.
분류 문제에서는 주어진 입력에 대해 여러가지 가능한 항목 중에서 한 가지를 선택하는 경우가 많지만, 기법에 따라서는 여러가지 선택하기도 합니다. 여러가지 항목에서 하나를 고르는 문제를 멀티 클래스(혹은 다중 클래스)분류라고 부르고, 복수를 고르는 문제를 멀티레이블(혹은 다중 레이블) 분류라고 부룹니다.
분류는 주어진 이미지가 어떤 항목에 속하는지 판별하는 문제, 생산 공정에서 제품의 데이터로 하자를 판별하는 문제, 텍스트의 의미가 어떤 항목에 해당하는지 판별하는 문제 등 셀수 없이 다양한 경우에 사용됩니다. 이전 시간을 고려한 주어진 상황을 보고 여러가능성이 있는 답 중에서 하나를 고르는 경우는 모두 분류 문제입니다.
분류문제를 풀 때는 기본적으로 로지스틱 회귀, 서프트 벡터 머신, 신경망 등 사용합니다. 그리고 입력이 시간에 따라 변화하는 데이터를 모델링 하는 경우에는 CRF나 RNN등을 사용합니다.
3.4.3 군집화 문제
군집화는 비슷한 성격의 데이터를 묶는 머신러닝 문제입니다. 물론 각각의 입력을 받아 출력으로 항목(레이블)을 예측하는 분류 문제로 볼 수도 있지만, 가장 큰 차이는 항목이 주어지는 것이 아니라 데이터에 내재된 분류를 찾아내야 한다는 점입니다. 즉, 받은 입력을 비슷한 성격별로 묶어야 합니다.
군비화 문제는 현재 주어진 데이터를 분석하는 데 사용합니다.(기존 데이터와 비교하여 미래 데이터에 대해서도 군집화를 할 수 있습니다). 예를 들어 비슷한 비디오나 문서끼리 묶어서 찾기 편하게 만들거나 비슷한 사용자를 군집화해서 군집별 특성을 살펴보는 경우를 떠올릴 수 있습니다. 군집화 기법을 문서에 활용한 것으로는 토픽 모델링이 유명합니다. 이 기법은 많은 그리고 레이블이 없는 문서에서 공통 주제를 발견하여 문서를 주제별로 묶습니다.
군집화 문제에서 가장 중요하게 여기는 것은 데이터 유사도(혹은 유사상)입니다. 이 유사도를 어떻게 정의하느냐에 따라 군집화 결과가 크게 달라집니다. 옐르 들어 이미지 데이터에서 크기에 따른 군집화와이미지 색상에 따른 군지화의 결과는 크게 다릅니다.
군집화 깁버은 종종 입력을 간추리는 용도로도 사용됩니다. 입력을 직접 사용하는 대신 군집에 얼마나 속하는지에 대한 강도를 이용해서 입력을 간단하게 표현해서 사용하는 것 입니다. 이렇게 변형된 군집화 기법은 다음 절에서 소개할 표현형학습과 유사한 역할을 수행하게 됩니다.
대표적인 기법으로는 K-평균 군집화(K-means clustering)와 평균이동 군집화(mean shift)가 있습니다. 문서를 토픽에 따라 군집화하는 방법은 토픽 모델링이라고 부르는데, LDA(latent dirichlet allocation, 잠재 디리클레 할당)라는 기법이 유명합니다. 엄밀하게 따지자면 군집화와 토픽 모델링은 접근하는 관점이 약간 다르지만, 비슷한 성격을 묶는다는 의미에서 이처럼 분류해보았습니다.
3.4.4 표현형학습(임베딩학습)
표현형학습은 앞서 설명한 3가지 문제보다 더 근본적인 문제를 다루는데 사용합니다. 표현형 학습은 풀고자 하는 문제에 적합한 표현형(representation)을 데이터로부터 추출하는 것을 말합니다. 표현형과 데이터로부터 직접적으로 추출하는 피처 차이는 다음과 같습니다.
1. 표현형은 피처보다 훨씬 더 간략합니다(표현형의 차원이 훨씬 더 낮습니다.)
2. 피처는 높은 차원을 가지는 경우가 많기 때문에 많은 부분이 0으로 차 있습니다(희박하다고 표현합니다). 표현형은 훨씬 낮은 차원으로서 데이터를 잘 설명해야 하기 때문에 밀집도가 높습니다.
블로그에 올린 일기를 통해 그날그날마다 글쓴이가 느낀 감정을 파악하려 하는 경우를 생각해봅시다. 분류 문제를 이용해서 푼다면 일기 문서의 구성 단어를 한하나 '긍정적'인 것과 '부정적'인 것으로 분류하고 그 수를 비교해서 날마다에 대해 긍정과 부정 두 가지 중 하나를 고르게 하는 문제로 환원할 수 있을 겁니다. 하지만 한 단어의 의미는 단순하게 긍정이나 부정만으로 결정되지 않죠. 더 복잡한 의미를 지니는 경우가 많습니다.
딥러닝에서는 이러한 문제를 해결할 때 긍정 혹은 부정에 속하는 단어 대신 표현형학습을 통해 얻은 단어 임베딩을 사용합니다. 보통표현형학습은 더 쉽게 구할 수 있는 다른 데이터를 이용해서 학습을 합니다. 예를 들어 데이터양이 훨씬 많은 워키백과를 이용하여 단어의 표현형을 학습한 후 그 표현형을 블로그 기사의 분류에 사용하여 문제를 풉니다.
이렇게 표현형은 다른데이터에서 학습하여 다양한 문제를 푸는데 사용할 수 있습니다. 즉, 위키백과에서 '고양이'라는 단어와 '야옹이'라는 단어의 표현형이 비슷하게 나오도록 학습한다면 그 결과를 블ㄹ로그 분류에도 유용하게 사용할 수 있습니다.
대표적인 임베딩 학습으로 문서 처리에 강점을 보이는 word2vec 모델과 그 파생모델, 행렬 분해가 있습니다.
- 데이터형에 따라 머신러닝에서 고려해야 할 점
- 데이터양과 품질이 달라질 때 고려해야 할 점
- 데이터에 치우침이 있을때 표준화하여 효율적으로 학습하는 방법
- 머신러닝 문제 휴형과 다양한 기법
3.1 데이터 형
데이터에도 유형이 있을까요? 있다면 머신러닝과 데이터형 간에는 어떤 연관이 있을까요? 이 절에서는 데이터형별 특징을 살펴보고 각각의 특징에 따라 어떤 머신러닝 기법을 사용하는지 살펴보겠습니다. 2장에서 설명한 바와 같이 머신러닝의 모델을 결정한느 것은 데이터에 대한 믿음(가정)입니다. 만일 우리가 다루는 데이터가 어떤 유형인지 알고 있다면 당연히 모델을 선택하는 일도 쉬워질 겁니다. 그리고 거의 대부분의 경우 데이터양이나 품질 때문에 생기는 고민에 비해 데이터형에 의한 고민은 쉽게 해결할 수 있습니다.
3.1.1 텍스트 데이터
텍스트 데이터는 다른 데이터에 비해 쉽게 구할 수 있습니다. 예를 들어워키피디아 덤프 파일이라든가 전자책 등 다양한 유형의 공개 텍스트 데이터가 있습니다. 또한 데이터의 의미 단위가 개별 단어나 연속하는 단어로 이루어져 있으므로 다른 데이터에 비해 의미 단위를 족각내기도 쉽습니다. 하지만 단위 데이터를 쉽게 조각내기 힘든 이미지 데이터나 음성 데이터와 달리 의미를 파악하려면 단어 간의 관계를 유추하거나 문법을 분석해야 합니다. 또한 의미 단위인 단어의 유형이 너무 다양하다는 문제도 있습니다. 텍스트 분석이 언뜻 보면 쉬워보이지만 이러한 문제들 때문에 사실은 굉장히 까다롭습니다.
특히 행렬을 이용해서 문서의 단어 분포를 표현하는 경우에는 존재하는 단어에 비해 문서에 나타나는 단어가 적으므로 행렬의 원소 대부분의 0인 상황이 많이 발생합니다. 이런 경우 데이터가 굉장히 희박(sparse)하다고 표현하며, 이러한 희박한 데이터를 이용하여 머신러닝을 하면 모델 학습이 잘못되어 실전 성능이 제대로 나오지 않게 됩니다. 그렇기 때문에 텍스트를 모델링하려면 이러한 상황을 해결해야 합니다.
또한 텍스트 데이터는 순서가 중요합니다. 순서에 따른 의미 변화를 모델링하기 위해서는 순차 모델 머신러닝 기법을 사용해야 합니다.
3.1.2 수치 데이터
수치 데이터는 금융, 생산 공정, 관측 장치 등으로 부터 얻을 수 있습니다. 많은 양을 쉽게 얻을 수 있지만 항상 노이즈가 섞여 있습니다. 노이즈가 생기는 이유는 다양하지만 일반적으로는 센서 노이즈와 현상의 무작위성이 원인입니다. 그렇기 때문에 수치 데이터 분석에는 노이즈에 강한 머신러닝 기법을 사용합니다.
수치 데이터는 대부분 수수자 피처를 가지기 때문에 텍스트에 비해 데이터 밀도가 높습니다. 그렇기 때문에 텍스트 데이터와는 다른 방식으로 접근해야 합니다.
텍스트 데이터와 비슷하게 데이터의 순서가 중요한 수치 데이터도 있습니다. 예를 들면 주식 데이터를 생각해 볼 수 있는데, 굉장이 노이즈가 많기 때문에 노이즈에 강한 숫차 모델, 예를 들면 칼만 필터(Kalman Filter)와 같은 모델을 사용합니다.
3.1.3 이미지 데이터
이미지 테이터는 최근 들어 주목받고 있습니다. 스마트폰, 디지털 카메라, 사진 및 동영상 공유 서비스가 발전함에 의해 사용할 수 있는 데이터양이 급격하게 늘어났고, 이는 데이터양이 많을 수록 성능이 향상되는 딥러닝을 이용한 모델이 전에 없던 성능을 낼 수 있게 해주었기 때문입니다.
이미지 데이터의 가장 큰 특징은 값(색상, 채도, 명도 등)과 위치에 따라 점의 의미가 달라진다는 겁니다.
이미지 데이터는 각 구역을 픽셀로 쪼개어 표현하게 되는데, 주변 픽셀에 따라 한 픽셀이 갖는 값의 의미가 달라집니다. 예를 들어 검은 구역이 있을 경우, 주변이 모두 검은 구역이면 사과의 가운데, 오른쪽 옆이 흰구역이면 사과의 가장자리가 되는 식이죠. 이렇게 픽셀 데이터가 근접한 픽셀 데이터와 강한 연관 관계를 가지는 것이 이미지 데이터의 가장 큰 특징입니다.
이러한 근접 연관성을 효과적으로 모델링하는 데는 '그래프 모델'에서 다룬 구조 모델 등을 사용합니다. 예를 덜어 대표적인 딥러닝 기법인 CNN(Convolutional Neural Network)은 한 픽셀과 그 주변 픽셀의 값을 동시에 고려하여 피처를 생성합니다.
비디오 데이터도 머신러인에 사용됩니다.예를 들어 비디오를 이용한 머신러닝을 할 경우, 비디오를 연속되는 이미지로 분할하여 사용하게 됩니다. 이때 연속된 이미지가 어떤 장면을 나타내는지 학습하기 위해 앞서 설명한 구조 모델을 확장시켜 화면의 위치 근접도와 시간 근접도를 복합적으로 사용하는기법이 적합합니다
3.1.4 음성 데이터
음성 데이터는 수치 데이터와 많은 부분에서 비슷합니다. 노이즈가 많으며 특히 시간에 따른 변화가 중요하기 때문에 시간에 따른 변화를 모델링할 수 있는 머신러닝 기법이 사용됩니다. 하지만 몇 가지 주목할 점이 있습니다.
음성 데이터는 소리에 관한 데이터이므로 일반적으로 소리의 높낮이나음색을 신호 처리 기법으로 전처리한 후에 사용하게 됩니다. 때표적인 기법으로 스펙트로그램(spectrogram)이나 퓨리에(Fourier)피처 등이 있습니다. 이러한 신호 처리 기법을 적용하여 얻은 피처와원래 신호를 조합하여 머시너닝을 하는 경우가 많습니다.
또한 데이터를 수집할 때 보통 한 가지 소리만 깔끔하게 얻는 경우는 많지 않습니다. 그래서 녹음된 소리를 여러 가지 소리로 나누는 머신러닝 기법을 사용한 이후에 앞서 언급한 기법을 일반적으로 사용합니다.
3.1.5 복합 데이터
복합 데이터는 다양한 유형의 데이터가 썩여 있는 것을 말합니다. 단순하게 생각하면 데이터의 특성에 따라 각기 다른 머신러닝 기법을 적용하여 분석을 할 수도 있지만, 연관 관계를 이용해서 여러 기법을 한번에 학습시키는 방법도 생각할 수 있습니다.
예를 들어 비디오(이미지 데이터, 음성 데이터)와 자막(텍스트 데이터)이 동시에 입력으로 들어오는 경우를 생각할 수 있습니다. 시간대별 이미지, 음성, 자막이 연관 관계를 가지고 있기 때문에 이러한 연관 관계를 한번에 학습할 수 있는 기법을 사용하면 각각을 따로 처리하는 것보다 더 좋은 결과를 얻을 수 있습니다. 최근에 각광받는 방법으로는 이미지 분류와 자막분류를 하는 신경망 모델을 동시에 중간 레이어를 공유해서 학습합니다. 그 결과로 이미지를 입력하면 그 이미지에 무엇이 있는지 텍스트로 설명할 수 있습니다.
3.2 데이터양과 품질
데이터양과 품질은 유형만큼이나 머신러닝 기법의 선택과 적용에 큰 영향을 끼칩니다. 이미 2장에서 간단히 설명했지만 여기서는 머시러닝을 실제로 적용할 때의 고려사항을 자세히 살펴보겠습니다.
3.2.1 데이터양과 머신러닝의 연관성
모든 머신러닝 기법은 데이터가 적으면 유용한 학습 결과를 얻기 힘듭니다. 그렇기 때문에 데이터를 많이 모으는 것은 괸장히 중요합니다. 하지만 데이터가 많더라도 특정 항목(레이블)에 대한 데이터가 적으면 해당 부분에 대해서는 학습이 제대로 되지 않을 수 있습니다. 이 문제는 이미지 태깅처럼 분류 항목 수가 굉장히 많은 경우에 비번히 발생합니다. 구체적으로 다음 세 경우를 들 수 있습니다.
데이터의 레이블을 얻기 굉장히 어려운 경우
사람이 일일이 예측 결과(예를 들면 이미지 태깅이나 문법 파싱)를 제공하는 경우에는 큰 비용이 들기 때문에 데이터의 레이블을 얻기 어렵습니다. 이런 경우에는 학습이 잘되지 않은 부분을 머신러닝 시스템이 사람에게 질문하는 방식으로 학습 성능을 높이는 액티브 러닝(active learning)기법을 사용하는 것도 좋습니다.
데이터양은 많지만 특정 레이블의 데이터가 부족한 경우
데이터가 전체적으로는 많지만 레이블 유형이 다양해서 어떤 레이블은 굉장히 적은 수의 데이터만 가지는 경우입니다. 사람이 레이블을 태깅하는 경우에는 특정 레이블로의 쏠림이 더 많이 발생해서 이 현상이 더 심각해지는 경우가 많습니다. 균형이 맞지 않게 되면 손실 함수는 데이터가 많은 레이블에 집중하고 나머지를 무시하기 때문에 균형잡힌 성능을 보여주지 못합니다.
이를 해결하려면 데이터양이 많은 레이블에서 임의로 데이터를 누락시켜(과소표집) 데이터의 균형을 맞추거나, 데이터가 적은 경우에도 잘 동작하는 원샷러닝(one-shot learning, 또는원샷학습)등의 기법을 사용할 수 있습니다. 데이터를 한 번만 보고 학습이 가능하다고 해서 원샷러닝이라 부릅니다.
데이터양은 많지만 특정 경우의 데이터가 아예 없는 경우
데이터가 들어오는데 새로운 레이블이 끊임없이 생성되는 경웅에는 제로샷러닝(zero-shot learning, 또는 제로샷학습)을 사용합니다. 대표적인 제로샷러닝 기법으로 다른 형식의 데이터를 이용해서 유사도를 판별하여 새로운 레이블을 학습 데이터 없이 예측하는 방법이 잇습니다. 예를 들어 비디오와 자막이 입력으로 주어지는 경우 자막의 유사도를 판단하여 이미지의 내용을 추측하는 방식을 사용할 수 있습니다.
3.2.2 데이터 품질과 머신러닝의 연관성
데이터양뿐만 아니라 품질도 머신러닝 기법 선택에 여향을 미칩니다. 데이터 품질은 노이즈양에 가장 크게 여향을 받습니다. 유용한 피처와 함께 레이블이 주어졌을 때 레이블의 무작위성에 따라 머신러닝 기법이 달라집니다. 데이터 품질에 따른 고려 사항은 다음과 같습니다.
관측 데이터에 노이즈가 많은 경우
무작위성이 높아 노이즈가 많은 경우는 수치 데이터에서 굉장히 비번하게 발생합니다. 데이터 양이 적으면 성능에 더 큰여향을 주는데, 이를 해결하려면 '간단한 모델'을 사용합니다. 간단한 모델은 가정이 가합니다. 이 모델은 유연하지 않고 노이즈에 강하기 때문에 이 경우에 적합한 모델이라고 볼 수 있습니다. 노이즈 자체를 가정에 두는 칼만 필터 같은 모델도 많이 이용됩니다.
레이블이 일정하지 않은 경우
크라우드 소싱이나 여러 사람이 수작업으로 레이블링하는 경우에는 작업자의 데이터나 레이블에 대한 해석 차이로 레이블이 통일되지 않을 수 있습니다. 예를 들어 단어가 긍정적인지 부정적인지 수치로 나타내는 작업을 여럿이서 한다고 가정합시다. A는 전반적으로 점수를 낮게 주고 B는 높게 주었다고 합시다. 이런 경우에는 평가자로 인한 편차를 줄이는 전처리 작업이 필요합니다. 수치값이라면 레이블의 평균과 분산을 구해서 맞춰주는 경우가 일반적이고, 여러 항목에서 고르는 작업의 경우에는 몇 명 이상이 동일한 항목을 고른 경우에만 그 값을 활용하는 방법이 있습니다. 즉, 전처리 작업을 한 후 에 머신러닝을 적용합니다.
3.3 데이터 표준화
데이터 척도 및 단위를 변환하여 데이터를 비교하기 쉽게 만드는 방법을 데이터 표준화(data normalization)라고 합니다. 데이터형에 따라서 데이터 표준화의 방식이 달라집니다. 여기서 다룰 데이터형에 따른 표준화 방법은 담음과 같습니다.
- 수치 데이터 표준화: 단위가 다른 수치 데이터를 비교하기 위해 데이터르 ㄹ변환하는 기법입니다.(예를 들면 키와몸무게_
- 카테고리 데이터 표준화: 숫자로 표현할 수 없는 항목을 수치 데이터로 변환하는 방버입니다.(예를 들면 성별)
- 서수 데이터 표준화: 순서 혹은 크기가 있는 데이터의 카테고리 차이를 나타내기 위해 데이터를 변환하는 기법입니다.(예를 들면 영화 별점, 설문지 응답등)
3.3.1 수치 데이터 표준화
키와 몸무게, 구매상품 수와 구매 가격 등 단위가 다른 두 수치 데이터를 직접 비교하는 것은 의미가 없습니다. 이럴 때는 각 데이터를 평균이 0, 표준편차 1인 데이터로 바꾼후 비교하면 효과적입니다. 이런 식으로 데이터를 변환하는 방법을 z-점수 표준화(z-score standardization)라고 합니다.
z-점수 표준화 외에 데이터를 변환하는 방법으로 척도화와 벡터 정규화가 있습니다. 척도화(scaling)는 피처의 최댓값 및 최솟값을 이용하여 피처 값의 범위를 조정하는 방법입니다. x를 원래 데이터의 피처값, x1를 변환된 피처값으로 표기할 때의 척도화를 식으로 나타내면 다음과 같습니ㅏㄷ.
척도화
x1 = (x - min(x)) /(max(x)-min(x))
벡터 정규화(vector normaliztion)는 한 데이터 가지는 피처 벡터의 노름(norm)이 1이 되게 하는 방법입니다. 노름은 벡터의 크기라고 볼수 있으며, 벡터 x의 노름은 ||x||로 표시하고 다음 식으료 계산합니다.
||x|| = 루트(xx1 + xx2 + xx3 .... xxn)
x1 = x / ||x||
즉, 벡터 정규화는 벡터의 각 요소를 벡터의 총 크기로 나누는 것이라고 볼 수 있습니다. 이렇게 하면 크기가 다른 벡터들의 요소를 비교할 수 있게 됩니다.
예를 들어 구매 항목 피처 벡터가 디비디 카테고리 구매 수와 간식 카테고리 구매수로 이루어진 경우을 생각해 봅시다. 사용자가 A가 디비디10개와 감자침 5개를 샀고, 사용자 B가 디비디 6개와 감자칩 3개를 샀다고 했을 때, 구매한 상품의 총 수는 다르지만 두 사용자 모두 디비디를 감자칩의 2배수로 구매했다는 점에서 두 사용자는 비슷하다고 볼 수 있습니다. 이 경우 두 사용자의 벡터 크기는 다음과 같이 구합니다.
사용자 A 의 피처 벡터 크기 루트(10*10 + 5*5) = 11
사용자 B 의 피처 벡터 크기 루트(6*6 + 3*3) = 7
각 피처 요소를 나누면(즉, 벡터 정규화를 하면) 사용자 A의 피처 벡터는 (10/11, 5/11), 사용자 B의 피처 벡터는 (6/7, 3/7)로 비슷한 값이 됩니다. 이 두 벡터로 유클리드 거리를 계산하면 사용자 A와 B의 유사도가 높게 나올 겁니다.
데이터형과 데이터 값 및 학습에 사용하는 모델에 따라 데이터 표준화 방법을 바꾸는 것이 성능에 도움이 됩니다. 예를 들어 선형 희귀의 경우 z-점수 표준화를 하는 편이 성능도 좋고 이해하기 쉬운 결과가 나옵니다. 길이가 다른 문서 안의 단어 빈도를 피처 벡터로 바꾼 뒤 유사도를 계산할 경우 피처 벡터를 벡터 정규화한 후 계산해야 정확한 비교를 할 수 있습니다.
3.3.2 카테고리 데이터 표준화
상품 카테고리의 차이는 어떻게 계산해야 할까요? 상품명, 상품 카테고리, 성별과 같은 비수치 데이터 사이의 유사도 계산에는 원-핫 인코딩(혹은 더미코딩)방식을 이용합니다. 원-핫 이코딩이란 카테고리 데이터 값을 피처로 만든 후 1 또는 0으로 지정하는 방법입니다. 예를 들어 '성별:나성, 성별:여성' 대신 남성은 0, 여성은 1로 수치화하여 '성별:0, 성별:1' 로 표기하는 겁니다.
3.3.3 서수 데이터 표준화
카테고리 데이터이면서 카테고리에 순서가 있는 데이터형을 서수(ordinal)데이터라고 합니다.
서수는 설문조사 답변(예를 들면 '전혀 그렇지 않다, 그렇지 않다, 어느 쪽도 아니다, 맞다, 매우 맞다')이나 영화의 별점을 생각하면 쉽습니다. 담변이나 별점을 1 부터 5의 숫자로 표시하기는 하지만 가능한 별점이 카테고리로 정해져 있으므로 이는 수치 데이터가 아닙니다. 예를 들어 가능한 별이 1~5점인 경우와 1~10점인 경우, 별점 3점이 가지는 의미는 매우 다릅니다.
하지만 각 카테고리가 동등하지 않다는 점에서 카테고리 데이터도 아닙니다. 따라서 다순히 수치 데이터로 취급하여 평균과 분산을 이용해서 표준화하거나 원-핫 인코딩으로 변환하는 것은 적절하지 않습니다. 이러한 경웅에는 일반적으로 다음 식을 이용하여 0과 1사이의 값으로 표준화 합니다.
서수형 데이터 표준화
(t -1 /2 )/M, i = 1,2,...,M
t: 카테고리
M: 가능한 카테고리
옐르 들어 별점 5점 만점에 별점 3인경우에는 표준화한 값이 (3-1)/5 = 0.5가 됩니다. 별점 3점이 1점과 5점의 한가운데임을 생각하면 0.5로 표시하는게 적절한 것 같습니다. 별점 10점 만점에 별점 3인 경우에는 (3 - 12)/10 = 0.25로 다른 값이 나오게 됩니다.
0부터 1사이로 척도화하는 방법도 생각할 수 있지만, 이 경우 별점 1을 수치 0 으로 바꾸게 되므로 데이터의 의미가 약간 달라집니다. 별점 1과 수치가 존재하지 않음을 의미하는 0은 다르니까요.
3.4 문제 유형
지금까지 데이터양과 품질에 따른 고려 사항을 살펴봤습니다. 이 절에서는 문제 유형에 따른 고려 사항을 살펴보겠습니다. 문제 휴형은 크게 희귀, 분류, 군집화, 표현형학습으로 구분할 수 있습니다. 문제 휴형을 잘못 선택하면 원하는 답을 얻지 못할 수 있기 때문에 문제를 구별하기는 기준은 꼭 숙지해둬야 합니다.
3.4.1 회귀문제
회귀는 머신러닝 문제 중에서도 가장 기본입니다. 회귀는 간단히 말해 입력을 받아서 가장 적합한 숫자값을 예측하는 문제입니다. 옐르 들어 주어진 상황 정보를 종합해서 주식값이 얼마가 될지 예측하는 겁니다.
회귀를 가장 기본적인 문제로 보는 이유는 출력된 결과를 해석하는 방식에 따라 여러가지 문제를 풀 수 있기 때문입니다. 예를 들어 출력된 숫자를 확률로도 특정 항목 번호로도 해석할 수 있습니다. 그렇기 때문에 어떤 문제를 풀더라도 결귀에는 회귀문제로 환원되는 경우가 많습니다.
회귀문제에 사용되는 머신러닝 기법은 다른 머신러닝 기법의 기초가되는 경우가 많습니다. 회귀문제를 푸는 기법으로는 통계학에서도 널리 쓰이는 선형회귀, 가우시안 프로세스 회귀, 칼만 필터가 유명합니다.
3.4.2 분류문제
분류는 회귀만큼이나 기본적인 머신러닝 문제입니다. 회귀 기법으로 분류 문제를 풀 수도 있지만, 보통은 분류에 해당하는 손실함수를 직접 최적화해서 푸는 방법을 많이 사용합니다. 예를 들어 인터넷 신문기사의 단어들을 가지고 이 기사가 어떤 분류 항목에 해당하는지 선택하는 경우들 들 수 있습니다.
분류 문제에서는 주어진 입력에 대해 여러가지 가능한 항목 중에서 한 가지를 선택하는 경우가 많지만, 기법에 따라서는 여러가지 선택하기도 합니다. 여러가지 항목에서 하나를 고르는 문제를 멀티 클래스(혹은 다중 클래스)분류라고 부르고, 복수를 고르는 문제를 멀티레이블(혹은 다중 레이블) 분류라고 부룹니다.
분류는 주어진 이미지가 어떤 항목에 속하는지 판별하는 문제, 생산 공정에서 제품의 데이터로 하자를 판별하는 문제, 텍스트의 의미가 어떤 항목에 해당하는지 판별하는 문제 등 셀수 없이 다양한 경우에 사용됩니다. 이전 시간을 고려한 주어진 상황을 보고 여러가능성이 있는 답 중에서 하나를 고르는 경우는 모두 분류 문제입니다.
분류문제를 풀 때는 기본적으로 로지스틱 회귀, 서프트 벡터 머신, 신경망 등 사용합니다. 그리고 입력이 시간에 따라 변화하는 데이터를 모델링 하는 경우에는 CRF나 RNN등을 사용합니다.
3.4.3 군집화 문제
군집화는 비슷한 성격의 데이터를 묶는 머신러닝 문제입니다. 물론 각각의 입력을 받아 출력으로 항목(레이블)을 예측하는 분류 문제로 볼 수도 있지만, 가장 큰 차이는 항목이 주어지는 것이 아니라 데이터에 내재된 분류를 찾아내야 한다는 점입니다. 즉, 받은 입력을 비슷한 성격별로 묶어야 합니다.
군비화 문제는 현재 주어진 데이터를 분석하는 데 사용합니다.(기존 데이터와 비교하여 미래 데이터에 대해서도 군집화를 할 수 있습니다). 예를 들어 비슷한 비디오나 문서끼리 묶어서 찾기 편하게 만들거나 비슷한 사용자를 군집화해서 군집별 특성을 살펴보는 경우를 떠올릴 수 있습니다. 군집화 기법을 문서에 활용한 것으로는 토픽 모델링이 유명합니다. 이 기법은 많은 그리고 레이블이 없는 문서에서 공통 주제를 발견하여 문서를 주제별로 묶습니다.
군집화 문제에서 가장 중요하게 여기는 것은 데이터 유사도(혹은 유사상)입니다. 이 유사도를 어떻게 정의하느냐에 따라 군집화 결과가 크게 달라집니다. 옐르 들어 이미지 데이터에서 크기에 따른 군집화와이미지 색상에 따른 군지화의 결과는 크게 다릅니다.
군집화 깁버은 종종 입력을 간추리는 용도로도 사용됩니다. 입력을 직접 사용하는 대신 군집에 얼마나 속하는지에 대한 강도를 이용해서 입력을 간단하게 표현해서 사용하는 것 입니다. 이렇게 변형된 군집화 기법은 다음 절에서 소개할 표현형학습과 유사한 역할을 수행하게 됩니다.
대표적인 기법으로는 K-평균 군집화(K-means clustering)와 평균이동 군집화(mean shift)가 있습니다. 문서를 토픽에 따라 군집화하는 방법은 토픽 모델링이라고 부르는데, LDA(latent dirichlet allocation, 잠재 디리클레 할당)라는 기법이 유명합니다. 엄밀하게 따지자면 군집화와 토픽 모델링은 접근하는 관점이 약간 다르지만, 비슷한 성격을 묶는다는 의미에서 이처럼 분류해보았습니다.
3.4.4 표현형학습(임베딩학습)
표현형학습은 앞서 설명한 3가지 문제보다 더 근본적인 문제를 다루는데 사용합니다. 표현형 학습은 풀고자 하는 문제에 적합한 표현형(representation)을 데이터로부터 추출하는 것을 말합니다. 표현형과 데이터로부터 직접적으로 추출하는 피처 차이는 다음과 같습니다.
1. 표현형은 피처보다 훨씬 더 간략합니다(표현형의 차원이 훨씬 더 낮습니다.)
2. 피처는 높은 차원을 가지는 경우가 많기 때문에 많은 부분이 0으로 차 있습니다(희박하다고 표현합니다). 표현형은 훨씬 낮은 차원으로서 데이터를 잘 설명해야 하기 때문에 밀집도가 높습니다.
블로그에 올린 일기를 통해 그날그날마다 글쓴이가 느낀 감정을 파악하려 하는 경우를 생각해봅시다. 분류 문제를 이용해서 푼다면 일기 문서의 구성 단어를 한하나 '긍정적'인 것과 '부정적'인 것으로 분류하고 그 수를 비교해서 날마다에 대해 긍정과 부정 두 가지 중 하나를 고르게 하는 문제로 환원할 수 있을 겁니다. 하지만 한 단어의 의미는 단순하게 긍정이나 부정만으로 결정되지 않죠. 더 복잡한 의미를 지니는 경우가 많습니다.
딥러닝에서는 이러한 문제를 해결할 때 긍정 혹은 부정에 속하는 단어 대신 표현형학습을 통해 얻은 단어 임베딩을 사용합니다. 보통표현형학습은 더 쉽게 구할 수 있는 다른 데이터를 이용해서 학습을 합니다. 예를 들어 데이터양이 훨씬 많은 워키백과를 이용하여 단어의 표현형을 학습한 후 그 표현형을 블로그 기사의 분류에 사용하여 문제를 풉니다.
이렇게 표현형은 다른데이터에서 학습하여 다양한 문제를 푸는데 사용할 수 있습니다. 즉, 위키백과에서 '고양이'라는 단어와 '야옹이'라는 단어의 표현형이 비슷하게 나오도록 학습한다면 그 결과를 블ㄹ로그 분류에도 유용하게 사용할 수 있습니다.
대표적인 임베딩 학습으로 문서 처리에 강점을 보이는 word2vec 모델과 그 파생모델, 행렬 분해가 있습니다.
2018년 3월 2일 금요일
CHAPTER 2 머신러닝의 주요 개념
머신러닝을 도입한다는 것은 단순히 프로그램에 새로운 기능을 추가하거나 좀 더 빠른 알고리즘을 도입하는 것과는 다릅니다. 머신러닝은 보유한 데이터의 성격이나 양, 품질에 따라서 결과가 달라지기 때문입니다. 머신러닝 시스템이 기계라면 데이터는 그 기계의 연료입니다. 맞지 않은 연료를 넣거나 양이 적절하지 않으면 기계가 고장나게 되지요.
머신러닝 시스템을 구축하는 과정은 탐구의 연속입니다. 보유한 데이터가 어떤 성질을 가지는지, 그 데이터에 맞는 머신러닝 기법은 무엇인지, 어떻게 해야 적합한 데이터를 얻을 수 있는지, 실제로 시스템을 구현해보니 기대했던 성능이 나오지 않는 이유는 무엇인지에 대한 해답을 찾아가는 과정입니다. 여러 기법을 시도해 보고 실패하고 수정하는 과정에서 좋은 머신러닝 시스템을 만들게 됩니다.
이런 탐구 과정에서 시행착오는 필연입니다. 머신러닝 이론을 알면 '왜'와 '어떻게'에 해당하는 문제를 좀 더 쉽고 효율적으로 진행할 수 있어 더 좋은 성능의 시스템을 구축할 수 있게 됩니다.
이 장에서 다룰 머신러닝의 4가지 햇김 개념은 다음과 같습니다.
- 모델: 데이터를 바라보는 시점과 가정
- 손실함수 : 모델의 수식화된 학습목표
- 최적화: 손실함수로 표현된 모델을 실제로 학습
- 모델 평가: 모델의 성능의 실제 상황에서 어떨지 추정
2.1 모델: 문제를 바라보는 관점
모델은 머신러닝의 시작점이라고도 할 수 있습니다. 이제부터 모델의 정의와 분류, 좋은 모델의 특징에 대해 알아보겠습니다.
2.1.1 모델이란?
데이터 분석을 하거나 머신러닝으로 문제를 해결하려면 무엇부터 해야 할까요? '데이터가 어떤 패턴을 가지지 않을 까? 그 패턴들을 이용하면 어떻게든 되지 않을까? 여러 생각이 들 겁니다. 그런데 패턴이 있기는 할까요? 사실 패턴이 있을 것이라는 생각도 어찌 보면 데이터 자체에 대한 믿음입니다. 그러한 믿음을 수학에서는 가정이라고 합니다. 이러한 여러 가정을 한데 모은 것을 머신러닝에서는 모델이라고 합니다.
쉽게 말해 현재 상태를 어떠한 시각으로 바라보고 어떠한 기대를 하고 있는가 하는 것이 모델입니다. 그러핟면 모델은 머신러닝에서 어떤 역할 을 할까요? 일반적으로 머신러닝의 과정은 다음과 같습니다.
모델 정하기-> 모델 수식화하기-> 모델 학습하기-> 모델 평가하기 -> 모델 정하기-> 모델 ...
1. 모델정하기(데이터가 어떻게 생겼을지 가정하기)
2. 모델의 학습 목표를 수식화 하기
3. 실제 데이터로 모델 학습하기(최적화)
4. 평가하기
그런데 과정1에서 데이터가 어떻게 생겼을지 이미 가정을 해 놓고 왜 또 과정 3에서 실제 데이터 학습이 필요할 까요?
예를 들어 과정 1의 가정이 다음과 같다고 합시다.
"이 데이터에서 x 와 y는 선형적인 상관관계를 가진다. 즉, 임의의 w 에 대해 y=wx 와 같은 관곌르 가질 것이다."
그렇다면 과정 3의 가정은 다음과 같을 수 있습니다.
"데이터 토대로 추측해본 결과 y = 2x와 같은 형태를 가진다. 즉, 데이터에 가장 잘 맞는 w 는 2다."
모델이란 가정에 따라 생성될 수 있는 함수들의 집합입니다. 이런 의미에서 보면 '모델이 바뀐다'는 것은 어떤 함수의 꼴이 완전히 달라진다는 겁니다. 정해진 함수(위에서는 y=wx) 안에서 함수의 파라미터(여기서는 w)를 데이터를 통해 추측하는 것을 학습이라고합니다. 머신러닝의 러닝의 바로 학습을 뜻합니다. 그리고 학습이라는 것은 그 모델이 표현하는 함수 집합 중에서 가장 데이터에 적합한 함수를 고르는 과정입니다.
1. 모델은 데이터를 어떻게 바라볼지에 대한 가정(통계학 용어로는 믿음(belief)이라고 함) 입니다.
2. 모델은 머신러닝의 시작점입니다.
2.1.2 간단한 모델
모델이 '가단하다'는 말은 '데이터의 구조가 간단하다'는 뜻입니다. 이는 '괸장이 강렭한 가정'을 한다고 해석할 수 있습니다.
가장 간단하지만 아주 효과적인 모델로 선형 모델이있습니다. 선형 회귀라는 말을 아마 많이 들어보았을 겁니다. 선형 휘귀는 대표적인 선형 모델입니다. 예측할 결괏값을 y라고 예측에 사용하는 값을 x1, x2, x3,...라 합시다.
선형 회귀, 데이터(x)로 학습된 모델(쩜선), 수식은 y -= 5 + 0.4x
선형 휘귀 정의는 다음과 같습니다.
- 수식: y = w0 + w1x1 + w2x2 +w3x3 + w4x4 ....
- 출력값(y)이 입력값(피처)(x1, x2, x3, ...)에 대새 선형적인 관계
선형 관계는 출력값이 입력값(피처값)들에 가중치를 곱한 값의 합(선형 결합)으로 표현되는 관계입니다. 한 가지 속성( x1)이 1만큼 증가하면 결괏값은 w1만큼 증가합니다. 감소하는 경우에는 w1만큼 감소합니다. 이러한 선형적인 관계는 많은 곳에서 찾을 수 있습니다. 옐르 들어 제품의생산량(x)과 불량품수(y)를 들 수 있습니다. 생산량과 불량품 수는 일반적으로 양의 상관관계인 경우가 많으므로선형 회귀 모델이 아주 적합합니다.
이러헌 선형 회귀 예시와 같이, 간단한 모델은 이해하기가 쉽다는 장점이 있습니다. 또한 간단한 모델은 일반적으로 데이터를 간단하게 설명하려 하기 때문에 가정 자체에 제약이 많고 데이터의변화에 비해 모델 자체의 변화폭(분산)이 적습니다. 모델 자체의 변화폭이 적으므로 예외 데이터가 들어와도 여향을 적게 받기 때문에 신경을 써야 할 부분이 적습니다.
간단함 모델의 단잠은 무엇일까요? 간단한 모델은 복잡한 관계를 학습할 수 없습니다. 입출력 데이터의 관계가 단순한 선형 관계가 아니면 어떻게 될까요? 예를 들어 w0x1/(w1+x1) 같은 관계 말이죠. 당연히 간단함 도델로 복잡한 데이터를 제대로 표현하지 못할 겁니다.
'간단한 모델'은 다음과 같이 간단히 정리할 수 있습니다.
- 데이터가 복잡하지 않고 간단하게 생겼다고 가정합니다.
- 결과를 이해하기 쉽습니다.
- 학습이 쉽습니다.
- 가정 자체가 강력해서 모델의 표현 능력에 제약이 많습니다.
2.1.3 복잡한 모델
복잡한 도델은 모델의 유연성을 더 중요시합니다. 예를 들어 결정 트리 모델을 들 수 있습니다.
- 트리의 한 분기점마다 한 가지 조건(보통 입력의 한 부분)을 검사하여 분기를 합니다.
- 모든 분기가 끝나는 리프노드(맨 끝의 노드)에는 결괏값이 들어 있습니다.
결정트리는 선형 모델과 달리 복수의 비교식으로 정의됩니다. 그리고 이 비교식은 무수히 늘어 날 수 있기 때문에 더 일반적이고 유현한 가정을 할 수 있습니다. 예를 들어 앞서 설명한 선형 모델도 결정 트리로 구현할 수 있습니다. 각 분기점마드 모든 값에 대한 결과를 작성해서 결과를 저장하면 되나까요. 복잡한 모델은 간단한 모델에 비해 전체 데이터에 일괄적인 가정이 적습니다. 예를 들어 결정 트리는 데이터 전체에 대한 가정이 거의없다시피 합니다. 그렇다면 왜 모든 걸 포함할 수 있는 복잡한 모델을 사용하지 않고 간단한 모델을 사용하는 걸까요? 2.1.5절 '좋은 모델이란 무엇인가?'에서 설명합니다.
복잡한 모델의 가장 큰 장점은 복잡한 데이터의 모델링에 적합하다는 겁니다. 결정 트리처럼 유연성이 뛰어난 모델은 많은 종류의 데이터를 모델링할 수 있습니다. 하지만 데이터의 모든 부분에 대해 일일이 가정을 만듦으로써 불필요한 노이즈까지 학습하여 성능이 나빠질 수 있습니다.
'복잡한 모델'은 다음과 같이 간단히 정리 할 수 있습니다.
- 데이터가 어떻게(간단하거나 복잡하게)생격ㅆ을 것이라는 가정 자체가 별로 없습니다.
- 결과를 이해하기 어려울 수도 있습니다.
- 학습이 복잡합니다.
- 한정된 데이터에서만의 변화를 그대로 학습하므로 새로운 데이터에 대해 성능이 떨어질 수 있습니다.
2.1.4 구조가 있는 모델
모델의 복잡도는 모델을 구분하는데 중요한 기준이지만, 몇 가지 특정 상황에서 요긴하게 쓰이는 모델이 있어서 따로 다룹니다. 바로 '구조 가 있는 모델'인데요, 단순히 입력과 출력의 상관관계를 학습할 뿐만 아니라 데이터 구조 자체를 모델링하는 조금 특이한 모델입니다. 앞서 말한 간단한 모델이나 복잡한 모델 어느 항목에도 속할 수 있다는 점에 유의해주세요.
구조가 있다는 말이 조금 생소하게 들릴 수도 있을 겁니다. 이는 입력과 출력 요서가 서로 연관관계가 있는 것으로 이해하면 됩니다. 구조가 있는 모델은 다양하지만, 이 절에서는 유명한 순차 모델과 그래프 모델에 대해 다루겠습니다.
2.1.4.1 순차 모델
순차 모델은 연속된 관측값이 서로 연관성이 있을 때 주로 사용합니다. 문서의 텍스트, 시간과관계된 데이터 분석을 예로 들 수 있습니다. 순차 모델의 대표적인 예로 문장 구조 분석에서 많이 사용하는 CRF(conditional random field)(조건부 랜덤 필드, 조건부 무작위장)와 RNN(recurrent neural net)(순환 신경망, 재귀 신경망)이 있습니다. 이들 모델의 가장 큰 특징은 특정 시점에서 상태를 저장하고, 상태가 각 시점의 입력과 출력에 따라 변화한다는 점입니다.
RNN은 여러 가지 변형 형태가 있지만 기본적인 정의는 다음과 같습니다.
- 수식: h1 = w0 + w1ht-1 + w2.xt
- 실재로 관측되지는 않았지만 특정 시점에 어떤 상태(ht)가 존재한다고 가정합니다.
- 현재의상태(ht)는 바로 직전의 상태(ht-1)와 현재의 입력 데이터(xt)에 영향을 받습니다.
- 상태(ht)에 따라 출력(yt)이 결정됩니다.
RNN과 같은 순차 모델은 숨겨진 상탯값을 가정하고 이를 이용해서 순차적인 의존성을 표현합니다. 이 순차적인 의존성을 가지는 상탯값이 다른 모델과의 가장 큰 차이점입니다. 물론 상태 (h)를 가지지 않는 모델도 현재 시점의 입력(xt)과 이전 시점의 입력(xt-1)을 합쳐서 비슷한 효괄르 낼 수 있지만, 모든 시점의 입력을 이용하기에는 계산의 한걔로 넓은 범위의 의존성을 나타내기 어렵습니다.
2.1.4.2 그패프 모델
그래프 모델은 그래프를 이용해서 순차 모델보다 좀 더 복잡한 구조를 모델링합니다. 예를 들어 문서의 문법 구조(보통 트리 형태)를 직접 모델링하거나 이미지의 픽셀 사이의 관계를 네트워크로 보고 그래프로 표현하여 모델링 합니다. 대표적인 예로 마르코프 랜덤 필드(MRF)를 살펴보겠습니다.
- 데이터의 숨겨진 상태(h) 사이에 어떤 연결 구조가 있다고 가정한 후, 연결된 위치의 숨겨진 상태끼리는 연관성이 있다고 가정합니다.
- 보통 사진을 처리할 때 많이 사용하는데, 이런 경우에는 사진의 특정 위치의 상태(h)가 바로 근접한 위치의 상태들과 관계가 있다고 가정합니다.
- 그리고 실제로 관측된 값(x)은 상태에 따라 결정된다고 가정합니다.
임의의 그래프 구조를 사용하면 순차 모델보다 더 복잡한 관계를 표현할 수 있습니다.
2.1.5 좋은 모델이란 무엇인가?
좋은 모델이란 쉽게 생각하면 '데이터의 패턴을 잘 학습한 모델'이라고 할 수 있습니다. 모델을 평가하는 이론이 많이 존재하지만, 이 책에서는 그중 모델의 복잡도와 표현력에 대한 균형을 다루는 편향-분산 트레이드오프와 균형을 자동으로 학습하는게 하는 정규화에 대해 알아 보겠습니다.
2.1.5.1 편향-분산 트레이드오프
앞에서 우리는 간단한 모델과 복잡한 모델의 특징에 대해 이미 살펴봣습니다. 그런데 모델의 표현력과 가정 강도와의 관계는 생각보다 더 오표합니다. 어떤 모델을 데이터 x로 학습한 결과를 f(x)라 하고 가능한 모든 모델 중에서 가장 좋은 모델을 f, 원하는 출력값(정답)을 y라 할 때 가장 기본적인 에러인 평균 제곱근 에러는 다음과 같은 성질을 만족합니다.
1번 식은 모델이 데이터를 예측할 때 생기는 오류가 편향(bias)의 제곱과 분산(variance)으로 쪼개진 다는 것을 보여줍니다. 즉, 모델이 더 나은 성능을 내려면 편향을 줄이거나 분산을 줄여야 한다는 뜻입니다.
2번 식(분산)은 데이터로 학습한 결과와 이상적인 모델 간의 차이입니다. 데이터로 학습한 결과가 모델의 표현력이 부족해서 이상적인 모델과 많은 차이를 보인다면 값이 커지게 됩니다. 간단한 모델일수록 가정이 강합니다. 따라서 표현력이 부족하므로 편향이 크게 나타납니다. 물론 약한 가정을 하는 복잡한 모델이라 할지라도 데이터와 터무니없이 맞지 않으면 편향이 강하게 나타날 수 있습니다.
3번 식(분산)은 데이터를 이용해서 얻은 모델이 학습할 때마다 얼마나 달라질 수 있는지 나타냅니다. 일반적으로 모델이 복잡할 수록 학습할 때마다 나타나는 모델 편차가 큽니다. 즉, 모델이 복잡할수록 분산이 더 크게 나타납니다.
그렇다면 모델의 성능을 극대화하려면 어떤 모델을 골라야 할까요? 편향이 너무 크지 않아 적당히 유연하면서 너무 복잡하지 않아 분산이 작게 나오는 모델이 적합할 겁니다. 즉, 표현력이 크다고 항상 좋은 것이 아니며 그렇다고 항상 단순한 모델만 이용해서는 제대로 된 결과를 얻지 못한다는 뜻입니다.
편향이나 분산을 직접적으로 줄이는 대표적인 예로 다음과 같은 방법을 들 수 있습니다.
- 부스팅:간단한 모델ㅇㄹ 여러 개 조합하여 편향을 줄이는 방법
- 랜덤 포레스트: 복잡한 모델인 결정 뜨리를 여러 개 조합하여 분산을 줄이는 방법
2.1.5.2 정규화
정규화는 정해진 모델이 필요 이상으로 복잡해지지 않도록 조절하는 트릭입니다. 모델이 데이터에 비해 필요 이상으로 복잡하면 불필요한 노이즈까지 학습해서 학습할 때는 성능이 좋지만, 실제로 사용할 때는 좋지 않은 성능을 보일 수 있습니다. 모델의 복잡도를 줄이는 방법으로는 크게 다음 2가지가 있습니다.
- 모델 변경: 데이터를 표현하는 방법을 완전히 새롭게 전환해서 적합한 모델을 찾는 방법
- 정규화: 모델에 들어 있는 인자에 제한을 주어 모델이 필요 이상으로 복잡해지지 않게 하는 방법
제약 조건과 데이터를 이용해서 적당한 복잡도를 가지는 모델을 자동적으로 찾아주는 기법이 정규화 입니다.
2.2 손실함수: 모델의 수식화된 학습목표
모델이 실제로 데이터를 바르게 표현했는지 혹은 얼마나 예측이 정확한지 수학적으로 표현하는 것이 손실함수(loss function)입니다. 앞서 다우었던 선형 회귀를 예로 들면 만들어낸 직선에서 데이터가 얼마나 떨어져 있는지 계산하는 함수가 손실함수입니다.
손실함수의 값이 작을수록 모델이 더 정확하게 학습된 겁니다. 이때 손실함수로 언은 결과값을 보통 에러라고 부릅니다.
손실함수는 거의 같은 모델을 대상으로 하더라도 중요하게 생각하는 데이터의 특성에 따라 변형될 수 있습니다. 예를 들어 데이터의 전체적인 패턴을 중시할지 지엽적인 패턴을 중시할지에 따라서 기본적인 손실함수에 추가적인 손실함수를 덧붙일 수 있습니다. 엄밀히 따지면 데이터를 보는 관점이 조금 변화했기 때문에 다른 모델이라고 말할 수도 있지만, 이러한 손실함수의 조합은 같은 모델의 변형으로 열겨지는 경우가 많으며 자주 사용되는 기법입니다.
손실함수는 데이터 전체에 대해 계산하는 함수지만, 간단하게 표시하기 위해 할 개의 데이터에 대새서만 정의하기도 합니다. 이때 각 데이터에 대한 손실함수 계산 결과의총합이 그 모델과 데이터 전체에 대한 손실함수 결과입니다. 물론 이런 경우에는 몇 가지 가정이 추가됩니다. 예를 들어 데이터셋에서 각각의 데이터가 서로 확률적 독립이고 같은 분포를 가진다는 i,i,d가정(independent and identically distributed:독립항등부포)이 대표적입니다.
여기서 다룰 손실함수는 다음 4가지 입니다.
- 산술 손실함수: 모델로 산술값을 예측할 때 데이터에 대한 예측값과 실제 관측값을 비교하는함수입니다. 주로 희귀 문제에서 사용합니다.
- 확률 손실함수: 모델로 항목이나 값에 대한 확률을 예측하는 경우에 사용합니다. 매우 유연하기 때문에 회귀 문제를 비롯해 보편적으로 사용합니다.
- 랭킹 손실함수: 모델로 순서를 결정할 때 사용합니다. 추천 시스템에서 주로 사용합니다.
- 모델 복잡도와 관련된 손실함수: 보통 위 손실함수들과 합쳐져서 모델이 필요 이상으로 복잡해지지 않도록 방지하는 손실함수입니다. 앞에서 설명한 정규화의 일종으로 볼 수 있습니다.
2.2.1 산술 손실 함수
모델로 산술값을 예측할 때는 각 데이터에 대한 예측값의 차이를 산술적으로 계산하는 손실함수를 많이 사용합니다. 차이의 제곱을 사용하는 제곱 손실함수와 차이의 절댓값을 사용하는 손실함수가 많이 쓰입니다.
제곱 손실합수는 굉장이 많은 곳에서 사용됩니다. 제곱 손실함수를 사용하는 이유에 대해서는 여러가지 이론이 존재하지만, 가장 간단한 설명으로는 주어진 데이터 출력값(y)과 모델의 예측값의 차이의 제곱을 계산하므로, 최적화가 쉽고, 손실값의 이해가 쉽기 때문입니다.
이 손실함수는 주어진 데이터 1개(입력x와 출력 y의 쌍)에 대해 학습된 모델(w0과 w1인자로 결정되는)이 얼마만큼의 에러를 가지는지, 즉 얼마나 데이터를 잘 표현하는지 정량적으로 계산합니다. 물론 이 식은 데이터 하나에 대해 정의를 한 것으로, 전체 데이터에 대해서는 각 손실함수의 값을 전부 합산하거나 평균을 낸값을 사용합니다.(참고로 평균의 경우에는 평균 제곱편차라는 표현을 주로 사용합니다.).
2.2.2 확률 손실함수
앞에서 산술값을 예측하기 위한 모델(회귀)에 적합한 산술 손실함수에대해 살펴봣습니다. 이와 달리 특정 항목을 고르는 분류 모델에서는 확률 손실함수가 더 적합합니다.
확률 손실함수는 모델이 관측된 데이터를 예측할 확률을 최대화하는 방식으로 계산됩니다. 접근 방식에 따라 많은 확률 손실함수를 만들 수 있습니다. 대표적으로 MLE(maximum likehood estimation:최대 가능도 방법)와 쿨백-라이블러 발산(Kullback Leibler divergence:KL-divergence, KL-발산)이 있습니다.
여기서는 확률 손실함수 중에서 자주 사용되는 교차 엔트로피(cross entropy)함수에대해 알아봅니다. 이 함수는 딥러닝에서 특히 많이 사용됩니다.
이 함수는 MLE방식에 따라 만들어 집니다. 여기서 가능도 likelihood(혹은 우도)란 주어진 데이터가 얼마나 적합한지에 대한 조건부 확률입니다. 수식은 p(x|f)와 같이 씁니다. 여기서 x는 데이터, f는 모델입니다. 가능도가 클 수록 주어진 모델이 관측된 데이터를 더 잘 표현한 것이 됩니다. 따라서 이 값을 최대화 하는 쪽으로 모델을 학습시켜야하는데, 보통 손신할수를 풀 때는 최소값을 구하는 최적화 방식을 사용하므로, 계산의 편의를 위하여 가능도의 부호를 바꾸고 log값을 취한 음의 로그 가능도(negative log-likelihood) 손실함수를 많이 사용합니다.
loss(f) = -log p(x|f)
음의 로그 가능도 손실함수는 실제 모델 f에 따라 정의되겠지만, 분류에서 많이 사용되는 다항로지스틱 희귀(multinomial logistic regression)의 음의 로그 가능도 손실 함수에 대해 살펴보겠습니다.
다항 로지스틱 희귀는 입력값을 {1,2,...C} 중 하나로 분류합니다. 이때 모델의 각 항의 예측 값은 0부터 1사이의 실수가 되고 모든 항의 예측값의 총합은 1이 됩니다. 각 분류에 대한 확률을 y라고 합시다.
2.2.3 랭킹 손실 함수
랭킹 손실함수는 지금까지 설명한 산술 소실함수나 확률 손실함수화는 달리 특정한 결괏값에 대한 손실을 측정하지 않습니다. 대신 모델이 예측해낸 결괏값의 순서가 맞는지만 판별합니다. 랭킹 손실함수는 목록에서 몇가지를 추천 시스템이나 랭킹학습 분야에서 많이 사용합니다.
순서를 예측하느 문제는 손실함수를 조금 다르게 표현합니다. 예플 들어 데이터의 순서가 x1> x2> x3> x4> x5라고 들어오면 모델은 각 데이터의 순서를 x의 의미에 따라 학습힌다. 여기서 x의숫자를 의미한다기보다는 어떤 개념을 나타낸다고 생각하면 됩니다.(예를 들어 x1은 사과, x2는 바나나등).
다양한 랭킹 손실함수가 있는데 그 중에서도 모델이 만들어낸 순서 목록에서 모든 쌍의 순서가 맞았는지 틀렸는지 확인하는 방식이 가장 간단합니다. 예를 들어 모델이 x3> x1> x2> x4> x5라는 결과를 만들었다면 각 쌍에 대해 다음과 같은 결괏값 쌍을 가집니다.
1. x3 > x1
2. x3 > x2
3. x3 > x4
4. x4 > x5
5. x1 > x3
6. x1 > x4
7. x1 > x5
8. x3 > x4
9. x3 > x5
10. x4 > x5
이 모델의 결괏값 쌍과 정답인 x1> x2> x3> x4> x5를 비교하면 10가지 관계 중에서 몇가지가 틀렸는지 알 수있습니다. 이 경우에는 x3 > x1, x3> x2이렇게 두 관계가 잘못되었으므로 손실 함수 결과는 2입니다. 이 손실함수는 페어와이즈 제로-원 손실함수 라고 부릅니다.
다른 방식으로손실을 측정하는 편집 거리 라는 랭킹 손실함수도 있습니다. 이 손실함수는 모델이 예측한 순서 목록에서 몇 번의 맞바꿈을 해야 워래 순서로 돌아갈 수 있는지 측정합니다.
2.2.4 모델 복잡도와 관련된 손실함수
2.3 최적화: 실제로 학습을 하는 방법
지급까지 데이터를 바라보는 시각인 모델과 모델이 데이터를 얼마나 잘 표현하는지를 수학적으로 측정하는 손실함수에 대해 알아봤습니다. 이제부터 손실함수를 이용해서 모델을 학습하는 방법에 대해 알아보겠습니다.
손실함수의 결괏값을 최소화하는 모델의 인자를 찾는 것을 최적화라고 합니다. 이렇게 임의로 여러 값을 대입해 보면서 손실을 최소화하는 방법도 있지만 보통은 수학적으로 손실함수를 분석해서 최적화 합니다. 그리고 그 방식은 사실 이미 고등하교 때 배웠습니다.
함수의 최솟값과 최대값에 대해서는 익히 알고 있으리라 생각합니다. 이 최속값은 위 식처럼 인수분해로 한번에 구할 수 있지만, 한번에 풀리는 경우는 많지 않고 보통은 여러 번 반복해서 업데이트를 해야 최소값을 찾을 수 있습니다. 이 절에서는 그중 많이 쓰이는 경사하강법, 뉴턴/준뉴턴 방법, 확률적 경사하강법, 역전파 그리고 몇가지 최신 최적화 방법을 살펴보겠습니다.
2.3.1 경사하강법
경사하강법은 간단한 최적화 방법 중 하나로 임의의 지점에서 시작해서 경사를 따라 내려갈 수 없을 때까지 반복적으로 내려가며 최적화를 수행합니다. [그림 2-8]에 경사하강법을 적용해보면 [그림 2-9]와 같습니다.
1. 현재 @값에서의 경사값을 구합니다.
2. 경사를 따라 n만큼 내려갑니다.
3. 손실함수의 출력값이 많이 줄어들었는지 보고 그렇다면 1로 돌아가 계속 하강합니다. 많이 줄어들지 않았다면 경사를 다 내려온 것으므로 @값 구하기를 그만둡니다.
위 그림은 @=4에서의 경사를 보여줍니다. 경사는 곡선의 접선 방향으로 결정되는데, 이를 계산하려면 손실함수 인자 @에 대해 미분하면 됩니다.
이 경사값을 이용해 앞에서 서술한 과정 1,2,3을 반복하면 손실함수를 쉽게 최적화 할 수 있습니다. 이런 이유로 미분 가능한 손실함수를 많이 사용합니다. 예를 들어 모든 점에서 미분 가능한 제곱 손실함수를 절댓값 손실함수보다 많이 씁니ㅏㄷ.
경사는 방향을 결정합니다. 얼마나 빨리 내려가는지를 학습률 혹은 스템 크기라고 불리는 n에 의해 결정됩니다.
2.3.2 뉴턴/준 뉴턴 방법
학습률 n와관련된 몇 가지 고전적인 이론이 있습니다. 대표적인 이론으로 뉴턴 방법과 준 뉴턴 방법이 있는데요, 그 중 뉴턴방법은 임의의 학습률을 사용하는 대신 1차 미분값과 2차 미분값을 활용하여 업데이트를 수행합니다.
뉴턴 방법은 단순한 경사하강법보다 훨씬 빠른 최적화 속도를 보이지만 현실적으로 2차 미분은 식을 풀기도 어렵고 계산에도 많은 자원이 필요해서 잘 사용하지 않습니다.
준 뉴턴 방식은 2차 미분을 직접 계산하는 대신 1차 미분값을 활용해 2차 미분값을 근사해서 사용합니다. 그렇기 때문에 2차 미분값을 계산하지 않고도 빠르게 많은 문제를 풀 수가 있으며, 대표적인 방법으로 BFGS(broyden-fletcher-goldfarb-shanno algorithm)와 LBFGS(limited-memory BFGS)가 있습니다. BFGS와 LBFGSㄴ느 데이터가 너무 많지 않은 경우에 뛰어난 성능을 보입니다. 최근에는 데이터가 많아지면서 확률적 경사하강법이 많이 쓰이기는 하지만 준 뉴턴 방식은 여전히 효율적인 최적화 방법입니다. 유명한 기법이라 대부분의 머신러닝 소프트웨어에 구현되어 있으니 궁금한 분은 사용해 보기 바랍니다.
2.3.3 확률적 경사하강법
앞에서 설명한 경사하강법이나 뉴턴/준뉴턴 방법은 데이터가 많아지면 계산량이 증가해서 학습 시간이 길어집니다(데이터에 따라 다르겠지만 몇심만 개 정도는 크게 문제가 없습니다.) 시간이 오래 걸리는 이유는 손실함수와 1차 미분값을 전체 데이터에 대해 걔산하기 때문입니다. 이를 해결하고자 일부 데이터만 이용해서 손실함수와 1차 미분값을 근사적으로 계산하는 확률적 경사하강법(SGD)이 만들어졌습니다.
SGD는 전체 데이터를 가지고 계산하기보다는 다음과 같이 n개의 샘플을 뽑아서 손실함수와 1차 미분값을 계산합니다.
-SGD의손실함수: (데이터 전체에 대한 평균을 사용하는 대신) 데이터에서 n개의 샘플을 뽑아서 그 평균을 사용합니다.
-SGD의 1차 미분값: 손실함수와 마찬가지로 n개의 샘플을 뽑아서 계산합니다.
SGD는 n개의 샘플을 뽑는 방법은 여러 가지가 있는데, 대표적으로 다음 2가지 방법을 많이 사용합니다.
-데이터를 처음부터 끝까지 하나씩 업데이트하는 SGD
-미니배치 SGD
데이터를 처음부터 끝까지 하나씩 업데이트 하는 SGD는 다음과 같이 동작합니다.
1. 데이터의 처음부터 끝까지 루프를 돕니다(i=0 ~ 데이터 크기).
a. 현재 @ 값과 현재 데이터(i번째 데이터)에서의 1차 미분값을 구합니다.
b. 경사를 따라 n만큼 내려갑니다(즉, 경사값에 -1을 곱한 방향으로 n 만큼 움직입니다.).
2. 손실 함수의 출력값이 많이 줄어들었는지 보고 그렇다면 1로 돌아가 반복합니다. 많이 줄어들지 않았다면 경사를 다 내려온 것이므로 @값 구하기를 그만둡니다.
이 방식에서는 데이터 업데이트가 일어날 때마다 데이터를 몇 번 처음부터 끝까지 훑으면서(sweep이라고 합니다) 손실함수가 수렴했는지 검사합니다(모든 데이터를 순차적으로 하나씩 뽑는 방식 대신 정해진 숫자만큼 임의로 데이터를 뽑아서확인하는 방식도 사용합니다). 이 방식은 경사하강법보다 굉장히 공격적으로 업데이트하기 때문에 속도가 빠르지만, 각 데이터의 1차 미분값은 전체 데이터를 이용한 1차 미분값보다 부 정확하므로 손실함수의 출력값이 불안정해지곤 합니다. 그래서 이 방식과 단순한 경사하강법의 중간 정도 되는 미니배치 SGD같은 방식이 자주 쓰입니다.
미니배치 SGD는 다음과 같이 동작합니다. 데이터를 n개씩 뽑아서 하는 SGD입니다.
1. 데이터를 랜덤하게 n개 뽑습니다.
a. 랜덤하게 뽑은 n개 데이터의 1차 미분값의 평균을 구합니다.
b. 경사를 따라 n만큼 내려갑니다(즉, 경사값에 -1을 곱한 방향으로 n만큼 움직입니다).
2. 손실함수의 출력값이 많이 줄어들었는지 보고 그렇다면 1로 돌아가 반복합니다.
이 경우는 데이터별로 1차 미분을 구해서 업데이트하는 방식에 비해 1차 미분값이 좀 더 정확하므로(전체 데이터에서 루하는 정확한 1차 미분값에 근접하므로) 매번 계산량은 비교적 많지만 좀 더 안정적인 모습을 보입니다. 미니배치 SGD는 최근 들어 많이 사용되고 있습니다.
SGD는 경사하강법에 비해 적은 양의 데이터를 사용하여 ㅂ차 미분값을 계산하므로 미분값이 불안정해집니다. 결과적으로 학습률에 많은 영향을 받습니다. 그렇기 때문에 SGD를 사용한다면 본격적으로 시스템을 완성하기 전에 미니 배치의 크기(n)와 학습률(N)을 다양하게 시도해 보고 적절한 값을 선택하는 것이 중요합니다. 이렇게 배치의 크기나 학습률의 초기 설정에 민감한 SGD의 성질 때문에 최근에는 이를 보안하는 많은 연구가 이루어지고 있습니다.
2.3.4 역전파
역전파 최적화 방법은 딥러닝에서 많이 사용하는 방식입니다. 딥러닝 모델은 보통 손실함수가 층층이 쌓인 구조로 정의됩니다. 그래서 입력과 출력 부분 외에도 많은 부분에 함수 인자가 들어갑니다. 예를 들면 다음 그림과 같이 구성됩니다.
층층이 쌓이 구조가 복잡해 보이더라도 모델을 일단 손실함수로 표현하면 최적화 방법으로 풀 수 있습니다. 하지만 파라미터가 서로 연관되고 얽혀 있어서(예를 들어 입력 계층과 은닉 계층 사이의 파라미터가 바뀌거나 은닉 계층과 출력 계층 사이의 파라미터가 바뀌면 출력값이 바뀌어서 손실이 뀌는 등단순한 최적화 방법을 사용하면 중복 연산이 많이 일어날 수 있습니다.
역전파는 이렇게 한쪽 방향으로 층층이 연결된 구조에서의 최적화를 효율적으로 수행하는 방식입니다. 한번에 손실함수를 계산하는 것이 아니라 다음과 같이 계층별로 계산해서 업데이트를 수행합니다.
1. 순방향 패스(forward-pass): 입력부터 출력까지 현재 파라미터로 예측함
a. 현재 파라미터를 넣고 한 계층씩 꼐산을 수행합니다.
b. 마지막 계층까지 계산하고 출력값을 꼐산합니다.
2. 역방향 패스(backward-pass):출력부터 입력까지 차례로 에러를 계산
a. 예측된 출력값과 데이터를 비교해서 에러를 계산합니다.
b. 바로 전 단계의 계층에 대한 손실을 순방향 패스와 반대로 거슬러 올라가며 계산합니다. 옐르 들어 은닉계층과 출력 계층 사이의 에러를 계산하고 이를 이용해서 입력 계층과 은닉계층의 에러를 계산합니다.
3. 역방향 패스에 의해 계산된 계층사이의 에러를 이용해서 1차 미분값을 구하고 그에 따라 업데이트 합니다.
4. 손실함수의 출력값이 많이 줄어들었는지 확인하고 그렇다면 1로 돌아가서 반복합니다. 많이 줄어들지 않았으면 종료합니다.
이렇게 에러가 뒤(출력계층)에서 앞(입력계층)으로 거슬러 퍼져나간다고 해서 역전파라고 합니다. '역전파'는 인터넷 상에도 자세한 설명이 많이 나와 있습니다. 또한 머신러닝소프트웨어가 이 부분을 구현하고 있으므로 실전에서는 직접 구현하지 않고도 사용할 수 있습니다.
2.3.5 최신 최적화 방법
경사하강법이나 SGD는 학습률에 따라 성능이 아주 크게 영향을 받습니다. 특히 데이터가 많을 때 SGD는 학습률에 따라 성능이 심하게 요동치기 때문에 이를 해결하려는 연구가 많습니다.
현재 1차 미분값과 과거의 1차 미분값이 변하는 추이를 이용해서 학습률을 자동으로 조절하는 방식도 있습니다. 대표적으로 Adam(Adaptive Moment Estimation)과 AdaGrad(Adaptive gradient)가 있습니다. Adam은 과거의 미분값의 방향과 분산을 계속 가중평균 내면서 효율적인 데이트 방향과 크기를 선택합니다. AdaGrad는 미분값의 크기를 추적하고 학습률을 데이터에 적응하면서 학습을 수행합니다. 최근에는 Adam을 더 많이 사용합니다.
2.4 모델 평가: 실제 활용헤서 성능을 평가하는 방법
손실함수가 모델을 최적화하고자 수식으로 표현하는 방법이라면 모델 평가(model evaluation)는 모델이 얼마나 좋은 성능을 보일지 평가하는 방법입니다. 손실함수와 모델 평가 방식이 정확하게 일치하는 경우도 있지만, 개념이 서로 다르고, 모델 평가 방식을 수학적으로 직접 최적화 하기 어려운 경우가 많아서 별도로 취급합니다.
모델 평가를 할 때는 학습 데이터뿐만 아니라 학습 데이터가 아닌 새로운 데이터가 들어왔을 때도 잘 동작하는지 측정합니다. 이를 일반화(generalization)라고 하며, 실제 머신러닝 시스템을 구축할 때 굉장히 중요한 요소입니다.
일반화가 중요한 이유는 학습에 사용되는 관측된 데이터들은 한정된 패턴들만 보여주기 때문입니다. 따라서 관측된 데이터에 지나치게 의존해 학습하면 진짜 분포(true distribution)에서 오히려 멀어질 수 있습니다. 결과적으로 학습된 데이터에서만 잘 동작하고 관측될 데이터에 대해서는 성능이 잘 나오지 않게 됩니다. 이런 문제를 과학습(오버피팅overfitting)이라고 합니다.
이런 문제가 발생하는 것을 막고자 '좋은 모델이란 무엇인가?'에서 '좋은 모델은 지나치게 복잡하지 않아야 한다'라 했던 것이면 정규화를 통해 복잡도를 적절하게 조절해보았습니다. 모델 평가는 정규화를 어느 정도 강하게 해야 할 지와 모델이 관측되지 않은 데이터에 대해서 잘 동작할지 평가합니다. 이 절에서는 모델의 일반화 특성을 살피고, 어떻게 해야 실제 사용에 가깝게 평가할 지 다룹니다.
2.4.1 모델의 일반화 특성 평가
모델 일반화 특성 평가는 한정적인 데이터를 이용해서 모델의 일반화 특성을 알아내는 것을 목표로 합니다. 다른 성능 평가 기준인 정확도나 정밀도 등과 복합적으로 상요합니다. 모델이 학습용 데이터에서만 잘 동작하는 것이 아니라 일반적인 상황에 잘 동작하는지 파악하는 일이기 때문에 중요한 평가 기준입니다.
일반화는 모델의 관측된 데이터(학습데이터)가 아닌 데이터에 대해서도 좋은 성능을 내는지를 의미합니다. 일반화가 얼마나 잘되었는 측정하는 에러를 일반화 에러(generalization error)라고 합니다. 일반화 에러를 구하는 방법은 다양하지만 여기서는 가장 유명한 학습-평가 데이터 나누기(train-test data split)와 교차검증(cross-validation)에 대해 알아보겠습니다.
2.4.1.1 학습-평가 데이터 나누기
학습-평가 데이터 나누기 방법은 데이터를 학습용과 평가용으로 나누어 평가하는 방법입니다. 간단하게 일반화 에러를 평가할 수 있어 가장 많이 쓰입니다. 이 방법은 평가하기에 앞서 데이터를 학습을 위한 데이터(학습셋)와 평가를 위한 데이터(평가셋)로 나눕니다. 80:20이나 50:50의 비율이 가장 흔하게 사용됩니다. 보통은 무작위로 해당 비율만큼 데이터를 선택하여 학습용 데이터를 만들고, 나머지를 평가용으로 사용합니다. 시간에 따라서 달라지는 데이터를 다룰 때는 오래된 데이터를 학습용으로 사용하고 최근 데이터를 평가용으로 사용하는 것이 일반적입니다.
데이터를 나누고 나서는 학습용 데이터로 모델을 학습시키고, 평가용 데이터로 성능을 평가합니다. 이렇게 평가용 데이터를 사용해서 모델이 학습용 데이터를 단순히 외운 것인지, 아니면 학습용 데이터에서 실제로 유용한 패턴을 학습했는지 검증합니다.
2.4.1.2 교차 검증
교차검증은 학습-평가 데이터 나누기를 한번만 하는 것이 아니라 여러 번 반복해서 좀 더 정확하게 일반화 에러를 평가하는 방법입니다. 교차검증은 학습-평가 데이터 나누기만큼 유명하고 많이 쓰이는 방법으로, 여기서는 교차검증 방법 중에 가장 유명한 K겹 교차검증(K-fold cross-validation)에 대해 알아보겠습니다.
K겹 교차검증은 다음과 같이 동작합니다.
1. 데이터셋을 K개로 나눕니다.
2. 그중 첫번째 세트를 제외하고 나머지에 대해 모델을 학습합니다. 그리고 첫번째 세트를 이용해서 평가를 수행합니다.
3. 과정 2를 마지막 세트까지 진행합니다.
4. 각 세트에 대해 구했던 평가 결과의 평균을 구합니다.
교차검증은 학습-평가 데이터 나누기에 비해 데이터의 여러 부분을 학습과 평가로 사용한 결과로 일반화 특성을 평가하므로 더 안정적이고 정확합니다. 하지만 여러 번 학습하고 평가하는 과정을 거치기 때문에 계산량이 많다는 단점이 있습니다.
2.4.2 정확도
정확도(accuracy)는 모델이 데이터를 얼마나 정확하게 분류했는지에 대한 평가 지표입니다. 상당수 머신러닝 시스템은 데이터를 활용하여 몇 가지 선택지 가운데에서 가장 적합한 것을 고르는데, 이때 선택이 올바른지 아닌지 측정하여 수치로 나타낸 것이 정확도입니다. 정확도의 정의는 다음과 같습니다.
정확도 = 맞게 분류한 데이터 숫자/평가하는 데 쓰는 총 데이터 숫자
즉, 정확도가 0.5라면 전체 데이터 중 반은 정답을 맞힌 것이고 반등ㄴ 틀린 겁니다. 0.9라면 전체 데이터 중 90%는 정답을 맞힌 것입니다.
정확도는 모델이 어떠한 성능을 보일지에 대한 아주 직관적인 평가 기준입니다. 하지만 정확도만으로 모델을 평가하면 여러 거지 문제가 발생할 수 있습니다.
2.4.3 정밀도와 포괄성
정확도가 0.9인 모델은 정말 좋은 모델이라고 볼수 있을 까요? 예를 들어 다음과 같은 경우에는 정확도가 상대적으로 덜 중요할 겁니다.
데이터의 항목이 한쪽으로 치우쳐져 있을 경우
사막에서 다음날 비가 올지 안 올지 예측하는 모델이 있다고 해봅시다. 사막에는 거의 비가 오지 않으므로 모델이 잘 동작하지 않더라도 비가 오지 않을 거라는 사실을 쉽게 맞출 수 있습니다. 실제로 비가 올 확률이 0.1%라면 항상 '그냥 비가 오지 않는다'는 결과를 돌려주는 모델이더라도 정확도가 0.999가 됩니다. 아주 높은 정확도지만 실제로 좋은 모델이라고 할 수 없습니다.
한 분류가 다른 분류보다 중요한 경우
혈액을 분석하여 암에 걸렸는지 판별하는 경우를 봅시다. 암에 걸렸는데 그렇지 않다고 잘못판별하면 자칫 환자의 생명이 위험할 수 있습니다. 반면 암에 걸리지 않았는데도 걸렸다고 예측할 경우에는 추가 검사를 하는 것이 일반적이므로 일반적인 과정이므로 검사비가 조금 더 들긴 하겠지만 생명이 위험하지는 않을 겁니다. 이런 경우에는 조금 부정확하더라도 암환자를 모두 발견해내는 것이 중요합니다.
이러한 경우에는 정확도만으로는 모델이 얼마나 유용하닞 판단이 어렵습니다. 그래서 모델의 유용성을 따질 때는 정밀도(precision)와 포괄성(recall)도 함께 고려합니다. 정밀도와 포괄성은 긎어(positive,양성)이냐 부정(negative,음성)이냐를 결정하는 이진화 분류 문제(binary classification)에서 다음 4가지 경우에 따라 정의 됩니다.
모델의 예측 값
데이터의 실제 값 양성으로 예측 음성으로 예측
실제로 양성 참 양성(진짜 양성) 거짓 음성
실제로 음성 거짓 양성 참 음성(진짜 음성)
정밀도는 모델이 양성으로 예측한 데이터 중에서 얼마나 정답을 맞혔는지 평가합니다. 정의는 다음과 같습니다.
정밀도 = 참 양성 / ( 참 양성 + 거짓 양성)
모델이 1년 중 10일 비가 온다고 예측했는데, 비가 오기로 예측한 날 중에서 실제로 며칠이나 비가 왔는지 평가한다고 합시다. 이때 예측한 10일 중 8일 비가 왔다면 정밀도는 0.8이 됩니다. 하지만 정밀도는 모델이 예측하지 못한 비오는 날(실제로는 비가 20일 왔을 경우)을 고려하지 않으므로 위에서 설명한 암환자 분류 문제에는 적합하지 않은 평가 방법이 될 것입니다.
포괄성은 실제로 양성인 데이터 중에서 몇 명이나 양성으로 예측했는지 평가합니다. 정의는 다음과 같습니다.
포괄성 = 참 양성 / (참 양성 + 거짓 음성)
암 환자가 10명인데 여기서 암환자를 몇 명이나 찾아냈는지가 바로 이 경우입니다. 하지만 단순희 포괄성만 높으면 좋지 않은데 그 이유는 모델이 모든 환자를 양성으로 판별된 모든 환자에 대해 정밀 검사를 해야 하므로 모델을 사용하는 의미가 없겠죠. 즉, 정밀도과 포괄성 둘 다 높으면 좋지만 단순하게 각각을 최대화 하는 것이 항상 좋은 것은 아닙니다.
그렇기 때문에 정밀도와 포괄성을 조합한 F1평가 방식이 있습니다. F1은 정밀도와 포괄성 두다 높아야 점수가 높아집니다 F1 은 다음과 같이 정의 됩니다.
F1 = (2 * 정밀도 * 포괄성)/ (정밀도 + 포괄성)
또한 AUC(area under receiver operating characteristic curve, ROC 커브 밑면적)를 이용해서 정밀도와 포괄성을 한번에 평가하는 방법도 있습니다. AUC는 모델의 결괏값을 해석하는데 있어서 정밀도와 포괄성의 트레이드오프를 계산함으로써 앞서 말씀드린 정확도보다 모델의 성능을 더 다각도로 평가합니다.
2.4.4 랭킹 평가
2.4.4.1 정밀도 @K
정밀도 @K(precision@K) 방식은 항목들의 랭킹을 구한 후 앞에서부터 K번째까지의 결과 중에서 몇개가 올바른지 검사합니다. 검색 엔진 결과를 생각하면 편한데요, 예를 들어 한 페이지에 검색 결과 20개를 보여주는 엔진이 있다고 합시다. 이때 검색 결과의 품질을 평가하기 위해 모든 결과가 아니라 1페이지의 결과 20개만 확인할 경우엔 정밀도 @20의 평가입니다. 사용자에게 ㄴ노출이 덜되는 랭킹이 낮은 항목에 대해서는 신경 쓰지 않으므로 모든 랭킹에 대해 신경 쓸 때 보다 더 효율적입니다.
2.4.4.2 NDCG
정밀도 @K와 비슷하게 랭킹이 높음 결과에 더 가중치를 줍니다. 하지만 정밀도@K와는 달리 NDCG(Normalized Discounted Cummulative Gain)는 중요도를 순휘에 따라 바꾸어가면 평가합니다. 정밀도 @K와 마찬가지로 K개만 ㅅ사용하는 경우도 있고 (NDCG@K) 전체를 사용하는 경우도 있습니다. 두 방법 모두 검색 엔진의 평가에 많이 쓰입니다.
머신러닝 시스템을 구축하는 과정은 탐구의 연속입니다. 보유한 데이터가 어떤 성질을 가지는지, 그 데이터에 맞는 머신러닝 기법은 무엇인지, 어떻게 해야 적합한 데이터를 얻을 수 있는지, 실제로 시스템을 구현해보니 기대했던 성능이 나오지 않는 이유는 무엇인지에 대한 해답을 찾아가는 과정입니다. 여러 기법을 시도해 보고 실패하고 수정하는 과정에서 좋은 머신러닝 시스템을 만들게 됩니다.
이런 탐구 과정에서 시행착오는 필연입니다. 머신러닝 이론을 알면 '왜'와 '어떻게'에 해당하는 문제를 좀 더 쉽고 효율적으로 진행할 수 있어 더 좋은 성능의 시스템을 구축할 수 있게 됩니다.
이 장에서 다룰 머신러닝의 4가지 햇김 개념은 다음과 같습니다.
- 모델: 데이터를 바라보는 시점과 가정
- 손실함수 : 모델의 수식화된 학습목표
- 최적화: 손실함수로 표현된 모델을 실제로 학습
- 모델 평가: 모델의 성능의 실제 상황에서 어떨지 추정
2.1 모델: 문제를 바라보는 관점
모델은 머신러닝의 시작점이라고도 할 수 있습니다. 이제부터 모델의 정의와 분류, 좋은 모델의 특징에 대해 알아보겠습니다.
2.1.1 모델이란?
데이터 분석을 하거나 머신러닝으로 문제를 해결하려면 무엇부터 해야 할까요? '데이터가 어떤 패턴을 가지지 않을 까? 그 패턴들을 이용하면 어떻게든 되지 않을까? 여러 생각이 들 겁니다. 그런데 패턴이 있기는 할까요? 사실 패턴이 있을 것이라는 생각도 어찌 보면 데이터 자체에 대한 믿음입니다. 그러한 믿음을 수학에서는 가정이라고 합니다. 이러한 여러 가정을 한데 모은 것을 머신러닝에서는 모델이라고 합니다.
쉽게 말해 현재 상태를 어떠한 시각으로 바라보고 어떠한 기대를 하고 있는가 하는 것이 모델입니다. 그러핟면 모델은 머신러닝에서 어떤 역할 을 할까요? 일반적으로 머신러닝의 과정은 다음과 같습니다.
모델 정하기-> 모델 수식화하기-> 모델 학습하기-> 모델 평가하기 -> 모델 정하기-> 모델 ...
1. 모델정하기(데이터가 어떻게 생겼을지 가정하기)
2. 모델의 학습 목표를 수식화 하기
3. 실제 데이터로 모델 학습하기(최적화)
4. 평가하기
그런데 과정1에서 데이터가 어떻게 생겼을지 이미 가정을 해 놓고 왜 또 과정 3에서 실제 데이터 학습이 필요할 까요?
예를 들어 과정 1의 가정이 다음과 같다고 합시다.
"이 데이터에서 x 와 y는 선형적인 상관관계를 가진다. 즉, 임의의 w 에 대해 y=wx 와 같은 관곌르 가질 것이다."
그렇다면 과정 3의 가정은 다음과 같을 수 있습니다.
"데이터 토대로 추측해본 결과 y = 2x와 같은 형태를 가진다. 즉, 데이터에 가장 잘 맞는 w 는 2다."
모델이란 가정에 따라 생성될 수 있는 함수들의 집합입니다. 이런 의미에서 보면 '모델이 바뀐다'는 것은 어떤 함수의 꼴이 완전히 달라진다는 겁니다. 정해진 함수(위에서는 y=wx) 안에서 함수의 파라미터(여기서는 w)를 데이터를 통해 추측하는 것을 학습이라고합니다. 머신러닝의 러닝의 바로 학습을 뜻합니다. 그리고 학습이라는 것은 그 모델이 표현하는 함수 집합 중에서 가장 데이터에 적합한 함수를 고르는 과정입니다.
1. 모델은 데이터를 어떻게 바라볼지에 대한 가정(통계학 용어로는 믿음(belief)이라고 함) 입니다.
2. 모델은 머신러닝의 시작점입니다.
2.1.2 간단한 모델
모델이 '가단하다'는 말은 '데이터의 구조가 간단하다'는 뜻입니다. 이는 '괸장이 강렭한 가정'을 한다고 해석할 수 있습니다.
가장 간단하지만 아주 효과적인 모델로 선형 모델이있습니다. 선형 회귀라는 말을 아마 많이 들어보았을 겁니다. 선형 휘귀는 대표적인 선형 모델입니다. 예측할 결괏값을 y라고 예측에 사용하는 값을 x1, x2, x3,...라 합시다.
선형 회귀, 데이터(x)로 학습된 모델(쩜선), 수식은 y -= 5 + 0.4x
선형 휘귀 정의는 다음과 같습니다.
- 수식: y = w0 + w1x1 + w2x2 +w3x3 + w4x4 ....
- 출력값(y)이 입력값(피처)(x1, x2, x3, ...)에 대새 선형적인 관계
선형 관계는 출력값이 입력값(피처값)들에 가중치를 곱한 값의 합(선형 결합)으로 표현되는 관계입니다. 한 가지 속성( x1)이 1만큼 증가하면 결괏값은 w1만큼 증가합니다. 감소하는 경우에는 w1만큼 감소합니다. 이러한 선형적인 관계는 많은 곳에서 찾을 수 있습니다. 옐르 들어 제품의생산량(x)과 불량품수(y)를 들 수 있습니다. 생산량과 불량품 수는 일반적으로 양의 상관관계인 경우가 많으므로선형 회귀 모델이 아주 적합합니다.
이러헌 선형 회귀 예시와 같이, 간단한 모델은 이해하기가 쉽다는 장점이 있습니다. 또한 간단한 모델은 일반적으로 데이터를 간단하게 설명하려 하기 때문에 가정 자체에 제약이 많고 데이터의변화에 비해 모델 자체의 변화폭(분산)이 적습니다. 모델 자체의 변화폭이 적으므로 예외 데이터가 들어와도 여향을 적게 받기 때문에 신경을 써야 할 부분이 적습니다.
간단함 모델의 단잠은 무엇일까요? 간단한 모델은 복잡한 관계를 학습할 수 없습니다. 입출력 데이터의 관계가 단순한 선형 관계가 아니면 어떻게 될까요? 예를 들어 w0x1/(w1+x1) 같은 관계 말이죠. 당연히 간단함 도델로 복잡한 데이터를 제대로 표현하지 못할 겁니다.
'간단한 모델'은 다음과 같이 간단히 정리할 수 있습니다.
- 데이터가 복잡하지 않고 간단하게 생겼다고 가정합니다.
- 결과를 이해하기 쉽습니다.
- 학습이 쉽습니다.
- 가정 자체가 강력해서 모델의 표현 능력에 제약이 많습니다.
2.1.3 복잡한 모델
복잡한 도델은 모델의 유연성을 더 중요시합니다. 예를 들어 결정 트리 모델을 들 수 있습니다.
- 트리의 한 분기점마다 한 가지 조건(보통 입력의 한 부분)을 검사하여 분기를 합니다.
- 모든 분기가 끝나는 리프노드(맨 끝의 노드)에는 결괏값이 들어 있습니다.
결정트리는 선형 모델과 달리 복수의 비교식으로 정의됩니다. 그리고 이 비교식은 무수히 늘어 날 수 있기 때문에 더 일반적이고 유현한 가정을 할 수 있습니다. 예를 들어 앞서 설명한 선형 모델도 결정 트리로 구현할 수 있습니다. 각 분기점마드 모든 값에 대한 결과를 작성해서 결과를 저장하면 되나까요. 복잡한 모델은 간단한 모델에 비해 전체 데이터에 일괄적인 가정이 적습니다. 예를 들어 결정 트리는 데이터 전체에 대한 가정이 거의없다시피 합니다. 그렇다면 왜 모든 걸 포함할 수 있는 복잡한 모델을 사용하지 않고 간단한 모델을 사용하는 걸까요? 2.1.5절 '좋은 모델이란 무엇인가?'에서 설명합니다.
복잡한 모델의 가장 큰 장점은 복잡한 데이터의 모델링에 적합하다는 겁니다. 결정 트리처럼 유연성이 뛰어난 모델은 많은 종류의 데이터를 모델링할 수 있습니다. 하지만 데이터의 모든 부분에 대해 일일이 가정을 만듦으로써 불필요한 노이즈까지 학습하여 성능이 나빠질 수 있습니다.
'복잡한 모델'은 다음과 같이 간단히 정리 할 수 있습니다.
- 데이터가 어떻게(간단하거나 복잡하게)생격ㅆ을 것이라는 가정 자체가 별로 없습니다.
- 결과를 이해하기 어려울 수도 있습니다.
- 학습이 복잡합니다.
- 한정된 데이터에서만의 변화를 그대로 학습하므로 새로운 데이터에 대해 성능이 떨어질 수 있습니다.
2.1.4 구조가 있는 모델
모델의 복잡도는 모델을 구분하는데 중요한 기준이지만, 몇 가지 특정 상황에서 요긴하게 쓰이는 모델이 있어서 따로 다룹니다. 바로 '구조 가 있는 모델'인데요, 단순히 입력과 출력의 상관관계를 학습할 뿐만 아니라 데이터 구조 자체를 모델링하는 조금 특이한 모델입니다. 앞서 말한 간단한 모델이나 복잡한 모델 어느 항목에도 속할 수 있다는 점에 유의해주세요.
구조가 있다는 말이 조금 생소하게 들릴 수도 있을 겁니다. 이는 입력과 출력 요서가 서로 연관관계가 있는 것으로 이해하면 됩니다. 구조가 있는 모델은 다양하지만, 이 절에서는 유명한 순차 모델과 그래프 모델에 대해 다루겠습니다.
2.1.4.1 순차 모델
순차 모델은 연속된 관측값이 서로 연관성이 있을 때 주로 사용합니다. 문서의 텍스트, 시간과관계된 데이터 분석을 예로 들 수 있습니다. 순차 모델의 대표적인 예로 문장 구조 분석에서 많이 사용하는 CRF(conditional random field)(조건부 랜덤 필드, 조건부 무작위장)와 RNN(recurrent neural net)(순환 신경망, 재귀 신경망)이 있습니다. 이들 모델의 가장 큰 특징은 특정 시점에서 상태를 저장하고, 상태가 각 시점의 입력과 출력에 따라 변화한다는 점입니다.
RNN은 여러 가지 변형 형태가 있지만 기본적인 정의는 다음과 같습니다.
- 수식: h1 = w0 + w1ht-1 + w2.xt
- 실재로 관측되지는 않았지만 특정 시점에 어떤 상태(ht)가 존재한다고 가정합니다.
- 현재의상태(ht)는 바로 직전의 상태(ht-1)와 현재의 입력 데이터(xt)에 영향을 받습니다.
- 상태(ht)에 따라 출력(yt)이 결정됩니다.
RNN과 같은 순차 모델은 숨겨진 상탯값을 가정하고 이를 이용해서 순차적인 의존성을 표현합니다. 이 순차적인 의존성을 가지는 상탯값이 다른 모델과의 가장 큰 차이점입니다. 물론 상태 (h)를 가지지 않는 모델도 현재 시점의 입력(xt)과 이전 시점의 입력(xt-1)을 합쳐서 비슷한 효괄르 낼 수 있지만, 모든 시점의 입력을 이용하기에는 계산의 한걔로 넓은 범위의 의존성을 나타내기 어렵습니다.
2.1.4.2 그패프 모델
그래프 모델은 그래프를 이용해서 순차 모델보다 좀 더 복잡한 구조를 모델링합니다. 예를 들어 문서의 문법 구조(보통 트리 형태)를 직접 모델링하거나 이미지의 픽셀 사이의 관계를 네트워크로 보고 그래프로 표현하여 모델링 합니다. 대표적인 예로 마르코프 랜덤 필드(MRF)를 살펴보겠습니다.
- 데이터의 숨겨진 상태(h) 사이에 어떤 연결 구조가 있다고 가정한 후, 연결된 위치의 숨겨진 상태끼리는 연관성이 있다고 가정합니다.
- 보통 사진을 처리할 때 많이 사용하는데, 이런 경우에는 사진의 특정 위치의 상태(h)가 바로 근접한 위치의 상태들과 관계가 있다고 가정합니다.
- 그리고 실제로 관측된 값(x)은 상태에 따라 결정된다고 가정합니다.
임의의 그래프 구조를 사용하면 순차 모델보다 더 복잡한 관계를 표현할 수 있습니다.
2.1.5 좋은 모델이란 무엇인가?
좋은 모델이란 쉽게 생각하면 '데이터의 패턴을 잘 학습한 모델'이라고 할 수 있습니다. 모델을 평가하는 이론이 많이 존재하지만, 이 책에서는 그중 모델의 복잡도와 표현력에 대한 균형을 다루는 편향-분산 트레이드오프와 균형을 자동으로 학습하는게 하는 정규화에 대해 알아 보겠습니다.
2.1.5.1 편향-분산 트레이드오프
앞에서 우리는 간단한 모델과 복잡한 모델의 특징에 대해 이미 살펴봣습니다. 그런데 모델의 표현력과 가정 강도와의 관계는 생각보다 더 오표합니다. 어떤 모델을 데이터 x로 학습한 결과를 f(x)라 하고 가능한 모든 모델 중에서 가장 좋은 모델을 f, 원하는 출력값(정답)을 y라 할 때 가장 기본적인 에러인 평균 제곱근 에러는 다음과 같은 성질을 만족합니다.
1번 식은 모델이 데이터를 예측할 때 생기는 오류가 편향(bias)의 제곱과 분산(variance)으로 쪼개진 다는 것을 보여줍니다. 즉, 모델이 더 나은 성능을 내려면 편향을 줄이거나 분산을 줄여야 한다는 뜻입니다.
2번 식(분산)은 데이터로 학습한 결과와 이상적인 모델 간의 차이입니다. 데이터로 학습한 결과가 모델의 표현력이 부족해서 이상적인 모델과 많은 차이를 보인다면 값이 커지게 됩니다. 간단한 모델일수록 가정이 강합니다. 따라서 표현력이 부족하므로 편향이 크게 나타납니다. 물론 약한 가정을 하는 복잡한 모델이라 할지라도 데이터와 터무니없이 맞지 않으면 편향이 강하게 나타날 수 있습니다.
3번 식(분산)은 데이터를 이용해서 얻은 모델이 학습할 때마다 얼마나 달라질 수 있는지 나타냅니다. 일반적으로 모델이 복잡할 수록 학습할 때마다 나타나는 모델 편차가 큽니다. 즉, 모델이 복잡할수록 분산이 더 크게 나타납니다.
그렇다면 모델의 성능을 극대화하려면 어떤 모델을 골라야 할까요? 편향이 너무 크지 않아 적당히 유연하면서 너무 복잡하지 않아 분산이 작게 나오는 모델이 적합할 겁니다. 즉, 표현력이 크다고 항상 좋은 것이 아니며 그렇다고 항상 단순한 모델만 이용해서는 제대로 된 결과를 얻지 못한다는 뜻입니다.
편향이나 분산을 직접적으로 줄이는 대표적인 예로 다음과 같은 방법을 들 수 있습니다.
- 부스팅:간단한 모델ㅇㄹ 여러 개 조합하여 편향을 줄이는 방법
- 랜덤 포레스트: 복잡한 모델인 결정 뜨리를 여러 개 조합하여 분산을 줄이는 방법
2.1.5.2 정규화
정규화는 정해진 모델이 필요 이상으로 복잡해지지 않도록 조절하는 트릭입니다. 모델이 데이터에 비해 필요 이상으로 복잡하면 불필요한 노이즈까지 학습해서 학습할 때는 성능이 좋지만, 실제로 사용할 때는 좋지 않은 성능을 보일 수 있습니다. 모델의 복잡도를 줄이는 방법으로는 크게 다음 2가지가 있습니다.
- 모델 변경: 데이터를 표현하는 방법을 완전히 새롭게 전환해서 적합한 모델을 찾는 방법
- 정규화: 모델에 들어 있는 인자에 제한을 주어 모델이 필요 이상으로 복잡해지지 않게 하는 방법
제약 조건과 데이터를 이용해서 적당한 복잡도를 가지는 모델을 자동적으로 찾아주는 기법이 정규화 입니다.
2.2 손실함수: 모델의 수식화된 학습목표
모델이 실제로 데이터를 바르게 표현했는지 혹은 얼마나 예측이 정확한지 수학적으로 표현하는 것이 손실함수(loss function)입니다. 앞서 다우었던 선형 회귀를 예로 들면 만들어낸 직선에서 데이터가 얼마나 떨어져 있는지 계산하는 함수가 손실함수입니다.
손실함수의 값이 작을수록 모델이 더 정확하게 학습된 겁니다. 이때 손실함수로 언은 결과값을 보통 에러라고 부릅니다.
손실함수는 거의 같은 모델을 대상으로 하더라도 중요하게 생각하는 데이터의 특성에 따라 변형될 수 있습니다. 예를 들어 데이터의 전체적인 패턴을 중시할지 지엽적인 패턴을 중시할지에 따라서 기본적인 손실함수에 추가적인 손실함수를 덧붙일 수 있습니다. 엄밀히 따지면 데이터를 보는 관점이 조금 변화했기 때문에 다른 모델이라고 말할 수도 있지만, 이러한 손실함수의 조합은 같은 모델의 변형으로 열겨지는 경우가 많으며 자주 사용되는 기법입니다.
손실함수는 데이터 전체에 대해 계산하는 함수지만, 간단하게 표시하기 위해 할 개의 데이터에 대새서만 정의하기도 합니다. 이때 각 데이터에 대한 손실함수 계산 결과의총합이 그 모델과 데이터 전체에 대한 손실함수 결과입니다. 물론 이런 경우에는 몇 가지 가정이 추가됩니다. 예를 들어 데이터셋에서 각각의 데이터가 서로 확률적 독립이고 같은 분포를 가진다는 i,i,d가정(independent and identically distributed:독립항등부포)이 대표적입니다.
여기서 다룰 손실함수는 다음 4가지 입니다.
- 산술 손실함수: 모델로 산술값을 예측할 때 데이터에 대한 예측값과 실제 관측값을 비교하는함수입니다. 주로 희귀 문제에서 사용합니다.
- 확률 손실함수: 모델로 항목이나 값에 대한 확률을 예측하는 경우에 사용합니다. 매우 유연하기 때문에 회귀 문제를 비롯해 보편적으로 사용합니다.
- 랭킹 손실함수: 모델로 순서를 결정할 때 사용합니다. 추천 시스템에서 주로 사용합니다.
- 모델 복잡도와 관련된 손실함수: 보통 위 손실함수들과 합쳐져서 모델이 필요 이상으로 복잡해지지 않도록 방지하는 손실함수입니다. 앞에서 설명한 정규화의 일종으로 볼 수 있습니다.
2.2.1 산술 손실 함수
모델로 산술값을 예측할 때는 각 데이터에 대한 예측값의 차이를 산술적으로 계산하는 손실함수를 많이 사용합니다. 차이의 제곱을 사용하는 제곱 손실함수와 차이의 절댓값을 사용하는 손실함수가 많이 쓰입니다.
제곱 손실합수는 굉장이 많은 곳에서 사용됩니다. 제곱 손실함수를 사용하는 이유에 대해서는 여러가지 이론이 존재하지만, 가장 간단한 설명으로는 주어진 데이터 출력값(y)과 모델의 예측값의 차이의 제곱을 계산하므로, 최적화가 쉽고, 손실값의 이해가 쉽기 때문입니다.
이 손실함수는 주어진 데이터 1개(입력x와 출력 y의 쌍)에 대해 학습된 모델(w0과 w1인자로 결정되는)이 얼마만큼의 에러를 가지는지, 즉 얼마나 데이터를 잘 표현하는지 정량적으로 계산합니다. 물론 이 식은 데이터 하나에 대해 정의를 한 것으로, 전체 데이터에 대해서는 각 손실함수의 값을 전부 합산하거나 평균을 낸값을 사용합니다.(참고로 평균의 경우에는 평균 제곱편차라는 표현을 주로 사용합니다.).
2.2.2 확률 손실함수
앞에서 산술값을 예측하기 위한 모델(회귀)에 적합한 산술 손실함수에대해 살펴봣습니다. 이와 달리 특정 항목을 고르는 분류 모델에서는 확률 손실함수가 더 적합합니다.
확률 손실함수는 모델이 관측된 데이터를 예측할 확률을 최대화하는 방식으로 계산됩니다. 접근 방식에 따라 많은 확률 손실함수를 만들 수 있습니다. 대표적으로 MLE(maximum likehood estimation:최대 가능도 방법)와 쿨백-라이블러 발산(Kullback Leibler divergence:KL-divergence, KL-발산)이 있습니다.
여기서는 확률 손실함수 중에서 자주 사용되는 교차 엔트로피(cross entropy)함수에대해 알아봅니다. 이 함수는 딥러닝에서 특히 많이 사용됩니다.
이 함수는 MLE방식에 따라 만들어 집니다. 여기서 가능도 likelihood(혹은 우도)란 주어진 데이터가 얼마나 적합한지에 대한 조건부 확률입니다. 수식은 p(x|f)와 같이 씁니다. 여기서 x는 데이터, f는 모델입니다. 가능도가 클 수록 주어진 모델이 관측된 데이터를 더 잘 표현한 것이 됩니다. 따라서 이 값을 최대화 하는 쪽으로 모델을 학습시켜야하는데, 보통 손신할수를 풀 때는 최소값을 구하는 최적화 방식을 사용하므로, 계산의 편의를 위하여 가능도의 부호를 바꾸고 log값을 취한 음의 로그 가능도(negative log-likelihood) 손실함수를 많이 사용합니다.
loss(f) = -log p(x|f)
음의 로그 가능도 손실함수는 실제 모델 f에 따라 정의되겠지만, 분류에서 많이 사용되는 다항로지스틱 희귀(multinomial logistic regression)의 음의 로그 가능도 손실 함수에 대해 살펴보겠습니다.
다항 로지스틱 희귀는 입력값을 {1,2,...C} 중 하나로 분류합니다. 이때 모델의 각 항의 예측 값은 0부터 1사이의 실수가 되고 모든 항의 예측값의 총합은 1이 됩니다. 각 분류에 대한 확률을 y라고 합시다.
2.2.3 랭킹 손실 함수
랭킹 손실함수는 지금까지 설명한 산술 소실함수나 확률 손실함수화는 달리 특정한 결괏값에 대한 손실을 측정하지 않습니다. 대신 모델이 예측해낸 결괏값의 순서가 맞는지만 판별합니다. 랭킹 손실함수는 목록에서 몇가지를 추천 시스템이나 랭킹학습 분야에서 많이 사용합니다.
순서를 예측하느 문제는 손실함수를 조금 다르게 표현합니다. 예플 들어 데이터의 순서가 x1> x2> x3> x4> x5라고 들어오면 모델은 각 데이터의 순서를 x의 의미에 따라 학습힌다. 여기서 x의숫자를 의미한다기보다는 어떤 개념을 나타낸다고 생각하면 됩니다.(예를 들어 x1은 사과, x2는 바나나등).
다양한 랭킹 손실함수가 있는데 그 중에서도 모델이 만들어낸 순서 목록에서 모든 쌍의 순서가 맞았는지 틀렸는지 확인하는 방식이 가장 간단합니다. 예를 들어 모델이 x3> x1> x2> x4> x5라는 결과를 만들었다면 각 쌍에 대해 다음과 같은 결괏값 쌍을 가집니다.
1. x3 > x1
2. x3 > x2
3. x3 > x4
4. x4 > x5
5. x1 > x3
6. x1 > x4
7. x1 > x5
8. x3 > x4
9. x3 > x5
10. x4 > x5
이 모델의 결괏값 쌍과 정답인 x1> x2> x3> x4> x5를 비교하면 10가지 관계 중에서 몇가지가 틀렸는지 알 수있습니다. 이 경우에는 x3 > x1, x3> x2이렇게 두 관계가 잘못되었으므로 손실 함수 결과는 2입니다. 이 손실함수는 페어와이즈 제로-원 손실함수 라고 부릅니다.
다른 방식으로손실을 측정하는 편집 거리 라는 랭킹 손실함수도 있습니다. 이 손실함수는 모델이 예측한 순서 목록에서 몇 번의 맞바꿈을 해야 워래 순서로 돌아갈 수 있는지 측정합니다.
2.2.4 모델 복잡도와 관련된 손실함수
2.3 최적화: 실제로 학습을 하는 방법
지급까지 데이터를 바라보는 시각인 모델과 모델이 데이터를 얼마나 잘 표현하는지를 수학적으로 측정하는 손실함수에 대해 알아봤습니다. 이제부터 손실함수를 이용해서 모델을 학습하는 방법에 대해 알아보겠습니다.
손실함수의 결괏값을 최소화하는 모델의 인자를 찾는 것을 최적화라고 합니다. 이렇게 임의로 여러 값을 대입해 보면서 손실을 최소화하는 방법도 있지만 보통은 수학적으로 손실함수를 분석해서 최적화 합니다. 그리고 그 방식은 사실 이미 고등하교 때 배웠습니다.
함수의 최솟값과 최대값에 대해서는 익히 알고 있으리라 생각합니다. 이 최속값은 위 식처럼 인수분해로 한번에 구할 수 있지만, 한번에 풀리는 경우는 많지 않고 보통은 여러 번 반복해서 업데이트를 해야 최소값을 찾을 수 있습니다. 이 절에서는 그중 많이 쓰이는 경사하강법, 뉴턴/준뉴턴 방법, 확률적 경사하강법, 역전파 그리고 몇가지 최신 최적화 방법을 살펴보겠습니다.
2.3.1 경사하강법
경사하강법은 간단한 최적화 방법 중 하나로 임의의 지점에서 시작해서 경사를 따라 내려갈 수 없을 때까지 반복적으로 내려가며 최적화를 수행합니다. [그림 2-8]에 경사하강법을 적용해보면 [그림 2-9]와 같습니다.
1. 현재 @값에서의 경사값을 구합니다.
2. 경사를 따라 n만큼 내려갑니다.
3. 손실함수의 출력값이 많이 줄어들었는지 보고 그렇다면 1로 돌아가 계속 하강합니다. 많이 줄어들지 않았다면 경사를 다 내려온 것으므로 @값 구하기를 그만둡니다.
위 그림은 @=4에서의 경사를 보여줍니다. 경사는 곡선의 접선 방향으로 결정되는데, 이를 계산하려면 손실함수 인자 @에 대해 미분하면 됩니다.
이 경사값을 이용해 앞에서 서술한 과정 1,2,3을 반복하면 손실함수를 쉽게 최적화 할 수 있습니다. 이런 이유로 미분 가능한 손실함수를 많이 사용합니다. 예를 들어 모든 점에서 미분 가능한 제곱 손실함수를 절댓값 손실함수보다 많이 씁니ㅏㄷ.
경사는 방향을 결정합니다. 얼마나 빨리 내려가는지를 학습률 혹은 스템 크기라고 불리는 n에 의해 결정됩니다.
2.3.2 뉴턴/준 뉴턴 방법
학습률 n와관련된 몇 가지 고전적인 이론이 있습니다. 대표적인 이론으로 뉴턴 방법과 준 뉴턴 방법이 있는데요, 그 중 뉴턴방법은 임의의 학습률을 사용하는 대신 1차 미분값과 2차 미분값을 활용하여 업데이트를 수행합니다.
뉴턴 방법은 단순한 경사하강법보다 훨씬 빠른 최적화 속도를 보이지만 현실적으로 2차 미분은 식을 풀기도 어렵고 계산에도 많은 자원이 필요해서 잘 사용하지 않습니다.
준 뉴턴 방식은 2차 미분을 직접 계산하는 대신 1차 미분값을 활용해 2차 미분값을 근사해서 사용합니다. 그렇기 때문에 2차 미분값을 계산하지 않고도 빠르게 많은 문제를 풀 수가 있으며, 대표적인 방법으로 BFGS(broyden-fletcher-goldfarb-shanno algorithm)와 LBFGS(limited-memory BFGS)가 있습니다. BFGS와 LBFGSㄴ느 데이터가 너무 많지 않은 경우에 뛰어난 성능을 보입니다. 최근에는 데이터가 많아지면서 확률적 경사하강법이 많이 쓰이기는 하지만 준 뉴턴 방식은 여전히 효율적인 최적화 방법입니다. 유명한 기법이라 대부분의 머신러닝 소프트웨어에 구현되어 있으니 궁금한 분은 사용해 보기 바랍니다.
2.3.3 확률적 경사하강법
앞에서 설명한 경사하강법이나 뉴턴/준뉴턴 방법은 데이터가 많아지면 계산량이 증가해서 학습 시간이 길어집니다(데이터에 따라 다르겠지만 몇심만 개 정도는 크게 문제가 없습니다.) 시간이 오래 걸리는 이유는 손실함수와 1차 미분값을 전체 데이터에 대해 걔산하기 때문입니다. 이를 해결하고자 일부 데이터만 이용해서 손실함수와 1차 미분값을 근사적으로 계산하는 확률적 경사하강법(SGD)이 만들어졌습니다.
SGD는 전체 데이터를 가지고 계산하기보다는 다음과 같이 n개의 샘플을 뽑아서 손실함수와 1차 미분값을 계산합니다.
-SGD의손실함수: (데이터 전체에 대한 평균을 사용하는 대신) 데이터에서 n개의 샘플을 뽑아서 그 평균을 사용합니다.
-SGD의 1차 미분값: 손실함수와 마찬가지로 n개의 샘플을 뽑아서 계산합니다.
SGD는 n개의 샘플을 뽑는 방법은 여러 가지가 있는데, 대표적으로 다음 2가지 방법을 많이 사용합니다.
-데이터를 처음부터 끝까지 하나씩 업데이트하는 SGD
-미니배치 SGD
데이터를 처음부터 끝까지 하나씩 업데이트 하는 SGD는 다음과 같이 동작합니다.
1. 데이터의 처음부터 끝까지 루프를 돕니다(i=0 ~ 데이터 크기).
a. 현재 @ 값과 현재 데이터(i번째 데이터)에서의 1차 미분값을 구합니다.
b. 경사를 따라 n만큼 내려갑니다(즉, 경사값에 -1을 곱한 방향으로 n 만큼 움직입니다.).
2. 손실 함수의 출력값이 많이 줄어들었는지 보고 그렇다면 1로 돌아가 반복합니다. 많이 줄어들지 않았다면 경사를 다 내려온 것이므로 @값 구하기를 그만둡니다.
이 방식에서는 데이터 업데이트가 일어날 때마다 데이터를 몇 번 처음부터 끝까지 훑으면서(sweep이라고 합니다) 손실함수가 수렴했는지 검사합니다(모든 데이터를 순차적으로 하나씩 뽑는 방식 대신 정해진 숫자만큼 임의로 데이터를 뽑아서확인하는 방식도 사용합니다). 이 방식은 경사하강법보다 굉장히 공격적으로 업데이트하기 때문에 속도가 빠르지만, 각 데이터의 1차 미분값은 전체 데이터를 이용한 1차 미분값보다 부 정확하므로 손실함수의 출력값이 불안정해지곤 합니다. 그래서 이 방식과 단순한 경사하강법의 중간 정도 되는 미니배치 SGD같은 방식이 자주 쓰입니다.
미니배치 SGD는 다음과 같이 동작합니다. 데이터를 n개씩 뽑아서 하는 SGD입니다.
1. 데이터를 랜덤하게 n개 뽑습니다.
a. 랜덤하게 뽑은 n개 데이터의 1차 미분값의 평균을 구합니다.
b. 경사를 따라 n만큼 내려갑니다(즉, 경사값에 -1을 곱한 방향으로 n만큼 움직입니다).
2. 손실함수의 출력값이 많이 줄어들었는지 보고 그렇다면 1로 돌아가 반복합니다.
이 경우는 데이터별로 1차 미분을 구해서 업데이트하는 방식에 비해 1차 미분값이 좀 더 정확하므로(전체 데이터에서 루하는 정확한 1차 미분값에 근접하므로) 매번 계산량은 비교적 많지만 좀 더 안정적인 모습을 보입니다. 미니배치 SGD는 최근 들어 많이 사용되고 있습니다.
SGD는 경사하강법에 비해 적은 양의 데이터를 사용하여 ㅂ차 미분값을 계산하므로 미분값이 불안정해집니다. 결과적으로 학습률에 많은 영향을 받습니다. 그렇기 때문에 SGD를 사용한다면 본격적으로 시스템을 완성하기 전에 미니 배치의 크기(n)와 학습률(N)을 다양하게 시도해 보고 적절한 값을 선택하는 것이 중요합니다. 이렇게 배치의 크기나 학습률의 초기 설정에 민감한 SGD의 성질 때문에 최근에는 이를 보안하는 많은 연구가 이루어지고 있습니다.
2.3.4 역전파
역전파 최적화 방법은 딥러닝에서 많이 사용하는 방식입니다. 딥러닝 모델은 보통 손실함수가 층층이 쌓인 구조로 정의됩니다. 그래서 입력과 출력 부분 외에도 많은 부분에 함수 인자가 들어갑니다. 예를 들면 다음 그림과 같이 구성됩니다.
층층이 쌓이 구조가 복잡해 보이더라도 모델을 일단 손실함수로 표현하면 최적화 방법으로 풀 수 있습니다. 하지만 파라미터가 서로 연관되고 얽혀 있어서(예를 들어 입력 계층과 은닉 계층 사이의 파라미터가 바뀌거나 은닉 계층과 출력 계층 사이의 파라미터가 바뀌면 출력값이 바뀌어서 손실이 뀌는 등단순한 최적화 방법을 사용하면 중복 연산이 많이 일어날 수 있습니다.
역전파는 이렇게 한쪽 방향으로 층층이 연결된 구조에서의 최적화를 효율적으로 수행하는 방식입니다. 한번에 손실함수를 계산하는 것이 아니라 다음과 같이 계층별로 계산해서 업데이트를 수행합니다.
1. 순방향 패스(forward-pass): 입력부터 출력까지 현재 파라미터로 예측함
a. 현재 파라미터를 넣고 한 계층씩 꼐산을 수행합니다.
b. 마지막 계층까지 계산하고 출력값을 꼐산합니다.
2. 역방향 패스(backward-pass):출력부터 입력까지 차례로 에러를 계산
a. 예측된 출력값과 데이터를 비교해서 에러를 계산합니다.
b. 바로 전 단계의 계층에 대한 손실을 순방향 패스와 반대로 거슬러 올라가며 계산합니다. 옐르 들어 은닉계층과 출력 계층 사이의 에러를 계산하고 이를 이용해서 입력 계층과 은닉계층의 에러를 계산합니다.
3. 역방향 패스에 의해 계산된 계층사이의 에러를 이용해서 1차 미분값을 구하고 그에 따라 업데이트 합니다.
4. 손실함수의 출력값이 많이 줄어들었는지 확인하고 그렇다면 1로 돌아가서 반복합니다. 많이 줄어들지 않았으면 종료합니다.
이렇게 에러가 뒤(출력계층)에서 앞(입력계층)으로 거슬러 퍼져나간다고 해서 역전파라고 합니다. '역전파'는 인터넷 상에도 자세한 설명이 많이 나와 있습니다. 또한 머신러닝소프트웨어가 이 부분을 구현하고 있으므로 실전에서는 직접 구현하지 않고도 사용할 수 있습니다.
2.3.5 최신 최적화 방법
경사하강법이나 SGD는 학습률에 따라 성능이 아주 크게 영향을 받습니다. 특히 데이터가 많을 때 SGD는 학습률에 따라 성능이 심하게 요동치기 때문에 이를 해결하려는 연구가 많습니다.
현재 1차 미분값과 과거의 1차 미분값이 변하는 추이를 이용해서 학습률을 자동으로 조절하는 방식도 있습니다. 대표적으로 Adam(Adaptive Moment Estimation)과 AdaGrad(Adaptive gradient)가 있습니다. Adam은 과거의 미분값의 방향과 분산을 계속 가중평균 내면서 효율적인 데이트 방향과 크기를 선택합니다. AdaGrad는 미분값의 크기를 추적하고 학습률을 데이터에 적응하면서 학습을 수행합니다. 최근에는 Adam을 더 많이 사용합니다.
2.4 모델 평가: 실제 활용헤서 성능을 평가하는 방법
손실함수가 모델을 최적화하고자 수식으로 표현하는 방법이라면 모델 평가(model evaluation)는 모델이 얼마나 좋은 성능을 보일지 평가하는 방법입니다. 손실함수와 모델 평가 방식이 정확하게 일치하는 경우도 있지만, 개념이 서로 다르고, 모델 평가 방식을 수학적으로 직접 최적화 하기 어려운 경우가 많아서 별도로 취급합니다.
모델 평가를 할 때는 학습 데이터뿐만 아니라 학습 데이터가 아닌 새로운 데이터가 들어왔을 때도 잘 동작하는지 측정합니다. 이를 일반화(generalization)라고 하며, 실제 머신러닝 시스템을 구축할 때 굉장히 중요한 요소입니다.
일반화가 중요한 이유는 학습에 사용되는 관측된 데이터들은 한정된 패턴들만 보여주기 때문입니다. 따라서 관측된 데이터에 지나치게 의존해 학습하면 진짜 분포(true distribution)에서 오히려 멀어질 수 있습니다. 결과적으로 학습된 데이터에서만 잘 동작하고 관측될 데이터에 대해서는 성능이 잘 나오지 않게 됩니다. 이런 문제를 과학습(오버피팅overfitting)이라고 합니다.
이런 문제가 발생하는 것을 막고자 '좋은 모델이란 무엇인가?'에서 '좋은 모델은 지나치게 복잡하지 않아야 한다'라 했던 것이면 정규화를 통해 복잡도를 적절하게 조절해보았습니다. 모델 평가는 정규화를 어느 정도 강하게 해야 할 지와 모델이 관측되지 않은 데이터에 대해서 잘 동작할지 평가합니다. 이 절에서는 모델의 일반화 특성을 살피고, 어떻게 해야 실제 사용에 가깝게 평가할 지 다룹니다.
2.4.1 모델의 일반화 특성 평가
모델 일반화 특성 평가는 한정적인 데이터를 이용해서 모델의 일반화 특성을 알아내는 것을 목표로 합니다. 다른 성능 평가 기준인 정확도나 정밀도 등과 복합적으로 상요합니다. 모델이 학습용 데이터에서만 잘 동작하는 것이 아니라 일반적인 상황에 잘 동작하는지 파악하는 일이기 때문에 중요한 평가 기준입니다.
일반화는 모델의 관측된 데이터(학습데이터)가 아닌 데이터에 대해서도 좋은 성능을 내는지를 의미합니다. 일반화가 얼마나 잘되었는 측정하는 에러를 일반화 에러(generalization error)라고 합니다. 일반화 에러를 구하는 방법은 다양하지만 여기서는 가장 유명한 학습-평가 데이터 나누기(train-test data split)와 교차검증(cross-validation)에 대해 알아보겠습니다.
2.4.1.1 학습-평가 데이터 나누기
학습-평가 데이터 나누기 방법은 데이터를 학습용과 평가용으로 나누어 평가하는 방법입니다. 간단하게 일반화 에러를 평가할 수 있어 가장 많이 쓰입니다. 이 방법은 평가하기에 앞서 데이터를 학습을 위한 데이터(학습셋)와 평가를 위한 데이터(평가셋)로 나눕니다. 80:20이나 50:50의 비율이 가장 흔하게 사용됩니다. 보통은 무작위로 해당 비율만큼 데이터를 선택하여 학습용 데이터를 만들고, 나머지를 평가용으로 사용합니다. 시간에 따라서 달라지는 데이터를 다룰 때는 오래된 데이터를 학습용으로 사용하고 최근 데이터를 평가용으로 사용하는 것이 일반적입니다.
데이터를 나누고 나서는 학습용 데이터로 모델을 학습시키고, 평가용 데이터로 성능을 평가합니다. 이렇게 평가용 데이터를 사용해서 모델이 학습용 데이터를 단순히 외운 것인지, 아니면 학습용 데이터에서 실제로 유용한 패턴을 학습했는지 검증합니다.
2.4.1.2 교차 검증
교차검증은 학습-평가 데이터 나누기를 한번만 하는 것이 아니라 여러 번 반복해서 좀 더 정확하게 일반화 에러를 평가하는 방법입니다. 교차검증은 학습-평가 데이터 나누기만큼 유명하고 많이 쓰이는 방법으로, 여기서는 교차검증 방법 중에 가장 유명한 K겹 교차검증(K-fold cross-validation)에 대해 알아보겠습니다.
K겹 교차검증은 다음과 같이 동작합니다.
1. 데이터셋을 K개로 나눕니다.
2. 그중 첫번째 세트를 제외하고 나머지에 대해 모델을 학습합니다. 그리고 첫번째 세트를 이용해서 평가를 수행합니다.
3. 과정 2를 마지막 세트까지 진행합니다.
4. 각 세트에 대해 구했던 평가 결과의 평균을 구합니다.
교차검증은 학습-평가 데이터 나누기에 비해 데이터의 여러 부분을 학습과 평가로 사용한 결과로 일반화 특성을 평가하므로 더 안정적이고 정확합니다. 하지만 여러 번 학습하고 평가하는 과정을 거치기 때문에 계산량이 많다는 단점이 있습니다.
2.4.2 정확도
정확도(accuracy)는 모델이 데이터를 얼마나 정확하게 분류했는지에 대한 평가 지표입니다. 상당수 머신러닝 시스템은 데이터를 활용하여 몇 가지 선택지 가운데에서 가장 적합한 것을 고르는데, 이때 선택이 올바른지 아닌지 측정하여 수치로 나타낸 것이 정확도입니다. 정확도의 정의는 다음과 같습니다.
정확도 = 맞게 분류한 데이터 숫자/평가하는 데 쓰는 총 데이터 숫자
즉, 정확도가 0.5라면 전체 데이터 중 반은 정답을 맞힌 것이고 반등ㄴ 틀린 겁니다. 0.9라면 전체 데이터 중 90%는 정답을 맞힌 것입니다.
정확도는 모델이 어떠한 성능을 보일지에 대한 아주 직관적인 평가 기준입니다. 하지만 정확도만으로 모델을 평가하면 여러 거지 문제가 발생할 수 있습니다.
2.4.3 정밀도와 포괄성
정확도가 0.9인 모델은 정말 좋은 모델이라고 볼수 있을 까요? 예를 들어 다음과 같은 경우에는 정확도가 상대적으로 덜 중요할 겁니다.
데이터의 항목이 한쪽으로 치우쳐져 있을 경우
사막에서 다음날 비가 올지 안 올지 예측하는 모델이 있다고 해봅시다. 사막에는 거의 비가 오지 않으므로 모델이 잘 동작하지 않더라도 비가 오지 않을 거라는 사실을 쉽게 맞출 수 있습니다. 실제로 비가 올 확률이 0.1%라면 항상 '그냥 비가 오지 않는다'는 결과를 돌려주는 모델이더라도 정확도가 0.999가 됩니다. 아주 높은 정확도지만 실제로 좋은 모델이라고 할 수 없습니다.
한 분류가 다른 분류보다 중요한 경우
혈액을 분석하여 암에 걸렸는지 판별하는 경우를 봅시다. 암에 걸렸는데 그렇지 않다고 잘못판별하면 자칫 환자의 생명이 위험할 수 있습니다. 반면 암에 걸리지 않았는데도 걸렸다고 예측할 경우에는 추가 검사를 하는 것이 일반적이므로 일반적인 과정이므로 검사비가 조금 더 들긴 하겠지만 생명이 위험하지는 않을 겁니다. 이런 경우에는 조금 부정확하더라도 암환자를 모두 발견해내는 것이 중요합니다.
이러한 경우에는 정확도만으로는 모델이 얼마나 유용하닞 판단이 어렵습니다. 그래서 모델의 유용성을 따질 때는 정밀도(precision)와 포괄성(recall)도 함께 고려합니다. 정밀도와 포괄성은 긎어(positive,양성)이냐 부정(negative,음성)이냐를 결정하는 이진화 분류 문제(binary classification)에서 다음 4가지 경우에 따라 정의 됩니다.
모델의 예측 값
데이터의 실제 값 양성으로 예측 음성으로 예측
실제로 양성 참 양성(진짜 양성) 거짓 음성
실제로 음성 거짓 양성 참 음성(진짜 음성)
정밀도는 모델이 양성으로 예측한 데이터 중에서 얼마나 정답을 맞혔는지 평가합니다. 정의는 다음과 같습니다.
정밀도 = 참 양성 / ( 참 양성 + 거짓 양성)
모델이 1년 중 10일 비가 온다고 예측했는데, 비가 오기로 예측한 날 중에서 실제로 며칠이나 비가 왔는지 평가한다고 합시다. 이때 예측한 10일 중 8일 비가 왔다면 정밀도는 0.8이 됩니다. 하지만 정밀도는 모델이 예측하지 못한 비오는 날(실제로는 비가 20일 왔을 경우)을 고려하지 않으므로 위에서 설명한 암환자 분류 문제에는 적합하지 않은 평가 방법이 될 것입니다.
포괄성은 실제로 양성인 데이터 중에서 몇 명이나 양성으로 예측했는지 평가합니다. 정의는 다음과 같습니다.
포괄성 = 참 양성 / (참 양성 + 거짓 음성)
암 환자가 10명인데 여기서 암환자를 몇 명이나 찾아냈는지가 바로 이 경우입니다. 하지만 단순희 포괄성만 높으면 좋지 않은데 그 이유는 모델이 모든 환자를 양성으로 판별된 모든 환자에 대해 정밀 검사를 해야 하므로 모델을 사용하는 의미가 없겠죠. 즉, 정밀도과 포괄성 둘 다 높으면 좋지만 단순하게 각각을 최대화 하는 것이 항상 좋은 것은 아닙니다.
그렇기 때문에 정밀도와 포괄성을 조합한 F1평가 방식이 있습니다. F1은 정밀도와 포괄성 두다 높아야 점수가 높아집니다 F1 은 다음과 같이 정의 됩니다.
F1 = (2 * 정밀도 * 포괄성)/ (정밀도 + 포괄성)
또한 AUC(area under receiver operating characteristic curve, ROC 커브 밑면적)를 이용해서 정밀도와 포괄성을 한번에 평가하는 방법도 있습니다. AUC는 모델의 결괏값을 해석하는데 있어서 정밀도와 포괄성의 트레이드오프를 계산함으로써 앞서 말씀드린 정확도보다 모델의 성능을 더 다각도로 평가합니다.
2.4.4 랭킹 평가
2.4.4.1 정밀도 @K
정밀도 @K(precision@K) 방식은 항목들의 랭킹을 구한 후 앞에서부터 K번째까지의 결과 중에서 몇개가 올바른지 검사합니다. 검색 엔진 결과를 생각하면 편한데요, 예를 들어 한 페이지에 검색 결과 20개를 보여주는 엔진이 있다고 합시다. 이때 검색 결과의 품질을 평가하기 위해 모든 결과가 아니라 1페이지의 결과 20개만 확인할 경우엔 정밀도 @20의 평가입니다. 사용자에게 ㄴ노출이 덜되는 랭킹이 낮은 항목에 대해서는 신경 쓰지 않으므로 모든 랭킹에 대해 신경 쓸 때 보다 더 효율적입니다.
2.4.4.2 NDCG
정밀도 @K와 비슷하게 랭킹이 높음 결과에 더 가중치를 줍니다. 하지만 정밀도@K와는 달리 NDCG(Normalized Discounted Cummulative Gain)는 중요도를 순휘에 따라 바꾸어가면 평가합니다. 정밀도 @K와 마찬가지로 K개만 ㅅ사용하는 경우도 있고 (NDCG@K) 전체를 사용하는 경우도 있습니다. 두 방법 모두 검색 엔진의 평가에 많이 쓰입니다.
2018년 2월 25일 일요일
CHAPTER 1 머신러닝 시작하기
1.1 머신러닝 소개
"데이터를 이용해서 명시적으로 정의도지 않은 패턴을 컴퓨터로 학습하여 결과를 만들어내는 학문 분야".
데이터, 패턴인식, 컴퓨터를 이용한 계산
1.2 머신러닝을 이해하는 데 필요한 배경지식
1.2.1 수학(선형대수, 미분, 통계, 확률)
1.2.2 프로그래밍
1.3 머신러닝 발전사
1.3.1 머신러닝의 역사와 현재 트렌드
고전적 인공지능 시대
1950년대 유명한 컴퓨터 학자인 앨런 튜링은 인공지능을 판별하는 튜링 데스크를 제안했는데, 이는 기계가 인간과 얼마나 비슷하게 대화하는지를 기준으로 기계의지능을 판별하는 테스트.
신경망 시대
1957년에 퍼셉트론(perceptron)이라는 기초적인 신경망이 개발. 이를 여러 개 묶어 복잡한 신경망을 구성하면서 입력과 출력을 굉장히 유연하게 연결할 수 있었지만, 당시 신경망에는 여러가지 문제점이 있었습니다. 우선 그 시대에는 구할 수 있는 데이터가 굉장히 한정적이라 신경망 성능이 생각보다 신통치 않았고, 기초 이론의 부족으로 한정적인 패턴만 학습이 가능했습니다. 많은 학자가 신경망 구축에 뛰어들었지만, 예상보다 결과가 잘 나오기 않아 침체된 상태가 이어졌습니다. 이 시기를 '인공지능의 겨울'이라 부르곤 합니다.
통계학적 머신러닝 시대
1990년대에 들어 통계학을 전산학과 접목시켜 대규모에서 패턴을 찾는 시도가 기존에 비해 진일보된 성과를 냈습니다. 기존의 방법과 가자으 큰 차이점은 데이터에 훨씬 더 중요한 비중을 두었다는 것입니다. 그 결과 기존의 그 어떤 방법보다 성능이 좋았기 때문에 큰 관심을 받고 산업의 여러 분야에 도입되었습니다. 이 시기에 머신러닝이라는 용어가 등장 했으며, 차후에 딥러닝이 나온 이후 이런 통계학에 중심을 둔 기법들을 통계학적 머신러닝알고 부르게 됩니다.
빅데이터 시대
통계학적 머신러닝은 웹에서 쏟아지는 데이터, 대용량 저장장치, 분산 처리 기술과 결합하여 엄청난 시너지를 만들었습니다. 빅데이터는 용어가 2010년대부터 유행했는데, 빅데이터 시대에는 기존보다 더 큰 데이터를 분석하기 위해 기존보다 휠씬 더 큰 규모의 머신러닝 시스템을 만들게 되고 그 덕택에 성능도 더욱 좋아지게 됩니다.
딥러닝 시대
데이터가 많아지고 연산 능력이 증가하면서(특히 GPU가 발전하면서) 머신러닝 연구자들은 예전 신경망 이론에 다시 한번 눈길을 돌렸습니다. 기존의 신경망 시대보다 훨씬 더 많은 데이터와 새로 개발된 이론을 합치자 단순히 통계학적 머신러닝만 사용하는 모델을 넘어서는 결과를 얻을 수 있게 되었습니다. 기존의 신경망보다 훨씬 더 복잡한, 즉 깊이가 깊은 신경망을 사용하게 되었기 때문에 이를 딥러닝이라고 부르게 됩니다. 딥러닝 시대의 신경망 연구는 60년대의 신경망 연구와는 성황과 성과가 달랐습니다. 오랜 기간 연구가 진행된 통계학적 머신러닝을 기존의 신경망에 접목했고, 데이터가 폭발적으로 증가해서 복잡한 신경망을 학습할 만한 기반도 생겼습니다. 그 덕분에 성능이 이전보다 훨씬 잘 나오게 되었습니다.
현재 트렌드
현재 머신러닝은 대량의 데이터를 바탕으로 하는 딥러닝 기법을 주로 사용합니다. 기존에는 해결하기 힘들었던 음성 인식, 번역, 이미지 인식에서 특히 좋은 성과를 보이고 있습니다. 이러한 성과는 다음과 같은 최근의 새로운 연구 결과에 의해 가능했습니다.
- 셩격이 다른 데이터를 연관시켜서 데이터를 더 효율적으로 사용하는 딥러닝 구조의 개발. 예) 비디오에 있는 자막과 이미지를 동시에 학습시키거나 영어, 프랑스어, 스페인어를 동시에 학습시키기
- 통계학적 머신러닝의 기법을 조합해 데이터를 더 효율적으로 사용하는 딥러닝(원샷, 제로샷 러닝) 기술의 발전
- 머신러닝, 특히 딥러닝을 거대 규모로 빠르게 연산할 수 있는 시스템의 발전(하드웨어 소프트웨어 모두)
- 딥러닝의 더 효울적인 학습을 위한 기술(드롭아웃, 비동기 SGD등)의 발전
- 데이터에 대한 더 큰 관심으로 인해 양적, 질적으로 크게 성장한 데이터 수집 기술의 발전
1.3.2 머신러닝의 3가지 관점
통창력, 데이터 적합성, 이론적 엄정성
통찰력, 통찰력 있는 모델 데이터에 대한 지식 데이터의 대한 믿음
데이터 적합성, 데이터 자체에 얼마나 가까운 모델을 만들 수 있는지 많은 데이터 데이터에 잘 맞는 모델
이론적 엄정성. 기법을 분석하기 쉬운지, 계산이 쉽게 가능한지 증명이 가능한 수학이론 잘 정의되고 보장된 성능
통찰력을 중시하는 경우는 결국 데이터가 어떤 패턴을 가지고 있다고 믿으며, 그 패턴에 대한 지식을 혹보하는 것을 중요시한다는 뜻입니다. 이처럼 데이터에 대한 믿음(prior라고 부릅니다)을 중시하는 대표적인 기법으로 베이지언 기법이 있습니다. 베이지언 기법은 현재의 가정에서 부족한 부분을 보완할 수 있는 데이터를 특히 중요하게 생각합니다.
데이터 적합성을 중시하는 기법들은 관측된 데이터를 가장 중요하게 생각합니다. 데이터에 대한 지식이 희박한 경우 또는 데이터가 아주 많아서 기존 지식 없이도 머신러닝 기법이 잘 동작하는 상항을 가정합니다. 이 경우는 노이즈에 영향을 많이 받으므로 보다 많은 데이터를 이용하여 노이즈의 영향을 줄이는 기술이 많이 쓰입니다 대표적으로 딥러닝과 랜덤 포레스트가 여기에 해당합니다. 둘 다 기업에서 많이 쓰는데, 모델이 수학적으로 간단해서 실제 구현하기 쉽고 스케일을 키우기도 좋기 때문입니다. 통계학에서 말하는 베이지언과 빈도론자(프리쿼티스트)중에 빈도론자에 대앙하는 부분입니다.
이론적 엄정성은 머신러닝 알고리즘 이론에 대한 분석에 집중합니다. 머신러인 알고리즘이 어느 정도의 성능을 보장하고, 거대한 스케일의 데이터를 얼마나 쉽게 다르며, 잘못될 확률이 얼마나 적은지에 대한 해답을 구하는 부분입니다. 많은 머신러닝 이론이 성능 보장을 위해 개발되었습니다. 이에 해당하는 대표적인 기법으로 스팩트럴 러닝, 서포트 벡터 머신(SVM), 볼록 최적화가 있습니다. 사실 이 관점은 너무 이론적인 데 치중하므로 쓸모없는 게 아닌가 하는 의문을 가질 수도 있습니ㅏㄷ 하지만 수학적으로 확실하게 증명되는 성능에 대한 보장은 의학, 로봇, 무인자동차, 우주여행등의 분야에서 매우 중요합니다.
최근에는 이용 가능한 데이터가 증가함에 따라 데이터 적합성에 중점을 둔 기법이 주로 개발됩니다. 하지만 한 가지 관점만 중요하게 여기는 머신러닝 기법은 없다고 할 수 있습니다. 앞서 언급한 통찰력, 이론적 엄정성, 데이터 적합성에 대한 중요도를 다르게 두되 모두 고려하고 있다고 해야 할 것 입니다. 이 3가지 관점은 머신러닝 기법이 잘 동작하는데 필요한 핵심 연구 방향입니다. 앞으로 머신러닝 기법을 분류해서 살펴볼 때 이 3가지 관점을 생각해보는 것이 이해와 분석에 도움이 될 것입니다.
1.4 머신러닝의 분류
머신러닝 기법은 풀고자 하는 목표에 따라 크게 지도학습, 비지도학습, 강화학습으로 분류할 수 있으며, 풀어내는 기법에 따라 통계와 딥러닝으로 분류할 수 있습니다.
1.4.1 지도학습, 비지도학습, 강화학습
머신러닝은 풀고자 하는 목표에 따라 3가지로 나누어볼 수 있습니다.
지도학습은 주어진 데이터와 레이블(정답)을 이용해서 미지의 상태나 값을 예측하는 학습 방법입니다. 대부분의 머신러닝 문제는 지도학습에 해당합니다. 예를 들어 '예전의 주식 시장 변화를 보고 내일의 주식 시장 변화 예측하기', '문서에 사용된 단얼르 보고 해당 문서의 카테고리 분류하기', '사용자가 구매한 상품을 토대로 다음에 구입할 상품 예측하기' 등이 여기에 속합니다.
비지도학습은 데이터와 그에 주어진 레이블 간의 연관 관계를 구하는 것이 아니라 데이터 자체에서 유용한 패턴을 찾아내는 학습 방법입니다. 예를 들어 비슷한 데이터끼리 묶는 군집화, 데이터에서 이상한 점을 찾아내는 이상 검출, 데이터 분포 추측이 있습니다. 지도학습과 가장 다른 점은 데이터가 주어졌을 때 특정 값을 계산하는 함수를 만드는 대신 데이터의 성질을 직접적으로 추측한다는 것입니다.
강화학습은 기계(에이전트)가 환경과의 상호작용(선택과 피드백의 반복)을 통해 장기적으로 얻는 이득을 최대화하도옥 하는 학습방법입니다. 지도학습과는 달리, 강화학습의 경우에는 입력값-출력값(레이블)의 쌍이 명시적으로 정해지지 않습니다. 예를 들어 바둑 프로그램이 바둑을 둔다고 했을 때 현재 판에서 다음 수를 선택하는 것을 학습하는 과정을 지도학습이라고 볼수 있습니다. 그런데 다음 수만을 고려하는게 아니라 게임 승패까지의 전체 수를 고려하여 게임에서 이길 경우 점수를 받고 그렇지 않으면 점수를 받지 못하도록 하여 일련의 이기는 수를 학습하는 과정은 강화학습니다.
강화학습에 대한 다른 예로 화성 탐사 로봇이 경로를 탐색하는 과정을 들 수 있습니다. 지구에서는 화성의 환경에 대한 데이터를 얻을 수 없으므로, 로봇은 실제로 환경이 어떤지에 대한 정보가 거의 없습니다. 따라서 어디로 갈지를 판단하고자 할 때, '현재 가진 정보를 이용해 다음 해동(action) 정하기'(이를 이용(exploitation이라고 합니다)와 '새로운 정볼르 얻기 위해 가보지 않은 곳 가기'(이를 탐색(exploration이라고 합니다) 중에서 하나를 선택해야 겠지요. 강화학습은 각각의 행동에 대한 피드백을 받아서 다음 행동을 정하는 알고리즘을 학습해나갑니다.
1.4.2 지도학습의 세부 분류
지도학습은 세부적으로 어떤 것을 예측하는냐에 따라 다시 희귀, 분류, 랭킹으로 구분할 수 있습니다.
1.4.2.1. 희귀와 분류
지도학습의 경우 데이터를 통해 학습하는 레이블이 어떤 성질을 지니는지에 따라서 크게 희귀(regression)와 분류(classification)로 구분합니다.
희귀의 경우에는 숫자값을 예측합니다. 대개 연속된 숫자(예를 들면 실수)를 얘측하는데, 예를 들어 기존 온도 추이를 보고 내일 온도를 예측하는 일을 들 수 있습니다.
분류는 입력 데이터들을 주어진 항목들로 나누는 방법입니다. 희귀와는 다르게 항목'1'과 항목'2'는 서로 다른 수치값이 아니라 서로 다른 항목임을 나타냅니다. 예를 들어 어떤 문서가 도서관 어떤 분류에 해당하는지 고르는 경우를 들 수 있습니다.
희귀와 분류는 서로 다른 것처럼 보일 수도 있지만, 희귀를 통해 손싑게 분류를 구현하거나 분류를 통해 희귀를 구현할 수있을 만큼 유사합니다. 예를 들어 어제의 온도와 구름의 양으로 내일의 날씨가 좋을지 안 좋을지 예측하는 분류시스템을 만든다고 했을 때, 날씨가 좋을 경우를 1, 나쁠 경우를 0으로 두어 희귀를 이용하여 날씨가 좋을 확률을 0부터 1 사이의 값으로 구하고, 이 값이 0.8이상이면 좋음, 아니면 나쁨으로 분류할 수 있습니다. 반대로 데이터를 받았을 때 먼저 분류를 구현한 다음, 그 분류에 따라서 연속적인 숫자값을 예측하는 방법을 생각해 볼 수 있습니다. 옐르 들어 사용자 그룹을 '행복한 사용자', '보통 사용자', '불행한 사용자'로 일차 분류한 다음에 '사용자 기분'이라는 연속적인 숫자값을 각각의 그룹에 (+0.1, 0, -1과 같은 식으로)부여하면 분류를 이용해서 휘귀한 것이 됩니다.
이렇게 각각 상호 전환이 가능하지만 수학적으로 더 간단하게 정의되는 '회귀'가 머신러닝의 더 기본적인 도구로 쓰입니다.
1.4.2.2 추천 시스템과 랭킹학습
추천 시스템은 상품에 대한 사용자 선호도(별점, 구매 여부 등)를 예측하는 시스템입니다. 상품과 사용자 데이터를 이용하여 값 혹은 레이블을 예측하는 것이므로 회귀의 일종으로도 볼 수 있습니다. 하지만 그 기법과 세팅이 특이해서 따로 분류하여 설명하겠습니다. 예를 들어 온라인 동호회에서 영화를 추천한다고 가정합시다. 다양한 추천 방법이 있겠지만, 각 영화에 대한 평점 데이터를 이용하여 아직 평점이 없는 영화 점수를 예측하고 그중 점수가 가장 높은 영화를 추천하는 방식을 취할 수 있습니다. 회귀와 다른점은 입력과 출력이 아니라 관객과 영화, 관객과 점수 등 다양한 관계를 고려한다는 것 입니다.
랭킹학습(learnning to rank)은 희귀에서처럼 각 입력 데이터의 출력값을 예측하는 것이 아니라 데이터의 순휘를 예측합니다. 예플 들어 영화 평점을 가지고 특정 관객이 각 영화에 대해 몇 점을 줄지 예측하는 것은 회귀입니다. 반면 좋아할 만한 영화 10편을 추천한다면 랭킹학습에 해당 합니다.
1.4.3 비지도학습의 세부 분류
비지도학습은 지도학습과는 달리 데이터를 직접 모델링하는 기법입니다. 대표적으로 군집화와 토픽 모델링, 밀도 추청, 차원 축소 등의 기법이 있습니다.
1.4..3.1 군집화와 토픽 모델링
군집화(클러스터링)는 비지도학습의 분류 중 하나로 비슷한 데이터들을 묶어서 큰 단위로 만드는 기법입니다. 즉, 비슷한 데이터들을 묶어서 몇 개의 그불을 만들어 데이터 패턴을 파악합니다. 옐르 들어 신문 기사를 수집하여 군집화를 적용해보면 비슷한 내용의 기사가 스포츠 클러스터 혹은 곙제 클러스터 등으로 묶이게 됩니다.
토픽 모델링은 군집화와 매유 유사하지만 주로 텍스트 데이터에 대해 사용됩니다. 토픽 모델링은 보통 한 문서가 토픽에 따라 만들어지고 그에 따라 단어가 생성되어 문서가 쓰여진다는 가정에 접근합니다. 군집화는 더 일반적인 데이터에 해당되기 때문에 가정의 성격이 크게 다릅니다. 또한 많은 군집화 기법이 문서 하나를 클러스터 하나로 분류하지만, 토픽 모델링은 일반적으로 관련 정보를 확률로 표현합니다(예를 들면 0.5는 스포츠, 0.4는 연예, 0.1은 경졔).
1.4.3.2 밀도 추정
밀도 추정은 관측한 데이터를 생성한 원래의 분포를 추측하는 방법입니다. 예를 들어 각국의 학생들의 키와 몸무게를 모아놓은 통계 자료에서 키와 몸무게의 관계를 분석한다고 합시다. 단순히 각각의 경우에 해당하는 뎅터 수를 세는 방법도 있지만, 여러가지 다른 기법으로 더 정확한 분폴ㄹ 얻을 수도 있습니다. 커널 밀도 추정과 가우스 혼합모델이 대표적인 기법입니다.
1.4.3.3 차원 축소
차원 축소는 말 그대로 '데이터의 차원을 낮추는' 기법으로, 디멘셔널리티 리덕션이라고 합니다. 보통은 데이터가 복잡하고 높은 차원을 가져서 시각화하기 어려울 때 2차원이나 3차원으로 표현하기 위해 사용합니다. 여러 가지 기법이 있지만, 일반적으로 주요 패턴을 찾아서 해당 패턴을 낮은 차원에서 보존하는 방식으로 이루어집니다. 이 기법은 독립적으로 쓰이지만, 데이터에 적합한 머신러닝 기법을 찾는 데이터 분석 초기 단계에 적용하기도 합니다. 대표적인 기법으로 주성분 분석(PCA) 및 특잇값 분해가 있습니다. 자세한 내용은 6.4.3절 '잠재성 요인 모델'과 7.3.4 절 '주성분 분석'에서 공부하겠습니다.
1.4.4 딥러닝
딥러닝은 신경망을 층층이 쌓아서 문제를 해결하는 기법의 총칭입니다. 딥러닝은 머신러닝이 풀고자 하는 목표에 따라 분류한 개면이 아니며, 사용하는 기법이 특정 형태를 가지는 것을 말합니다. 즉, 앞서 설명한 지도학습이나 비지도학습을 딥러닝을 이용해서 풀 수도 있고 통계학적인 방법을 이용해서 풀 수도 있습니다.
이는 데이터양에 의존하는 기법으로, 다른 머신러닝 기법보다 문제에 대한 가정이 적은 대신 다양한 패턴과 경우에 유연하게 대응하는 구조를 만들어 많은 데이터를 이용하여 학습시키는 것으로 모델의 성능을 향상시킵니다. 즉, 데이터에서 잘 동작하는 방법입니다.
최근에는 공공기관과 연구 기관에서 데이터를 공개하는 추세이고 이미 공개된 데이터가 많아 그러한 데이터를 잘 활용하면 다른 기법보다 딥러닝의 성능이 우수한 경우가 많습니다. 하지만 데이터가 많더라도 경우의 수가 너무 많거나 패턴이 너무 복잡하면 제대로 동작하기 어려울 수 있습니다. 이를 위해 많은 연구가 이루어지고 있으며, 딥러닝을 다른 통계적 머신러닝 기법과 함께 적용하는 방법도 개발 중 입니다. 더 자세한 이론은 2장에서, 예제는 7장에서 확인하기 바랍니다.
"데이터를 이용해서 명시적으로 정의도지 않은 패턴을 컴퓨터로 학습하여 결과를 만들어내는 학문 분야".
데이터, 패턴인식, 컴퓨터를 이용한 계산
1.2 머신러닝을 이해하는 데 필요한 배경지식
1.2.1 수학(선형대수, 미분, 통계, 확률)
1.2.2 프로그래밍
1.3 머신러닝 발전사
1.3.1 머신러닝의 역사와 현재 트렌드
고전적 인공지능 시대
1950년대 유명한 컴퓨터 학자인 앨런 튜링은 인공지능을 판별하는 튜링 데스크를 제안했는데, 이는 기계가 인간과 얼마나 비슷하게 대화하는지를 기준으로 기계의지능을 판별하는 테스트.
신경망 시대
1957년에 퍼셉트론(perceptron)이라는 기초적인 신경망이 개발. 이를 여러 개 묶어 복잡한 신경망을 구성하면서 입력과 출력을 굉장히 유연하게 연결할 수 있었지만, 당시 신경망에는 여러가지 문제점이 있었습니다. 우선 그 시대에는 구할 수 있는 데이터가 굉장히 한정적이라 신경망 성능이 생각보다 신통치 않았고, 기초 이론의 부족으로 한정적인 패턴만 학습이 가능했습니다. 많은 학자가 신경망 구축에 뛰어들었지만, 예상보다 결과가 잘 나오기 않아 침체된 상태가 이어졌습니다. 이 시기를 '인공지능의 겨울'이라 부르곤 합니다.
통계학적 머신러닝 시대
1990년대에 들어 통계학을 전산학과 접목시켜 대규모에서 패턴을 찾는 시도가 기존에 비해 진일보된 성과를 냈습니다. 기존의 방법과 가자으 큰 차이점은 데이터에 훨씬 더 중요한 비중을 두었다는 것입니다. 그 결과 기존의 그 어떤 방법보다 성능이 좋았기 때문에 큰 관심을 받고 산업의 여러 분야에 도입되었습니다. 이 시기에 머신러닝이라는 용어가 등장 했으며, 차후에 딥러닝이 나온 이후 이런 통계학에 중심을 둔 기법들을 통계학적 머신러닝알고 부르게 됩니다.
빅데이터 시대
통계학적 머신러닝은 웹에서 쏟아지는 데이터, 대용량 저장장치, 분산 처리 기술과 결합하여 엄청난 시너지를 만들었습니다. 빅데이터는 용어가 2010년대부터 유행했는데, 빅데이터 시대에는 기존보다 더 큰 데이터를 분석하기 위해 기존보다 휠씬 더 큰 규모의 머신러닝 시스템을 만들게 되고 그 덕택에 성능도 더욱 좋아지게 됩니다.
딥러닝 시대
데이터가 많아지고 연산 능력이 증가하면서(특히 GPU가 발전하면서) 머신러닝 연구자들은 예전 신경망 이론에 다시 한번 눈길을 돌렸습니다. 기존의 신경망 시대보다 훨씬 더 많은 데이터와 새로 개발된 이론을 합치자 단순히 통계학적 머신러닝만 사용하는 모델을 넘어서는 결과를 얻을 수 있게 되었습니다. 기존의 신경망보다 훨씬 더 복잡한, 즉 깊이가 깊은 신경망을 사용하게 되었기 때문에 이를 딥러닝이라고 부르게 됩니다. 딥러닝 시대의 신경망 연구는 60년대의 신경망 연구와는 성황과 성과가 달랐습니다. 오랜 기간 연구가 진행된 통계학적 머신러닝을 기존의 신경망에 접목했고, 데이터가 폭발적으로 증가해서 복잡한 신경망을 학습할 만한 기반도 생겼습니다. 그 덕분에 성능이 이전보다 훨씬 잘 나오게 되었습니다.
현재 트렌드
현재 머신러닝은 대량의 데이터를 바탕으로 하는 딥러닝 기법을 주로 사용합니다. 기존에는 해결하기 힘들었던 음성 인식, 번역, 이미지 인식에서 특히 좋은 성과를 보이고 있습니다. 이러한 성과는 다음과 같은 최근의 새로운 연구 결과에 의해 가능했습니다.
- 셩격이 다른 데이터를 연관시켜서 데이터를 더 효율적으로 사용하는 딥러닝 구조의 개발. 예) 비디오에 있는 자막과 이미지를 동시에 학습시키거나 영어, 프랑스어, 스페인어를 동시에 학습시키기
- 통계학적 머신러닝의 기법을 조합해 데이터를 더 효율적으로 사용하는 딥러닝(원샷, 제로샷 러닝) 기술의 발전
- 머신러닝, 특히 딥러닝을 거대 규모로 빠르게 연산할 수 있는 시스템의 발전(하드웨어 소프트웨어 모두)
- 딥러닝의 더 효울적인 학습을 위한 기술(드롭아웃, 비동기 SGD등)의 발전
- 데이터에 대한 더 큰 관심으로 인해 양적, 질적으로 크게 성장한 데이터 수집 기술의 발전
1.3.2 머신러닝의 3가지 관점
통창력, 데이터 적합성, 이론적 엄정성
통찰력, 통찰력 있는 모델 데이터에 대한 지식 데이터의 대한 믿음
데이터 적합성, 데이터 자체에 얼마나 가까운 모델을 만들 수 있는지 많은 데이터 데이터에 잘 맞는 모델
이론적 엄정성. 기법을 분석하기 쉬운지, 계산이 쉽게 가능한지 증명이 가능한 수학이론 잘 정의되고 보장된 성능
통찰력을 중시하는 경우는 결국 데이터가 어떤 패턴을 가지고 있다고 믿으며, 그 패턴에 대한 지식을 혹보하는 것을 중요시한다는 뜻입니다. 이처럼 데이터에 대한 믿음(prior라고 부릅니다)을 중시하는 대표적인 기법으로 베이지언 기법이 있습니다. 베이지언 기법은 현재의 가정에서 부족한 부분을 보완할 수 있는 데이터를 특히 중요하게 생각합니다.
데이터 적합성을 중시하는 기법들은 관측된 데이터를 가장 중요하게 생각합니다. 데이터에 대한 지식이 희박한 경우 또는 데이터가 아주 많아서 기존 지식 없이도 머신러닝 기법이 잘 동작하는 상항을 가정합니다. 이 경우는 노이즈에 영향을 많이 받으므로 보다 많은 데이터를 이용하여 노이즈의 영향을 줄이는 기술이 많이 쓰입니다 대표적으로 딥러닝과 랜덤 포레스트가 여기에 해당합니다. 둘 다 기업에서 많이 쓰는데, 모델이 수학적으로 간단해서 실제 구현하기 쉽고 스케일을 키우기도 좋기 때문입니다. 통계학에서 말하는 베이지언과 빈도론자(프리쿼티스트)중에 빈도론자에 대앙하는 부분입니다.
이론적 엄정성은 머신러닝 알고리즘 이론에 대한 분석에 집중합니다. 머신러인 알고리즘이 어느 정도의 성능을 보장하고, 거대한 스케일의 데이터를 얼마나 쉽게 다르며, 잘못될 확률이 얼마나 적은지에 대한 해답을 구하는 부분입니다. 많은 머신러닝 이론이 성능 보장을 위해 개발되었습니다. 이에 해당하는 대표적인 기법으로 스팩트럴 러닝, 서포트 벡터 머신(SVM), 볼록 최적화가 있습니다. 사실 이 관점은 너무 이론적인 데 치중하므로 쓸모없는 게 아닌가 하는 의문을 가질 수도 있습니ㅏㄷ 하지만 수학적으로 확실하게 증명되는 성능에 대한 보장은 의학, 로봇, 무인자동차, 우주여행등의 분야에서 매우 중요합니다.
최근에는 이용 가능한 데이터가 증가함에 따라 데이터 적합성에 중점을 둔 기법이 주로 개발됩니다. 하지만 한 가지 관점만 중요하게 여기는 머신러닝 기법은 없다고 할 수 있습니다. 앞서 언급한 통찰력, 이론적 엄정성, 데이터 적합성에 대한 중요도를 다르게 두되 모두 고려하고 있다고 해야 할 것 입니다. 이 3가지 관점은 머신러닝 기법이 잘 동작하는데 필요한 핵심 연구 방향입니다. 앞으로 머신러닝 기법을 분류해서 살펴볼 때 이 3가지 관점을 생각해보는 것이 이해와 분석에 도움이 될 것입니다.
1.4 머신러닝의 분류
머신러닝 기법은 풀고자 하는 목표에 따라 크게 지도학습, 비지도학습, 강화학습으로 분류할 수 있으며, 풀어내는 기법에 따라 통계와 딥러닝으로 분류할 수 있습니다.
1.4.1 지도학습, 비지도학습, 강화학습
머신러닝은 풀고자 하는 목표에 따라 3가지로 나누어볼 수 있습니다.
지도학습은 주어진 데이터와 레이블(정답)을 이용해서 미지의 상태나 값을 예측하는 학습 방법입니다. 대부분의 머신러닝 문제는 지도학습에 해당합니다. 예를 들어 '예전의 주식 시장 변화를 보고 내일의 주식 시장 변화 예측하기', '문서에 사용된 단얼르 보고 해당 문서의 카테고리 분류하기', '사용자가 구매한 상품을 토대로 다음에 구입할 상품 예측하기' 등이 여기에 속합니다.
비지도학습은 데이터와 그에 주어진 레이블 간의 연관 관계를 구하는 것이 아니라 데이터 자체에서 유용한 패턴을 찾아내는 학습 방법입니다. 예를 들어 비슷한 데이터끼리 묶는 군집화, 데이터에서 이상한 점을 찾아내는 이상 검출, 데이터 분포 추측이 있습니다. 지도학습과 가장 다른 점은 데이터가 주어졌을 때 특정 값을 계산하는 함수를 만드는 대신 데이터의 성질을 직접적으로 추측한다는 것입니다.
강화학습은 기계(에이전트)가 환경과의 상호작용(선택과 피드백의 반복)을 통해 장기적으로 얻는 이득을 최대화하도옥 하는 학습방법입니다. 지도학습과는 달리, 강화학습의 경우에는 입력값-출력값(레이블)의 쌍이 명시적으로 정해지지 않습니다. 예를 들어 바둑 프로그램이 바둑을 둔다고 했을 때 현재 판에서 다음 수를 선택하는 것을 학습하는 과정을 지도학습이라고 볼수 있습니다. 그런데 다음 수만을 고려하는게 아니라 게임 승패까지의 전체 수를 고려하여 게임에서 이길 경우 점수를 받고 그렇지 않으면 점수를 받지 못하도록 하여 일련의 이기는 수를 학습하는 과정은 강화학습니다.
강화학습에 대한 다른 예로 화성 탐사 로봇이 경로를 탐색하는 과정을 들 수 있습니다. 지구에서는 화성의 환경에 대한 데이터를 얻을 수 없으므로, 로봇은 실제로 환경이 어떤지에 대한 정보가 거의 없습니다. 따라서 어디로 갈지를 판단하고자 할 때, '현재 가진 정보를 이용해 다음 해동(action) 정하기'(이를 이용(exploitation이라고 합니다)와 '새로운 정볼르 얻기 위해 가보지 않은 곳 가기'(이를 탐색(exploration이라고 합니다) 중에서 하나를 선택해야 겠지요. 강화학습은 각각의 행동에 대한 피드백을 받아서 다음 행동을 정하는 알고리즘을 학습해나갑니다.
1.4.2 지도학습의 세부 분류
지도학습은 세부적으로 어떤 것을 예측하는냐에 따라 다시 희귀, 분류, 랭킹으로 구분할 수 있습니다.
1.4.2.1. 희귀와 분류
지도학습의 경우 데이터를 통해 학습하는 레이블이 어떤 성질을 지니는지에 따라서 크게 희귀(regression)와 분류(classification)로 구분합니다.
희귀의 경우에는 숫자값을 예측합니다. 대개 연속된 숫자(예를 들면 실수)를 얘측하는데, 예를 들어 기존 온도 추이를 보고 내일 온도를 예측하는 일을 들 수 있습니다.
분류는 입력 데이터들을 주어진 항목들로 나누는 방법입니다. 희귀와는 다르게 항목'1'과 항목'2'는 서로 다른 수치값이 아니라 서로 다른 항목임을 나타냅니다. 예를 들어 어떤 문서가 도서관 어떤 분류에 해당하는지 고르는 경우를 들 수 있습니다.
희귀와 분류는 서로 다른 것처럼 보일 수도 있지만, 희귀를 통해 손싑게 분류를 구현하거나 분류를 통해 희귀를 구현할 수있을 만큼 유사합니다. 예를 들어 어제의 온도와 구름의 양으로 내일의 날씨가 좋을지 안 좋을지 예측하는 분류시스템을 만든다고 했을 때, 날씨가 좋을 경우를 1, 나쁠 경우를 0으로 두어 희귀를 이용하여 날씨가 좋을 확률을 0부터 1 사이의 값으로 구하고, 이 값이 0.8이상이면 좋음, 아니면 나쁨으로 분류할 수 있습니다. 반대로 데이터를 받았을 때 먼저 분류를 구현한 다음, 그 분류에 따라서 연속적인 숫자값을 예측하는 방법을 생각해 볼 수 있습니다. 옐르 들어 사용자 그룹을 '행복한 사용자', '보통 사용자', '불행한 사용자'로 일차 분류한 다음에 '사용자 기분'이라는 연속적인 숫자값을 각각의 그룹에 (+0.1, 0, -1과 같은 식으로)부여하면 분류를 이용해서 휘귀한 것이 됩니다.
이렇게 각각 상호 전환이 가능하지만 수학적으로 더 간단하게 정의되는 '회귀'가 머신러닝의 더 기본적인 도구로 쓰입니다.
1.4.2.2 추천 시스템과 랭킹학습
추천 시스템은 상품에 대한 사용자 선호도(별점, 구매 여부 등)를 예측하는 시스템입니다. 상품과 사용자 데이터를 이용하여 값 혹은 레이블을 예측하는 것이므로 회귀의 일종으로도 볼 수 있습니다. 하지만 그 기법과 세팅이 특이해서 따로 분류하여 설명하겠습니다. 예를 들어 온라인 동호회에서 영화를 추천한다고 가정합시다. 다양한 추천 방법이 있겠지만, 각 영화에 대한 평점 데이터를 이용하여 아직 평점이 없는 영화 점수를 예측하고 그중 점수가 가장 높은 영화를 추천하는 방식을 취할 수 있습니다. 회귀와 다른점은 입력과 출력이 아니라 관객과 영화, 관객과 점수 등 다양한 관계를 고려한다는 것 입니다.
랭킹학습(learnning to rank)은 희귀에서처럼 각 입력 데이터의 출력값을 예측하는 것이 아니라 데이터의 순휘를 예측합니다. 예플 들어 영화 평점을 가지고 특정 관객이 각 영화에 대해 몇 점을 줄지 예측하는 것은 회귀입니다. 반면 좋아할 만한 영화 10편을 추천한다면 랭킹학습에 해당 합니다.
1.4.3 비지도학습의 세부 분류
비지도학습은 지도학습과는 달리 데이터를 직접 모델링하는 기법입니다. 대표적으로 군집화와 토픽 모델링, 밀도 추청, 차원 축소 등의 기법이 있습니다.
1.4..3.1 군집화와 토픽 모델링
군집화(클러스터링)는 비지도학습의 분류 중 하나로 비슷한 데이터들을 묶어서 큰 단위로 만드는 기법입니다. 즉, 비슷한 데이터들을 묶어서 몇 개의 그불을 만들어 데이터 패턴을 파악합니다. 옐르 들어 신문 기사를 수집하여 군집화를 적용해보면 비슷한 내용의 기사가 스포츠 클러스터 혹은 곙제 클러스터 등으로 묶이게 됩니다.
토픽 모델링은 군집화와 매유 유사하지만 주로 텍스트 데이터에 대해 사용됩니다. 토픽 모델링은 보통 한 문서가 토픽에 따라 만들어지고 그에 따라 단어가 생성되어 문서가 쓰여진다는 가정에 접근합니다. 군집화는 더 일반적인 데이터에 해당되기 때문에 가정의 성격이 크게 다릅니다. 또한 많은 군집화 기법이 문서 하나를 클러스터 하나로 분류하지만, 토픽 모델링은 일반적으로 관련 정보를 확률로 표현합니다(예를 들면 0.5는 스포츠, 0.4는 연예, 0.1은 경졔).
1.4.3.2 밀도 추정
밀도 추정은 관측한 데이터를 생성한 원래의 분포를 추측하는 방법입니다. 예를 들어 각국의 학생들의 키와 몸무게를 모아놓은 통계 자료에서 키와 몸무게의 관계를 분석한다고 합시다. 단순히 각각의 경우에 해당하는 뎅터 수를 세는 방법도 있지만, 여러가지 다른 기법으로 더 정확한 분폴ㄹ 얻을 수도 있습니다. 커널 밀도 추정과 가우스 혼합모델이 대표적인 기법입니다.
1.4.3.3 차원 축소
차원 축소는 말 그대로 '데이터의 차원을 낮추는' 기법으로, 디멘셔널리티 리덕션이라고 합니다. 보통은 데이터가 복잡하고 높은 차원을 가져서 시각화하기 어려울 때 2차원이나 3차원으로 표현하기 위해 사용합니다. 여러 가지 기법이 있지만, 일반적으로 주요 패턴을 찾아서 해당 패턴을 낮은 차원에서 보존하는 방식으로 이루어집니다. 이 기법은 독립적으로 쓰이지만, 데이터에 적합한 머신러닝 기법을 찾는 데이터 분석 초기 단계에 적용하기도 합니다. 대표적인 기법으로 주성분 분석(PCA) 및 특잇값 분해가 있습니다. 자세한 내용은 6.4.3절 '잠재성 요인 모델'과 7.3.4 절 '주성분 분석'에서 공부하겠습니다.
1.4.4 딥러닝
딥러닝은 신경망을 층층이 쌓아서 문제를 해결하는 기법의 총칭입니다. 딥러닝은 머신러닝이 풀고자 하는 목표에 따라 분류한 개면이 아니며, 사용하는 기법이 특정 형태를 가지는 것을 말합니다. 즉, 앞서 설명한 지도학습이나 비지도학습을 딥러닝을 이용해서 풀 수도 있고 통계학적인 방법을 이용해서 풀 수도 있습니다.
이는 데이터양에 의존하는 기법으로, 다른 머신러닝 기법보다 문제에 대한 가정이 적은 대신 다양한 패턴과 경우에 유연하게 대응하는 구조를 만들어 많은 데이터를 이용하여 학습시키는 것으로 모델의 성능을 향상시킵니다. 즉, 데이터에서 잘 동작하는 방법입니다.
최근에는 공공기관과 연구 기관에서 데이터를 공개하는 추세이고 이미 공개된 데이터가 많아 그러한 데이터를 잘 활용하면 다른 기법보다 딥러닝의 성능이 우수한 경우가 많습니다. 하지만 데이터가 많더라도 경우의 수가 너무 많거나 패턴이 너무 복잡하면 제대로 동작하기 어려울 수 있습니다. 이를 위해 많은 연구가 이루어지고 있으며, 딥러닝을 다른 통계적 머신러닝 기법과 함께 적용하는 방법도 개발 중 입니다. 더 자세한 이론은 2장에서, 예제는 7장에서 확인하기 바랍니다.
2018년 2월 13일 화요일
CHAPTER5 Frontend Components
Laravel is primarily a PHP framework, but it also has a series of components focused on generating frontend code. Some of thesse, like pagination and message bags, are PHP helpers that target the frontend, but Laravel also provides g Gulp-based build system called Elixir and some conventions around non-PHP assets.
Since Elixir is at the core of the non-PHP frontend components, let's start there.
Elixir
Elixir (not to be confused with the funcgtional programming language) is a build tool that provides a simple user interface and a series of conventions on top of Gulp. Elixir's core feature is simplifying the most common Gulp tasks by means of a cleaner API and a series of naming and application structure conventions.
A Quick Introduction to Gulp
Gulp is a JavaScript tool designed for compiling static assets and coordinating other steps of your build process.
Gulp is similar to Grunt, Rake, or make-it allows you to define anaction (called a "task" in Gulp) or series of actions to take every time you build your application. This will commonly include running a CSS preprocessor like Sass or LESS, copying files, concatenating and minifying JavaScript, and much more.
Gulp, and therefire Elixir, is based on the idea of streams. Most tasks will begin by loading some files into the stream buffer, and then the task will apply transformationis to the content-preprocess it, minify it, and then maybe save the content to a new file.
At ists core, Elixir is just a tool in your Gulp toolbox.l there isn't even such a thing as an Elixir file; you'll define your Elixir tasks in your gulpfile.js. But they look a log different from vanilla Gulp tasks, and you;ll have to do a log less work to get them running out of the box.
Let's look at a common example: runnig Sass to preprocess your CSS styles. In a normal Gulp environment, that might look a little bit like Example 5-1.
Example 5-1. Compiling a Sass file in Gulp
var gulp = require('gulp'),
sass = require('gulp-ruby-sass'),
autoprefixer = require('gulp-autoprefixer'),
rename = require('gulp-rename'),
notify = require('gulp-notify'),
livereload = require('gulp-livereload'),
lr = require('tiny-lr'(,
server = lr();
gulp.task('sass', function() {
return gulp.src('resources/asserts/sass/app.scss')
.pip(sass({
style: 'compressed',
sourcemap: true
}))
.pipe(autoprefixer('last 2 version', 'ie 9', 'ios 6'))
.pipe(gulp.dest('public/css'))
.pipe(rename({suffix: '.min'}))
.pipe(livereload(server))
.pipe(notify({
title: "Karani",
message: "Styles task complete."
}));
});
Now, I've seen worse. It reads well, and it's clear what's going on. But there's a lot happening that you'll just pull into every site you ever mark. It can get confusing and repertitive.
Let's try that same task in Elixir (Exmaple 5-2).
Example 5-2. Compiling a Sass file in Elilxir
var elixir = require('laravel-elixir');
elixir(function (mix){
mix.sass('app.scss');
});
Tha's it. That covers all the basics- preprocessing, notificationi, folder structure, autoprefixing, and much more.
ES6 in Elixir 6
Elixir 6, which coame out with Laravel 5.3, changed a lot fothe syntax to use ES6, the llatest version of JavaScript. Here's what Example 5-2 looks like in Elixir 6:
const elixir = require('laravel-elixir');
elixir(mix =>{
mix.sass('app.scss')
});
Don't worry; this does exactly the same thing.
Elixir Folder Structure
Much of Elixir's simplicity comes from the assumed directory structure. Why make the decision fresh in every new application about where the source and compiled assert live? Just stick with Elixir's convention, and you won't have to think about it ever again.
Every new Laravel app comes with a resources folder with an assets subfolder, which is where Elixir will expect your frontend assets to live. You Sass will live in reosurces/assets/sass, or your LESS in resources/assets/less, and your JavaScript will live in resources/assets/js. There will export to public/css and public/js.
But if you're interested in changing the structure, you can always change the source and public paths by changing the appropriate properties (assetsPath and public Path) on the elixir .config object.
Running Elixir
Since Elixir runs on Gulp, you'll need to set up a few tools before using it:
Since Elixir is at the core of the non-PHP frontend components, let's start there.
Elixir
Elixir (not to be confused with the funcgtional programming language) is a build tool that provides a simple user interface and a series of conventions on top of Gulp. Elixir's core feature is simplifying the most common Gulp tasks by means of a cleaner API and a series of naming and application structure conventions.
A Quick Introduction to Gulp
Gulp is a JavaScript tool designed for compiling static assets and coordinating other steps of your build process.
Gulp is similar to Grunt, Rake, or make-it allows you to define anaction (called a "task" in Gulp) or series of actions to take every time you build your application. This will commonly include running a CSS preprocessor like Sass or LESS, copying files, concatenating and minifying JavaScript, and much more.
Gulp, and therefire Elixir, is based on the idea of streams. Most tasks will begin by loading some files into the stream buffer, and then the task will apply transformationis to the content-preprocess it, minify it, and then maybe save the content to a new file.
At ists core, Elixir is just a tool in your Gulp toolbox.l there isn't even such a thing as an Elixir file; you'll define your Elixir tasks in your gulpfile.js. But they look a log different from vanilla Gulp tasks, and you;ll have to do a log less work to get them running out of the box.
Let's look at a common example: runnig Sass to preprocess your CSS styles. In a normal Gulp environment, that might look a little bit like Example 5-1.
Example 5-1. Compiling a Sass file in Gulp
var gulp = require('gulp'),
sass = require('gulp-ruby-sass'),
autoprefixer = require('gulp-autoprefixer'),
rename = require('gulp-rename'),
notify = require('gulp-notify'),
livereload = require('gulp-livereload'),
lr = require('tiny-lr'(,
server = lr();
gulp.task('sass', function() {
return gulp.src('resources/asserts/sass/app.scss')
.pip(sass({
style: 'compressed',
sourcemap: true
}))
.pipe(autoprefixer('last 2 version', 'ie 9', 'ios 6'))
.pipe(gulp.dest('public/css'))
.pipe(rename({suffix: '.min'}))
.pipe(livereload(server))
.pipe(notify({
title: "Karani",
message: "Styles task complete."
}));
});
Now, I've seen worse. It reads well, and it's clear what's going on. But there's a lot happening that you'll just pull into every site you ever mark. It can get confusing and repertitive.
Let's try that same task in Elixir (Exmaple 5-2).
Example 5-2. Compiling a Sass file in Elilxir
var elixir = require('laravel-elixir');
elixir(function (mix){
mix.sass('app.scss');
});
Tha's it. That covers all the basics- preprocessing, notificationi, folder structure, autoprefixing, and much more.
ES6 in Elixir 6
Elixir 6, which coame out with Laravel 5.3, changed a lot fothe syntax to use ES6, the llatest version of JavaScript. Here's what Example 5-2 looks like in Elixir 6:
const elixir = require('laravel-elixir');
elixir(mix =>{
mix.sass('app.scss')
});
Don't worry; this does exactly the same thing.
Elixir Folder Structure
Much of Elixir's simplicity comes from the assumed directory structure. Why make the decision fresh in every new application about where the source and compiled assert live? Just stick with Elixir's convention, and you won't have to think about it ever again.
Every new Laravel app comes with a resources folder with an assets subfolder, which is where Elixir will expect your frontend assets to live. You Sass will live in reosurces/assets/sass, or your LESS in resources/assets/less, and your JavaScript will live in resources/assets/js. There will export to public/css and public/js.
But if you're interested in changing the structure, you can always change the source and public paths by changing the appropriate properties (assetsPath and public Path) on the elixir .config object.
Running Elixir
Since Elixir runs on Gulp, you'll need to set up a few tools before using it:
CHATER4 Blade Templating
Compared to most other backend languages, PHP actually functions relatively well as a templating language. But it has its shortcomings, and it's also just ugly to be using
Laravel offers a custom templating engine called Blade, which is inspired by .NET'S Razor engine. It boasts a concise syntax, a shallow learning curve, a powerful and intuitive inheritance model, and easy extensibility.
For a quick look at what writing Blade looks like, check out Example 4-1.
Example 4-1. Blade samples
{!! $group->heroImageHtml() !!}
@forelse ($user as $user)
{{ $user->first_name }} {{$user -> last_name }}
@empty
No user in this group
@endforelse
As you cna see, Blade introduces a convention in which its custom tags, called "directives," are prefixed with an @. You'll use directives for all of your control structures and also for inheritance and any custom functionality you want to add.
Blade's syntax is clean and concise, so at its core it's just more pleasant and tidy to work with than the alternatives. But the moment you need anything of any complexity in your templates-nested inheritance, complex conditionals, or recursion-Blade starts to really shine. Just like the best Laravel components, it takes complex application requirements and makes them easy and accessible.
Additionally, since all Blade syntax is compiled into normal PHP code and then cached, it's fast and it allows you to use native PHP in your Blade files if you want. However, I'd recommmend avoiding usage of PHP if at all possible-usually if you need to do anything that you can't do with Blade or a custom Blade directive, it doesn't belong in the template.
Using Twig with Laravel
Unlike many other Symfony-based frameworks, Laravel doesn't use Twig by default. But if you're just in love with Twig, there's a TwigBridge package that makes if easy to use Twig instead of Blade.
Echoing Data
As you can see in Example 4-1, {{and}} are used to wrap sections of PHP that you'd like to echo. {{ $variable }} is similar to in plain PHP.
It's different in one way, however, and you might've guessed this already: Blade escapes all echoes by default useing PHP's htmlentities() to protect your users from malicious script insertion. That means {{ $variable }} is functionally equivalent to . If yhou want to echo without the escaping, use {!! and !!} instead.
{{ and }} When Using a Frontend Templating Framework
You might've noticed that the echo syntax for Blade ({{ }}) is similar to the echo syntax for many frontend frameworks. So, how does Laravel know when you're wirting Blade versus Handlebars?
Blade will ignore any {{ that's prefaced with an @. So, it will parse the first of the following examples, but the second will be echoed out directly:
// Parsed as Blade; the value of $bladeVariable is echoed to the view
{{ $bladeVariable }}
// @ is removed, and "{{ handlebarsVariable }}" echoed to the view directly
@{{ handlebarsVariable }}
Control Structures
Most of the control structures in Blade will be very familiar. Many directly echo the name and structure of the same tag in PHP.
There are a few convenience helpers, but in general, the control structures just look cleaner than they would in PHP.
Conditionals
First, let's take a look at the control strucrures that allow for logic.
@if
Blade's @if ($condition) compiles to . @else, @elseif, and @endif also compile to the exact same syntax in PHP. Take a look at Example 4-2 for some examples.
Example 4-2. @if, @else, @elseif, and @endif
@if ( count($talks) === 1)
There is one talk at this time period.
@elseif (cout($talks) === 0)
There are no talks at this time period.
@else
There are {{ count($talks) }} talks at this time period.
@endif
Just liek with the native PHP conditionals, you can mix and match these how you want. They don't have any special logic; there's literally a parser looking for someting with the shape of @if $condition) and replacing it with the appropriate PHP code.
@unless and @endunless
@unless, on the other hand, is a new syntax that doesn't have a direct equivalent in PHP. It's the direct inverse of @if. @unless condition) is the same as
Example 4-3. @unless and @endunless
@unless $user -> hasPaid())
You can complete your payment by switching to the payment tab.
@endunless
Loops
Next, let's take a look at the loops.
@for, @foreach, and @while
@for, @foreach, and @while work the same in Blade as they do in PHP; see Examples 4-4, 4-5, and 4-6.
Example 4-4. @for and @endfor
@for ($i = 0; $i < $talk ->slotsCount(); $i++)
The number is {{ $i }}
@endfor
Example 4-5. @foreach and @endforeach
@foreach ($talks as $talk)
{{ $talk -> title }} ({{ $talk->length }} minutes)
@endforeach
Example 4-6. @while and @endwhile
@while $item = array_pop($items))
{{ $item->orSomething() }}
@endwhile
@forelse
@forelse is a @foreach that also allows you to program in a fallback if the object you're iterating over is empty. We saw it in action at the start of this chapter; Example 4-7 shows another example.
Exmaple 4-7. @forelse
@forelse $talks as $talk)
{{ $talk -> title }} ( {{ $talk ->length }} minutes )
@empty
No talks this day
@endforelse
$loop Within @foreach and @forelse
The @foreach and @forelse directives in Laravel 5.3 add one feature that's not available in PHP foreach loops: the $loop variable. Used within a @foreach or @forelse loop, this variable will return a stdClass object with the following properties:
index
The 0-based index of the current item in the loop; 0 would mean "first item"
iteration
The 1-based index of the current item in the loop; 1 would mean "first item"
remaining
How many items remain in the loopo; if the current item is the first of three, this will be 2
count
The count of items in the loop
last
A boolean indicating whether this is the last item in the loop
depth
How many "levels" deep this loop is: 1 for a loop, 2 for a loop within a loop, etc.
parent
A reference to the $loop variable for the parent loop item; if this loop is within
another @foreach loop otherwise, null
Here's an example fo how to use it:
@foreach pages as $page)
{{ $loop->iteratiion }} : {{ $page ->title }}
@if $page->hasChildren())
@foreach $page->children() as $child)
{{ $loop->parent->iteration }}.
{{ $loop->iteration }}:
{{ $child->title }}
@endforeach
@endif
@endforeach
or
If you're ever unsure whether a variable is set, you're probably used to checking isset() on it before echoing it, and echoing something else if it's not set. Blade has a convenience helper,or , that does this for you and lets you set a default fallback: {{ $title ro "Default" }} will echo the value of $title if it's set, or "Default" if not.
Template Inheritance
Blade provides a structure for template inheritance that allows views to extends, modify, and include other views.
Here's how inheritance is structured with Blade.
Defining Sections with @section/@show and @yield
Let's start with a top-level Blade layout, like in Example 4-8. This is the definition of the generic page wrapper that we'll later place page-specific content into.
Example 48. Blade layout
My site | @yield('title', 'Home Page')
@yield('content')
@sectionfooterScripts')
@show
This looks a bit like a normal HTML page, but you can see we've yielded in two places(title and content), and we've defined a section in a third(footerScripts).
We have threee Blade directive here that each look a little different: @yield('title', 'Home Page') alone, @yield('content')with a deined default, and @section ...
@show with actual content in it.
All three function essentially the same. All three are defining that there's a section with a given name (which is the first parameter). All three are defining that the section can be extended later. And all three are defining what to do if the section isn't extedned, either by providing a string fallback ('Home Page'), no fallback (which will just not show anything if it's not extednded), or an entrie block fallback (in this case, ).
What's different? Well, clearly, @yieldcontent') has no default content. But addtionanlly, the default content in @yield('title') only will be shown if it's never extended. If it is extended, its child sections will not have programmatic access to the default value. @section ... @show, on the other hand, is both defining a default and doing so in a way that its default contents will be available to its children, through @parent.
Once you have a parent layout like this, you can extend it like in Example 4-9.
Example 4-9. Extending a Blad layout
@extendslayouts.master;)
@section('title', 'Dashboard')
@section('content')
Welcom to your application dashboard!
@endsection
@section('footerScripts')
@parent
@endsection
@show versus @endsection
You may have noticed that Example 4-8 uses @section ... @show, but Example 4-9 uses @section ... @endsection. What's the difference?
Use @show when you're defining the place for a section, in the parent template. Use @endsection when you're defining the content for a template in a child template.
This child view will actually allow us to cover a few new concepts in Blade inheritance.
@extends
First, with @extends('layouts.master'), we define that this view should not be rendered on its own, but that is instead extends antoer view. That means its role is to define the content of various sections, but not to stand alone. It's almost more like a series of buckets of content, rrather than an HTML page. This line also defines that the view it's extending lives at resources/views/layouts/master.blade.php.
Each file should only extend one other file, and the @extends call should be the first line of the file.
@section and @endsection
Second, with @section('title', 'Dashboard'), we provide our content for the first section, title. Since the content is so shortk, instead of using @section and @endsection we're just using a shortcut. This allows us to pass the content in as the second parameter of @section and then move on. If it's a bit disconcerting to see @section without @endsection, you could just use the normal syntax.
Third, with @section('content') and on, we use the normal syntax to define the contents of the contentn section. We'll just throw a little greeting in for now. Note however, that when yhou;re using @section in a child view, you end it with @endsection r its alias @stop), instead of @show, which is reserved for defining sections in parent views.
@prent
Fourch, with @section('footerScripts') and on, we use the normal syntax to define the contents of the footerScripts section.
But remember, we actually defined that content (or, at least, its "default") already in the master layout. So this time, we have two options:we can either overwrite the content from the parent view, or we can add to it.
You can see that we have the option to include the content from the parent by using the @parent directive within the section. If we didn't, the content of this section would entirely overwrite anything defined in the parent for this section.
@include
Now that we've established the basics of inheritance, there are a few more tricks we can perform.
What if we're in a view and want to pull in another view? Maybe we have a cll-to-action "sign up" button that we want to re-use around the site. And maybe we want to customize its button text every time we use it. Take a look at Example 4-10.
Example 4-10. Includeng view partials with @include
Here's why you should sign up for our app: It's Gread.
@include('sign-up-button', ['text' => 'See just how great it is' ])
{{ $text }}
@include pulls in the partial and, optionally, passes data into it. Note that not only can you explicitly pass data to an include via the second parameter of @include, but you can also reference any variables within the included file that are available to the including view ($pageName, in this example). Once again, you can do whatever you want, but I would recommend you consider always explicitly passing every variable that you intend to use, just for clarity.
@each
You can probably imagin some circumstances in which you'd need to loop over an array or collection and @include a partial for each item. There's a directive for that:@each.
Let's say we have a sidebar compposed of modules,and we want to include multiple modules, each with a different tile. Take a look at Example 4-11.
Example 4-11. Using view partials in a loop with @each
@eachpartials.module', $modules, 'module', 'partials.empty-module')
No modules :(
View Composers and Service Injection
As we covered in Chapter 3, it's simple to pass data to our views from the route definition (see Example 4-12).
Example 4-12. Reminder on how to pass data to views
Route::get('passing-data-to-views', function(){
return view('dashboard')
->with('key', 'value');
});
There are times, however, when you will find yourself passing the same data over and over to multiple views. Or, you might find yourself using a header partial or something similar that requires some data; will you now have to pass that data in from every route definition that might ever load that header partial?
Binding Data to Views Using View Composers
Thankfully, there's a simpler way. The solution is called a view composer, and it allows you to define that any t ime a particular view loads, it should have certain data passed to it-without the route definition having to pass that data in explicitly.
Let's say you have a sidebar on every page, which is defined in a partial named partials.sidebar (resources/views/partials/sidebar.blade.php) and then included on every page, This sidebar shows a list of the last seven posts that were published on your site. If it's on every page, every route definition would normally have to grab that list and pass it in, like in Example 4-13.
Example 4-13. Passing sidebar data in from every route
Route::get('home', function(){
return view('home')
->with('posts', Post::recent());
});
Route::get('about', function(){
return view('about')
->with('posts', Post::recent());
});
That could get annoying quickly.Instead, we're going to use view composers to "share" that variable with a prescribed set of views. We can do this a few ways,, so let's start simple and move up.
Sharing a variable globally
First, the ismplest option: just globally "share" a variable with every view in your application like in Example 4-14.
Example 4-14. Sharing a variable globally
// Some service provider
public funciton boot()
{
....
view()->share('posts', Post::recent());
}
If you want to use view()->share(),the besst place would be the boot() method of a service provider so that the binding runs on every page load. You can create a custom ViewComposerService Provider (see Chapter 11 for more about service providers), but for now just put it in App\Providers\AppServiceProvider in the boot() method.
Using view()->share() makes the variable accessible to every view in the entire application, however, so might be overkill.
Closure-based view composers
The next option is to use a closure-based view composer to share variables with a single view, like in Example 4-15.
Example 4-15. Creating a closure-based view composer
view()->composer('partials.sidebar', function ($view){
$view->with('posts', Post::recent());
});
As you can see, we've defined the name of the view we want it shared with in the first parameter(partials.sidebar)and then passed a closure to the second parameter; in the closure, we've used $view->with() to share a variable, but now only with a specific view.
Anywhere a view composer is binding to a particular view (like in Example 4-15, which binds to partials.sidebar), you can pass an array of view names instead to bind to multiple views.
You can also use an asterisk in the view path, as in partials.*, tasks.*, or just *:
view()->composer(
['partials.header', 'partials.footer'],
function(){
$view->with('posts', Post::recent());
}
);
view()->composer('partials.*', function(){
$view->with('posts', Post::recent());
});
Class-based view composers
Finally, the most flexible but also most complex option is to create a dedicated class for your view composer.
First, let's create the view composer class. There's no formally defined palce for view composers to live, but the docs recommend App\Http\ViewComposers. So, let's create App\Http\ViewComposers\RecentPostsComposer like in Example 4-16.
Example 4-16. A view composer
namespace App\Http\Viewcomposers;
user App\Post;
user Illuminate\Contracts\View\View;
class RecentPostsComposer
{
private $posts;
public function __construct(Post $posts)
{
$this->posts = $posts;
}
public function compose(View $view)
{
$view->with('posts', $this->posts->recent());
}
}
As you can see, we're injecting the Post model (typehinted constructor parameters of view composers will be automatically injected; see Chapter 11 for more on the container and dependency injection). Note that we could skip the private $posts and the constructor injection and just usse Post::recent() in the compose() method if we wanted. Then, when this composer is clalled, it runs the compose() method, in which we bind the posts variable to the result of running the recent() method.
Like the other methods of sharing variables, this view composer needs to have a binding somewhere. Again, you'd likely create a custom ViewComposerServiceProvider, but for now, as seen in Exmaple 4-17, we'll just put it in the boot() method of App \Providers\AppServiceProvider.
Example 4-17. Registering a view composer in AppServiceProvider
//AppServiceProvider
public function boot()
{
...
view()->composer(
'partials.sidebar',
\App\Http\Viewcomposers\RecentPostsComposer::class
);
}
Note that this binding is the same as closure-based view composer, but instead of passing a closure, we're passing the class name of our view composer. Now, every time Blade renders the partials.sidebar view, it'll automatically run our provider and pass the view a posts variable set to the results of the recent() method on our Post model.
Blade Service Injection
There are three primary types of data we're most likely to inject into a view: collections of data to iterate over, single objects that we're displayingn on the page, and services that generate data or views.
With a service, the pattern will most likely look like Example 4-18, where we inject an instance of our analytics service into the route definition by typehinting it in the route's method signature, and then pass it into the view.
Example 4-18. Injecting service into a view via the route definition constructor
Route::get('backend/sales', function(AnalyticsService $analytics){
return view('bakcend.sales-graphs')
-> with('analytics', $analytics);
});
Just as with view composers, Blade's service injection offers a convenient shortcut to reduce duplicationin your route definitions. Normally, the content of a view using our analytics service might look like Exmaple 4-19.
Example 4-19. Using an injected navigation service in a view
{{ $analytics->getBalance() }} / PP $analytics->getBudget() }}
Blade serviceinjection makes it easy to inject an instance of a class outside of the container directly from the view, like in Example 4-20.
Example 4-20. Injecting a service directly into a view
@injectanalytics', 'App\Services\Anaalytics')
{{ $analytics->getBalance() }} / {{ $analytics->getBudget() }}
As you can see, this @inject directive has acually made an $analytics variable available, whichwe're using later in our view.
The first parameter of @inject is the name of the variable you're injecting, and the second parameter is the class of interface that you want to inject an instance of.
This is resolved just like when you type hint a dependency in a constructor elsewhere in Laravel;if you're unfamiliar with how that works, go to Chapter 11 to learn more.
Just like view composers, Blade service injection makes it easy to make certain data or functionality available to every instance of a view, without having toinject it via the route definition every time.
Custom Blade Directives
All of the built-in syntax of Blade tahtwe've covered so far-@if, @unless, and so on - are called directives, Each Blade directive is a mapping between a pattern (e.g., @if ($condition)) and a PHP output( e.g., ).
Directives aren't just for the core; you can actually create your own. You might think directives are good for making little shortcuts to bigger pieces of code-for example, using @button('buttonName') and having it expand to a larger set of button HTML.
This isn't a terrible idea, but for simple code expansion like this you might be better off including a view partial.
I've found custom directives to be the most useful when they simplify some form of repeated logic. Say we're tired of having to wrap our code with @if (auth()->guest() (to check if a user is logged in or not)and we want a custom @ifGuest directive. As with view composers, it might be worth having a custom service provider to register these, but for no let's just put it in the boot() method of App\Profiders\AppServiceProvider. Take a look at Example 4-21 to see what this binding will look like.
Example 4-21. Binding a custom Blade directive
// AppServiceProvider
public function boot()
{
Blade::directive('ifGuest', function() {
return "guest()): ?>"
});
}
We've now registered a custom directive, @ifGuest, which will be replaced with the PHP code guest()): ?>.
This might feell strange. You're writing a string that will be returned and then executed as PHP, But what this means is that you can now take the complex, or ugly, or unclear, or repetitive aspects of your PHP templating code and hide them behind clear, simple, and expressive syntax.
Custom directive result caching
You might be tempted to do some logic to make your custom directive faster by performing an operation in the binding and then embedding the result within the returned string:
Blade::directive('fiGuest', function(){
// Antipattern! Do not cop.
ifGuest = auth()->guest();
return "";
});
The problem with this idea is that it assumes this directive will be re-created on every page load. However, Blade caches aggressively, so you're going to find yourself in a bad spot if you try this.
Parameters in Custom Blade Directives
What if you want to check a condition in your custom logic? Check ou Example 4-22.
Example 4-22. Creating a Blade directive with parameters
//Binding
Blade::directive('newlinesToBr', function($expression){
return "";
});
//In use
@newlinesToBr($message->body)
The $expression parameter received by the closure represents whatever's within the parentheses. As you can see, we then generate a valid PHP cod snippet and return it.
$expression parameter scoping before Laravel 5.3
Before Laravel 5.3, the $expression parameter also included the parentheses themselves. So, in Exmaple 4-22, $expression (which is $message->body in Laravel 5.3 and later) would have instead been ($message->body), and we would've had to write .
If you find yourself constantly writing the same conditional logic over and over, you should consider a Blade directive.
Example: Using Custom Blade Directives for a Multienant App
So, let's imagine we're building an application that supports multitenancy, which means users might be visiting the site from www.myapp.com, client1.myapp.com, client2.myapp.com, or elsewhre.
Suppose we have written a class to encapsulate some of our multitenancy logic and named it Context. this class will capture information and logic about the context of the current visit, such as who the authenticated user is and whether the user is visiting the public website or a client subdomain.
We'll probably frequently resolve that Context class in our views and perform conditionals on ti, like in Example 4-23. The app('context') is aa shortcut to get an instance of a class from the container, which we'll learn more about in Chapter 11.
Example 4-23. Conditional on context without a custom Blade directive
@if (app('context')-> isPublic())
© Copyright MyApp LLC
@else
© Copyright {{ app('context') -> client ->name }}
@endif
What if we could simplify @if (app('context') ->isPublic()) to just @ifPublic?
Le's do it, Check out Example 4-24/
Example 4-24.Conditionals on context with a custom Blade directive
// Binding
Blade::directive('ifPublilc', function(){
return "isPublic()): ?>";
});
// In user
@ifPublic
© Copyright MyApp LLC
@else
© Copyright {{ app('context')->client->name }}
@eldif
Since this resolves to a simple if statement, we can still rely on the native @else and @endif conditionals. But if we wanted, we could also create a custom @elseIfClient directive, or a separate @ifClient directive, or really whatever else we want.
Testing
The most common method of testing views is through application testing, meaning that you're actually calling the route that displays the views and ensuring the views have certain content (see Example 4-25). You can also click buttons or submit forms and ensure that you are redirected to a certain page, or that you see a certain error.
(You'll learn more about testing in Chapter 12.)
Example 4-25. Testing that a view display certain content
// EventsText.php
public function test_list_page_show_all_events()
{
$event1 = factory(Event::class) -> create();
$event2 = factory(Event::class) -> create();
$this->visit('events')
->see($event1->title)
->see($event2->ttile)
}
You can also test that a certain view has been passed a particular set of data, which, if it accomplishes your testing goals, is less fragile than checking for certain text on the page. Example 4-26 demonstrates this approach.
Example 4-26. Testing that a view was passed certain content
// EventsTest.php
public function test_liat_pageshows_all_events()
{
$event1 = factory(Event::class)->create();
$event2 = factory(Event::class)->create();
$this->visit('events');
$this->assertViewHas('events', Event::all());
$this->assertViewHasAll([
'events' => Event::all(),
'title' => 'Events Page'
]);
$this->assertViewMissing('dogs');
}
In 5.3, we gained the ability to pass a closure to $assertViewHas(), meaning we can customize how we want to check more complex data structures. Example 4-27 illustrates how we might use this.
Example 4-27. Passing a closure to assertViewHas()
//EventsTest.php
public function test_list_page_shows_all_events()
{
$event1 = factory(Event::class)->create();
$this->visit('events/' . $event1->id);
$this->assertViewHas('event', function($event) use ($event1) {
return $event->id === $event1->id;
});
}
TL;DR
Blade is Laravel's templating engine. Its primary focus is a clear, concise, and expressive syntax with powerful inheritance and extensibility. Its "safe echo" brackets are {{ and }}, it unprotected echo brackets are {!! and !!}, and it has a series of custom tags called directives that all begin with @@if and @unless, for example).
You can define a paret template and leave "holes" in it for content useing @yield and @section/@show. You can then teach its child views to extedd it using @extends('parent.view.name'), and define their sections using @section/@endsection. You use @parent to reference the content of the block's parent.
View composers make it easy to define that, every time a particular view or subview loads, it should have certain information available to it. And service injection allows the view itself to request data straight from the application container.
Laravel offers a custom templating engine called Blade, which is inspired by .NET'S Razor engine. It boasts a concise syntax, a shallow learning curve, a powerful and intuitive inheritance model, and easy extensibility.
For a quick look at what writing Blade looks like, check out Example 4-1.
Example 4-1. Blade samples
{{ $group->title }}
{!! $group->heroImageHtml() !!}
@forelse ($user as $user)
{{ $user->first_name }} {{$user -> last_name }}
@empty
No user in this group
@endforelse
As you cna see, Blade introduces a convention in which its custom tags, called "directives," are prefixed with an @. You'll use directives for all of your control structures and also for inheritance and any custom functionality you want to add.
Blade's syntax is clean and concise, so at its core it's just more pleasant and tidy to work with than the alternatives. But the moment you need anything of any complexity in your templates-nested inheritance, complex conditionals, or recursion-Blade starts to really shine. Just like the best Laravel components, it takes complex application requirements and makes them easy and accessible.
Additionally, since all Blade syntax is compiled into normal PHP code and then cached, it's fast and it allows you to use native PHP in your Blade files if you want. However, I'd recommmend avoiding usage of PHP if at all possible-usually if you need to do anything that you can't do with Blade or a custom Blade directive, it doesn't belong in the template.
Using Twig with Laravel
Unlike many other Symfony-based frameworks, Laravel doesn't use Twig by default. But if you're just in love with Twig, there's a TwigBridge package that makes if easy to use Twig instead of Blade.
Echoing Data
As you can see in Example 4-1, {{and}} are used to wrap sections of PHP that you'd like to echo. {{ $variable }} is similar to in plain PHP.
It's different in one way, however, and you might've guessed this already: Blade escapes all echoes by default useing PHP's htmlentities() to protect your users from malicious script insertion. That means {{ $variable }} is functionally equivalent to . If yhou want to echo without the escaping, use {!! and !!} instead.
{{ and }} When Using a Frontend Templating Framework
You might've noticed that the echo syntax for Blade ({{ }}) is similar to the echo syntax for many frontend frameworks. So, how does Laravel know when you're wirting Blade versus Handlebars?
Blade will ignore any {{ that's prefaced with an @. So, it will parse the first of the following examples, but the second will be echoed out directly:
// Parsed as Blade; the value of $bladeVariable is echoed to the view
{{ $bladeVariable }}
// @ is removed, and "{{ handlebarsVariable }}" echoed to the view directly
@{{ handlebarsVariable }}
Control Structures
Most of the control structures in Blade will be very familiar. Many directly echo the name and structure of the same tag in PHP.
There are a few convenience helpers, but in general, the control structures just look cleaner than they would in PHP.
Conditionals
First, let's take a look at the control strucrures that allow for logic.
@if
Blade's @if ($condition) compiles to . @else, @elseif, and @endif also compile to the exact same syntax in PHP. Take a look at Example 4-2 for some examples.
Example 4-2. @if, @else, @elseif, and @endif
@if ( count($talks) === 1)
There is one talk at this time period.
@elseif (cout($talks) === 0)
There are no talks at this time period.
@else
There are {{ count($talks) }} talks at this time period.
@endif
Just liek with the native PHP conditionals, you can mix and match these how you want. They don't have any special logic; there's literally a parser looking for someting with the shape of @if $condition) and replacing it with the appropriate PHP code.
@unless and @endunless
@unless, on the other hand, is a new syntax that doesn't have a direct equivalent in PHP. It's the direct inverse of @if. @unless condition) is the same as
Example 4-3. @unless and @endunless
@unless $user -> hasPaid())
You can complete your payment by switching to the payment tab.
@endunless
Loops
Next, let's take a look at the loops.
@for, @foreach, and @while
@for, @foreach, and @while work the same in Blade as they do in PHP; see Examples 4-4, 4-5, and 4-6.
Example 4-4. @for and @endfor
@for ($i = 0; $i < $talk ->slotsCount(); $i++)
The number is {{ $i }}
@endfor
Example 4-5. @foreach and @endforeach
@foreach ($talks as $talk)
{{ $talk -> title }} ({{ $talk->length }} minutes)
@endforeach
Example 4-6. @while and @endwhile
@while $item = array_pop($items))
{{ $item->orSomething() }}
@endwhile
@forelse
@forelse is a @foreach that also allows you to program in a fallback if the object you're iterating over is empty. We saw it in action at the start of this chapter; Example 4-7 shows another example.
Exmaple 4-7. @forelse
@forelse $talks as $talk)
{{ $talk -> title }} ( {{ $talk ->length }} minutes )
@empty
No talks this day
@endforelse
$loop Within @foreach and @forelse
The @foreach and @forelse directives in Laravel 5.3 add one feature that's not available in PHP foreach loops: the $loop variable. Used within a @foreach or @forelse loop, this variable will return a stdClass object with the following properties:
index
The 0-based index of the current item in the loop; 0 would mean "first item"
iteration
The 1-based index of the current item in the loop; 1 would mean "first item"
remaining
How many items remain in the loopo; if the current item is the first of three, this will be 2
count
The count of items in the loop
last
A boolean indicating whether this is the last item in the loop
depth
How many "levels" deep this loop is: 1 for a loop, 2 for a loop within a loop, etc.
parent
A reference to the $loop variable for the parent loop item; if this loop is within
another @foreach loop otherwise, null
Here's an example fo how to use it:
@foreach pages as $page)
@if $page->hasChildren())
@foreach $page->children() as $child)
{{ $loop->iteration }}:
{{ $child->title }}
@endforeach
@endif
@endforeach
or
If you're ever unsure whether a variable is set, you're probably used to checking isset() on it before echoing it, and echoing something else if it's not set. Blade has a convenience helper,or , that does this for you and lets you set a default fallback: {{ $title ro "Default" }} will echo the value of $title if it's set, or "Default" if not.
Template Inheritance
Blade provides a structure for template inheritance that allows views to extends, modify, and include other views.
Here's how inheritance is structured with Blade.
Defining Sections with @section/@show and @yield
Let's start with a top-level Blade layout, like in Example 4-8. This is the definition of the generic page wrapper that we'll later place page-specific content into.
Example 48. Blade layout
@yield('content')
@sectionfooterScripts')
@show
This looks a bit like a normal HTML page, but you can see we've yielded in two places(title and content), and we've defined a section in a third(footerScripts).
We have threee Blade directive here that each look a little different: @yield('title', 'Home Page') alone, @yield('content')with a deined default, and @section ...
@show with actual content in it.
All three function essentially the same. All three are defining that there's a section with a given name (which is the first parameter). All three are defining that the section can be extended later. And all three are defining what to do if the section isn't extedned, either by providing a string fallback ('Home Page'), no fallback (which will just not show anything if it's not extednded), or an entrie block fallback (in this case, ).
What's different? Well, clearly, @yieldcontent') has no default content. But addtionanlly, the default content in @yield('title') only will be shown if it's never extended. If it is extended, its child sections will not have programmatic access to the default value. @section ... @show, on the other hand, is both defining a default and doing so in a way that its default contents will be available to its children, through @parent.
Once you have a parent layout like this, you can extend it like in Example 4-9.
Example 4-9. Extending a Blad layout
@extendslayouts.master;)
@section('title', 'Dashboard')
@section('content')
Welcom to your application dashboard!
@endsection
@section('footerScripts')
@parent
@endsection
@show versus @endsection
You may have noticed that Example 4-8 uses @section ... @show, but Example 4-9 uses @section ... @endsection. What's the difference?
Use @show when you're defining the place for a section, in the parent template. Use @endsection when you're defining the content for a template in a child template.
This child view will actually allow us to cover a few new concepts in Blade inheritance.
@extends
First, with @extends('layouts.master'), we define that this view should not be rendered on its own, but that is instead extends antoer view. That means its role is to define the content of various sections, but not to stand alone. It's almost more like a series of buckets of content, rrather than an HTML page. This line also defines that the view it's extending lives at resources/views/layouts/master.blade.php.
Each file should only extend one other file, and the @extends call should be the first line of the file.
@section and @endsection
Second, with @section('title', 'Dashboard'), we provide our content for the first section, title. Since the content is so shortk, instead of using @section and @endsection we're just using a shortcut. This allows us to pass the content in as the second parameter of @section and then move on. If it's a bit disconcerting to see @section without @endsection, you could just use the normal syntax.
Third, with @section('content') and on, we use the normal syntax to define the contents of the contentn section. We'll just throw a little greeting in for now. Note however, that when yhou;re using @section in a child view, you end it with @endsection r its alias @stop), instead of @show, which is reserved for defining sections in parent views.
@prent
Fourch, with @section('footerScripts') and on, we use the normal syntax to define the contents of the footerScripts section.
But remember, we actually defined that content (or, at least, its "default") already in the master layout. So this time, we have two options:we can either overwrite the content from the parent view, or we can add to it.
You can see that we have the option to include the content from the parent by using the @parent directive within the section. If we didn't, the content of this section would entirely overwrite anything defined in the parent for this section.
@include
Now that we've established the basics of inheritance, there are a few more tricks we can perform.
What if we're in a view and want to pull in another view? Maybe we have a cll-to-action "sign up" button that we want to re-use around the site. And maybe we want to customize its button text every time we use it. Take a look at Example 4-10.
Example 4-10. Includeng view partials with @include
Here's why you should sign up for our app: It's Gread.
@include('sign-up-button', ['text' => 'See just how great it is' ])
{{ $text }}
@include pulls in the partial and, optionally, passes data into it. Note that not only can you explicitly pass data to an include via the second parameter of @include, but you can also reference any variables within the included file that are available to the including view ($pageName, in this example). Once again, you can do whatever you want, but I would recommend you consider always explicitly passing every variable that you intend to use, just for clarity.
@each
You can probably imagin some circumstances in which you'd need to loop over an array or collection and @include a partial for each item. There's a directive for that:@each.
Let's say we have a sidebar compposed of modules,and we want to include multiple modules, each with a different tile. Take a look at Example 4-11.
Example 4-11. Using view partials in a loop with @each
@eachpartials.module', $modules, 'module', 'partials.empty-module')
{{ $module->title }}
No modules :(
View Composers and Service Injection
As we covered in Chapter 3, it's simple to pass data to our views from the route definition (see Example 4-12).
Example 4-12. Reminder on how to pass data to views
Route::get('passing-data-to-views', function(){
return view('dashboard')
->with('key', 'value');
});
There are times, however, when you will find yourself passing the same data over and over to multiple views. Or, you might find yourself using a header partial or something similar that requires some data; will you now have to pass that data in from every route definition that might ever load that header partial?
Binding Data to Views Using View Composers
Thankfully, there's a simpler way. The solution is called a view composer, and it allows you to define that any t ime a particular view loads, it should have certain data passed to it-without the route definition having to pass that data in explicitly.
Let's say you have a sidebar on every page, which is defined in a partial named partials.sidebar (resources/views/partials/sidebar.blade.php) and then included on every page, This sidebar shows a list of the last seven posts that were published on your site. If it's on every page, every route definition would normally have to grab that list and pass it in, like in Example 4-13.
Example 4-13. Passing sidebar data in from every route
Route::get('home', function(){
return view('home')
->with('posts', Post::recent());
});
Route::get('about', function(){
return view('about')
->with('posts', Post::recent());
});
That could get annoying quickly.Instead, we're going to use view composers to "share" that variable with a prescribed set of views. We can do this a few ways,, so let's start simple and move up.
Sharing a variable globally
First, the ismplest option: just globally "share" a variable with every view in your application like in Example 4-14.
Example 4-14. Sharing a variable globally
// Some service provider
public funciton boot()
{
....
view()->share('posts', Post::recent());
}
If you want to use view()->share(),the besst place would be the boot() method of a service provider so that the binding runs on every page load. You can create a custom ViewComposerService Provider (see Chapter 11 for more about service providers), but for now just put it in App\Providers\AppServiceProvider in the boot() method.
Using view()->share() makes the variable accessible to every view in the entire application, however, so might be overkill.
Closure-based view composers
The next option is to use a closure-based view composer to share variables with a single view, like in Example 4-15.
Example 4-15. Creating a closure-based view composer
view()->composer('partials.sidebar', function ($view){
$view->with('posts', Post::recent());
});
As you can see, we've defined the name of the view we want it shared with in the first parameter(partials.sidebar)and then passed a closure to the second parameter; in the closure, we've used $view->with() to share a variable, but now only with a specific view.
Anywhere a view composer is binding to a particular view (like in Example 4-15, which binds to partials.sidebar), you can pass an array of view names instead to bind to multiple views.
You can also use an asterisk in the view path, as in partials.*, tasks.*, or just *:
view()->composer(
['partials.header', 'partials.footer'],
function(){
$view->with('posts', Post::recent());
}
);
view()->composer('partials.*', function(){
$view->with('posts', Post::recent());
});
Class-based view composers
Finally, the most flexible but also most complex option is to create a dedicated class for your view composer.
First, let's create the view composer class. There's no formally defined palce for view composers to live, but the docs recommend App\Http\ViewComposers. So, let's create App\Http\ViewComposers\RecentPostsComposer like in Example 4-16.
Example 4-16. A view composer
namespace App\Http\Viewcomposers;
user App\Post;
user Illuminate\Contracts\View\View;
class RecentPostsComposer
{
private $posts;
public function __construct(Post $posts)
{
$this->posts = $posts;
}
public function compose(View $view)
{
$view->with('posts', $this->posts->recent());
}
}
As you can see, we're injecting the Post model (typehinted constructor parameters of view composers will be automatically injected; see Chapter 11 for more on the container and dependency injection). Note that we could skip the private $posts and the constructor injection and just usse Post::recent() in the compose() method if we wanted. Then, when this composer is clalled, it runs the compose() method, in which we bind the posts variable to the result of running the recent() method.
Like the other methods of sharing variables, this view composer needs to have a binding somewhere. Again, you'd likely create a custom ViewComposerServiceProvider, but for now, as seen in Exmaple 4-17, we'll just put it in the boot() method of App \Providers\AppServiceProvider.
Example 4-17. Registering a view composer in AppServiceProvider
//AppServiceProvider
public function boot()
{
...
view()->composer(
'partials.sidebar',
\App\Http\Viewcomposers\RecentPostsComposer::class
);
}
Note that this binding is the same as closure-based view composer, but instead of passing a closure, we're passing the class name of our view composer. Now, every time Blade renders the partials.sidebar view, it'll automatically run our provider and pass the view a posts variable set to the results of the recent() method on our Post model.
Blade Service Injection
There are three primary types of data we're most likely to inject into a view: collections of data to iterate over, single objects that we're displayingn on the page, and services that generate data or views.
With a service, the pattern will most likely look like Example 4-18, where we inject an instance of our analytics service into the route definition by typehinting it in the route's method signature, and then pass it into the view.
Example 4-18. Injecting service into a view via the route definition constructor
Route::get('backend/sales', function(AnalyticsService $analytics){
return view('bakcend.sales-graphs')
-> with('analytics', $analytics);
});
Just as with view composers, Blade's service injection offers a convenient shortcut to reduce duplicationin your route definitions. Normally, the content of a view using our analytics service might look like Exmaple 4-19.
Example 4-19. Using an injected navigation service in a view
{{ $analytics->getBalance() }} / PP $analytics->getBudget() }}
Blade serviceinjection makes it easy to inject an instance of a class outside of the container directly from the view, like in Example 4-20.
Example 4-20. Injecting a service directly into a view
@injectanalytics', 'App\Services\Anaalytics')
{{ $analytics->getBalance() }} / {{ $analytics->getBudget() }}
As you can see, this @inject directive has acually made an $analytics variable available, whichwe're using later in our view.
The first parameter of @inject is the name of the variable you're injecting, and the second parameter is the class of interface that you want to inject an instance of.
This is resolved just like when you type hint a dependency in a constructor elsewhere in Laravel;if you're unfamiliar with how that works, go to Chapter 11 to learn more.
Just like view composers, Blade service injection makes it easy to make certain data or functionality available to every instance of a view, without having toinject it via the route definition every time.
Custom Blade Directives
All of the built-in syntax of Blade tahtwe've covered so far-@if, @unless, and so on - are called directives, Each Blade directive is a mapping between a pattern (e.g., @if ($condition)) and a PHP output( e.g., ).
Directives aren't just for the core; you can actually create your own. You might think directives are good for making little shortcuts to bigger pieces of code-for example, using @button('buttonName') and having it expand to a larger set of button HTML.
This isn't a terrible idea, but for simple code expansion like this you might be better off including a view partial.
I've found custom directives to be the most useful when they simplify some form of repeated logic. Say we're tired of having to wrap our code with @if (auth()->guest() (to check if a user is logged in or not)and we want a custom @ifGuest directive. As with view composers, it might be worth having a custom service provider to register these, but for no let's just put it in the boot() method of App\Profiders\AppServiceProvider. Take a look at Example 4-21 to see what this binding will look like.
Example 4-21. Binding a custom Blade directive
// AppServiceProvider
public function boot()
{
Blade::directive('ifGuest', function() {
return "guest()): ?>"
});
}
We've now registered a custom directive, @ifGuest, which will be replaced with the PHP code guest()): ?>.
This might feell strange. You're writing a string that will be returned and then executed as PHP, But what this means is that you can now take the complex, or ugly, or unclear, or repetitive aspects of your PHP templating code and hide them behind clear, simple, and expressive syntax.
Custom directive result caching
You might be tempted to do some logic to make your custom directive faster by performing an operation in the binding and then embedding the result within the returned string:
Blade::directive('fiGuest', function(){
// Antipattern! Do not cop.
ifGuest = auth()->guest();
return "";
});
The problem with this idea is that it assumes this directive will be re-created on every page load. However, Blade caches aggressively, so you're going to find yourself in a bad spot if you try this.
Parameters in Custom Blade Directives
What if you want to check a condition in your custom logic? Check ou Example 4-22.
Example 4-22. Creating a Blade directive with parameters
//Binding
Blade::directive('newlinesToBr', function($expression){
return "";
});
//In use
@newlinesToBr($message->body)
The $expression parameter received by the closure represents whatever's within the parentheses. As you can see, we then generate a valid PHP cod snippet and return it.
$expression parameter scoping before Laravel 5.3
Before Laravel 5.3, the $expression parameter also included the parentheses themselves. So, in Exmaple 4-22, $expression (which is $message->body in Laravel 5.3 and later) would have instead been ($message->body), and we would've had to write .
If you find yourself constantly writing the same conditional logic over and over, you should consider a Blade directive.
Example: Using Custom Blade Directives for a Multienant App
So, let's imagine we're building an application that supports multitenancy, which means users might be visiting the site from www.myapp.com, client1.myapp.com, client2.myapp.com, or elsewhre.
Suppose we have written a class to encapsulate some of our multitenancy logic and named it Context. this class will capture information and logic about the context of the current visit, such as who the authenticated user is and whether the user is visiting the public website or a client subdomain.
We'll probably frequently resolve that Context class in our views and perform conditionals on ti, like in Example 4-23. The app('context') is aa shortcut to get an instance of a class from the container, which we'll learn more about in Chapter 11.
Example 4-23. Conditional on context without a custom Blade directive
@if (app('context')-> isPublic())
© Copyright MyApp LLC
@else
© Copyright {{ app('context') -> client ->name }}
@endif
What if we could simplify @if (app('context') ->isPublic()) to just @ifPublic?
Le's do it, Check out Example 4-24/
Example 4-24.Conditionals on context with a custom Blade directive
// Binding
Blade::directive('ifPublilc', function(){
return "isPublic()): ?>";
});
// In user
@ifPublic
© Copyright MyApp LLC
@else
© Copyright {{ app('context')->client->name }}
@eldif
Since this resolves to a simple if statement, we can still rely on the native @else and @endif conditionals. But if we wanted, we could also create a custom @elseIfClient directive, or a separate @ifClient directive, or really whatever else we want.
Testing
The most common method of testing views is through application testing, meaning that you're actually calling the route that displays the views and ensuring the views have certain content (see Example 4-25). You can also click buttons or submit forms and ensure that you are redirected to a certain page, or that you see a certain error.
(You'll learn more about testing in Chapter 12.)
Example 4-25. Testing that a view display certain content
// EventsText.php
public function test_list_page_show_all_events()
{
$event1 = factory(Event::class) -> create();
$event2 = factory(Event::class) -> create();
$this->visit('events')
->see($event1->title)
->see($event2->ttile)
}
You can also test that a certain view has been passed a particular set of data, which, if it accomplishes your testing goals, is less fragile than checking for certain text on the page. Example 4-26 demonstrates this approach.
Example 4-26. Testing that a view was passed certain content
// EventsTest.php
public function test_liat_pageshows_all_events()
{
$event1 = factory(Event::class)->create();
$event2 = factory(Event::class)->create();
$this->visit('events');
$this->assertViewHas('events', Event::all());
$this->assertViewHasAll([
'events' => Event::all(),
'title' => 'Events Page'
]);
$this->assertViewMissing('dogs');
}
In 5.3, we gained the ability to pass a closure to $assertViewHas(), meaning we can customize how we want to check more complex data structures. Example 4-27 illustrates how we might use this.
Example 4-27. Passing a closure to assertViewHas()
//EventsTest.php
public function test_list_page_shows_all_events()
{
$event1 = factory(Event::class)->create();
$this->visit('events/' . $event1->id);
$this->assertViewHas('event', function($event) use ($event1) {
return $event->id === $event1->id;
});
}
TL;DR
Blade is Laravel's templating engine. Its primary focus is a clear, concise, and expressive syntax with powerful inheritance and extensibility. Its "safe echo" brackets are {{ and }}, it unprotected echo brackets are {!! and !!}, and it has a series of custom tags called directives that all begin with @@if and @unless, for example).
You can define a paret template and leave "holes" in it for content useing @yield and @section/@show. You can then teach its child views to extedd it using @extends('parent.view.name'), and define their sections using @section/@endsection. You use @parent to reference the content of the block's parent.
View composers make it easy to define that, every time a particular view or subview loads, it should have certain information available to it. And service injection allows the view itself to request data straight from the application container.
피드 구독하기:
글 (Atom)