본문 바로가기
Fundamentals & Beyond/테스트 주도 머신러닝

1. 테스트 주도 머신 러닝의 소개

by Toddler_AD 2026. 7. 20.

이 책에서는 복잡한 소프트웨어를 작고, 관리 가능한 여러 단계를 통해 개발할 수 있는 방법을 소개한다. 또한 테스트 주도 개발(Test-Driven Development, TDD) 기법을 이용해 어떻게 머신 러닝 솔루션을 개발할 수 있는지도 소개한다. 이 책을 통해 여러분이 TDD를 완벽하게 이해하고 다룰 수 있을 거라고는 기대하지 않는다. 앞으로 접하게 될 문제를 참신하게 해결하는 데 유용한 기본 원리들을 알아보고, 흥미를 불러일으키는 데 도움이 되기를 바란다.

 

이 장에서는 다음 세가지 질문에 대한 내용을 알아볼 것이다.

  • TDD와 동작 주도 개발(Behavior-Driven Development, BDD)의 의미는 무엇인지?
  • TDD와 BDD를 머신러닝 개념에 적용시키는 방법, 추론분석(inference)과 예측분석(prediction) 모델을 만들 수 있는 방법은 무엇인지?
  • 실제로 이런 분석 모델이 어떻게 작동하는지?

이러한 질문에 대해 답할 수 있다면, 우리는 실제 현장에서 경험하는 문제들을 다룰 준비가 된 것이다. 이 책에서는 이러한 개념을 머신 러닝 문제를 푸는 데 어떻게 적용하는지 알아본다.이 책에서 다루는 이론적인 내용 중 가장 많은 부분이 이번 장에 포함되어 있다. 이후 나머지 이론적인 부분은 예제를 통해 설명한다.

 

애플리케이션에 초점을 맞추었기 때문에, TDD와 BDD 이론 자체보다 훨씬 더 많은 것들을 배울 수 있을 것이다. 하지만 이 책에서 다루지 않는 실무 관점의 내용들도 있다. TDD와 BDD에 관한 이론과 새로운 정보를 포함한 궁금한 사항들은 아래 사람들이 만든 자료들을 참고하기 바란다. 

  • 켄트 벡(Kent Beck): 'TDD의 아버지'로 불린다.
  • 댄 노스(Dan North): 'BDD의 아버지'로 불린다.
  • 마틴 파울러(Martin Fowler): '리팩토링(Refactoring)의 아버지'로 불린다. 이 분야에 대한 방대한 연구 결과를 집대성했다.
  • 제임스 쇼어(James Shore): "The Art of Agile Development"의 저자 중 한 사람으로, TDD 분야의 깊이 있는 이론적 지식을 보유하고 있으며, 실무 관점에서 TDD의 가치에 대해서도 아주 잘 설명한다.

TDD나 BDD는 개념상으로는 매우 단순하지만 완벽하게 익히려면 상당히 오랜시간 동안 노력해야 한다. 특히 머신 러닝에 적용하기 위해서는 알고리즘에 기인한 확률 프로세스를 제어하고 이의 성능을 측정하기 위한 새로운 방법을 찾아야 한다. 앞으로 여러 장에 걸쳐 이에 대해 자세히 학습할 것이다. 다음 절에서 TDD의 기본 내용과 적용 방안들을 알아보자.

 

테스트 주도 개발

켄트 벡이 쓴 책에 의하면, TDD는 다음과 같은 2가지 규칙으로만 구성되어 있다.

  • 새 코드를 작성하기 전에 자동화된 실패 테스트(falling automated test)부터 먼저 만들 것
  • 중복된 부분을 제거할 것

이는 다음과 같은 메시지로 요약되는데, 얼핏 보면 '마법의 주문' 같다는 생각이 들 수도 있다.

"빨간색(Red), 초록색(Green), 리팩토링(Refactor)"

 

이게 뭘 의미하는지 잘 모르겠다면, TDD는 '어떤 소프트웨어를 실제로 개발하기에 앞서, 이것이 어떻게 동작할지를 구체적으로 기술하는 코드를 작성하는 소프트웨어 개발 프로세스'라는 걸 기억하기 바란다. TDD의 핵심 가치는 단계별로 에러 없이 동작하는 소프트웨어와 소프트웨어 개발 명세 사항들(specification)을 아이템화 하는 것이다.

 

TDD 소프트웨어 개발 프로세스에서는 다음 사항을 반드시 지켜야 한다.

  • 프로그램의 동작(behavior)이 의도한 대로 바뀌었는지를 추적하는 코드 작성
  • 각 반복수행(iteration) 단계 후 제대로 작동하는 소프트웨어를 만들어 내는 빠른 반복수행 사이클
  • 어떤 버그인지에 대한 명확한 정의, 만약 테스트가 실패하지 않은 상태에서 버그가 발견되면, 이건 버그가 아니고 새로운 기능이 된다.

켄트가 말한 또 다른 중요한 점은, 이 기법은 궁극적으로 소프트웨어 개발 과정에서 두려움을 줄여준다는 것이다. 각 테스트가 목표를 달성하는 과정에서 체크포인트가 되기 때문이다. 만약 이 과정에서 너무 많이 벗어나서 문제 해결이 어려워지면, 제대로 적용되지 않는 테스트들을 전부 다 삭제하고 이전 상태(즉, 나머지 테스트들이 통과하는 상태)로 되돌아가도록 코드를 작성하면 된다. TDD 과정에서 나오는 시행착오가 많긴 하지만 이건 머신 러닝에 적용할 때도 똑같다.

 

결국, 이러한 프로세스 전체를 통해 우리가 생각하는 방식이 바뀌게 된다. TDD를 이용해 설계한 소프트웨어는 모듈화가 잘 되어 있으므로 소프트웨어 내에서 여러 컴포넌트들의 실행 순서를 뒤바꿀 수도 있다. 이 책의 나머지 장에서 이에 대해 자세히 살펴볼 것이다.

 

여러분 중에 테스트 케이스를 기반으로 개발하는 것과 TDD가 별 차이가 없다고 생각하는 분도 있을 것이다. 하지만 대부분의 경우를 보면, 말로 표현한 것과 글로 표현한 것이 다른 경우가 흔하게 발생하며, 심지어 처음 생각했던 것과는 더욱 달라져 있기도 한다. 우리가 어떤 코드를 작성하기 전에 코드에 대한 목적과 작성 이유를 만들어 놓으면, 소프트웨어 설계에 '만일의 경우를 대비하는 식의 코드'를 만들어 넣지 않도록 할 수 있다. 여기서 말하는 '만일의 경우를 대비하는 식의 코드'란 문제가 있을지 없을지 확신할 수 없어서 일단 그냥 작성하고 보는 식의 코드를 말한다. TDD를 사용하면 테스트 케이스를 생각해보고, 현재 지원이 안 되는 것을 증명하고, 이런 결과를 통해 이를 수정할 수 있다. 우리가 테스트 케이스를 생각해 낼 수 없다면 이러한 수정사항을 코드에 추가 반영할 수 없다.

 

TDD는 개발 과정에서 소프트웨어의 다양한 수준에 맞게 운영이 가능하다. 테스트 케이스는 함수, 메소드, 클래스 전체, 프로그램, 웹 서비스, 신경망 알고리즘(Neural networks), 랜덤 포레스트 알고리즘(Random Forest algorithm), 머신 러닝 파이프라인 전체에 이르기까지 모든 경우에서 작성 가능하다. 각 수준에 따라 테스트 케이스는 예상되는 클라이언트의 입장을 고려해 작성하면 된다. 이게 과연 머신 러닝과 어떤 관계가 있다는 걸까? 잠시 앞에서 얘기했던 것들을 다시 생각해보자.

 

머신 러닝 용어에는 함수, 메소드, 클래스, 수학적 모델 구현 결과에 대해 테스트를 작성할 수 있다는 의미가 담겨 있다. TDD는 심지어 직접적이고 집중된 방식으로 특정 기술과 메소드를 알아보는 용도로 사용될 수 있다. 마치 여러분이 REPL(코드 스티핏을 테스트해 볼 수 있는 대화형 쉘 프로그램)이나 IPython(대화형 파이썬 프로그램) 세션을 사용하는 것처럼 말이다.

 

TDD 사이클

TDD 사이클은 아직 프로그램화되지 않은 무언가를 시도해보기 위해 코드에 작은 함수를 작성하는 것이다. 이 작은 테스트 메소드는 3개의 섹션으로 구성된다. 첫 번째 섹션에는 객체 또는 테스트 데이터를 작성해 넣는다. 두 번째 섹션은 테스트할 코드가 작동하는 부분이다. 마지막 섹션은 우리가 예상했던 대로 코드가 제대로 작동했는지 검증하는 부분이다. 이제 여러분은 테스트를 통과하기 위한 요령 있는 코드(보통 lazy code 라고 한다)를 작성할 것이다. 만약 여러분이 여기에 맞추어 작업을 진행하고 있다면, 이 광경을 지켜보고 있는 사람들 중 일부는 여러분의 게으른 태도와 매우 세세한 작업 단꼐 때문에 무척 짜증을 낼 것이다. 일단 테스트가 초록색 상태가 되고 나면, 여러분이 마음속으로 생각했던 대로 코드를 리팩토링할 수 있다. 여기서 '리팩토링'이란 여러분의 코드를 어떻게 변경할지를 의미하는 것으로, 코드가 어떻게 실행되어 동작할지를 바꾸는 게 아니라는 점에 주의하기 바란다.

 

TDD의 세 가지 단계인 빨간색, 초록색, 리팩토링에 대해 좀 더 깊이 있게 알아보자.

 

빨간색

우선, 실패 테스트를 임의로 하나 생성한다. 물론 이를 위해서는 실패의 결과가 어떤 모습인지 당연히 알고 있어야 한다. 머신 러닝의 가장 상위 개념 수준에서 이는 베이스라인 테스트로 볼 수 있는데, 여기서 베이스라인이란 아무렇게나 하는 것 보다는 약간 구체적인 수준의 테스트를 의미한다. 예를 들면, '아무거나 예측하는 경우'도 가능하고, '매번 똑같은 결과를 예측하는 경우'도 해당될 수 있다. 아무리 베이스라인이어도 코드가 최소한 어느 정도 수준은 되어야 한다고 생각하는 사람들도 있겠지만, 이 정도면 시작하기엔 좋은 수준이 아닐까 한다. 머신 러닝 분야에서 경험했던 공통적인 문제점은 소위 '진정한 알고리즘'이라는 걸 구현하는 데 들이는 시간보다, 그 앞 단계에서(예를 들면 개념 정리와 같은 작업으로) 쓰이는 시간이 너무 많다는 점이다. 하지만 완전이 무작위적인 상태보다 좋아질 수 있다며느, 이러한 변화는 여러분의 비즈니스에서 수익을 창출해내는 데 꽤 유용할 거라고 본다.

 

초록색

실패 테스트를 만들었다면, 이것을 초록색 상태가 되도록 작업할 준비가 된 것이다. 임의의 상위 개념 수준의 테스트를 시작하면, 이를 하위 수준에서 고려할만한 여러 개의 실패 테스트를 진행하도록 개념적으로 나누는 데 도움이 되는 방법을 알아볼 수도 있다. 이 장의 뒷부분에서 좀 더 깊이 있게 다루겠지만, 지금은 가능한 한 테스트를 통과시키려면, 거짓말하고, 치팅하고, 뭔가를 훔친다는 정도로만 알아두자. 실제로 치팅은 소프트웨어 테스트를 더욱 강력하게 만들어준다. "소프트웨어 개발을 위해선 교과서 같은 절차, 방식을 꼭 지켜야 해"라는 충고 따위는 가볍게 무시하고, 그냥 여기에 함께 맞장구를 쳐주면 된다. 다음 단계에서 이러한 문제를 어떻게 고치는지 알아볼 것이다.

 

리팩토링

이 단계에서는 마치 해커가 하듯이 모든 수단을 동원해서 테스트를 통과시켜야 한다. 우선, 코드를 리팩토링해야 한다. 주의할 점은 이 '리팩토링'이라는 단어를 만만하게 해석하면 안 된다는 것이다. 리팩토링이란 코드가 어떻게 작동할지, 결과가 어떻게 나오는지에 대한 일관성을 유지하면서 소프트웨어를 고쳐나가야 하는 것을 의미한다. 만약 if 문이나 특정 조건을 다루는 코드를 추가하는 등의 경우는 더 이상 리팩토링이라고 할 수 없다. 코드 리팩토링을 완료했다면, 다음 단계로 테스트 없이 소프트웨어를 구현한다. 여러분이 더 이상 리팩토링이라고 할 수 없는 상태인지 여부를 알고 싶으면, 이전 단계에서 통과했던 테스트가 여전히 제대로 되는지 여부를 보면 된다. 만약 제대로 통과되지 않는다면, 테스트를 통과했던 시점으로 돌아가야 한다. 다소 헷갈릴 수도 있겠지만 코드 실행 과정에서 어디가 달라졌는지 알아내느라 시간을 들이는 게 전부는 아니다. 리팩토링에 대해 관심이 있다면 마틴 파울러의 "리팩토링: 코드 품질을 개선하는 객체지향 사고법"을 참고하기 바란다. 이 책에서는 리팩토링 코드가 폼(form)과 무브먼트(movement)의 세트로 이루어져 있는 것을 그림을 이용해 설명한다.

 

이상이 TDD와 관련되 일반적인 이론 내용이다. 하지만 실제 테스트에서는 어떤 형태로 나타날까? 이 프로세스가 실제 문제에서는 어떻게 진행될까?

 

동작 주도 개발

BDD는 TDD보다 더 일반화된 모습을 띠는데, TDD의 기술적 고려사항에 비즈니스 고려사항을 추가한 것이다. 이를 통해 사람들이 TDD에 대한 경험을 더욱 많이 쌓을 수 있도록 해준다. 일반적으로 당면한 문제에 어떤 패턴을 명시하는 것으로 시작했다. 특히, BDD 관련 권위자인 댄 노스는 이러한 문제를 쉽게 해결 할 수 있도록 전용 프로그래밍 언어와 스트럭처를 제안했다. 그가 주목한 이슈 중 일부를 다음에 소개한다.

  • 사람들은 테스트를 할 때, 다음 단계에서 무엇을 테스트하는지 이해하는 데 어려워한다.
  • 테스트의 이름을 정하는 데 어려워한다.
  • 단일 테스트 내에서 얼마나 많은 테스트를 해야 하는지 매번 감을 잡지 못하는 것 같다.

컨텍스트가 주어지면 BDD를 정확하게 정의할 수 있다. 즉, 동작이 어떻게 바뀌는 지를 알려주도록 테스트를 작성하기만 하면 된다. 작성된 테스트가 비즈니스 이해관계자에게 설명할 만한 가치가 있는지를 스스로에게 질문해보는 것도 좋은 방법 중 하나다. 이는 이전 단계의 문제를 어떻게 해결했는지에 대해서는 다소 불명확하지만 현재 문제가 어떻게 생겼는지를 설명하는 데 있어서는 실질적인 도움이 된다. BDD는 '주어진 조건(Given), 언제(When), 그러고 나면(Then)'과 같은 3개로 구성된 스트럭처를 따른다. 이러한 스타일을 준수하기 위해서는 구체적인 프레임워크 또는 많은 테스팅 과정이 필요하다. 따라서 이 책에서는 다음과 같이 테스트에서 이를 다소 느슨하게 따르려고 한다. 이 스타일로 작성된 테스트 명세의 예를 보자. "빈 데이터세트가 주어진 경우, 분류분석기의 학습 단계가 완료된 상태라면 '유효하지 않은 명령(invalid operation)'이라고 예외처리를 할 것이다."

 

이 문장은 다루기에 충분히 작은 크기의 작업처럼 보인다. 하지만 이것 역시도 여러분이 일하고 있는 분야에 익숙한 비즈니스 사용자가 이해할 수 있고, 개인적인 의견(피드백)도 줄 수 있는 수준이어야 한다.

이 내용에 대한 댄 노스의 견해가 궁금하면, 다음 웹사이트를 참고하기 바란다.

  • http://dannorth.net/introducing-bdd/

BDD 추종자들은 비즈니스 이해관계자가 볼 수 있도록 프로그래밍 언어와 테스트 결과 리포트를 만드는 데 특정 툴을 사용하려는 경향이 있다. 나의 경험을 포함해서 다른 동료들과 의견 교환을 통해 얻은 결론은 이런 식으로 쓸데없이 우아하게 만들어 놓은 것이 실제로는 잘 쓰이지도 않고, 가치도 별로 없어 보인다는 점이다. 이 책을 통해 배우는 접근법은 BDD에 대한 배경 지식이 전혀 없는 사람들도 가능한 한 쉽게 익힐 수 있는 방법이다.

이러한 점을 기억하고 예제를 통해 익혀보자.

 

첫 번째 테스트

파이썬 예제 코드를 이용해 시작해보자. 앞으로 이 책에서는 nosetests를 사용할 것이다. 이유는 라이브러리 설치에 많은 노력을 들이지 않아도 되기 때문이다. 특히, 이 라이브러리는 모든 작업을 단순한 형태로 만들어줄 것이다. 파이썬에서 기본으로 제공하는 단위 테스트 솔루현은 설치하는 데 많은 노력을 기울여야 한다. 무엇보다도, nose를 사용하면 필요로 하는 확장 기능을 찾을 때 빌트인(built-in) 솔루션을 사용하는 테스트에 언제든 반영시킬 수 있다.

 

우선 다음 명령어를 이용해 설치를 진행한다.

pip install nose

 

pip를 사용해 본 적이 없으면, 이번 기회를 통해 파이썬에서 신규 라이브러리를 설치하는 간단한 방법을 익히기 바란다.

 

이제, 'hello world' 같은 예제처럼 클래스를 하나 만들어보자. 이 클래스는 '무언가 숫자를 하나 추측해내는 데 앞 단계에서 얻은 추측값을 사용하는' 기능을 한다. 생각해볼 만한 가장 간단한 코드 예제 중 하나다. 이를 대상으로 앞에서 학습한 TDD 사이클을 사용하고, 약간 수고스러울 수 있는 세부 작업에 대해서 우리의 첫 번째 테스트를 만들 것이다. 우리의 첫 번째 테스트가 성공하고 학습했던 것들이 구체적인 결과로 나타나면, 우리가 만든 테스트를 자세히 분석해보자.

 

우선, 실패 테스트를 하나 작성해야 한다. 다음과 같이 가장 간단한 실패 테스트를 생각해봤다.

def given_no_information_when_asked_to_guess_test():
  # given
  number_guesser = NumberGuesser()
  # when
  guessed_number = number_guesser.guess()
  # then
  assert guessed_number is None, 'there should be no guess.

 

assert를 위한 컨텍스트는 테스트 이름 내에 포함되어 있다. 테스트 이름을 읽어 보면 assert 이름이 이 테스트가 어떤 것인지를 잘 설명하고 있음을 알 수 있다. 이 테스트에서 NumberGuesser 객체를 초기화한 점에 주목하기 바란다. 이 클래스는 아직 이름만 있는 상태이므로, 여러분은 모든 과정을 꼼꼼하게 확인해야 한다.

다시말하면, 현재는 "이걸 이렇게 사용하겠다."라고만 해놓은 것과 같다. 따라서 맨 처음 시작하기에 가장 좋은 상태로 볼 수 있다. 왜냐하면 이 클래스가 현재 없으니, 테스트 결과가 당연히 실패할 것 같지 않은가? 이제 이렇게 가정한 게 맞는지 실제로 테스트해보자.

 

테스트를 실행시키기 위해, 테스트 파일명이 _tests.py로 끝나는지 확인하기 바란다. 위의 코드가 포함된 디렉토리에서 다음 명령어를 실행시킨다. 이 테스트를 실행시키면 다음과 같은 결과가 나타난다.

 

화면에 많은 메시지가 나왔다. 하지만 이들 중 뒷부분에 중요한 정보가 있다. 이 메시지에는 NumberGuesser가 아직 없다고 나와 있는데, 이는 우리가 코드를 아직 작성하지 않았기 때문이고, 예상했던 그대로 나온 것이다. 이 책 전반에 걸쳐 테스트 결과로 나타날 에러 메시지 리스트(stack trace)를 줄여나갈 것이다. 앞으로는 내용을 잘 이해했는지 확인하기 위해 상세한 사항들을 기록할 것이다. 지금 우리는 TDD 사이클에서 빨간색 상태에 있다.

  • 다음으로, NumberGuesser.py라는 이름의 파일에 다음과 같은 클래스를 생성한다.
class NumberGuesser:
  """Guesses numbers based on the history of your input"""
  • import NumberGuesser 문을 이용해 테스트 파일의 맨 위에 신규 클래스를 임포트한다.
  • nosetests를 재실행시키면, 다음과 같은 메시지가 나온다.
TypeError: 'module' object is no callable

 

이런! 클래스 임포트를 재대로 안했나보다. 이건 작은 스텝이긴 아지만 우리의 테스트가 일관성 있게 나아가는 데 매우 중요한 역할을 한다. 이런 점들에 스트레스를 받지 않도록 꼼꼼히 해 나가자. 다음 장부터는 이런 것들을 이 정도까지 상세히는 아니겠지만, 되도록이면 가능한 한 많이 짚고 넘어갈 생각이다.

  • import 문을 다음과 같이 바꾼다.
from NumberGuesser import NumberGuesser
  • nosetests를 다시 실행시키면, 다음처럼 메시지가 나타날 것이다.
AttributeError: NumberGuesser instance has no attribute 'guess'
  • 에러 메시지가 바뀌었다. 즉, 새로 수정할 부분이 생겼다는 뜻이다. 여기서 테스트 통과에 필요한 것을 구현하기만 하면 된다.
class NumberGuesser:
  """Guesses numbers based on the history of your input"""
    def guess(self):
      return None
  • nosetests를 다시 실행시키면, 다음과 같은 결과가 나타날 것이다.

자, 이렇게 해서 첫 번째 테스트가 성공적으로 끝났다! 이 테스트 과정 중 너무 시시해 보여서 꼭 이걸 해야 하나 싶은 것들도 있었다. 솔직히 말해, 그런 것들이 시간 낭비일 수도 있다. 이런 것들은 얼마나 세세하게 작업을 하느냐에 따라 작업 수준을 결정하면 된다. 앞으로 발생할 수 있는 논쟁에 효과적으로 대응하기 위해 되도록이면 각 직업 단위를 작게 유지하는 것이 좋다. 이렇게 하면 TDD상에서 어느 단계까지 와 있는지, 그 다음 단계는 어떤 것들이 있는지 설명하기에 충분하니까 말이다. 우리는 엄청나게 규모가 크고, 통제하기도 어려운 단계들을 어떻게 코드로 작성해야 하는지 잘 알고 있다. 상세 모듈 형태로 코딩하는 기법을 익히는 것은 일부러라도 연습해야 하며, 그만큼 가치 있는 일이다. 이제 우리가 했던 첫 번째 테스트에서 어떤 것들을 했는지 알아보자.

 

테스트 상세 분석

개념 수준에서 시작해서 파이썬과 작업 과정에서 결과를 어떻게 주고받았는지 다시 한 번 생각해보자. 그저 테스트를 만들고 파이썬은 테스트했던 클래스가 없다고 에러 메시지를 리턴했었다. 그 다음 단계로, 클래스를 생성했지만 파이썬은 제대로 클래스 임포트를 하지 않았다고 에러 메시지를 리턴했다. 그래서 여기에 맞추어 클래스를 임포트시켰으나, 파이썬은 guess 메소드가 없다고 다시 한 번 에러 메시지를 리턴했다. 이에 대해 테스트에서 예상했던 방법을 구현해 반영시켰고, 그 결과 파이썬의 에러 메시지는 더 이상 나타나지 않았다. 

 

이것이 TDD가 추구하는 방식이다. 즉, 여러분과 여러분의 컴퓨터가 대화를 하듯 작업을 진행하는 것이다. 작업하기 좋은 정도로 단계를 나누어 작업하면 된다. 앞에서 했던 것들은 다 건너뛰어도 된다. 하지만 맨 처음 단계에서 했던 파이썬 클래스 작성과 이 클래스를 올바르게 임포트시키는 작업은 꼭 해야 한다. 여러분이 일종의 '시스템과의 대화' 없이 작업을 해나갈수록, 가능한 한 단순하게 작업하려 했던 당초 계획에서 점점 멀어질 것이다.

 

첫 번째 샘플 테스트를 모듈화할 수 있도록 좀 더 깊이 들여다보고 해부해보자. 앞에서 얘기하진 않았지만, 여러분이 작성하는 모든 테스트에서도 되새겨보도록 첫 번째 샘플 테스트를 작업 단위별로 나누어보았다.

def given_no_information_when_asked_to_guess_test():
  # given
  number_guesser = NumberGuesser()
  # when
  guessed_number = number_guesser.guess()
  # then
  assert guessed_number is None, 'there should be no guess.'

 

주어진 조건

이 절에서는 테스트의 내용을 작성한다. 앞에서 다룬 샘플 테스트에서 객체에 대해 어떤 사전 정보도 나타나지 않았다. 다양한 머신 러닝 테스트에서, 이는 테스트 과정 중 매우 복잡한 부분이 될 것이다. 예를 들면, 임의의 데이터를 읽어들이거나, 때때로 데이터에 포함된 특정 사항을 이슈화하거나, 우리가 예상하는 특정 사항을 처리할 소프트웨어를 테스트하는 것 등이다. 테스트 과정 중 우리가 이 섹션에 대해 생각할 때는 "이러이러한 시나리오가 주어지면...(Given)"과 같이 문구를 써 보기 바란다. 우리가 다룬 샘플 테스트를 예로 들면, "NumberGuesser에 대한 사전 정보가 주어지지 않았다면(Given no prior information)..." 정도가 될 것이다.

 

언제 

이 부분은 테스트의 가장 단순한 측면 중 하나일 것이다. 여러분이 '주어진 조건'에서 테스트 내용을 작성했다면, 테스트하려는 동작이 시작되게 하는 단순 액션이 있어야 할 것이다. 테스트 과정 중 이 섹션은 "...한 것들이 일어날 때 (When)" 형태로 작성하기 바란다. 앞에서 다룬 샘플 테스트를 예로 들면, "NumberGuesser가 숫자 하나를 추측했을 때 (When)"라고 쓸 수 있을 것이다.

 

그러고 나면

이 부분에서는 변수와 (가능한 경우) 리턴 결과 등의 상태를 확인한다. 이 절은 테스트가 무척 단순하다. 테스트에서 객체에 대한 변경사항을 일으키는 딱 한 개짜리 액션만 있기 때문이다. 이렇게 되는 이유는 예를 들어 어떤 테스트에 2개의 액션이 필요하다면, 이는 결국 관련 분야에서 의미있는 용어로 설명할 수 있는 하나의 액션으로 합치려고 할 가능성이 높기 때문이다. 좋은 예로, 파일에서 학습용 데이터를 불러와서 분류분석기(classifier)를 학습시키는 작업을 생각해볼 수 있다. 만약 이를 위한 작업량이 너무 많다면, 파일로부터 데이터를 불러들이는 메소드 한 개를 만들기만 하면 된다.

 

이 책을 학습하다보면, 결과가 어떤 특정 방식으로 바뀌어야 하는지 결정해야 하는데 도움이 될 만한 함수가 필요할 것이다. 이를테면, 코드 스멜(code smell)과 같은 것들이 대표적인 예다. 앞서 했던 테스트를 다시 한 번 생각해보자. 우리가 코드에 추가해야 했고, 결과를 이해해야 했던 것들은 결국 테스트하는 코드 속에 모두 포함되어 있을 것이다.

 

'주어진 조건(Given), 언제(When), 그러고 나면(Then)'은 TDD에서 꼭 지켜야 하는 요구사항은 아니다. 우리가 앞에서 다룬 TDD의 정의에는 2개만 가지고 작성되었기 때문이다.(코드에서 필요했던 건 실패 테스트와 중복을 제거하는 것 정도였다.) 그렇더라도 이 책에서는 이러한 규약을 지키기로 하자. 이유는 다음과 같다.

  • 이 책 전반에 걸쳐 규약을 따르면 읽기가 훨씬 수월해진다.
  • 이 규약은 TDD 사용 관점에서 패턴을 찾아내려고 노력한 수많은 사람들의 고민을 집대성해 얻은 결과다. 테스트를 어떻게 시도하면 될지에 대해 바꾼 기술이고, 이 책에서는 이 규약을 사용할 것이다.

조금만 열정이 있으면 충분히 할 수 있는 것이다. 만약 이 문구가 여전히 거부감이 든다면, 머릿속으로 다음과 같이 바꿔서 생각해보기 바란다. "정리하라(Arrange), 액션을 취한다(Act), 주장 한다(Assert)" 마지막으로 왜 이렇게 구체적이고, 정교한 단어를 사용하는지에 대해 생각해보자.

 

머신 러닝에 TDD 적용

이제, 여러분은 TDD가 어떻게 머신 러닝에 사용될지, 회귀분석 또는 분류분석 문제에 TDD를 사용할 수 있는지 등에 대해 매우 궁금할 것이다. 모든 머신 러닝 알고리즘에는 여러분이 하는 작업에 대해 정량적이고 정성적 관점에서 측정하는 방법이 있다. 예를 들면, 선형 회귀분석에서는 '조정된 결정계수' 값이 있다. 또한 분류분석 문제에서는 ROC곡선 값, 분류행렬 등이 있다. 이들은 정량적 수치로 테스트가 가능한 것들이다.한 자기 주의할 점은 이러한 정략적 측정치들이 해당 알고리즘에 대해 좋다, 나쁘다를 결정해 알려주지는 못한다는 것이다.

 

자. 이제 완전 초보 수준 내지는 무식할 정도로 단순한 알고리즘을 만들어서, 문제 해결을 위한 작업을 시작해보자. 리턴값으로 받을 스코어는 보통 결과, 이전 결과, 무작위로 생성된 결과를 나타낸다. 일단 무작위로 생성된 결과 스코어보다 나은 결과를 내는 알고리즘을 만들면, 그 다음부터는 가장 높은 스코어를 얻을 때까지 계속 반복해서 시도하기만 하면 된다. 알고리즘 벤치마크 테스트를 통해 조금 더 깊이 있게 알아볼 수 있을 것이다. 

 

이 책에서 무작위로 생성된 결과 스코어를 찾는 아주 단순한 알고리즘을 구현할 것이다. 또한 이 분석 모델이 다른 곳에도 쓰일 수 있도록 테스트 패키지(test suite) 형태로 만들 것이다. 이렇게 하면 우리가 앞에서 파이썬을 가지고 했던 것과 같은 방식으로 우리가 만든 머신 러닝 분석 모델에도 대화형 방식을 사용할 수 있게 될 것이다.

 

머신 러닝 개발 전문가에게 테스트와 관련해 가장 이상적인 측정치는 기댓값(예: 수익)에 대해 위험요소(예: 금융관련 위험 노출도)를 비교하는 수익 모델이라고 할 수 있다. 이는 우리가 감당할 수 있는 에러가 어떤 것들이 있는 지, 얼마나 많은지를 잘 볼 수 있게 해준다. 머신 러닝에서 완벽한 분석 모델은 아직까지는 없기 때문에 가능한 한 '제일 좋은' 분석 모델을 찾아내야 한다. 여러분이 금융에 관련된 가설을 어떻게 분석 모델로 만드는지를 알아가면, 분석 모델 간에 어떤 것이 좋은지 결정하는 데도 많은 도움이 될 것이다. 이 책 전반에 걸쳐 이와 관련된 내용을 다룰 계획이니, 열심히 학습해주기 바란다.

 

확률적 속성 문제 해결

알고리즘에서 확률적 속성을 다룬다는 것은 TDD를 어떻게 사용할지 배우고 있는 사람들에게는 매우 막막하게 느껴질 수도 있다. TDD는 최종 결과가 정해져 있고, 과정도 예상한 대로며, 통제가 가능하기 때문에 (확률 프로세스에서 설명하는 것처럼) 초기 단계에서 어떤 육감 같은 것에 의존하는 동작이란 게 TDD에서는 불가능하게 생각될 수도 있다. 하지만 이래서 TDD가 주목받는 것 아니겠는가. 어떻게 하면 되는지 알아보자.

 

우리가 앞에서 다루었던 NumberGuesser 예제를 다시 보자. 여기에 요구사항을 추가해 사용자가 생각한 숫자를 임의로 선택하게끔 할 것이다. 아무렇게나 하는 게 아니라 가장 그럴듯한 숫자를 선택하도록 말이다.

 

이렇게 하려면, 우선 이전 숫자가 뭐였든지 간에 '사용자가 생각한 숫자를 추측해 보라'고 할 때마다 NumberGuesser가 매번 숫자를 추측하게 해야 한다. 이것을 테스트하는 코드는 다음과 같다.

def given_one_datapoint_when_asked_to_guess_test():
  #given
  number_guesser = NumberGuesser()
  previously_chosen_number = 5
  number_guesser.number_was(previously_chosen_number)
  #when
  guessed_number = number_guesser.guess()
  #then
  assert type(guessed_number) is int, 'the answer should be a number'
  assert guessed_number == previously_chosen_number, 'the answer should be the previously chosen number.'

 

이것은 클래스에 변수값을 설정하기만 하면 되는 간단한 테스트다. 이전 입력값을 근거로 예측하는 것은 나름 타당성을 지닌다. 시작 단계에서 할 수 있는 가장 단순한 예측 방법이라고 할 수 있다.

여기서 테스트를 실행시키면, 실패했다고 결과가 나타날 것이다. 테스트를 통과했다면 코드는 다음처럼 되어 있을 것이다.

class NumberGuesser:
  """Guesses numbers based on the history of your input"""
  def __init__(self):
    self._guessed_numbers = None
  def number_was(self, guessed_number):
    self._guessed_numbers= guessed_number
  def guess(self):
  	return self._guessed_number

 

이 테스트를 통과시키기 위해 모든 리팩토링 방안을 생각해볼 수 있다. 이 코드는 여전히 짧고 단순하니, 일단 이 상태를 유지하자. 다음으로 NumberGuesser가 최종 단계의 추측값만 고려하는 게 아닌, 앞에서 추측했던 숫자들 전체 중 하나를 임의로 선택하게 하려고 한다. 즉, 추측값이 이전에 검토했던 숫자들 중 하나라는 것을 확인하는 것에서 이 테스트를 시작할 것이다.

def given_two_datapoint_when_asked_to_guess_test():
  #given
  number_guesser = NumberGuesser()
  previously_chosen_number = [1,2,5]
  number_guesser.number_was(previously_chosen_numbers)
  #when
  guessed_number = number_guesser.guess()
  #then
  assert guessed_number in previously_chosen_numbers, 'the guess should be one of the previously chosen numbers'

 

이 테스트를 실행시키면 새로운 실패 결과가 나타날 것이다. 나는 이 테스트가 제대로 동작하도록 하려니 너무 귀찮아서, 시간을 대폭 절약할 수 있도록 아이디어를 냈다. 대단한 건 아니고, 새로 메소드를 만들고 리스트의 첫 번째 엘리먼트로 받게 하면 된다.

class NumberGuesser:
  """Guesses numbers based on the history of your input"""
  def __init__(self):
    self._guessed_numbers = None
  def numbers_were(self, guessed_numbers):
    self._guessed_numbers = guessed_numbers[0]
  def number_was(self, guessed_number):
    self._guessed_number = guessed_number
  def guess(self):
  	return self._guessed_number

 

뭔가 목적을 달성하는 데는 역시 '게으름 정신(laziness)'이 최고인 것 같다. 게으름은 어떤 문제를 해결할 때 지나치게 공학적 마인드로 접근하지 않도록 우리를 보호해주고, 우리가 만들고 있는 테스트 솔루션을 좀 더 견고하게 다져주는 것 같다. 이는 문제를 좀 더 빠르게 해결해주고, 가급적 극단적인 케이스의 테스트를 하지 않도록 해주기 때문이 아닌가 한다.

 

자, 이제 매번 같은 숫자를 선택하지 않아도 되게끔 어서션(assert)을 하려고 한다. 항상 다른 숫자를 만들어내지는 못하고, 같은 숫자가 일부 섞여 있을 것이다. 이를 테스트하기 위해 테스트 코드를 리팩토링하고 새로운 어서션을 다음과 같이 추가 할 것이다.

def given_multiple_datapoints_when_asked_to_guess_many_times_test():
  #given
  number_guesser = NumberGuesser()
  previously_chosen_numbers = [1,2,5]
  number_guesser.numbers_were(previously_chosen_numbers)
  #when
  guessed_numbers = [number_guesser.guess() for i in range(0,100)]
  #then
  for guessed_number in guessed_numbers:
    assert guessed_number in previously_chosen_numbers, 'every guess should be one of the previously chosen numbers'
  assert len(set(guessed_numbers)) > 1, "It shouldn't always guess the same number."

 

테스트를 실행시키면, It shouldn't always guess the same number.라는 실패 메시지가 리턴될 것이다. 테스트 목표에 딱 맞는 메시지다. 이 테스트는 다른 테스트의 실패를 유발할 가능성이 있으므로, 모든 테스트가 초록색 상태가 되게끔 가장 단순하게 작업을 하려고 한다. 다음 코드는 그 결과다.

import random
class NumberGuesser:
  """Guesses numbers based on the history of your input"""
  def __init__(self):
    self._guessed_numbers = None:
  def numbers_were(self, guessed_numbers):
    self._guessed_numbers = guessed_numbers
  def number_was(self, guessed_number):
    self._guessed_number= [guessed_number]
  def guess(self):
    if self._guessed_numbers == None:
      return None
    return random.choice(self._guessed_numbers)

 

이 테스트를 통과시키는 방법은 여러 가지가 있다. 우리는 이제 막 시작했기 때문에 이런 방식으로 해결한 것이고, 이 정도면 괜찮은 방법이라고 본다. 그런데 여기서 리팩토링이 필요하다면? 각 메소드는 guess 메소드 외에는 모두 한 줄밖에 안된다. guess 메소드도 간단하니까, 일단 이 상태를 유지하기로 하자.

 

이제, 이전 숫자들을 파악해서 입력하는 number_was를 사용했다면, 이전 숫자를 추측하기만 한다는 점을 생각해보자. 사실 이게 썩 마음에 들진 않는다. 따라서 이 숫자를 잡아내는 새로운 테스트가 필요한 것 같다. 이를 반영한 새로운 테스트를 다음처럼 작성해보자.(4번째 버전이다.)

def given_a_starting_set_of_observations_followed_by_a_one_off_observation_test():
    #given
  number_guesser = NumberGuesser()
  previously_chosen_numbers = [1,2,5]
  number_guesser.numbers_were(previously_chosen_numbers)
  one_off_observation = 0
  number_guesser.number_was(one_off_observation)
  #when
  guessed_numbers = [number_guesser.guess() for i in range(0,100)]
  #then
  for guessed_number in guessed_numbers:
    assert guessed_number in previously_chosen_numbers + [one_off_observation], 'every guess should be one of the previously chosen numbers'
  assert len(set(guessed_numbers)) > 1, "It shouldn't always guess the same number."

 

여기서는 맨 마지막 어서션에서 실패한다. 다음 코드를 이용해 테스트를 통과시킬 것이다.

import random
class NumberGuesser:
  """Guesses numbers based on the history of your input"""
  def __init__(self):
    self._guessed_numbers = []
  def numbers_were(self, guessed_numbers):
    self._guessed_numbers = guessed_numbers
  def number_was(self, guessed_number):
    self._guessed_numbers.append(guessed_number)
  def guess(self):
    if self._guessed_numbers == []:
      return None
    return random.choice(self._guessed_numbers)

 

이 실패 테스트에 대해 몇 가지 문제가 있다. 단일 관찰변수(observation, 입력 데이터와 같은 의미) 여러 개의 관찰변수 집합(observation set)을 둘 다 지원하면 이제까지 나열한 모든 어서션은 성공해야 한다는 점이다. 따라서 다음과 같이 NumberGuesser가 모든 숫자를 최소한 한 번씩은 추측하도록 새로운 테스트를 작성하려고 한다.

def given_a_one_off_observation_followed_by_a_set_of_observations_test():
  #given
  number_guesser = NumberGuesser()
  print(number_guesser._guessed_numbers)
  previously_chosen_numbers = [1,2]
  one_off_observation = 0
  number_guesser.number_was(one_off_observation)
  number_guesser.numbers_were(previously_chosen_numbers)

  all_observations = previously_chosen_numbers + [one_off_observation]
  #when
  guessed_numbers = [number_guesser.guess() for i in range(0,100)]
  #then
  for guessed_number in guessed_numbers:
    print(guessed_number, all_observations)
    assert guessed_number in all_observations, 'every guess should be one of the previously chosen numbers'
  assert len(set(guessed_numbers)) == len(all_observations), "It should eventually guess every number at least once."

 

이제 최종 코드는 다음과 같은 모습일 것이다.

import random
class NumberGuesser:
  """Guesses numbers based on the history of your input"""
  def __init__(self):
    self._guessed_numbers = []
  def numbers_were(self, guessed_numbers):
    self._guessed_numbers += guessed_numbers
  def number_was(self, guessed_number):
    self._guessed_numbers.append(guessed_number)
  def guess(self):
    if self._guessed_numbers == []:
      return None
    return random.choice(self._guessed_numbers)

 

기술 관점에서 이 테스트는 임의로 실행되었을 때 실패할 수 도 있다. 다만, 이 경우 실패할 확률은 0.5∧100로, 대략 7.9 x 10 ∧-31 정도 된다. 사실상, 확률이 0이라고 할 수 있다.

 

개선된 분석 모델의 검증 방법

분석 모델의 성능은 만든 모델의 종류와 목적에 따라 모두 다르게 검증될 수밖에 없다. 이 책에서 다루는 몇몇 머신 러닝 기법의 경우, 각각의 분석 모델 성능을 검증하는 여러 가지 방법이 있다.

 

분류분석 개요

방금 전 다루기 했지만, 개념 수준으로 분류분석 용어들을 다시 한 번 복습해보자. 지도형 학습의 성능을 정량적으로 검증할 때 ROC 커브 값을 사용할 수 있다. 이 값을 얻으려면 1) 커브 아래쪽 전체 영역(AUC)을 계산하거나, 2) 변곡점의 위치를 파악하거나, 3) 전체 시간 비율에 따라 제대로 분류가 되어야 하는 데이터의 최대치를 설정하면 된다.

 

또 다른 검증 기법으로 분류행렬(혼동행렬이라고도 함)이 있다. 테스트에 도움이 되도록 행렬 내 임의의 셀에 한계치가 할당되어 있을 수 있다. 아울러, 향후 나타날 문제점을 알아내는 진단 툴로 사용될 수도 있다.

 

보통 k-fold 교차 검증 기법도 많이 사용된다. 교차 검증 기법은 데이터세트에서 임의의 샘플을 추출한다. 이렇게 한 후, 남은 데이터세트를 일정한 크기로 나누고, 여기에 샘플 데이터에서 일정량 만큼씩을 섞어 넣는 기법이다. 그런 다음, 여러 개의 데이터세트중 하나를 사용해 다음과 같은 작업을 수행할 수 있다. 1) 다른 데이터세트에 대해 개발하거나 2) 데이터가 오버피팅 되지 않았는지를 검증하거나, 3) 다른 것들이 제대로 분석되었는지를 보기 위해 최종 검토 단계에서 제3의 데이터세트로 사용하는 등이다. 이렇게 분리된 데이터세트 모두 일반적으로 적용 가능한 모델이 개발되었는지 확인하는 용도로 쓰인다. 이를테면, 학습 데이터를 예측하는 것뿐만 아니라 프로덕션 단계에서 멀어지는지 여부 등이다.

 

회귀분석

선형 회귀분석의 성능은 보통 '조정된 결정계수'와 이 계수를 측정해 패턴에 맞지 않는 분석 모델의 오차값을 함께 계산해 결정한다. 어떻게 하면 자동화된 테스트 내에서 이것을 할 수 있을까?

 

조정된 결정계수 값은 대부분의 통계분석 툴에서 제공하고 있다. 분석 모델을 설명하기 위해 데이터 내에 얼마나 많은 변수들이 필요한지를 측정해준다. 모델이 포함하고 있는 가정을 검증하는 것은 상당히 어려운 작업이다. 따로따로 구분되어 있고, 구체적으로 만들어진 여러 개의 테스트를 보는 것 보다는 분석 결과인 패턴을 살펴보는 것이 훨씬 쉽다. 

 

결국 어렵다는 얘기인데, 그래도 이와 관련한 다른 몇 가지 테스트 방법이 있다. 어찌보면, 더 쉬우면서도 훨씬 중요한 테스트라고 생각되는데, 바로 교차 검증이다. 교차 검증을 이용하면, 잘못된 결과가 포함된 테스트 데이터를 선택 적용해서 개발, 테스트, 프로덕션 준비 단계에 이르기까지 결정계수를 비교해 나갈 수 있다. 계산 결과 중 급격하게 낮아지는 수치가 발견되면, 되돌아가서 다시 반복한다.

 

클러스터링

클러스터링 (또는 군집분석)은 분류분석 모델과 유사한 분석 방법으로 보면 된다. 따라서 데이터에 대해 교차 검증 기법을 통해 테스트할 수 있다. 교차 검증 기법은 클러스터링 알고리즘에서 특히 효용성이 좋은데, 대표적으로 k-평균 클러스터링 알고리즘과 같이, 클러스터링 알고리즘과 같이, 클러스터링 결과가 다양하게 나타나지 않도록(즉, 되도록 일관된 결과를 얻을 수 있도록) 피드백 결과가 클러스터 개수 조정에 반영되는 특성을 지니고 있다. 한 가지 꼭 기억할 것은 교차 검증 기법용 데이터를 계속 바꿔가면서 테스트를 진행할수록, 이전 테스트에서 얻은 학습 결과가 지속되지 않는다는 점이다. 이렇게 함으로써 최종 결과가 좀 더 나은 객관성을 갖게 된다.

 

분류분석 모델의 정량적 분석

이제까지 학습한 내용을 제대로 이해했는지 확인하는 차원에서, ROC 그래프와 AUC 스코어를 예제를 통해 살펴보자. 파이썬의 scikit-learn 문서를 참고하면 ROC 그래프와 AUC를 계산하는 방법을 알 수 있다. 다음 URL을 참고하기 바란다.

  • http://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html

위의 ROC 그래프는 머신 러닝 분야에서는 매우 유명한 붓꽃 데이터를 대상으로 분류분석기를 실행해 얻은 결과다. 여기서 참으로 판단한 결과 맞는 비율은 참으로 판단한 결과가 틀린 비율이 주어졌을 때 그만큼 가능한지 여부를 나타낸다. 예를 들어, 50%의 참으로 판단한 결과가 틀린 비율로도 괜찮았다면, 90%의 참으로 판단한 결과가 맞는 비율 정도를 예상할 수 있다. 또한 AUC 비율이 80%이라는 점을 눈여겨보자. 완벽한 분류분석기의 스코어가 100%라는 점을 생각한다면, 이는 매우 수치일 것이다. 그래프에서 점선으로 표시된 것은 별로 좋지 않거나, 또는 아무렇게나 (즉, 전혀 예측을 하지 않고) 분류한 분석 모델의 결과다. 가장 이상적인 분석 모델은 가능한 한 그래프 상에서 왼쪽 상단에 있어야 한다. 여기서는 2개의 결과가 어느 정도 격차를 보이고 있는데, 이 정도면 꽤 성능이 좋다고 볼 수 있다. 분석 모델의 검증 결과를 받아들일지 여부는 문제가 해결되었는지에 달려 있다. 어떻게 그럴까?

 

우리가 만든 분류분석기를 이용해 어떤 고객이 광고에 잘 반응하는지를 알아내고자 한다면 어떻게 하면 될까? 별로 관심이 없는 고객에게까지 모두 광고를 한다면 사업을 하기가 매우 어려워질 것이다. 즉, 다소 극단적일 수도 있겠지만 "비용이 너무 많이 들기 때문에 참으로 판단한 결과가 틀린 경우를 배재시켜야 한다."고 얘기해볼 수 있다. 앞에서 본 ROC 그래프 결과로 본다면, 10% - 15% 정도의 참으로 판단한 결과가 맞는 경우 정도로만 선별해낼 것이다. 이 예제를 통해, 분석 모델의 성능이 향상될수록 수익이 증가하고, 이에 따라 우리의 상황도 더 좋아질 수 있음을 알 수 있다.

 

실수로 누군가에게 특정 광고를 잘못 내보낼 경우가 10,000번 중 1번 정도 발생하고, 이로 인해 소송을 당해서 드는 처리 비용이 평균 $25,000 정도라고 가정해보자. 어떤 분석 모델이 더 나아 보이는가? 다음의 그래프는 앞에서 본 ROC 데이터를 그대로 사용했지만, 새로운 매개변수 값을 적용한 결과다.

 

그래프상에서 최대 순이익은 참으로 판단한 결과가 틀린 비율 값이 1.9%쯤일 때로 파악된다. 이전 그래프에서 분류기 성능이 좋은 것으로 나타났음에도, 참으로 판단한 결과가 틀린 경우의 값이 커질수록 이익은 급격하게 감소함을 보여주고 있다. 이 장의 목표였던 단계적인 접근 방법을 통한 코딩 작업에 대해 걱정스러울 수도 있을 것이다. 앞으로는 이런 종류의 그래프는 그냥 만들면 된다. 이런 결과를 이용해 분석 작업을 어떻게 다듬어 나갈지에 대해 계속 배워나갈 것이다.

 

요약

이 장에서는 TDD와 BDD에 대한 개요를 설명했다. 기본 개념의 설명과 함께, 머신 러닝에 어떻게 적용할지에 대한 기본적인 사항도 알아보았다. 소프트웨어에서 필요로 하는 명세사항을 이해하기 쉽도록 문장을 작성하는 방식도 살펴보았다.

 

이러한 기본사항을 통해 개념 수준으로 테스팅을 다루었다. ROC 그래프와 AUC 측정치를 이용해 분류기를 정량적 관점에서 사용할 수 있도록 개념을 정립했다. 다른 모델들에 대해서도 이처럼 정량적으로 살펴보면, 이들에 대해 비교해볼 수도 있을 것이다. 

 

이 장을 통해 우리는 테스트 주도 기법을 이용해 머신 러닝을 학습하는 데 필요한 것들을 갖추었다. 다음 장에서는 TDD를 이용해 간단한 퍼셉트론 알고리즘을 만들과 이에 대한 성능도 측정해볼 것이다.