페이지

2018년 10월 31일 수요일

WEB SERVICE

- Service delivered over the web?

What aout interoperability?

Will a .Net application be able to call the services in the JAR?

WEB SERVICE -W3C DEFINITION

Software system designed to support interoperable machine-to-machine interaction over a network.

3KEYS
- Designed for machine-to-machine(or application-to-application) interaction

- Should b interoperable- Not platform dependent

- Should allow communication over a network


2018년 10월 26일 금요일

Data Science: Logistic Regression in Python

Outline
- What is classification (in the broader context of all ML)? Review
- Linear classifiers in general
- Biological inspiration - neurons and perceptron
- Logistic regression schematic
- Feed-forward mechanism, probabilistic interpretation
- Cross-entropy error function
- Maximum-likelihood
- Gradient descent
- Practical problems:regularizaion, donut problem, XOR problem




Deep Learning in Python

##Chapter 1: What is a neural network?

Specifically, neurons are the perfect model for a yes / no, true / false, 0 / 1 type of problem. We call this "binary classification" and th machine learnng analogy would be the "logistic regression" algorithm.

The above image is pictorial representation of the logistic regression model. It takes as inmput x1, x2, and x3 which you can imagine as the outputs of other neurons or some other input signal (i.e. the visual receptors in your eyes or the mechanical receptors in your fingertips), and outputs another signal which is a combination of  these inputs, weighted by the strength of those input neurons to this output neuron.

Becaise we're going to have to eventually deal with actual numbers and formulas, let's look at how we can calculate y from x.


y = sigmoid(w1*x1 + w2*x2 + w3*x3)


2018년 8월 7일 화요일

3. 분류 3.1 MNIST

이 장에서는 고등학생과 미국 인구조사국 직원들이 손으로 쓴 70,000개의 작은 숫자 이미지를 모은 MNIST 데이터셋을 상요하겠습니다. 각 이미지에는 어떤 숫자를 나타내는지 레이블되어 있습니다. 이 데이터셋은 학습용으로 아주 많이 사용되기 때문에 머신러닝 분야의 'Hello World'라고 불립니다. 새로운 분류 알고리즘이 나올 때마다 MNIST 데이터셋에서 얼마나 잘 작동하는지 봅시다. 머신러닝을 배우는 사람이라면 머지않아 MNIST 데이터셋을 맞닥뜨리게 될 것입니다.

사이킷런에서 제공하는 여러 헬퍼 함수를 사용해 잘 알려진 데이터셋을 내려받을 수 있습니다. MNIST도 그 중 하나입니다. 다음은 MNIST 데이터셋을 내려받는 코드입니다.

사이킷런에서 읽어 들인 데이터셋들은 일반적으로 비슷한 딕셔너리 구조를 가지고 있습니다. 

3.3.3 정밀도와 재현율
사이킷런은 정밀도와 재현율을 포함하여 분류기의 지표를 계산하는 여러 함수를 제공합니다.

이제 '5-감지기'가 정확도에서 봤을 때만큼 멋져 보이지는 않네요. 5로 판별된 이미지 중 77%만 정확합니다. 더군다나 전체 숫자 5에서 80%만 감지했습니다.
정밀도와 재현율을 F1점수라고 하는 하나의 숫자로 만들면 편리할 때가 많습니다. 특히 두 분류기를 비교할 때 그렇습니다. F1점수는 정밀도와 재현율의 조화평균(harmonic mean)입니다.

F1 = 2/(1/정밀도 + 1/재현율) = 2 * (정밀도 * 재현율)/(정밀도 + 재현율) = TP/ (TP + (FN+FP)/2)

F1 점수를 계싼하려면 f1_score()함수를 호출하면 됩니다.

정밀도와 재현율이 비슷한 분류기에스는 F1점수가 높습니다. 하지만 이게 항상 바람직한 것은 아닙니다. 상황에 따라 정밀도가 중요할 수도 있고 재현율이 중요할수도 있습니다. 예를 들어 어린아이에게 안전한 동영상을 걸러내는 분류기를 훈련시킨다고 가정해보겠습지다. 재현율은 높으나 정말 나쁜 동양상이 몇 개 노출되는 것보다 좋은 동영상이 많이 제외되더라도(낮은 재현율) 안전한 것들만 노출시키는 (높은 정밀도)분류기를 선호할 것입니다(이런 경우에는 분류기의 동양상 선택 결과를 확인하기 위해 사람이 참여하는 분석 파이프라인을 추가할지도 모릅니다). 다른 예로, 감시 카메라를 통해 좀도둑을 잡아내는 분류기를 훈련시킨다고 가정해보겠습니다. 분류기의 재현율이 99%라면 정확도가 30%만 되더라도 괜찮을지 모릅니다(아마도 경비원이 잘못된 호출을 종종 받게 되겠지만, 거의 모든 좀도둑을 잡을 것입니다).

안됐지만 이 둘을 모두 얻을 수는 없습니다. 정밀도를 올리면 재현율이 줄고 그 반대도 마찬가지입니다. 이를 정밀도/재현율 트레이드오프 라고 합니다.

3.3.4 정밀도/재현율 트레이드오프
SGDClassifier가 분류를 어떻게 결정하는지 살펴보며 이 트레이드오프를 이해해보겠습니다.
이 분류기는 결정함수(decision function)를 사용하여 각 샘플의 점수를 계산합니다. 이 점수가 임곗값보다 크면 샘플을 양성 클래스에 할당하고 그렇지 않으면 음성 클래스를 할당합니다.[그림 3-3]에 가장 낮은 점수부터 가장 높은 점수까지 몇 개의 숫자를 나열했습니다. 결정 임곗값이 가운데(두 개의 숫자 5사이)화살표라고 가정해보겠습니다. 임곗값 오른쪽에 4개의 진짜 양성(실제 숫자5)과 하나의 거짓 양성(실제 숫자6)이 있습니다. 그렇기 때문에 이 임곗값에서 정밀도는 80%(5개 중 4개)입니다. 하지만 실제 숫자 5는 6개고 분류기는 4개만 감지했으므로 재션율은 67%(6개중 4개)입니다. 이번 입곗값을 높이면(임곗값을 오른쪽 화살표로 옮기면) 거짓 양성(숫자 6)이 진짜 음성이 되어 정밀도가 높아집니다(이 경우에 100%가 됩니다). 하지만 진짜 양성 하나가 거짓 음성이 되었으므로 재현율이 50%로 줄어듭니다. 반대로 임곗값을 내리면 재현율이 높아지고정밀도가 줄어듭니다.

사이킷런에서 임곗값을 직접 지정할 수는 없지만 예측에 사용한 점수는 확인할 수 있습니다. 분류기의 predict()메서드 대신 decision_function()메서드를 호출하면 각 샘플의 점수를 얻을 수 있습니다. 이 점수를 기반으로 원하는 임곗값을 정해 예측을 만들수 있습니다.

SDGClassifier의 임곗값이 0이므로 위 코드는 predict()메서드와 같은 결과(즉, True)를 반환합니다.

이 결과는 임곗값을 높이면 재현율이 줄어든다는 것을 보여줍니다. 이미지가 실제로 숫자 5이고 임곗값이 0일때는 분류기가 이를 감지했지만, 임곗값을 200,000으로 높이면 이를 놓치게 됩니다.

그렇다면 적절한 임곗값을 어떻게 정할 수 있을까요? 이를 위해서는 먼저 cross_val_predict()함수를 사용해 훈련 세트에 있는 모든 샘플의 점수를 구해야 합니다. 하지만 이번에는 예측 결과가 아니라 결정 점수를 반환받도록 지정해야 합니다.

이 점수로 precision_recall_curve()함수를 사용하여 가능한 모든 임곗값에 대해 정밀도와 재현율을 계산할 수 있습니다.



2018년 8월 3일 금요일

2. 장고의 클래스 기반 뷰와 하이퍼링크 API 사용

2.1 모델 직렬화기를 사용해 중복 코드 제거하기
GameSerializer 클래스는  Game 모델에서 사용한 것과 같은 이름을 가진 여러 속성을 선언하고 타입 및 max_length 값과 같은 정보를 반복한다. GameSerializer 클래스는 rest_Framework.serializers.Serializer의 서브 클래스이며, 수동으로 적절한 타입으로 매핑한 속성을 선언하고 create와 update메서드를 오버라이드한다.

이제 rest_framework.serializers.ModelSerializer 클래스에서 상속받을 GameSerializer 클래스의 새 버전을 만들것이다. ModelSeializer 클래스는 기본 필드 집합과 기본 유효성 검사기 집합을 자동으로 채운다. 또한 이 클래스는 create와 update메서드에 대한 기본 구현을 제공한다.

참고 웹 프레임워크에 대한 경험이 있는 경우, Serializer 및 ModelSerializer 클래스는 Form 및 ModelForm 클래스와 비슷하다는 것을 알아차릴 것이다.

이제 gamesapi/games 폴더로 가서 serializers.py파일을 연다. 이 파일의 코드를 GameSerializer 클래스의 새 버젼을 선언하는 아래 코드로 바꾼다.

새 GameSerializer 클래스는 model과 fields의 두 속성을 선언하는 Meta 내부 클래스를 선언한다. model 속성은 serializer와 관련된 모델, 즉 Game 클래스를 지정한다. fields 속성은 관련 모델의 직렬화에 포함시키는 필드 이름을 문자열의 튜플로 지정한다.

여기서는 일반 동작으로도 충분하기 때문에 create 또는 update 메서드를 오버라이드할 필요가 없다. ModelSerializer 슈퍼 클래스는 두 메서드 모두에 대한 구현을 제공한다.
우리는 GameSerializer 크랠스에서 필요하지 않은 사용구 코드를 줄였다. 단지 튜플에 원하는 필드 잡합을 지정하기만 하면 됐다. 이제 Game 클래스에는 게임필드와 관련된 타입만 들어가 있다.

Ctrl+C를 눌러 장고의 개발 서버를 종료하고 다음 명령을 실행해 다시 시작하라.
python manage.py runserver

2.2 API 뷰를 작성하기 위한 래퍼 작업
games/view.py파일의 코드는 JSONResponse 클래스와 2개의 함수 기반 뷰를 선언했다. 이들 함수는 응답이 JSON 데이터를 반환해야 할 때 JSONResponse를 반환하고, 응답이 HTTP 상태 코드일 때 django.Http.Response.HttpResponse인스턴스를 반환했다.

HTTP 요청 헤더에 지정된 허용 콘텐트 타입에 관계 없이 뷰 함수들은 항상 응답 본문에 동일한 콘텐트를 제공한다. 다음 두 명령을 실행해 Accept 요청 헤더에 대해 다른 값(text/html와 application/json)을 가진 모든 게임을 얻어보라

http :8000/games/ Accept:text/html
http :8000/games/ Accept:application/json

다음은 이와 동일한 curl명령이다.
curl -H 'Accept: text/html' -iX GET :8000/games/
curl -H 'Accept: application/json' -iX GET :80800/games/

위의 명령은 GET http://localhost:8000/games/라는 HTTP 요청을 작성해 보낸다.
첫 번째 명령은 Accept요청 헤더에 대해 text/html 값을 정의한다. 두번째 명령은 Accept 요청 헤더에 대해 application/json 값을 정의한다.

두 명령 모두 동일한 결과를 생성하므로이들 뷰 함수는 HTTP요청에 있어서 Accept 요청 헤더에 지정된 값을 고려하지 않는다. 두 명령의 헤더 응답에는 다음 행이 포함될 것이다.

Content-Type: application/json

두번째 요청은 text/html 만 허용하지만 응답에는 JSON 본문, 즉 application/json 콘텐트가 포함되게 지정됐다. 따라서 레스트풀 API 의 첫번째 버젼은 JSON과 다른 콘텐트를 렌더링할 준비가 돼 있지 않다. 우리는 API에서 다른 내용을 렌더링할 수 있게 약간 변경할 것이다.

레스트풀 API 에서 자원 또는 자원콜렉션이 지원하는 메서드를 잘 모르겠다면 OPTIONS HTTP 동사와 함께 그 자원 또는 자원 콜렉션의 URL이 들어간 HTTP요청을 작성해보내면 된다. 레스트풀 API에 자원 또는 자원 콜렉션에 대한 OPTION HTTP동사가 구현돼 있다면, 응답에 지원하는 메서드 또는 HTTP동사를 쉼표로 구분한 리스트를 Allow헤더의 값으로 나타내 준다. 또한 응답 헤더에는 요청에서 파싱할 수 있는 컨텐트 형식 및 응답에서 렌더링할 수 있는 콘텐트 형식 등 지원되는 다른 옵션에 대한 추가 정보도 나타난다.

예를 들어, 게임 컬렉션이 지원하는 HTTP 동사를 알고 싶다면 다음 명령을 실행하면 된다.

http OPTION :8000/games/

다은은 이와 동일하 curl 명령이다.
curl -ix OPTIONS :8000/games/

위의 명령은 OPTIONS http://localhost:8000/games/라는 HTTP 요청을 작성해 보낸다. 이 요청은 views.game_list 함수, 즉 games/views.py 파일 내에 선언된 game_list함수를 찾아 실행한다. 이 함수는 reqeust.method가 'GET' 또는 'POST'와 같은때만 코드를 실행한다. 여기서는 request.mthod가 'OPTIONS'와 같으므로 이 함수는 코드를 실행하지 않으며, 응답을 반환하지 않는다. 즉, HttpResponse 인스턴스를 반환하지 않는 것이다. 결과적으로 장고의 개발 서버 콘솔 출력으로 다음의 Internal Server Error가 나열될 것이다.

아래 행에는 장고에 디버그 모드가 활성화돼 있기 때문에 오류에 대한 자세한 정보가 들어간 세부적인 HTML 문서의 출력용 헤더가 나타나 있다. 500 Internal Server Error 상태 코드를 받은것이다.

분명히 우리는 보다 일관된 API를 제공하고자 하며, OPTIONS 동사가 게임 자원또는 게임 켈렉션에 대한 요청을 받을 때 정확한 응답을 제공하길 원한다.

게임 자원에 대해 OPTIONS 동사가 들어간 HTTP 요청을 작성해 보내면, views.game_detail 함수는 request.metohd가 'GET', 'PUT', 'DELETE'과 같을  때만 코드를 실행하기 때문에 동일한 오류를 나타내며, 서보 비슷한 응답을 보게 될 것이다.

아래 명령은 id또는 기본 키가 3인 게임 자원에 대해 제공되는 옵션을 보려고 할 때 설명이 들어간 오류를 생성할 것이다. 여기서 3은 여러분이 구성에 존재하는 게임의 기본 키 값으로 대체하는 것을 잊지말라.

http OPTIONS :8000/games/3/

다음은 이와 동일한 curl 명령이다.

curl -iX OPTIONS :8000/games/3/

레스트풀 API에 대해 분석한 이 문제를 해결하려면 games/view.py파일을 약간 변경해야 한다. 함수 기반 뷰에 대해서는 rest_framework.decorators에 선언된 @api_view데터레이터를 사용할 것이다. 이 테커레이터는 우리 함수가 처리할 수 있는 HTTP 동사를 지정할 수 있게 해준다. 뷰 함수로 처리해야 하는 요청에 지정 문자열ㄹ 리스트에 포함되지 않은 HTTP 동사가 @api_view 데커레이터의 http_method_names 인자로 들어가면, 기본 행위는 405 Method Not Allowed 상태 코드를 반환한다. 이렇게 되면 함수 뷰 내에서 고려되지 않은 HTTP 동사를 받을 때마다 지원되지 않는 HTTP 동사나 메서드에 대한 응답을 데커레이터가 처리하기 때문에 예기치 않는 오류가 발생하지 않을 것이다.

게다가 지원되는 HTTP 동사가 있는 문자열 리스트를 지정하면 데커레이터는 지원되는 메서드와 파서 및 렌더링 기능을 사용해 OPTIONS HTTP 동사에 대한 응답을 자동으로 만든다. API의 실제 버전은 JSON을 출려으로 렌더링할 수 있다. 데커레이터를 사용하면 장고가 뷰 함수를 호출할 때 항상 rest_framwrork.request.Request 클래스의 인스턴스를 request인자로 받을 수 있게 한다. 또한 데커레이터는 함수 뷰가 파싱 문제를 일으킬 수 있는 request.data 특성에 접근할 때 ParserError예외도 처리한다.

내부적으로 @api_view 데터레이터는 함수기반 뷰를 rest_Framework.views.  APIView 클래스의 서브크랠스로 변환하는 래퍼다. 이 클래스는 장고 레스트프레임워크의 모든 뷰에 대한 베이스 클래스다. 예상하다시피 클래스 기반 뷰로 작업하고 싶다면 이 클래스를 상속하는 클ㄹ래스를 만들면 되며, 데커레이터를 사용하는 함수 기반 뷰에 대해 분석한 것과 같은 이점을 얻을 수 있다. 앞으로 나오는 예제에서는 클래스 기반 뷰들로 작업할 것이다.

2.3 기본 파싱 및 렌더링 옵션 사용과 JSON으로 이동
APIView 클래스는 gamesapi/setting.py 파일에서 적절한 값을 지정하거나 서브 클래스의 클래스 속성을 오버라이드함으로써오버라이드 가능한각 뷰의 기본 설정을 지정한다. 앞서 설명한 것처럼 내부적으로 APIView클래스를 사용하면 데터레이터가 이러한 기본 설정을 적용한다. 따라서 데커레이터를 사용할 때마다 기본 파서 클래스와 기본 렌더러 클래스가 함수 뷰와 연결될 것이다.

기본적으로 DEFAULT_PARSER_CLASSES의 값은 다음과 같은 크래스의 튜플이다.

(
     'rest_framework.parsers.JSONParser',
     'rest_framework.parsers.FormParser',
     'rest_framework.parsers.MultiPartParser'
)

테커리이터를 사용해 reqeust.data 속성에 접근할 때 API는 적절한 파서를 통해 다음 콘텐트 타입 중 하나를 처리하게 된다.
- application/json
- application/x-www.form-urlkencoded
- application/form-data

함수에서 rest_framework.parsers.JSONParser 클래스의 사용을 제거해야 구성된 모든 파서로 작업하고 JSON으로만 작동하는 파서로 작업하는 것을 중단할 수 있다.

함수에서 reuest.data속성에 접근하면, 장고 레스트 프레임워크는 들어오는 요청의 Content-Type 헤더 값을 검사하고 요청 내용을 파싱할 적절한 파서를 결정한다. 이전에 설명한 기본값을 사용하면 장고 레스트 프레임워크는 위에 나열한 콘텐트 타입을 파싱할 수 있다. 하지만 요청에서 Content-Type 헤더에 적절한 값을 지정하는 것이 아주 중요하다.

game_list 함수는 request.mthod 가 'POST'일 때 다음 두 행을 실행한다.

game_data = JSONParser().parser(request)
game_serializer = GameSerizlizer(data=game_data)

JSONParser를 사용하는 첫 번째 행을 제거하고, GameSerializer의 데이터 인자로 request.data를 전달한다. 위의 행을 다음 행으로 대체한다.

game_serializer = GameSerializer(data=request.data)

game_detail 함수는 request.method가 'PUT'일때 다음 두 행을 싱행한다.

game_data = JSONParser().parser(request)
game_serilizer = GameSerializer(game, data=game_data)

game_list 함수에서 코드에 대해 이전 과 동일하게 편집한다. JSONParser를 사용하는 첫 번째 행을 제거하고, GameSerializer(game, data= request.data)

기본적으로 DEFAULT_RENDERER_CLASSES의 값은 다음과 같은 클래스의 튜플이다.
(
    'rest_framework.renderers.JSONRenderer',
    'rest_framework.renderers.BrowsableAPIRenderer',
)

데커레이터를 사용해 rest_framework.response.Response로 작업할 때, API는 적절한 렌더러를 통해 요청에서 다음 콘텐트 타입을 렌더링 할 수 있다.
- application/json
- text/html

기본적으로 DEFAULT_CONTENT_NEGOTIATION_CLASS의 값은 rest_framework.negotiation.DefaultcontentJNegotiation클래스다. 데커레이터를 사용하면 API는 이러한 콘텐트 협상 클래스를 사용해 들어오는 요청에 따라 응답에 적합한 렌더러를 선택한다. 이런 식으로 요청에서 text/html을 허용하게 지정하면 콘텐트 협상 클래스가 rest_framework.renderers.BrowsableAPIRenderer를 선택해 응답을 렌더일하고 applilcation/json대신 text/html을 생성한다.

함수 속에서는 JSONResponse와 HttpResponse 클래스를 rest_framework.response.
Response 클래스로 데체해야 한다. Response클래스는 이전에 설명한 콘텐트 협상 기능을 사용하고 수신된 데이터를 적절한 콘텐트 타입으로 렌더링하고 나서 클라이언트에게 반환하다.

이제 gameapi/games 폴더로 가서 views.py파일을 연다. 이 파일 코드 중에서 다음과 같이 JSONResponse클래스를 제거하고 함수에 @api_view데커레이터를 붙이며 rest_Framework.response.Response클래스를 사용한다.

응답 헤더에는 Allow 키가 보이는데 이곳에 GET, POST, OPTIONS, PUT 과 같이 자원에서 지원하는 쉼표 구분의 HTTP동사 리스트가 나타나 있다. 응답 본문은 자원 파싱 콘텐트 타입과 렌더링 콘텐트 타입을 지정하며, 이 OPTIONS요청에 수신된 것과 동일한 콘텐트가 자원 모음, 즉 게임 컬렉션에 적용된다.

1장에서는 POST와 PUT명령을 작성해 보낼 때 curl에서 -d옵션 뒤에 지정한 데이터를 기본적인 application/x-www-form-urlencoded대신 application/json로 보내기 원해 -H "Content-Type: application/json"옵션에 지정한 application/x-www-formurlencoded와 multipart/form-data 데이터도 파싱할 수 있다. 따라서 우리는 API로 변경된 사항이 들어간 데이터를 application/xwww-form-urlencoded로 보내는 POST 명령을 작성해 전송 할 수 있다.

새 게임을 생성하는 HTTP요청을 작성해 보내보자. 여기서는 다음과 같이 HTTPie에 대해 -f옵션을 사용하고, 양식 필드로 명령 행에서 데이터 항목을 직렬화하여 Content-Type 헤더 키를 application/x-www-form-urlencoded값으로 설정할 것이다.

http -f POST :8000/games/ name='Toy Story 4' game_category='3D RPG' played=false release_data='2016-05-18T03:02:00.776594Z'

다음은 이와 동일한 curl 명령이다. -H 옵션을 사용하지 않으면 curl이 기본적인 application/x-www-form-urlencoded으로 데이터를 전송한다.

curl -ix POST -d '{"name":"Toy Story 4", "game_category":"3D RPG", "played":"false", "release_date":"2016-05-18T03:02:00.776594Z"}' :8000/games/

이명령은 Content-Type헤더 키가 application/x-www-form-urlencoded 값으로 지정되고, 데이터는 다음과 같이 설정된 POST http://localhost:8000/games/라는 HTTP요청을 자성해 보낸다.

이 요청에는 /games/가 들어가므로 '^games/$'가 적용돼 views.game_list 함수, 즉 games/views.py파일 내에서 선언하고 업데이트한 game_detail 함수를 실행한다. 요청에 대한 HTTP동사가 'POST'이기 때문에 request.method 속성은 'POST'와 같으므로 이 함수는 request.data를 data 인자로 해서 GameSerializer 인스턴스를 생성하는 코드를 실행한다. rest_framework.parsers.FromParser클래스는 요청에서 받은 데이터를 파싱해서 새 Game을 생성하고 데이터가 유효하면 새 Game을 저장한다. 새 Game이 데이터베이스에 성공적으로 저장되면 함수는 HTTP 201 Created 상태 코드와 함케 응답 본문에 JSON으로 직렬화된 최근 저장의 Game을 반환한다. 다음 행은 HTTP요청에 대한 응답의 예를 보여주는데, 여기에는 JSON응답 속의 새 Game객체가 들어가 있다.

앞서 코드를 변경한 후에 지원되니 않는 HTTP동사로 HTTP요청을 작성하고 보낼때 어떤 일이 발생되는지 알아보기 위해 다음 명령을 실행해 보자.

http PUT :8000/games/

이 명령은 PUT http://localhost:8000/games/라는 HTTP 요청을 작성해 보낸다. 이 요청은 views.game_list 함수, 즉 games/views.py파일 내에 선언한 game_list 함수를 찾아 실행하려 할 것이다. 이 함수에 추가한 @api_view 테커레이터는 허용된 HTTP동사가 있는 문자열 리스트에 'PUT'을 포함시키지 않으므로 기본 행위로 405 Method Not Allowed 상태 코드를 반환한다. 다음 행은 위 요엋의 응답이 들어간 출력을 보여준다. 여기 JSON 콘텐트에는 detail키로 PUT 메서드가 허용됮 않음을 나타내는 문자열 값이 들어 있다.

2.4 브라우저블 API
우리는 최근 편집을 통해 우리의 API가 장고 레스트 프레임워크에서 구성된 기본 콘텐트 렌더러를 사용할 수 있게 만들었으므로 API가 text/html 콘텐트를 렌더링할 수 있다. 우리는 장고 레스트 프레임워크에 포함된 기능인 브라우저블 API를 활용할 수 있는데, 이 API를 사요하면 요청 헤더에서 Content-type키의 값으로 text/html을 지정할 때마다 각 자원에 대한 친숙한 HTML출력을 생성해 준다.

웹 브라우저에서 API 자원의 URL을 입력할 때마다 웹 브라우저에서 HTML응답이 필요하므로 장고 레스트 프레임워크는 Bootstrap을 사용해 HTML응답을 제공한다. 이 응답에는 JSON의 자원 콘텐트를 표시하는 영역, 다양한 요청을 수행하는 버튼, 자원에게 데이터를 제출하는 양식이 들어갈 것이다. 장고 레스트프레임워크의 모든 기능과 마찬가지로 브라우저블 API를 생성하는데 사용되는 템플릿과 테마를 사용자 정의할 수 있다.

웹 브라우저를 열고 http://localhost:8000/games/를 입력해 보라, 브라우저블 API는 /games/에 대한 GET요청을 자성해 보내고 그 실행 결과, 즉 헤더 및 JSON 게임리스트를 나타낼 것이다. 다음 스크린 샷은 웹브라우저에서 URL을 입력한 후에 자원 디스크립션과 함께 렌더링된 웹 페이지를 보여준다.

브라우저를 API는 자원에 대해 허용된 메서드 정보를 사용해서 이들 메서드를 실행하는 데 필요한 버튼을 제공한다. 브라우저를 API는 자원 디스크립션의 오른쪽에 OPTIONS 버튼과 GET 드롭다운 버튼을 나타낸다. OPTIONS 버튼을 사용해 /games/에 대한, 즉 현재 자원에 대한 OPTIONS요청을 할 수 있다. GET 드롭다운 버튼을 사용하면 /games/에 GET 요청도 할 수 있다. 아래쪽 화살표를 클릭하거나 탭하면 json옵션을 선택할 수 있고, 이 선택으로 브라우저를 API는 헤더가 없는 /games/에 대한 GET 요청의 원시 JSON 결과를 나타낼 것이다.

브라우저블 API는 렌더링된 웹 페이지으 ㅣ맨 아래에 /games/에 대한 POST 요청을 생성할 수 있는 컨트롤을 제공한다. Media type 드롭다운 메뉴에서 다음과 같이 이 API에 구성된 지원 파서를 서택할 수 있다.
- application/json
- application/x-www-form-urlencoded
- multipart/form-data

Content 텍스트 박스에는 Media type 드롭다운에 지정된 형식의 POST 요청으로 보낼 데이터를 지정할 수 있다. Media type 드롭다운에서 application/json을 선택하고 Content 텍스트 박스에 다음 JSON콘텐트를 입력해 보자.

POST를 클릭하거나 탭하라. 브라우저블 API는 앞서 지정한 데이터와 함께 /games/에 대한 POST 요청을  JSON으로 작성해 전송하므로 웹 브라우저에 그 호출 결과가 나타날 것이다.

2.5 복잡한 PostgreSQL 데이터베이스와 대화하는 레스트 풀 API 디자인
지금까지 레스트풀 API는 단 하나의 데이터베이스 테이블에서 CRUD 작업을 수행했다. 이제 장고 레스트 프레임워크로 보다 복잡한 레스트풀 API를 작성해 복잡한 데이터베이스 모델과 대화하게 해야 하는데, 이런 모델을 이용하면 게임 카테고리에 따라 그룹화한 게임에 대해 플레이어 점수를 동록할 수 있다. 이전 레스트풀 API에서는 문자열 필드를 사용해 게임의 카테고리를 지정했다. 여기서는 특정 게임 카테고리에 속한 모든 게임을 쉽게 얻을 수 있게 게임과 게임 카테고리 간의 관꼐를 맺게 할 것이다.

- 게임 카테고리(GameCategory 모델)
- 게임(Game 모델)
- 플레이어(Player 모델)
- 플레이어 점수(PlayerScore 모델)

게임카테고리 에는 이르만 있으면 되며 게임에 대해서는 다음 데이터가 필요하다.

- 게임카테고리 대한 왜리키
- 이름
- 출시일
- 플레이어가 적어도 한번 게임을 했는지 여부를 나타내는 bool값
- 게임을 데이터베이스에 삽입한 날짜와 시간이 찍힌 타임 스탬프

플렝이어(Player)에 대해서는 다음 데이터가 필요하다.

- 성별 값
- 이름
- 플레이어를 데이터베이스에 삽입한 날짜와 시간이 찍힌 타임스탬프

플레이어가 얻은 점수(PlayerScore)에 대해서는 다음 데이터가 필요하다.

- 플레이어에 대한 왜래 키(Player)
- 게임에 대한 외래키(Game)
- 점수 값
- 플레이어가 이 점수 값을 달성한 날짜

우리가 관련 자원으로 잦ㄱ업할 때 장고 레스트 프레임워크가 제공하는 다양한 옵션을 분석하기 위해 모든 자원과 이들의 관계를 활요할 것이ㅏㄷ. 관련 자원을 표시하기 우해 동일한 구성을 사용하는 API를 작성하는 대신 개발 중인 api의특징 요구하상에 따라 가장 적합한 옵션을 선택할 수 있는 다양한 구성을 사용할 것이다.

2.6 각 HTTP 메서드가 수행하는 작업 이해
다음 표는 새 API가 지원해야 하는 메서드의 HTTP동사, 범위, 의미를 보여준다. 각 메서드는 HTTP 동사와 범위로 구성되며, 모든 메서드는 모든 자원과 컬렉션에 대해 잘 정의된 의미를 갖는다.

______________________________________________________________________

API 에서 기존 자원의 필드를 하나씩 업데이트 할 수 있게 만들어야 하므로 PATCH 메서드를 구현해야 한다. PUT 메서드는 전체 자원을 대체하기 위한 것이고, PATCH메서드는 기존 자원에 델타를 적용하기 위한 메서드다. 더욱이 레스트풀 API는 모든 자원과 자원 컬렉션에 대해 OPTIONS 메서드도 지원해야 한다.

이전 API에서도 볼수 있듯이 우리는 가장 적절한 ORM을선택하고 구성하는 데 시간을 허비하지 않을 것이다. 가능한 빨리 레스트 풀 api를 만무리해 상호작용하기 만 하면 된다. 장고 레스트 프레임워크에 포함된 모든 기능과 재사용 가능한 요소를 사용하면 api를 쉽게 만들 수 있다. 우리는 PostgreSQL데이터베이스로 작업할 것이다. 하지만 PostgreSQL을 설치하는 데 시간을 허비하지 않으려면 장고 레스트 프레임워크 ORM 구성을 변경하지 말고 그냥 기본 SQLite 데이터베이스로 작업을 계속하면 된다.

앞의 표에서 많은 메서드와 범위가 있다. 다음 리스트는 이 표에 언급된 각 범위에 대한 URL을 열거한 것이다. 여기서 {id}는 숫자 id 또는 자원의 기본키로 대체해야 한다.

- Collection of game categories: /game-categories/
- Game category: /gaeme-category/{id}/
- Collection fo games : /games/
- Game: /game/{id}
- Collection of players: /players/
- Player: /palyer/{id}/
- Collection of scores: /palyer-scores/
- Score: /player-score/{id}

2.7 모델과의 관계 선언

이제 게임 카테고리, 게임, 플레이어, 점수, 그 관계를 표현하고 유지하기 위해 사용할 모델을 만들것이다. /games/models.py 파일을 열고 그 내용을 아래 코드로 바꾼다. 각 모델과 관련된 필드를 선언하는 행은 코드 리스트에서 굵게 나타냈다.

이 코드는 django.db.models.Model 클래스의 서브 클래스인 다음 4개 모델, 즉 4개의 클래스를 선언한다.

- GameCategory
- Game
- Player
- PlayerScore

장고는 각 모델과 관련된 데이터베이스 테이블을 생성할 때 id라는 자동 증가 정수 기본 키 열을 자동으로 추가한다. 이 코드에서는 많은 속성에 대해 필드 타입, 최대 길이, 기본값을 지정했다. 각 클래스는 ordering 속성을 선언하는 Meta 내부 클래스를 선언한다. PlayerScore 클래스 내에서 선언한 Meta 내부 클래스는 '-score'를 oredering 튜플의 값으로 지정한다. 대시는 필드 이름의 접두어로 사용되며, 기본적인 오름차순이 아닌 내림차순으로 score를 정렬한다.

GameCategory, Game, Player 클래스에서는 각 모델의 이름 또는 타이틀을 제공하는 name 속성의 내용을 반환하는 __str__메서드를 선언한다. 따라서 장고는 해당 모델에 대해 사람이 읽을 수 있는 표현을 제공해야 할 때마다 이 메서드를 호출한다.

Game 모델은 다음 행으로 game_category필드를 선언한다.
위의 행은 django.db.models.ForeingKey클래스를 사용해 GameCategory모델에 다대일 관계를 제공한다. related_name 인자에 지정한 'games' 값은 GameCategory모델에서 Game모델로의 역방향 관계를 만든다. 이 값은 관련 GameCategory 객체에서 Game 객체로의 관계에 사용할 이름을 나타낸다. 이제 특정 게임 카테고리에 속한 모든 게임에 접근할 ㅜㅅ 있다. 게임 카테고리를 삭제할 때마다 이 카테고리에 속한 모든 게임이 삭제돼야 하므로 on_delete인자에 대해 models.CASCADE값을 지정했다.

PlayerScore 모델은 다음 행으로 player필드를 선언한다.

위의 행은 django.db.models.ForeignKeuy클래스를 사용해 Player 모델에 다대일 관계를 제공한다. related_name인자에 지정한 'scores'값은 Player 객체에서 PlayerScore객체로의 고나계에 사용할 이름을 나타낸다. 이제 특정 플렝어가 기록한 모든 점수에 접근할 수 있다. 플레이어를 삭제할 때마다 이 플레이어가 얻은 모든 점수를 삭제해야하므로 on_delete 인자에 대해 models.CASCADE값을 지정했다.

PlayerScore모델은 다음 행으로 game필드를 선언한다.

위의 행에서 django.db.models.ForeignKey 클래스를 사용해 Game모델에 다대일 관계를 제공한다. 여기서는 역방향 관계가 필욯없으므로 이 관계를 만들지 않는다. 따라서 related_name 인자로 값을 지정하지 않았다. 게임을 삭제할 때마다 이 게임의 모든 증록 점수가 삭제돼야 하므로 on_delete인자에 대해 models.CASCADE값을 지정했다.

이예제로 작업할 새 가상 환경을 만들었거나 책의 샘플 코드를 다운로드 한 경우, 기본 데이터베이스를 삭제할 필요는 없다. 하지만 이 API예제 코드를 변경하는 경우에는 gamesapi/db.sqlite3 파일과 games/migrations 폴더를 삭제해야 한다.

그리고 나서 최근에 코딩한 새 모델의 초기 마이그레이션을 만들어야 한다. 아래의 파이썬 스크립트를 실행하면 데이터베이스를 처음으로 동기화할 수 있다. 위의 예제 API에서 배웠듯이 기본적으로 장고는 SQLite 데이터베이스를 사용하낟. 이 예젱서 PostgreSQL데이터베이스로 작업할 것이다. 하지만 SQLite를 사용하고 싶다면 PostgreSQL과 관련된 단계와 장고에서의 구성을 생략하고 migratijons generation 명령으로 건너뛰면 된다.

여러분의 컴퓨터 또는 개발 서버에서 PostgreSQL 데이터베이스르 ㄹ사용한 적이 없다면 PostgreSQL 데이터베이스를 다운로드하고 설치해야 한다. 이 데이터베이스 관리 시스템은 웹페이지에서 다운로드해 설치할 수 있다.

PostgreSQL의 bin 폴더가 PATH 환경 변수에 포함돼 있는지확인해야 한다. 현재 터미널이나 명령 프롬프트에서 psql 명령 행 유틸리티를 실행할 수 있어야 하기 때문이다. 이 폴더가 PATH에 포함되지 않으면, psycopg2패키지를 설치하려고 할 때 pg_config파일을 찾을 수 없다는 오류가 발생한다. 또한 이후 단계에서 사용할 PostgreSQL명령 행 도구에 대해서도 전체 경로를 사용해야 한다.

PostgreSQL 명령행 도구를 사용해 games라는 새 데이터베이스를 생성할 것이다. 이 이름을 가진 PostgreSQL 데이터베이스가 이미 있는 경우에는 모든 명령과 구성에 있어서 다른 이름을 사용해야 한다. PostgreSQL GUI 도구로 동일한 작업을 수행할 수 있다. 리눅스에서 개발할 경우, postgres사용자로 명령을 실행해야 한다. 맥 os 또는 원도우에서는 다음 명령을 실행해 games라는 새 데이터베이스를 생성한다. 이 명령은 출력을 나타내지 않을 것이다.

createdb games

리눅스에서는 다음 명령을 실행해 postgres사용자를 사용한다.

sudo -u postgres createdb games

이제 psql 명령행 도구를 사용해 장고에서 사용할 특정 사용자를 생성하고 필요한 역할을할당하는 SQL문을 실행할 것이다. 맥OS 또는 원도우에서 psql을 시작하려면 다음 명령을 실행하라.
psql

맥OS에서 PostgreSQL을 설치한 방식에 따라 위의 명령이 작동하지 않는 경우, 다음 명령을 실행해 postgres로 psql을 시작하면 된다.

sudo -u postgres psql

리눅스에서 postgres를 사용하려면 다음 명령을 실행하라.

sudo -u psql

그런 다음, 아래 SQL문을 실행하고 마지막으로 \q를 입력해 psql 명령 행 도구를 종료하라. user_name을 새 데이터베이스에서 사용할 사용자 이름으로 바꾸고 password를 여러분이 원하는 암호로 지정하라, 우리는 장고 설정에서 지정한 사용자 이름과 암소르 ㄹ사용할 것이다. 만약 PostgreSQL의 특정 사용자로 이미 작업중이며 그 사용자의 데이터베이스에 대한 권한을 이미 부여한 경우라면 아래 단계를 수행할 필요가 없다.

CREATE ROLE user_name WITH LOGIN PASSWORD 'password';
GRANT ALL PRIVILEGES ON DATABASE games TO user_name;
ALTER USER user_name CREATEDB;
\q

기본 SQLite 데이터베이스 엔진과 데이터베이스 파일 이름은 gamesapi/settings.py파이썬 파일에 지정돼 있다. 이 예제에서 SQLite대신 PostgreSQL을 사용하기로 했다면 DATABASES 딕셔너리 선언을 아래 행으로 바꾸어야 한다. 아래 중첩된 딕셔너리는 default라는 데이터베이스를 django.db.backends.postgresql데이터베이스 엔진, 원하는 데이터베이스 이름, 그 외 설정으로 맞춘다. 여기서는 games라는 데이터베이스르 생성한다. 'NAME' 키의 값에 원하는 데이터베이스 이름을 지정하고 PostgreSQL구성에 따라 사용자, 비밀번호, 호스트 및 포트를 구성하라. 이전 단계를 수행했다면, 다음과 같이 이들단계에서 지정한 설정을 사용한다.

DATABASES = {
     'default':{
        'ENGINE': 'django.db.backends.postgresql',
        # games 를 원하는 데이터베이스 이름으로 바꾼다.
        'NAME':'games',
        # username을 원하는 사용자 이름으로 바꾼다.
         'USER': 'user_name',
        # password 를 원하는 암호로 바꾼다.
        'PASSWORD': 'password',
        # 127.0.0.1을 PostgreSQL로 바꾼다.
         'HOST': '127.0.0.1',
         # 기본 포트를 사용하지 않을 경우에는
         #  5432을 PostgreSQL 설정 포트로 바꾼다.
         'PORT': '5432'
     }
 }

PostgreSQL을 사용하기로 했다면, 위의 변경사항을 적용한 후에 Paycopg2 패키지(psycopg2)를 설치해야 한다. 이 패키지는 Python-PostgreSQL 데이터 어탭터이며 장고는 이 패키지를 사용해 PostgreSQL데이터베이스와 대화한다.

맥 OS 설치에서 PosgreSQL의 bin 폴더가 PATH 환경 변수에 포함돼 있는지 확인해야 한다. 예를 들어, bin 포더의 경로가 /Applications/Postgres.app/Contents/Versions/Latest/bin인 경우에 이 폴더를 PATH환경 변수에 추가하려면 다음 명령을 실행한다.

export PATH=$PATH:/Applications/Postgres.app/Contents/Versions/latest/bin

PostgresSQL 의 bin 폴더가 PATH환경 변수에 포함돼 있는지 확인한 후에는 다므명령을 실행해 이 패키지를 설치해야 한다.

pip install psycopg2
다음과 같이 출력의 마지막 행은 psycopg2 패키지가 성고적으로 설치됐음을 나타낸다.

이제, 다음 파이썬 스크립트를 실행에 데이터베이스르 처음으로 동기화할 수 있는 마이그레이션을 생성한다.

python maange.py makemigrations games

다음 행은 위의 명령을 실행한 후에 생성된 출력을 보여준다.

위의 코드는 많은 migrations.CreateModel의 작업 리스트를 정의하는 Migration 이라는 django.db.migrations.Migration 클래스의서브 클래스를 정의 한다. 각 migrations.CreateModel은 각 관련 모델에 대한 테이블을 생성한다. 장고는 각 모델에 대해 id 필드를 자동으로 추가했다. operations는 리스트에 나타난 순서대로 실행된다. 이 크도는 GameCategory에 대한 외래 키가 있는 Game에 Game, GameCategory, Player, PlayerScore, 그리고 마지막으로 game_category 필드를 추가하는데 그 이유는 GameCategory모델에서 앞서 Game 모델을 생성했기 때문이다. 이 코드는 모델을 만들때 PlayerScore에 대한 외래키를 만든다.

이제 다음 파이썬 스크립트를 실행해 생성된 모든 마이그레이션을 적용한다.

python manage.py migrate

다음 행이 이 명령을 실행한 후 생성된 출력을 보여준다.

윙의 명령을 실행한 후에는 PostgreSQL 명령 행 또는 장고가 생성한 데이블을 점검하기 위해 PostreSQL 데이터베이스의 내용을 쉽게 점검 가능한 다른 애플리케이션을 사용할 수도 있다. SQLite로 작업할 경우, 1장 '장고를 이용한 레스트 풀API 개발'테이블을 점검하는 방법을 이미 배웠다.

다음 명령을 실행해 생성된 테이블을 나열해보라

psql --username=user_name --dbname-games --command="\dt"

다음 행은 생성된 모든 테이블 이름이 나타난 출력을 보여준다.
이전 예에서 살펴봤듯이 장고는 games 애플리케이션과 관련된 다음 4개의 테이블이름에 games_접두어를 사용한다. 장고의통합 ORM은 우리 모델에 포함된 정보를 기반으로 이러한 테이블과 외래 키를 생성했다.

- games_game: Game 모델 유지
- games_gamecategory: GameCategory 모델 유지
- games_player: Player 모델 유지
- games_playerscore:PlayerScore 모델 유지

다음 명령은 HTTP 요청을 작성하고 레스트풀 API에 보내어 4개의 테이블에 대해 CRUD작업을 한 후 그 테이블 내용을 확인할 수 있게 한다. 이 명령은 사용자가 명령을 실행하는 곳과 같은 컴퓨터에서 PostgreSQL을 실행 중이라고 가정한 것이다.

PostgresSQL 명령 행 유틸리티로 작업하는 대신, GUI 도구를 사용해 PostgreSQL 데이티베이스의 내용을 확인할 수 있다. 또한 선호하는 IDE에 포함된 데이터베이스 도구를 사용해 PostfreSQL 데이터베이스의 내용을 확인할 수도 있다.

장고는 나중에 사용할 웹프레임워크와 인증 기능을 지원하는  데 필요한 추가 테이블을 생성한다.

2.8 관계 및 하이퍼링크를 사용해 직렬화 및 역직렬화 관리하기
우리는 새 레스트풀 웹 API는 GameCategory, Game, Player, PlayerScore 인스턴스를 JSON표현으로 직렬화 및 역직렬화할 수 있어야 한다. 여기서 JSON에 대한 직렬화 및 역직렬화를 관리하기 위해 직렬화 클래스를 작성할 때 여러 모델 간의 관계에 특히 주의해야 한다.

이전 API의 마지막 버전에서는 직렬화를 보다 쉽게 생성하고 상용구 코드를 줄이기 위해 rest_framework.serializers.ModelSerializer클래스의 서브 클래스를 만들었다. 이 경우에는 ModelSerializer에서 상속받은 클래스를 선언하지만 나머지 클래스는 rest_Framework.serializers.HyperlinkModelSerializer 클래스에서 상속받는다.

HyperlinkedModelSerializer는 기본 키 관계 대신 하ㅣ퍼링크 관계를 사용하는 ModelSerializer 형식이므로 기본 키 값이 아닌 하이퍼링크를 사용해 다른 모델 인스턴스에 대한 관계를 나타낸다. 게다가 HyperlinkModelSerializer는 자원의 URL을 값으로 사용해 url이라는 필드를 생성했다. ModelSerializer의 경우에서 보듯이 HyperlinkedmodelSerializer 클래스는 create및 update메서드의 기본 구현을 제공한다.

이제 gamesapi/games 폴더로 가서 serializers.py파일을 연다. 이 파일의 코드를 필요 임포트 및 GameCategorySerializer 클래스를 선언하는 아래 코드로 바꾼다. 나중에 이 파일에 더 많은 클래스를 추가할 것이다. 이 샘플 코드는 restful_python_chapter_02_03 폴더에 들어 있다.

GameCategorySerializer 클래스는 HyperlinkedModelSerializer 클래스의 서브 클래스다. 일대다 관계이면서 읽기 전용이므로 GameCategorySerializer 클래스 many와 read_only가 True로 된 serializers.HyperlinkedRelatedField 인스턴스로 games속성을 선언한다. game_category 필드를 models.ForeignKey 인스턴스로 만들때 related_name 문자열 값으로 지정한 games 이름을 Game 모델에서 사용한다. 이렇게 하면 games 필드는 케임 카테고리에 속한 각 게임에 대한 하이퍼링크 배열을 제공할 것디아. view_name값은 'game-detail'인데, 그 이유는 사용자가 하이퍼링크를 클릭하거나 탭할 때 브라우저블 API가 그 하이퍼링크를 렌더링해 게임 세부사항 뷰를 사용할 수 있게 해야 하기 때문이다.

GameCategorySerializer 클래스는 model과 fields의 두 속성을 선언하는 Meta 내부 클래스를 선언한다. model 속성의 직렬화기와 관련된 모델, 즉 GameCategory클래스를 지정한다. fields속성은 관련 모델의 직렬화에 포함시키려는 필드 이름을 나타내는 값의 튜플을 지정한다. 기본 키와 URL을 포함하고자 하므로 튜플의 멤버로 'pk'와 'url'을 모두 지정하는 코드가 필요하다. 여기서는 일반 동작으로도 충분하므로 create또는 update 메서드를 오버라이드할 필요가 없다. HyperlinkedModelSerializer 슈퍼클래스는 두 메서드 모두에 대한 구현을 제공한다.

이제 serializer.py 파일에 아래 코드를 추가해 GameSerializer 클래스를 선언하라. 이샘플코드는 restful_python_chapter_02_03 폴더에 들어 있다.

GameSerializer 클래스는 HyperlinkedModelSerializer 클래스의 서브 클래스다. GameSerializer 클래스는 game_Category 속성을 serializers.SlugRelatedField의 인스턴스로 선언하드데, 이때 이 인스턴스의 queryset인자는 GameCategory.objects.all()로 설정하고 slug_field인자는 'name'으로 설정한다. SlugRelatedField는 고유 슬러그 속성, 즉 디스크립션에 의해 관계의 대상을 나타내는 읽기/쓰기 필드다. 우리는 game_category 필드를  Game 모델의 models.ForegnKey 인스턴스로 생성했고 게임 카테고리의 이름을 관련 GameCategory에 대한 디스크립션(슬러그 필드)으로 표시하려고 한다.따라서 slug_field로 'name'을 지정했다. 관련 게임 카터고리에 대한 가능한 옵션을 브라우저를 API의 양식에 나타내야 하는 경우, 장고는 queryset인자에 지정된 표현식을 사용해 가능한 모든 인스턴스를 얻어 지정된 슬러그 필드를 나타낼 것이다.

GameCategorySerializer 클래스는 model과 fields의 두 속성을 선언하는 Meta 내부 클래스를 선언한다. model속성은 serializer와 관련된 모델, 즉 Game클래스를 지정한다. fields속성은 관련 모델의 직렬화에 포함시키려는 필드 이름을 값에 대한 문자열의 튜플로 지정한다. URL을 포함하기만 하면 'url'을 튜플의 멤버로 지정한 코드가 된다. game_category필드는 관련 GameCategory의 name필드를 지정한다.

이제 serializers.py 파일에 다음 코드를 추가해 ScoreSerializer 클래스를 선헌한다. 이 샘플 코드는 restful_python_chapter_02_03 폴더에 있다.

ScoreSerializer 클래스는 HyperlinkedModelSerializer 클래스의 서브 클래스다. 우리는 ScoreSerializer 클래스를 사용해 Player와 관련된 PlayerScore 인스턴스를 직렬화하느데, 이 말은 Player를 직렬화할 때 특정 플레이어의 모든 점수를 표시한다는 뜻이다. 관련 Game의 세부사항 모두를 표시하고 싶지만 Player가 ScoreSerializer 직렬화를 사용할 것이기 때문에 관련 Player를 포함시키지 않는다.

ScoreSerializer 클래스는 game속성을 이전에 코딩한 GameSerialzer클래스의 인스턴스로 선언한다. PlayerScore 모델의 models.ForeignKey 인스턴스로 game 필드를 생성했고 GameSerializer 클래스에서 코딩한 게임에 대한 것과 같은 데이터를 직렬화할 것이다.

ScoreSerializer 클래스는 model과 fields의 두 속성을 선언하는 Meta 내부 클래스를 선언한다.  model 속성은 직렬화기와 관련된 모델, 즉 PlayerScore 클래스를 지정한다. 이전에 설명했듯이 플레이어의 직렬화를 피하기 위해 문자열의 fileds 튜플에 'player'필드 이름을 포함시키지 않는다. 우리는 PlayerSerializer를 마스터로 사용하고 ScoreSerializer를 세부 사항으로 사용할 것이다.

이제 serializer.py파일에 다으 코드를 추가해 PlayerSerializer클래스를 선언하다. 이 샘플 코드 파일은 restful_python_chapter_02_03 폴더에 들어 있다.

PlayerSerizlier 클래스는 HyperlinkModelSerizlizer클래스의 서브 클래스다. PlayerSerializer 클래스를 사용해 Player 인스턴스를 직렬화하고 앞서 선언한 ScoreSerializer 클래스를 사용해 Player와 관련된 모든 PlayerScore 인스턴스를 직렬화한다.

PlayerSerializer 클래스는앞서 코딩한 ScoreSerializer 클래스의 인스턴스로 score속성을 선언한다. 일대다 관계이므로 many인자는 True로 설정한다. Player 필드를 PlayerScore모델의 models.ForeignKey 인스턴스로 생성할 때 related_name문자열 값으로 지정한 scores 이름을 사용한다. 이렇게 하면 score 필드는 앞서 선언한 ScoreSerializer를 사용해 Palyer에 대한 속한 각 PlayerScore를 렌더링한다.







2018년 8월 1일 수요일

2.7 모델 세부 튜닝

가능성 있는 모델들을 추렸다고 가정하겠습니ㅏㄷ. 이제 이 모델들을 세뷰 튜닝해야 합니다. 그 방법을 몇 개 살펴 봅시다.

2.7.1 그리드 탐색
가장 단순한 방법은 만족할 만한 하이퍼파라미터 조합을 찾을 때까지 수동으로 하이퍼파라미터를 조정하는 것입니ㅏㄷ. 이는 매우 지루한 작업이고 많은 경우의 수를 탐색하기에는 시간이 부족할 수도 있습니다.

대신 사이킷런의 GridSearchCVㄹ르 사용하는 것이 좋습니다. 탐색하고자 하는 하이퍼파라미터와 시도해볼 값을 지정하기만 하면 됩니다. 그러면 가능한 모든 하이퍼파라미터 조합에 대해 교차 검증을 사용해 평가하게 됩니다. 예를 들어 다음 코드 RandomForestRegressor에 대한 최적의 하이퍼파라미터 조합을 탐색합니다.

TIP 어떤 하이퍼파라미터 값을 지정해야 할지 모를 때는 연속된 10의 거듭 제곱 수로 시도해 보는 것도 좋습니다.(더 세밀하게 탐색하려면 위 예제의 n_estimators 하이퍼파라미터처럼 더 작은 값을 지정합니다.)

param_grid 설정에 따라 사아킷런이 먼저 첫 번째 dict에 있는 n_estimators와 max_features 하이퍼파라미터의 조합인 3*4=12개를 평가하고(지금은 이 하이퍼파라미터가 무엇을 의미하는지 걱정하지 않아도 됩니다. 7장에서 설명하겠습니다), 그런 다음 두 번째 dict에 있는 하이퍼파라미터의 조합인 2*3=6개를 시도합니다. 하지만 두 번째 bootstrap하이퍼파라미터를 True(기본값)가 아니라 False로 설정합니다.

모두 합하면 그리드 탐색의 RandomForestRegressor하이퍼파라미터 값의 12 + 6 = 18개 조합을 탐색하고, 각각 다섯 번 모델을 훈련시킵니다(5-겹 교차 검증을 사용하기 때문에). 다시 말해 전체 훈련 횟수는 18*5 = 90이 됩니다.

NOTE_ GridSearchCV가 (기본값인)refit=True로 초기화되었다면 교차 검증으로 최저의 추정기를 찾은 다음 전체 훈련 세트로 다시 훈련시킵니다. 일반적으로 데이터가 많을수록 성능이 향상되므로 좋은 방법입니다.

이 예에서는 max_features하이퍼파라미터가 8, n_estimators 하이퍼파라미터가 30일때 최적의 솔류션입니다. 이때 RMSE 점수가 49,694로 앞서 기본 하이퍼파라미터 설정으로 얻은 52,564 점 보다 조금 더 좋습니다.

2.7.2 랜덤 탐색
그리드 탐색 방법은 이전 예제와 같이 비교적 적은 수의 조합을 탐구할 때 괜찮습니다. 하지만 하이퍼파라미터 탐색 공간이 커지면 RandomizedSearchCV를 사용하는 편이 더 좋습니다.
RandomizedSearchCV는 GridSEarchCV와 거의 같은 방식으로 사용하지만 가능한 모든 조합을 시도하는 대신 각 반복마다 하이퍼파라미터의 임의의 수를 대입하여 지정한 횟수만큼 평가합니다. 이 방식의 주요 장점은 다음 두 가지 입니다.
- 랜덤 탐색을 1,000회 반복하도록 실행하면 하이퍼파라미터마다 각기 다른 1,000개의 값을 탐색합니다(그르드 탐색에서는 하이퍼파라미터마다 몇 개의 값만탐색합니다).
- 단순히 반복 횟수를 조절하는것만으로 하이퍼파라미터 탐색에 투입할 컴퓨팅 자원을 제어할 수 있습니다.

2.7.3 양상블 방법
모델을 세밀하게 튜닝하는 또 다른 방법은 최상의 모델을 연결해 보는 것입니다.(결정 트리의 앙상블인 랜덤 포레스트가 결정 트리 하나보다 더 성능이 좋은 것처럼) 모델의그룹(또는 앙상블)이 최상의 단일 모델보다 더 나은 성능을 발휘할 때가 많습니다. 특히 개개의 모델이 각기 다른 형태의 오차를 만들 때 그렇습니다. 이 주제는 7장에서 자세히 살펴보겠습니다.

2.7.4 최상의 모델과 오차 분석
최상의 모델을 분석하면 문제에 대한 좋은 통찰을 얻는 경우가 많습니다. 예를 들어 RandomRorestRegressor가 정확한 예측을 만들기 위한 각 특성의 상대적인 중요도를 알려줍니다.
중요도 다음에 그에 대응하는 특성 이름을 표시해보겠습니다.
이 정보를 바탕으로 덜 중요한 특성들을 제외할 수 있습니다.(예를 들어 ocean_proximity 카테고리 중 하나만 실제로 유용하므로 다른 카테고리는제외할 수 있습니다).

시스템이 특정한 오차를 만들었다면 왜 그런 문제가 생겼는지 이해하고 문제를 해결하는 방법이 무엇인지 찾아야 합니다(추가 특성을 포함시키거나, 반대로 불필요한 특성을 제거하거나, 이상치를 제외하는 등).

2.7.5 테스트 세트로 시스템 평가하기
어느정도 모델을 튜닝하면 마침내 만족할 만한 모델을 얻게 됩니다. 그럼 이제 테스트 세트에서 최종 모델을 평가할 차례입니다. 이 과정에 특별히 다른 점은 없습니다. 테스트 세트에서 예측 변수와 레이블을 얻은 후 full_pipeline을 사용해 데이터를 변환하고(fit_transform()이 아니라 transfor()을 호출해야 합니다) 테스트 세트에서 최종 모델을 평가합니다.

하이퍼파라미터 튜닝을 많이 했다면 교차 검증을 사용해 측정한 것보다 조금 성능이 낮은 것이 보통입니다(우리 시스템이 검증 데이터에서 좋은 성능을 내도록 세밀하게 튜닝되었기 때문에 새로운 데이터셋에는 잘 작동하지 않을 가능성이 큽니다). 이 예제에서는 성능이 낮아지진 않았지만, 이런 경우가 생기더라도 테스트 세트에서 성능 수치를 좋게 하려고 하이퍼파라미터를 튜닝하려 시도해서는 안됩니다. 그렇게 향상된 성능은 새로운 데이터에 일반화됙 어렵습니다.

이제 프로젝트 론칭 직전 단계에 왔습니다. (학습한 것, 한일과 하지 않은 일, 수립한 가정, 시스템 제한 사항 등을 가종하면서) 솔루션과 문서를 출시하고, 깔끔한 도표와 기억하기 쉬운 제목으로(예를 들면'수입의 중간값이 주택 가격 예측의 가장 중요한 지표다') 멋진 발표자료를 만들어야 합니다.

2.6 모델 선택과 훈련

2.6.1 훈련 세트에서 훈련하고 평가하기
이전 단계의 작업 덕분에 생각보다 훨씬 간단해 졌습니다. 먼저 앞 장에서 했던 것처럼 선형회귀모델을 훈련시켜보겠습니다.

2.6.2 교차 검증을 사용한 평가
결정트리 모델을 평가하는 밥버을 생각해보겠습니다. 우선 train_test_split 함수를 사용해 훈련 세트를 더 작은 훈련 세트와 검증 세트로 나누어, 더 작은 훈련 세트에서 모델을 훈련시키고 검증 세트로 모델을 평가하는 방법이 있습니다. 조금 수고스럽지만 너무 어렵지 않으며 매우 잘 작동합니다.

훌륭한 대안으로 사이킷런의 교차 검증 기능을 사용하는 방법도 있습니다. 다음 코드는 K-겹교차 검증을 수행합니다. 훈련 세트를 폴드라 불리는 10개의 서브셋으로 무작위로 분할합니다. 그런 다음 결졍 트리 모델을 10번 훈련하고 평가하는데, 매번 다른 폴드를 선택해 평가에 사용하고 나머지 9개 폴드는 훈련에 사용합니다. 10개의 평가 점수가 담긴 배열이 결과가 됩니다.

CAUTION_ 사이킷런의 교차 검증 기능은 scoring 매개변수에 (낮을수록 좋은) 비용 함수가 아니라(클수록 좋은)효용 함수를 기대합니다. 그래서 평균 제곱오차(MSE)의 반댓값(즉, 음숫값)을 계산하는 neg_mean_squared_error 함수를 사용합니다. 이런 이유로 앞선 코드에서 제곱근을 계산하기 전에 -scores로 부호르 바꿨습니다.

결정 트리 결과가 이전만큼 좋아 보이지 않습니다. 실제로 선형 회귀모델보다 나쁩니다! 교차 검증으로 모델의 성능을 추정하는 것뿐만 아니라 이 추정이 얼마나 정확한지 측정할 수 있습니다. 결정 트리 점수가 대략 평균 71.379에서 +-2,458 사이입니다. 검증 세트를 하나만 사용했다면 이런 정보를 얻지 못했을 것입니다. 하지만 모델을 여러 번 훈련시켜야 해서 비용이 비싸므로 교차검증을 언제나 쓸 수 있는 것은 아닙니다.

비교를 위해 선형 회귀모델의 점수를 계산해 보겠습니다.

확실히 결정 트리 모델이 과대적합되어 선형 회귀모델보다 성능이 나쁩니다.

마지막으로 RandomForestRegressor 모델을 하나 더 시도해보겠습니다. 7장에서 보게 되겠지만 랜덤 포레스트 특성을 무작위로 선택해서 많은 결정 트리를 만들고 그 예측을 평균 내는 방식으로 작동합니다. 여러 다른 모델을모아서 하나의 모델을 만드는 것을 앙상블 학습이라고 하며 머신러닝 알고리즘의 성능을 극대화하는 방법 중 하나입니다. 다른 모델들과 기본적으로 비슷하므로 코드 설명은 대부분 건너뛰도록 하겠습니다.

훈련 세트에 대한 점수가 검증 세트에 대한 점수보다 훨씬 낮으므로 이 모델도 여전히 훈련세트에 과대적합되어 있습니다. 과대적합을 해결하는 방법은 모델을 간단히 하거나, 제한을 을 하거나(즉, 규제), 더 많은 훈련 데이터를 모드는 것이빈다. 그러나 랜덤 포레스트를 더 깊이 들어가기 전에, 여러 종류의 머신러닝 알고리즘으로 하이퍼파라미터 조정에 너무 많은 시간을 들이지 않으면서 다양한 모델(다양한 커널의 서프트 벡터머신, 신경망 등)을 시도해봐야 합니다. 가능성 있는 2~5개 정도의 모델을 선정하는 것이 목적입니다.

TIP 실험한 모델을 모두 저장해두면 필요할 때 쉽게 모델을 복원할 수 있습니다. 교차 검증 점수와 실제 예측값은 물론 하이퍼파라미터와 훈련된 모델 파라미터 모두 저장해야 합니다. 이렇게 하면 여러 모델의 점수와 모델이 만든 오차를 쉽게 비교할 수 있습니다. 파이썬의 pickle 패키지나( 넘파이 배열을 저장하는 데 아주 효율적인)sklearn.externals.joblib을 사용하여 사이킷런 모델을 간단하게 저장할 수 있습니다.



2018년 7월 31일 화요일

2.5 머신러닝 알고리즘을 위한 데이터 준비

이제 머신러닝 알고리즘을 위해 데이터를 준비할 차례입니다. 이 작업을 그냥 수동으로 하는 대신 함수를 만들어 자동화해야 하는 이유가 있습니다.

- 어떤 데이터셋에 대해서도 데이터 변환을 손쉽게 반복할 수 있습니다(예를 들어 다음번에 새로운 데이터셋을 사용할 때).
- 향후 프로젝트에 사용할 수 있는 변환 라이브러리를 점진적으로 구축하게 됩니다.
- 실제 시스템에서 알고리즘에 새 데이터를 주입하기 전에 변환시키는 데 이 함수를 사용할 수 있습니다.
- 여러 가지 데이터 변환을 쉽게 시도해볼 수 있고 어떤 조합이 가장 좋은지 확인하는데 편리합니다.

하지만 먼저 원래 훈련 세트로 복원하고(strat_train_set을 다시 한번 복사합니다), 예측 변수와 타깃 값에 같은 변형을 적용하지 않기 위해 예측 변수와 레이블을 분리하겠습니다(drop()은 데이터 복사본을 만들며 strat)train)set에는 여향을 주지 않습니다).

2.5.1 데이터 정제
대부분의 머신러닝 알고리즘은 누락된 특성을 다루지 못하므로 이를 처리할 수 있는 함수를 몇개 만들겠습니다. 앞서 tatal)bedrooms 특성에 값이 없는 경우를 보았는데 이를 고쳐보겠습니다. 방법은 세 가지 입니다.

데이터프레임의 dropna(), drop(), fillna() 메서드를 이용해 이런 작업을 간단하게 처리할 수 있습니다.

옵션 3을 선태갛면 훈련 세트에서 중간값을 계산하고 누락된 값을 이 값으로 채워 넣어야 합니다. 하지만 계산된 중간 값을 저장하는 것 또한 잊지 말아야 합니다. 나중에 시스템을 평가할 때 테스트 세트에 있는 누락된 값을 바꾸기위해 필요하고 시스템이 실제 운영될 때 새로운 데이터에서 누락된 값을 바꿔야 하기 때문입니다.

사이킷런의 imputer는 누락된 값을 손쉽게 다루도록 해줍니다. 어떻게 사용하는지 살펴보죠, 먼저 누락된 값을 특성의 중간값으로 대체한다고 지정하고 Imputer의 객체를 생성합니다.

중간값이 수치형 특성에서만 계산될 수 있기 때문에 텍스트 특성인 ocean_proximity를 제외한 데이터 복사본을 생성합니다.

이제 imputer 개체의 fit() 메서드를 사용해 훈련 데이터에 적용할 수 있습니다.
imputer.fit(housing_num)

imputer는 각 특성의 중간값을 계산해서 그 결과를 객체의 statistics_속성에 저장합니다.
total_bedrooms 특성에만 누락된 값이 있지만 나중에 시스템에 서비스될 때 새로운 데이터에서 어떤값이 누락될지 확신할 수 없으므로 모든 수치형 특성에 imputer를 적용하는 것이 바람직합니다.

사이킷런의 설계 철학
사이킷런의 API는 아주 잘 설계되어 있습니다. 주요 설계 원칙은 다음과 같습니다.

- 일관성. 모든 객체가 일관되고 단순한 인터페이스를 공유합니다.
   - 추정기(estimator). 데이터셋을 기반으로 일련의 모델 파라미터들을 추정하는 객체를 추정기라고 합니다(예를 들어 imputer 객체는 추정기입니다). 추정 자체는 fit()메서드에 의해 수행되고 하나의 매개변수로 하나의 데이터셋만 전달합니다(지도 학습 알고리즘에서 매개변수가 두 개로, 두 번째 데이터셋은 레이블을 담고 있습니다). 추정 과정에서 필요한 다른 매개변수들은 모두 하이퍼ㄹ파라미터로 간주되고(예를 들면 imputer 객체의 strategy 매개변수), 인스턴스 변수로 저장됩니다(보통 생성자의 매개변수로 전달합니다).

   - 변환기(transformer). (imputer 같이) 데이터셋을 변환하는 추정기를 변환기라고 합니다. 여기서도 API는 매우 단순합니다. 변환은 데이터셋을 매개변수로 전달받느 transform()메서드가 수행합니다. 그리고 변환된 데이터셋을 반환합니다. 이런 변환은 일반적으로 imputer의 경우와 같이 학습된 모델 파라미터에 의해 결정됩니다. 모든 변환기는 fit()과 transform()을 연달아 호출하는 것과 동일한 fit)transform()메서드도 가지고 있습니다(이 따금 fit_transform()이 최적화되어 있어서 더 빠릅니다).

    - 예측기(predictor). 일부 추정기는 주어진 데이터셋에 대해 예측을 만들 수 있습니다. 예를 들어 앞 장에 나온 LinearRegression모델이 예측기입니다. 어떤 나라의 1인당 GDP가 주어질 때 삶의 만족도를 예측했습니다. 예측기의 PREDICT()메서드는 새로운 데이터셋을 받아 이에 상응하는 예측값을 반환합니다. 또한 테스트 세트(지도 학습 알고리즘이라면 레이블도 함께)를 사용해 예측의 품질을 측정하는 score()메서드를 가집니다.

- 검사 기능. 모든 측정기의 하이퍼파라미터는 공개(public) 인스턴스 변수로 직접 접근할 수 있고(예를 들면 imputer.strategy), 모든 추정기의 학습된 모델 파라미터도 접미사로 밑줄을 붙여서 공개 인스턴스 변수로 제공됩니다(예를 들면 imputer.statistics_).

- 클래스 남용 방지. 데이텃셋을 별도의 클래스가 아니라 넘파이 배열이나 사이파이 회소(sparse)행렬로 표현합니다. 하이퍼파라미터는 보통의 파이썬 문자열이나 숫자입니다.

- 조합성. 기존의 구성요소를 최대한 재사용합니다. 앞으로 보겠지만 예를 들어 여러 변환기를 연결한 다음 마지막에 추정기 하나를 배치한 Pipeline추정기를 쉽게 만들 수 있습니다.

- 합리적인 기본값. 사이킷런은 일단 돌아가는 기본 시스템을 빠르게 만들 수 있도록 대부분의 매개 변수에 합리적인 기본값을 지정해 두었습니다.

2.5.2 텍스트와 범주형 특성 다루기
앞서 범주형 특성 ocean_proximity가 텍스트라 중간값을 계산할 수 없어서 그냥 남겨뒸습니다.
대부분의 머신러닝 알고리즘은 숫자형을 다루므로 이 카테고리를 텍스트에서 숫자로 바꾸도록 하겠습니다. 이를 위해 각 카테고리를 다른 정숫값으로 매핑해주는 판다스 factorize()메서드를 사용합니다.

훨씬 나아져쎄요. 이제 housing_cat_encoded는 완전히 숫자입니다. factorize()메서드는 카테고리 리스트로 반환해줍니다.

이 표현 방식의 문제는 머신러닝 알고니즘이 가까이 있는 두 값이 떨어져 있는 두 값보다 더 비슷하다고 생각한다는 점입니다. 실제로는 그렇지 않습니다(예를 들어 카테고리 0과 1보다 카테고리 0과 4가 더 시슷합니다). 이 문제는 일반적으로 카테고리별 이진 특성을 만들어 해결합니다. 카테고리가 '<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
사이킷런은 숫자로 된 범주형 값을 원0핫 벡터로 바꿔주는 OneHotEncoder를 제공합니다. 카테고리들을 원-핫 벡터로 인코딩해보겠습니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">fit_transform()메서드는 2차원 배열을 넣어줘야 하는데 housing_cat_encoded는 1차원 배열이므로 구조를 바꿔야 합니다. 또한 출력을 보면 넘파이 배열이 아니고 사이파이SciPy 회소 행렬sparse matrix입니다. 이는 수천개의 카테고리가 있는 범주형 특성일 경우 매우 효울적입니다. 이런 특성을 원-핫 인크딩하면 열이 수천 개인 행렬로 변하고 각 행은 1이 하나뿐이고 그 외에는 모두 0으로 채워져 있을 것입니다.. 0을 모두 메모리에 저장하는 것은 낭비이므로 회소 행렬은 0이 아닌 원소의 위치만 저장합니다. 이 행렬의 거의 일반적인 2차원 배열처럼 사용할 수있지만 (밀집된)넘파이 배열로 바꾸려면 toarrray()메서드를 호출하면 됩니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">텍스트 카테고리를 숫자 카테고리로, 숫자 카테고리를 원-핫 벡터로 바꿔주는 이 두가지 변환을 CategoricalEncoder를 사용하여 한번에 처리할 수 있습니다. 이 파이썬 클래스는 사이킷런 0.19.0과 그 이전 버전에는 포함되어 있지 않습니다. 하지만 곧 추가될 예정이라 아마 이 책을 읽을 때쯤이면 이미 포함되어 있을 가증성이 많습니다. 그렇지 않다면 이 장의 주피터 노트북에서 가져와 사용할 수 있습니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">기본적으로 CategoricalEncoder는 회소 행렬을 축결하지만 밀집행렬을 원할 경우 encoding 매개변수를 "onehot-dense"로 지정할 수 있습니다.

<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">categories_ 인스턴스 변수를 사용해 카테고리 리스트를 얻을 수 있습니다. 이 리스트는 카테고리 특성마다 1D 카테고리 배열을 담고 있습니다(여기에서는 하나의 카테고리 특성만 있기 때문에 배열 하나만을 담고 있는 리스트입니다).
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">Tip 카테고리 특성이 담을 수 있는 카테고리 수가 많다면(예를 들면 국가 코드, 직업, 생물 종류 등) 원-핫 인코디은 많은 수의 입력 특성을 만듭니다. 이는 훈련을 느리게 하고 성능을 감소시킬 수 있습니다. 이런 경우에는 임베딩 이라고 하는 조금 더 조밀한 표현을 사용할 수 있지만 신경망에 대한 이해가 필요합니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">2.5.3 나만의 변환기
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">사이킷런이 유용한 변환기를 많이 제공하지만 특별한 정제 작업이나 어떤 특성을 조합하는 등의 작업을 위해 자신만의 변환기를 만들어야 할 때가 있습니다. 내가 만든 변환기를 (파이프라인과 같은) 사이킷런의 긴으과 매끄럽게 연동하고 싶을 것입니다. 사이킷런은 (상속이 아닌)덕 타이핑(duck typing)을 지원하므로 fit()(self를 반환), transform(), fit_transform()메서드를 구현한 파이썬 클래스를 만들면 됩니다. 마지막 메서드 TransformerMixin을 상속하면 자동으로 생성됩니다. 또한 BaseEstimator를 상속하면(그리고 생성자에 *args 나 **kargs를 사용하지 않으면) 하이퍼파라미터 튜닝에 필요한 두 메서드(get_params()와 set_params())를 추가로 얻게 됩니다. 옐르 들어 다음 앞서 이야기한 조합 특성을 추가하는 간단한 변환기입니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">이 경우에는 변환기가 add_bedrooms_per_room 하이퍼파라미터 하나를 가지고 있고 기본값을 True로 지정합니다(합리적인 기본값을 주는 것이 좋습니다). 이 특성을 추가하는 것이 머신러닝 알고리즘에 도움이 될지 안될지 이 하이퍼파라미터로 쉽게 확인해 볼 수 있습니다. 일반적으로 100% 확신이 없는 모든 데이터 준비 단계에 대해 하이퍼라라미터를 추가할 수 있습니다. 이런 데이터 준비 단곌르 자동화할수록 더 많은 조합을 자동으로 시도해 볼 수 있고 최상의 조합을 찾을 가능성을 매우 높여줍니다(그리고 시간도 많이 절약됩니다).
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">2.5.4 특성 스케일링
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">데이터에 적용할 가장 중요한 변환 중 하나가 특성 스케일링입니다. 몇 가지를 빼고는, 머신러닝 알고리즘은 입력 숫자 특성들의 스케일이 많이 다르면 잘 작동하지 않습니다. 주택가격 데이터도 이에 해당합니다. 즉, 전체 방 개수의 범위는 6에서 39,320인 반면 중간 소득의 범위는 0에서 15까지입니다. 타갓 값에 대한 스케일링을 일반적으로 불필요합니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">모든 특성의 범위를 같도록 만들어주는 방법으로 min-max 스케일링과 표준화standardization가 널리 사용됩니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">min-max 스케일링은 매우 간단합니다(많은 사람이 이를 정규화 라고 부릅니다. 0~1 범위에 들도록 값을 이동하고 스케일을 조정하면 됩니다. 데이터에서 최솟값을 뺀 후 최댓값과 최솟값의 차이로 나누면 이렇게 할 수 있습니다. 사이킷런에는 이에 해당하는 MinMaxScaler 변환기를 제공합니다. 0~1사이를 원하지 않는다면 feature_range 매개변수로 범위를 변경할 수 있습니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">표준화는 많이 다릅니다. 먼저 평균을 뺀 후(그래서 표준화를 하면 항상 평균이 0이 됩니다) 표준편차로 나누어 결과 분포의 분산이 1이 되도록 합니다. min-max스케일링과는 달리 표준화는 범위의 상한과 하한이 없어 어떤 알고리즘에는 문제가 될 수 있습니다.(예를 들어 신경망은 종종 입력값이 범위로 0에서 1사이르 기대합니다), 그러나 표준화는 이상치에 영향을 덜 받습니다. 예를 들어 중간 소득을(잘못해서) 100이라 입력한 구역을 가정해 봅시다. min-max스케일링은 0~15사이의 모든 다른 값으 0~0.15로 만들어버리겠지만, 표준화는 크게 영향받지 않습니다. 사이킷런에는 표준화를위한 StandardScaler 변환기가 있습니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">CAUTION_
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">모든 변환기에서 스케일링은(테스트 세트가 포함된) 전체 데이터가 아니고 훈련 데이터에 대해서만 fit()메서드를 적용해야 합니다. 그런 다음 훈련 세트와 테스트세터(그리고 새로운 데이터)에 대해 transform()메서드를 사용합니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">2.5.5 변환 파이프라인
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">앞서 보았듯이 변환 단계가 많으며 정확한 숫서대로 실행되어야 합니다. 다행이 사이킷런에는 연소된 변환을 순서대로 처리할 수 있도록 도와주는 Pipeline클래스가 있습니다. 다음은 숫자 특성을 처리하는 간단한 파이프라인입니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">Pipeline은 연속된 단계를 나타내는 이름/추정기 쌍의 목록을 입력으로 받습니다. 마지막 단계에는 변환기와 추정기를 모두 사용할 수 있고 그 외에는 모두 변환기여야 합니다(즉, fit_transform()메서드를 가지고 있어야 합니다). 이름은 무엇이든 상관없지만, 이중 밑줄 문자(__)는 포함하지 않아야 합니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">파이프라인의 fit()메서드를 호출하면 모든 변환기의 fit_transfor()메서드를 순서대로 호추하면서 한 단계의 출력을 다음 단계의 입력으로 전달합니다. 마지막 단계에서는 fit()메서드만 호출합니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">파이프라인 객체는 마지막 추정기와 동일한 메서드를 제공합니다. 이 예에서는 마지막 추정기가 변환기 StandardScaler이므로 파이프라인이 데이터에 대해 모든 변환을 순서대로 적용하는 transform() 메서드를 가지고 있습니다(또한 fit()과 transform()을 차례대로 호출하는 대신에 사용할 수 있는 fit_transform() 메서드도 가지고 있습니다).
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">수치형 컬럼을 넘파이 배열로 추출하는 대신 판다스의 데이터프레임을 파이프라인에 직접 주입할 수 있다면 좋을 것입니다. 사이킷런의 판다스의 데이터프레임을 다룰 수는 없지만 이를 처리하는 변환기를 직접 만들 수는 있습니다.
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
DataFrameSelector는 나머지는 버리고 필요한 특성을 선택하여 데이터프레임을 넘파이 배열로 바꾸는 식으로 데이터를 변환합니다. 이를 이용해 데이터프레임을 받아 수치형만 다루는 파이프라인을 손쉽게 만들 수 있습니다. 수치형 특성을 선택한 DataFrameSelector로 파이프라인을 시작해서 앞서 이야기한 다른 전처리 단계들을 나열합니다. 범주형 특성을 다루는 또 다른 파이프라인도 DataFrameSelector로 범주형 특성을 선택하고 CategoricalEncoder를 적용하면 간단히 만들 수 있습니다.

하지만 어떻게 이 두 파이프라인을 하나의 파이프라인으로 합칠 수 있을까요? 정답은 사이킷런의 ReatureUnion입니다. 변환기 목록(또는 모두 변환기로 이뤄진 파이프라인)을 전달하고 transform()메서드를 호출하면 각 변환기의 transform()메서드를 병렬로 실행합니다. 그런 다음 각 변환기의 결과를 합쳐 반환합니다(물론 fit() 메서드를 호춯하면 각 변환기의 fit()메서드를 실행합니다) 숫자형과 범주형 특성을 모두 다루는 전체 파이프라인은 다음과 같습니다.


<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">   
<1h 0="" 1="" encoding="" ocean="" one-hot="" p="">




2018년 7월 25일 수요일

1. 장고를 이용한 레스트풀 API 개발

- 간단한 SQLite 데이터베이스와 대화하는 레스트풀 API 디자인
- 각 HTTP 메서드가 수행하는 작업 이해
- 경량 가상 환경에서의 작업
- 장고 레스트 프레임워크에서의 가상 환경 설정
- 모델 제작
- 직렬화와 역직렬화 관리
- API 뷰 작성
- API에 대항 HTTP 요청
- 명령 행 도구로 작업 - curl과 httpie
- GUI 도구로 작업 -Postman과 기타
- 지식 테스트

1.1 간단한 SQLite 데이터베이스와 대화하는 레스트풀 API 디자인

먼저 주요 자원에 대한 요구사항을 지정(속성, 필드)
- 정수 식별자
- 이름 또는 타이틀
- 출시일
- 3D RPG 또는 2D 모바일 아케이드와 같은 게임 카테고리 디스크립션
- 플레이어가 적어도 한 번 게임을 했는지 여부를 나타내는 bool 값

다음 표에서는 첫 번째 API 버전에서 지원해야 하는 HTTP 동사verb, 범위, 그 메서드에 대한 의미를 보여준다. 각 메서드는 HTTP 동사와 범위로 구성되며, 모든 메서드는 모든 게임 및 컬렉션에 대해 잘 정의된 의미를 갖는다.

HTTP 동사           범위                   의미
GET           게임 켈렉션         겔렉션의 모든 저장된 게임을 얻고, 이름에 따라 오름차
                                       순으로 정렬한다.
GET           게임                  게임 하나만 얻는다.
POST         게임 켈렉션         케렉션에 새 케임 생성
PUT           게임                  기존 게임 업데이트
DELETE       게임                  기존 게임 삭제

레스트풀 API에서 각 자원은 고유한 URL를 가진다. 그러므로 우리 API에는 각 게임마다 고유한 URL이 있게 된다.


1.2 각 HTTP 메서드가 수행하는 작업 이해

http://localhost:8000/games/가 게임 겔렉션을 위한 URL이라고 생각해 보자.
http://localhost:8000/games/12/ 는 id또는 기본 키가 12인 게임을 식별한다.

새 게임을 만들려면 저 아래의 HTTP 동사(POST)와 요청 URL(http://hocalhost:8000/games/)로 HTTP요청을 작성해 보내야 한다. 더욱이 JSON(JavaScript Object Notation) 키-값쌍으로 필드 이름과 값을 제공해 새 게임을 만들어야 한다. 요청 결과, 서버는 필드에 제공된 값의 유효성을 검사하고 유효한 게임인지 확인 후 데이터베이스에 저장한다.

서버는 적절한 데이블에 새 게임이 들어간 새 행을 삽입하고, JSON으로 직렬화된 최근 추가 게임의 JSON본문과 201created상태 코드를 반환하는데, 여기에는 데이터 베이스가 자동으로 생성하고 게임 책체에 지정한 할당 id또는 기본 키가 포함된다.
POST http://localhost:8000/games/

id 또는 기본키가 지정된 숫자 값과 일치하는 게임을 얻으려면 HTTP 동사(GET)와 요청 URL(http://localhost:8000/games/{id}/)을 사용해야 저 아래의 HTTP 요청을 작성해 보내야 하는데, 여기서 {id}에는 id나 기본키에 해당하는 숫자 값을 지정한다.

예를들어, 요청 URL인 http://localhost:u8000/games/50/을 사용하면, 서버는 id 또는 기본 키가 50과 일치하는 게임을 얻는다.

요청 결과, 서버는 데이터베이트에 지정된 id또는 기본 키를 가진 게임을 얻고 파이썬에서 적절한 게임 개체를 생성할 것이다. 게임이 발견되면 서버는 게임 객체를 JSON으로 직렬화하고, 200OK 상태 코드와 함께 직렬화된 게임 객체가 들어간 JSON 본문을 반환한다. 지정된 id또는 기본 키와 일치하는 게이미 없으면 서버는 404 not Found 상태를 반환한다.
GET http://localhost:8000/games/{id}/

id 또는 기본 키가 지정된 숫자 값과 일치하는 게임을 업데이트하려면 HTTP 동사 (PUT)와 요청 URL(http://localhost:8000/games/{id}/)을 사용해 저 아래의 HTTP 요청을 작성해 보내야 한느데, 여기서 {id} 자리에는 제공되 ㄴ데이터로 생성된 게임의 해당 값으로 대체한다. 게다가 JSON 키-값 싸으로 필드 이름과 값을 제공해 기존 게임을 대체할 새 게임을 만들어야 한다. 요청 결과, 서버는 필드에 제공된 값의 유효성을 검사하고 유효한 게임인지 확인한 다음, 데이터베이스에서 지정된ID 또는 기본키와 일치하는 값을 새 값으로 바꾼다. 게임의 id또는 기본 키는 업데이트 작업 후에도 동일할 것이다. 서버는 새당 테이블의 기존 행을 업데이트하고, 200 ok 상태 곹드와 함께 최근에 업데이트 된 게임을 JSON으로 직렬화한 JSON본문을 반환한다. 새로운 게임에 필요한 모든 데이터를 제공하지 않으면 서버에서 400 Bad Request 상태코드를 반환 할 것이다. 서버가 지정된 ID로 게임을 찾지 못하면 404 Not Found 상태를 반환한다.
PUT http://localhost:8000/games/{id}/
id 또는 기본 키가 지정된 숫자 값과 일치하는 게임을 제거하려면 HTTP 동사(DELETE)와 요청 URL(http://localhost:8000/games/{id}/)을 사용해서 저 아래의 HTTP 요청을 작성해 보내야 하는데, 여기서 {id} 자리에 해당 숫자를 적는다. 예를 들어, 요청 URL로 http://localhost:8000/games/20/을 사용하면 서버는 id 또는 기본키가 20과 일치하느 게임을 삭제한다. 요청 결과, 서버는 데이터베이스에서 해야 id 또는 기본키를 가진 게임을 얻어 파이썬으로 적절한 게임 객체를 만든다. 게임이 발견되면 서브는 ORM에 요청해 이 게임 객체와 관련된 게임 행을 삭제하고 204 No Content 상태 코드를 반환한다. 지정된 id또는 기본 키와 일치하느 게임이 없으면 서버는 404 Not Found상태만 반환할 것이다.
DELETE http://locahost:8000/games/{id}

1.3 경량 가상 환경에서의 작업
파이썬에 통합된 venv 모듈로 생성된 가상 환경을 활성화하려면 자체에 설명해놓은 단계를 따르지 말고 필요한 경우에 적절한 메커니즘을 사용해 가상 환경을 활성화해야한다. venv 모듈은 도입한 PEP 405 파이썬 가상 환경에 대한 자세한 정보는 https://www.python.org/dev/peps/pep-0405/에서 볼 수 있다.

vens로 생성한 각 가상환경은 결리된 환경이며 사이트 디렉토리에 자체 파이썬 패키지가 독립적으로 설치된다. 파이썬 3.4 이상 버전에서 venv로 가상 환경을 만들면 pip가 새로운 가상환경에 포함된다. 파이썬 3.3에서는 가상 환경을 만든 후에 pip를 수동으로 설치애야 했다. 제공한 지지사항은 파이썬 3.5.x를 포함해 파이썬 3.4 이상과 호환된다는 점에 주목하라. 이 후의 명령들은 맥 os, 리눅스, 원도우에 파이썬 3.5.x가 설치돼 있다고 가정한 것이다.

먼저, 가상 환경을 위한 대상 폴더 또는 디렉터리를 선택해야 한다. 저 아래에 있는 것은 맥 os 와 리눅스용 예제에서 사용할 경로다. 가상 환경의 대상 폴더는 홈 디렉터리 내의 PythonREST/Django 폴더다. 예를 들어, 맥 os또는 리눅스의 홈 디렉터리가 /Users/gsaton이라면 /Users/gaston/PythonREST/Django에 가상 환경이 만들어진다.
각 명령에서 지정한 경로는 여러분이 원하는 경로로 바꿀 수 있다.

~/PythonREST/Django

다음은 원도우용 예제에서 사용할 경로다. 가상 환경의 대상 폴더는 사용자 프로필 폴더 내의 PythonREST/Django 폴더다. 예를 들어, 사용자 프로필 폴더가 C:\Users\Gaston이라면, 가상 환경은 C:\Users\gaston\PytonbREST\Django 내에 만들어진다. 각 명령에서 지정한 경로는 여러분이 원하는 경로로 바꿀 수 있다.

%USERPROFILE%\PythonREST\Django

이제 -m옵션 뒤에 venv모듈 이름과 원하는 경로를 사용해 파이썬이 이 모듈을 스크립트로 실행해서 지정된 경롤에 가상환경을 만들게 한다. 이 지시사항은 가상환경을 만들 플랫폼에 따라 다르다.

맥OS 또는 리눅스에 터미널을 열고, 다름 명령을 실행해 가상 환경을 만든다.

python3 -m venv ~/PytjhonREST/Django01

원도우에서는 다음 명령을 실행해 가상 환경을 만든다.

python -m venv %USERPROFILE%\PythonREST\Django01

이 명령은 아무런 출려도 내지 않는다. 이 스크립트는 --without-pip 옵션을 지정하지 않았으므로 ensurepip를 호출해 지정된 대상 폴더를 생성하면서 pip를 설치한다. 지정된 대상 폴더에는 파이썬 실행 파일과 가상환경임을 나타내는 가가 파일이 포함된 새로운 디렉터리 트리가 생긴다.

pyenv.cfg 구성 파일로 가상환경에 대한 다양한 봅션을 지정하는데, 이 파일에 존재한다느느 것은 가상환겨으이 루트 폴더라는 것을 나타내는 지표가 된다. 맥 os와 리눅스에서의 루트 폴더에는 bin, include, lib, lib\python3.5, lib\python3.5\sitepackages와 같은 주요 서브 폴더가 있다. 원도우에서의 이 루트 폴더에는 Include, Lib, Lib]site-packages, Scripts와 같은 주요 서브 폴더가 있다. 각 프랫폼의 가상 환경에 대한 디렉토리 트리는 이 플랫폼에서의 파이썬 설치 레이아웃과 동일하다. 다음 스크린샷은 맥OS의 Django01가상 환경에 대해 생성된 디렉터리 트리의 폴더와 파일을 보여준다.

다음 스크린샷은 원도우의 가상 환경에 대해 생성된 디렉토리 트리의 메인 폴더를 보여준다.

가상환경을 활성화한 후, 타사 패키지를 가상 환경에 설치하면 모듈은 플랫폼에 따라 lib/python3.5\site-packages 또는 Lib\site-packages 폴더에 배치될 것이다. 실행 파일은 플랫롬에 따라 bin 또는 Scripts 폴더에 복사된다. 이렇게 설치한 패키지는 다른 가상 환경 또는 기본 파이썬 환경을 변경시키지 않는다.

가상 환경을 만들었으므로 이제 해당 플랫폼에 따른 스크립트를 실행해 가상 환경을 활성화 하낟. 가상 환경을 활성화 한 후에는 이 가상 환경에서만 사용할 수 있는 패키지를 설치할 것이다.

맥 os 또는 리눅스의 터미널에서 다음 명령을 실행하라. 터미널 세션에서 기본 셀이 아닌 다른 셀을 실행한 것이 아니라면, 이 명령의 결과가 정확하게 나올 ㅓㄳ이라는 점에 주목한다. 의심이 가는 경우에는 터미널 구성과 기본 설정을 점검하라.

echo $SHELL

이 명령은 터미널에서 사용중인 셀 이름을 표시한다. 맥 OS에 기본 값은 /bin/bash이며, 이는 bash셀로 작업하고 있음을 의미한다. 셀에 따라 맥os또는리눅스에서 다른 명령을 실행해 가상 환경을 활성화 해야 한다.

맥OS또는 리눅스에서 bash셀을 사용하게 터미널을 구성한 경우, 다음 명령을 실행해 가상 환경을 활성화 하라. 이 명령은 zsh셀에서도 작동한다.

source ~/PythonREST/Django01/bin/activate

터미널이 csh또는 tcsh셀을 사용하게 구성된 경우, 다음 명령을 실행해 가상 환경을 활성화 하라.

source ~/PythonREST/Django01/bin/activate.csh

터미널의 fish 셀을 사용하게 구성돼 있으면, 다음 명령을 실행해 가상 환경을 활성화 하라.

%USERPROFILE%\PythonREST\Django01\Scripts\activate.bat

Windows PowerShell을 선호한다면, 이를 실행하고 다음 명령을 실행해 가상 환경을 활성화하라. 하지만 스크립ㅌ느를 실행하려면 Windows PowerShell에서 스크립트 실행을 사용할 수 있도록 설정해야 한다는 점에 유의하라.

cd $env:USERPROFILE
pythonREST\Django01\Sripts\Activate.ps1

가상환경을 활성화하면, 명령 프롬프트에 가상 환경 루트 폴더 이름이 괄호로 묶여 기본 프롬프트의 접두어로 표시되므로 가상 환경에서 작업하고 있음을 알게 해준다. 여기서는 활성화된 가상 환경의 로트 폴더가 Django01이기 때문에 (Django01)이 명령 프롬프트의 접두어로 표시된다.

다음 스크린샷은 위에 나타낸 명령을 실행한 후, bash셀이 있는 맥os EL Capitan 터미널에서 활성화된 가상 환경을 보여준다.

위에 설명한 과정을 ㅗ생성된 가상 환경을 ㅣㅂ활성화하는 것은 매우 쉽다. 맥 os 또는 리눅스에서 deactivate를 입력하고 Enter를 누른다. 원도우에서는 명령프롬프트에서 Scripts폴더에 들어 있는 deativate.bat 배치 파일을 실행해야 한다. Windows PowerShell에서 Scripts 폴더에서 Deactivate.ps1스크립트를 실행해야 한다. 비 활성화하면 환경 변수에서 수행된 모든 변경사항이 제거될 것이다.

1.3 장고 레스트 프레임워크에서의 가상환경 설정
pip install django

pip install djangorestframework

cd ~/PythonREST/Django01

django-admin.py startproject gamesapi

cd gamesapi

paytho manage.py startapp games

occure "

Django manage.py runserver invalid syntax" error

Note that from exc is removed from the file. It is not required in the manage.py file.
(https://stackoverflow.com/questions/47880626/django-manage-py-runserver-invalid-syntax)


위의 명령으로 다음 파일들이 들어간 새 gamesapi/games서브 폴더가 생성됐다.

- __init__.py
- admin.py
- apps.py
- models.py
- tests.py
- views.py

gamesapi/games 폴더 내에 있는 apps.py 파일의 파이썬 코드를 확인해 보자. 

이 코드는 GamesConfig클래스를 장고 애플리케이션 및 해당 구성을 나타내는 django.apps.AppConfig 클래스의 서브 클래스를 선언한다. GamesConfig 클래스는 name 클래스 속성을 정의하면서 해당 값을 'games'로 설정한다. gamesapi 장고 프로젝트의 설정을 구성하는 gamesapi/settings.py 파일에는 설치 앱 중 하나로서 games.app.GamesConfig를 추가해야 한다. 이 문자열은 app-name+.apps.+클래스 이름으로 구성됐는데, 즉 games + .apps. + GamesConfig 식이 된다. 더욱이 장고 레스트 프레임워크를 사용할 수 있게 rest_framework앱도 추가해야 한다.

gamesapi/settings.py 팡리은 파시썬 모듈로서 gamesapi 프로젝트의 장고 구성을 정의하는 모듈 레벨 변수가 들어 있다. 우리는 이 장고 설정 파일을 약간 변경할 것 이ㅏㄷ.
gamesapi/settings.py 파일을 열고 설치된 앱 선언의 문자열 리스트를 지정하는 다음 행을 찾는다.

INSTALLED_APPS = [
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
]

INSTALLED_APPS 문자열 리스트에 다음 두 문자열을 추가하고 변경 내용을 gamesapi/settings.py 파일에 저장한다.

- 'rest_framework'
- 'games.apps.GamesConfig'

다음 행에서 추가된 행을 굵게 나나낸 INSTALLED_APPS 문자열 리스트 선언의 새코드를 보여준다. 이 샘플 코드 파일은 restful_python_chapter_01_01 폴더에 들어 있다.


1.4 모델 제작
games/models.py 파일을 연다. 

Game 클래스는 django.db.models.Model 클래스으 서브 클래스다. 정의된 각 속성은 데이터베이스 열 또는 필드를 나타낸다. 장고는 모델과 관련된 데이터베이스 테이블을 생성할 때 id라는 자동 증가 정수 기본 키 열을 자동으로추가한다. 하지만 모델은 해당 모델의 pk라는 속석에 id 열을 대응시킨다. 우리는 여러 속성에 대해 필드 타입, 최대 길이, 기본값을 지정했다. 이 클래스는 정렬 속성을 선언하며 첫 벚째 값이 'name'인 문자열의 튜플로 값을 설정하는 Meta 내부 클래스를 선언했는데, 기본적으로 name 속성에 따라 오름차순으로 정렬된 결과가 나타난다.

그 다음으로는 최근에 코딩한 새 Game 모델의 초기 마이그레이션을 만들어야 한다. 다음 파이썬 스트립트를 실행하면 데이터베이스를 처음으로 동기화할 수 있다. 기봊ㄴ적으로 장고는 SQLite 데이터베이스를 사용한다. 이 예제에서는 이런 기본 구서응로 작업할 것이다.

python manage.py makemigrations games

games/migrations/0001_initaial.py 생성

이 코드는 Game 모델의 테이블을 만드는 작업을 정의하는Migration이라는 django.db.migrations.Migration 클래스의 서브 크랠스를 정의 한다. 이제, 생성된 마이그레이션을 적용하기 위해 다음 파이썬 스크립트를 실행한다.

python manage.py migrate

위의 명령을 실행하면 gamesapi 프로젝트의 루트 폴더에 db.sqlite3파일이 생긴것을 볼 수 있다. 장고가 생성한 테이블을 보려면 SQLite 명령 행 또는 SQLite 데이터베이스의 테이블을 쉽게 점검할 수 있게 해주는 애플리케이션을 사용하면 된다.

다음 명령을 실행해 생성되 ㄴ테이블을 나열해 보라.

sqlite3 db.sqlite3 '.tables'

다음 명령을 실행해 games_game 테이블을 만드는데 사용된 SQL을 알아본다.

sqlite3 db.sqlite3 '.schema games_game'

다음 명령을 사용하면 HTTP 요청을 작성해 레스트풀 API에 보내고 games_game 테이블에 CRUD 작업을 수행한 후 games_game 테이블의 내용을 점검할 수 있다.

sqlite3 db.sqlite3 'SELECT * FROM games_game ORDER BY name;'

SQLite 명령 행 유틸리티로 작업하는 대신 GUI 도구를 사용해 SQLite 테이터 베이스의 내용을 확인할 수 있다.

SQLite 데이터베이스 엔진과 데이터베이스 파일이름은 gamesapi/settings.py 파일씬 파일에 지정돼 있다. 다음 행은 장고가 사용하는 모든 데이터베이스에 대한 설정을 담고 있는 DATABASE 딕셔너리의 선언을 보여준다. 중첩된 딕셔너리는 default라는 데이터베이스를 django.db.backends.sqlite3 데이터베이스 엔진과 BASE_DIR폴더(gameapi)에 있는 db.,sqlite3 데이터베이스 파일에 대응시킨다.

games_game 테이블은 우리가 최근 생성한 Game 클래스, 특히 Game 모델의 데이터베이스 속에 유지된다. 장고의 통합 ORM은 Game 모델을 기반으로 games_game 테이블을 생성했다. games_game 테이블에는 SQLite 형식의 다음 행(필드라고도 함)이 있으며, 그 중 모두가 null 값도 가능한 것은 아니다.

- id: The interger primary key, an autoincrement row
- created: datetime
- name: varchar(200)
- release_Date: datetime
- game_category: varchar(200)
- played: bool

다음 행은 우리가 마이그레이션을 실행했을 때 장고가 생성한 SQL 생성 스크립니다.

CREATE TABLE "games_game"(
     "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
     "created" datetime NOT NULL,
     "name"  varchar(200) NOT NULL,
     "release_date"  datetime NOT NULL,
     "game_category" varchar(200) NOT NULL,
     "played" bool NOT NULL
)


장고는 나중에 사용할 웹 프레임워크와 인증 기능을 지원하는데 필요한 테이블들을 추가해 생성했다.

1.5 직렬화 역직렬화 관리
레스트풀 웹 API는 게임 인스터스를 JSON 표현은로 직렬화와 역직렬화할 수 있어야 한다. 장고 레스트 프레임워크를 ㅏ숑ㅇ하면, 게임 인스턴스가 JSON으로의 직렬화와 JSON로 부터의 역직렬화를 관리할 수 있게 직렬화기serializer 클래스를 만들어야 한다.
장고 레스트 프레임워크는 직렬화를 위해 2단계 과정을 사용한다. 직렬화기는 모델 인스턴스와 파이썬 프리미티브 사이의 중개자다. 파서parser와 렌더러renderer는 파이썬 프리미티브와 HTTP 요청과 응답 사이의 중개자로 처리한다. rest_framework.serializers.Serializer 클래스의 서브 크랠스에서 구성할 수 있는 모든것을 아게 될거 ㅅ이다. 하지만 다으 ㅁ예제에서는 나중에 사용구 코드boilerplate code를 줄일 수 있는 단추 ㄱ코드로 작업한다. ModelSerializer 클래스를 사용해 다음 예제에서의 장고 코드 양을 줄일 것이다.

이제, gamesapi/games 폴더로 이동해 serializers.py라는 새 파이썬 코드 파일을 생성한다. 다음 행은 새 GameSerializer 클래스를 선언하는 코드를 보여준다. 이 샘플 코드 파일은 restful_python_chpter_01_01폴더에 들어 있다.

GameSerializer 클래스는 직렬화할 필드를 나타내는 속성을 선언한다. 여기에는 Game모델에 존재했던 created 속성이 빠졌다는 것에 유의하라. 이 크랠스에 있어서 상속된 save 메서드 호출이 있을 때, 오버라이드한 create와 update 메서드는 인스턴스를 생성하거나 수정하는 방법을 정의한다. 사실, 이들 메서드는 베이스 선언에서 NotImplementedError 예외를 발생시키기 때문에 이 클래스에서 구현해야 한다.

create 메서든 ㄴvalidated_data 인자로 유효 데이터를 받는다. 이코드는 수신된 유효 데이터에 따라 새 Game 인스턴스를 생성해 반환한다.

update 메서드는 instance와 validated_Data 인자로 업데이트될 기존 Game 인스턴스와 새 유효 데이터를 받는다. 이 코드는 인스턴스의 속성에 대한 값을 유효 데이터에서 얻고 업데이트된 속성 값으로 업데이트 하며, 업데이트된 Game 인스턴스용 save 메서드를 호출해서 업데이트되고 저장된 인스턴스를 반환한다.

기보 ㄴ파이썬 대화형 셀을 실행해 모든 장고 프로젝트 모듈을 사용가능하게 만든 후에 이를 모듈릉 시작할 ㅜㅅ 있다. 이렇게 하면 직렬화기가 예상대로 작동하는지 점검할 수 있다. 더욱이 장고에서 직렬화가 어떻게 작동하는지 이해하는데 도움이 될 것이다. 대화형 세을 시작하려면 다음 명령을 실행하라. 이때, 터미널 또는 명령 프로프트에서 gameapi폴더 내에 위치해 있어야 한다.

python manage.py shell

기본 파이썬 대화형 셀로 들어가는 보통의 행 다음에 (InteractiveConsole)이라는 행이 나타날 것이다. 파이썬 대화영 셀에 아래 코드를 입력해 Game 모델과 이 모델의 직렬화기를 테스트 하는 데 필요한 모든 항목을 임포트한다. 이 샘플 코드는restful_python_chapter_01_01 폴더의 serializers_test_01.py 파일에 있다.

아래 코드를 입력해 Game 모델으 ㅣ두 인스턴스를 생성하고 저장하라. 이 샘플 코드도restful_python_chapter_01_01 폴더의 serializers_test_01.py파일에 들어 있다.

위의 코드를 실행한 후에는 앞서 소개한 명령 행 또는 GUI 도구로 SQLite 데이터베이스를 점검해 games_game 테이블의 내용을 확인할 수 있다. 이 테이블에는 2개의 행이 생기면 각 열에는 Game 인스턴스으 ㅣ각 속성에 제공했던 값이 들어 있을 것이다.

대화형 셀에 다음 명령을 입력해 저장된 Game 인스턴스의 기본 키 또는 식별자 값을 점검하는데, created 속성 값에는 데이터베이스에 인스턴스를 저장한 날짜와 시간이 들어간다. 이 샘플 코드 restful_python_chapter_01_01 폴더의 serailizers_test_01.py 파일에 들어있다.

이제 첫 번째 게임 인스턴스(game1)를 직렬화한느 아래 코드를 작성하자, 이 샘플 코드는 restful_python_cahpter_01_01 폴더의 serializers_test_01.py 파일에 들어있다.

다음 행은 이렇게 해서 생서된 딕셔너리, 즉 rest_Framework.utils.serializer_helpers.ReturnDict 인스턴스를 보여준다.

아래 코드로 두 번째 게임 인슨턴스(game2)를 직렬화하자. 이 샘플 코드도 restful_python_chapter_01_01 폴더의 serializers_test_01.py파일에 들어 있다.


다음 행은 이렇게 해서 생성된 딕셔너리를 보여준다.

rest_framework.renderers.JSONRenderer 클래스를 사용하면 data 속성에 저장된 딕셔너리를 JSON 으로 쉽게 만들 수 있다. 아래 행은 이 클래스의 인스턴스를 생성하고 나서 render 메서드를 호출해 data 속성에 저장된 딕셔너리를 JSON으로 만든다. 이 샘플 코드는 restful_python_chapter_01_01폴더의 serializers_test_01.py파일에 들어있다.

다음 행은 render메서드를 두 번 호출해 생성된 출력을 보여준다.

이제 직렬화된 데이터에서 Game 인스턴스의 배치로 역방향 작업을 할 것이다. 아래 행은 jSON 문자열(직렬화된 데이터)로 부터 새 GAME인스턴스를 생성, 즉 역직렬화할 것이다. 이 샘플 코드도 restful_python_chapter_01_01 폴더의 serializers_test_01.py 파일에 들어 있다.

첫 번째 행은 새 게임(json_string_for_new_game)을 정의하는 JSON으로 새 문자열을 생성한다. 그리고 나서 이 코드는 문자열을 바이트로 변환하고 json_bytes_for_new_game변수에 변환 결과를 저장한다. django.utils.siz.ByteIO 클래스는 내장 메모리 바이트 버퍼를 사용해 버퍼링된 I/O 구현을 제공한다. 여기 코드는 이 클래스로 직렬화 데이터인 json_byte_for_new_game으로 앞서 생성한 JSON 바이트로부터 스트림을 만들어 stream_for_new_game 변수에 생성된 인스턴스를 저장한다.

rest_framework.parsers.JSONParser 클래스를 사용하면 스트림을 파이썬 모델로 쉽게 역직렬화해 파싱할 수 있다. 그 다음 행에서 이 클래스의 인스턴스르르 생성하고, stream_for_new_game을 인자로 해서 parse메서드를 호출하고 스트림을 파이썬 네이티브 데이터 타입으로 파싱한 다음, 그렬과를 parsed_new_game변수에 저장한다.

위의 행을 실행한 후, parssed_new_game은 스트림으로 부터 파싱된 파이썬 딕셔너리를 저장한다. 다음 행은 위의 코드 부분을 실행한 후에 생성된 출력을 보여준다.

아래 행은 GameSerializer 클래스를 사용해 스트림에서 파싱된 파이썬 딕셔너리로부터 new_game이라는 값이 완전히 채워진 Game 인스턴스를 생성한다. 이 샘플 코드도 restful_python_chapter_01_01폴더의 serializers_test_01.py 파일에 들어 있다.

먼저, 이 코드는 data키워드 인자로 전달된 스트림(parsed_new_game)으로부터 앞서 파싱한 파이썬 딕셔너리를 사용해 GameSerializer 클래스의 인스턴스를 생성한다. 그리고 나서 is_valied 메서드를 호출해 데이터가 유효한지 알아낸다. 직렬화기의 생성에 있어서 data키워드 인자를 전달할 때 직렬화된 데이터 표현에 접근하기 전에 항항 is_valid를 호출해야 한다.

이 메서드가 true를 반환하면 data 속성에 있는 직렬화된 표현에 접근할 수 있으므로 이 코드는 save 메서드를 호출해 데이터베이스에 해당 행을 삽입하고, 값이 완전히 채워진 Game 인스턴스의 속성중 하나를 출력한다.

위의 코드를 실행한 후에는 new_game1_instance와 new_game2_instance라는 2개의 Game인스턴스가 값들로 완전히 채워졌다.

위의 코드에서 배울 수 있듯이 장고 레스트 프레임워크를 사용하면 쉽게 객체에서 JSON 으로 직렬화하고, JSON에서 객체로 역직렬화 할 수 있는데, 이런일은 CRUD 연산을 수행해야 하는 레스트 풀 웹 API에 반드시 필요한 사항이다.

직렬화와 역질열화를 테스트한 장고 프로젝트 모듈의 셀을 끝내려면 다음 명령을 입력한다.
quit()

1.6 API 뷰 작성
이제 앞서 생성했던 GameSerializer 클래스를 사용해 API가 처리할 각 HTTP 요청에 대해 JSON 표현을 반환하는 장고 뷰를 만들 것이다. games/views.py 파일을 연다. 다음 행은 이 파일의 초반 코드를 보여주는데, 단 하나의 import 문과 함께 뷰를 만들라는 주석이 있다.

아래 행은 games/views.py 파일에 넣을 새 코드를 보여주는데 JSONResponse 클래스를 만들고 game_list와 game_detail이라는 2개 함수를 선언한 것이다. 여기서는 API의 첫 번째 버전을 만들 것이며, 이들 함수를 사용해 코드를 최대한 단순하게 만든다. 다음 예제에서는 클래스와 함께 좀 더 복잡한 코드로 작업할 것이다. 굵게 나타낸 행은 HTTP 동사에 따라 수행할 작업을 결정하기 위해 request.method속성 값을 평가하는 표현식을 나타낸 것이다. 이 샘플 코드 파일은 restful_python_chapter_01_01 폴더에 들어 있다.

JSONResponse 클래스는 django.http.HttpResponse 클래스의 서브 클래스다. 이 슈퍼 클래스는 내용이 문자열로 된 HTTP 응답을 나타낸다. JSONResponse 클래스는 내용을 JSON으로 렌더링한다. 이 클래스에서는 __init__ 메서드만 선언하는데, 이 메서드는  rest_framework.renderers.JSONRenderer 인스턴스를 생성해 render 메서들ㄹ 호출해서는 수신된 데이터를 JSON으로 렌더링한 후 반환된 바이트 분잘열을 CONTENT 로컬 변수에 저장한다. 그러고 나서 'application/json'의 응답 헤더에 'content_type'키를 값으로써 추가한다. 마지막으로, JSON바이트 문자열과 헤더에 추가된 키-값 쌍으로 베이스 클래스의 초기자initializer를 호출한다. 이렇게 해서 이 클래스는 두 함수를 통해 JSON응답을 쉽게 반환한다.

이 코드는 두 함수에 @csrf_exempt 데커레이터를 적용해 뷰가 사이트 간 요청 위조(CSRF,Cross_Site Request Forgery) 쿠키를 설정하게 한다. 이를 통해 제품 준비 상태의 웹 서비스를 나타내지 않는 이 예제에 대해 테스트를 단순화시킨다. 레스트풀 API에 보안 기능은 나중에 추가할 것이다.

장고 서버가 HTTP 요청을 받으면 장고는 HttpRequest 인스턴스, 특히 django.http.HttpRequest 객체를 생성한다. 이 인스턴스에는 HTTP 동사를 비롯한 요청에 대한 메타 데이터가 들어간다. method 속성은 요청에 사용된 메서드 또는 HTTP 동사를 나타내는 문자열을 제공한다.

장고는 요청을 처리할 적절한 뷰를 로드할 때, HttpRequest 인스턴스를 뷰 함수의 첫 번째 인자로 전달한다. 뷰 함수는 HttpResponse 인스턴스, 특히 django.http.HttpResponse 인스턴스를 반환해야 한다.

game_list 함수는 모든 게임을 나열하거나 새 게임을 생성한다. 이 함수는 request인자로 HttpRequest 인스턴스를 받는다. 이 함수는 GET 및 POST의 두 가지 HTTP동사에 따라 실행할 코드를 달리한다. HTTP 동사가 GET이라면, request.method == 'GET' 표현식이 True가 돼 모든 게임을 나열한다. 이 코드는 데이터베이스에서 모든 Game 객체를 얻고 GameSerializer를 사용해 모두 직렬화하며 GameSerializer에서 생성한 데이터로 작성된 JSONResponse 인스턴스를 반환한다. many = True 인자로 GameSerializer 인스턴스를 생성하면 여러 인스턴스를 직렬화할 것을 지정한 것이 된다. many 인자 값을 True로 설정하면 장고는 내부적으로 ListSerializer를 사용한다.

HTTP동사가 POST라면, HTTP 요청에 포함된 JSON 데이터로 새 게임을 생성한다. 먼저 JSONParser 인스턴스를 사용하고 request를 인자로 받는 parse 메서드를 호출해서 요청 속에 들어 있는 JSON으로 된 게임 데이터를 파싱해 그 결과를 game_data 로컬 변수에 저장한다. 그리고 나서 이렇게 저장된 데이터로 GameSerializer 인스턴스를 생성하고 is_valid메서드를 호출해 Game인스턴스가 유효한지 알아낸다. 그 인스턴스가 유효하면 save메서드를 호출해 인스턴스를 데이터베이스에 저장하고, 이 저장된 데이터와 함께 status.HTTP_201_CREATED인상태, 즉 201 Created로 나타나는 상태를 담은 JSONResponse를 반환한다.

200 OK 상태와는 다른 특성 상태를 반환해야 할 때면, rest_framework.status 모델에서 정의한 모듈 변수를 사용하고 고정된 숫자 값은 사용하지 않는 것이 좋다.

game_detail 함수는 기존 게임을 검색, 업데이트, 삭제한다. 이 함수는 reqeuset 인자로 HttpRequest 인스턴스를 받고 pk 인자로 검색, 업데이트, 삭제할 게임의 기본 키 또는 식별자를 받는다. 이 함수는 GET, PUT, DELETE의 세가지 HTTP 동사를 처리할 수 있다. 이 코드는 request.method 속성의 값을 점검해 HTTP 동사에 따라 실행할 코드를 결정한다. HTTP 동사가 무엇이든 관계없이 이 함수는 받은 pk를 pk 인자로 해서 Game.objects.get메서드를 호출해 지정된 기본 키 또는 식별자에 따라 데디터베이스에서 Game 인스턴스를 찾아 game 로컬 변수에 저장한다. 지정된 기본 키 또는 식별자의 게임이 데이터베이스에 없는 경우에는 status.HTTP_404_NOT_FOUND와 동일한 상태, 즉 404 Not Found 가 들어간 HttpResponse를 반환한다.

HTTP 동사가 GET이라면, game을 인자로 해서 GameSerializer인스턴스를 생성하고 기본적인 200 ok 상태가 들어간 JSONResonse를 통해 질결화된 게임 데이터를 반환한다. 가져온 JSON 직렬화 게임 데이터를 반환한 것이 된다.

HTTP동사가 PUT이라면, HTTP 요청에 포함된 JSON 데이터로 새 게임을 만들어 기존 ㅔㄱ임을 대체하게 된다. 먼저, JSONParser 인스턴스를 사용하고 reqeuset를 인자로 해서 parse메서드를 호출해 요청에서 JSON 데이터로 제공된 게임 데이터를 파싱한 후 그 결과를 game_data 로컬변수에 저장한다. 그리고 나서 앞서 데이터베이스로부터 얻은 Game 인스턴스와 기존 데이터(game_data)를 대체할 검색된 데이터롤 GameSerializer 인스턴스르 생성한다. 그런 다음, is_valid메서들ㄹ 호출해 Game인스턴스가 유효한지 점검한다. 인스턴스가 유효하면, save 메서드를 호출해 데이터베이스에 바뀐 값으로 인스턴스를 저장하고 저장된 데이터가 있는 본문과 기본적인 200 ok 상태가 들어간 JSONResponse를 반환한다. 파싱된 데이터로 유효한 Game인스턴스가 생성되지 않으면, status.HTTP_400_BAD_REQUEST와 동일한 상태, 즉 400 Bad Request가 들어간 JSONResponse를 반환한다.

HTTP동사가 DELETE라면, 앞서 데이터베이스로부터 얻은 Game 인스턴스(game)에서 delete메서드를 호출한다. delete 메서드를 호출하면 games_game테이블의 기본 행이 지워지므로 해당 게임을 더 이상 이용할 수 없게 된다. 그리고 나서 status.HTTP_204_NO_CONTENT와 같은 상태, 즉 204 No Content가 들어간 JSONResonse를 반환한다.

이제 games폴더에 urls.py라는 새 파이썬 파일, 즉 games/url.py파일을 만들어야 한다. 아래 행은 이 파일에 대한 코드이면, view.py 파일에 정의한 특정 함수를  실행하기 위해 해당되는 정규 표현식을 요청에 지정하는 URL 패턴을 정의한다. 이 샘플 코드 파일은 restful_python_chapter_01_01폴더에 들어 있다.

urlpattern리스틀 사용하면 URL을 뷰로 보낼 수 있다. 이코드는 해당 정규 표현식과 함께 뷰 모듈에서 정의한 뷰 함수를 인자로 해서 django.conf.urls.url 함수를 호출해 urlpatterns리스트의 각 항목에 대해 RegrexURLPattern인스턴스를 생성한다.

gamesapi 폴더의 urls.py 파일, 즉 game/urls.py 파일을 만들어야 한다. 아래 행은 이 파일에 대한 코드이며, views.py 팡리에 정의한 특정 함수를 실행하기 위해 해당되는 정규 표현식을 요청에 지정하는 URL 패턴을 정의 한다. 이 샘플를 코드파일은 restful_python_chapter_01_01폴더에 들어 있다.

urlpatterns 리스트를 사용하면 URL을 뷰로 보낼수 있다.  이 코드는 해당 정규 표현식과 함께 뷰 모듈에서 정의한 뷰 함수를 인자로 해서 django.conf.urls.url 함수를 호출해 urlpatterns리스트의 각 항목에 대해 RegexURLPattern 인스턴스를 생성한다.

gamesapi 폴더의 urls.py 파일, 즉 gamesapi/urls.py 팡리의 코드는 변경해야 한다. 이파일은 로트 URL 구성을 저의하므로 위에서 코딩한 gamesapi/urls.py 파일에 선언된 URL 패턴을 포함시켜야 한다. 아래 행은 gameapi/urls/.py 파일의 새 코드를 보여준다. 이 샘플 코드 파일도 restful_python_chapter_01_01폴더에 들어 있다.

이제 장고의 개발 서버를 시작해 HTTP요청을 작성하고 비보안 웹 API에 그 요청을 보낼수 있다. 다음 명령을 실행하라.

python manage.py runserver 0.0.0.0:8000

LAN에 연결된 다른 컴퓨터 또는 장치에서 HTTP 요청을 작성해 전송하려면, localhost 대신 개발 컴퓨터의 할당된 ip 주소를 사용해야 한다는 점에 유의하라. 예를 들어, 컴퓨터의 할당된 IPV4용 IP주소가 locahost:8000 대신 192.168.1.106인 경우 192.168.1.106:8000을 사용해야 한다.


1.7 API에 대한 HTTP 요청

장고 개발 서버는 localhost에서 실행 중이고, 포트 8000에서 리스닝하며 HTTP요청을 기다린다. 이제 개발 컴퓨터 똔느 LAN에 연결된 다른 컴퓨터 또는 장치에서 로컬로 HTTP 요청을 작성해 보낼 것이다. 우리는 다음과 같은 여러 종류의 도구를 사용해 이 책에서의 HTTP 요청을 작성하고 보낼 것이다.

- 명령 행 도구
- gui 도구
- 파이썬 코드
- 자바스크립트  코드

1.8 명령행 도구로 작업 - curl과 httpie
명령 행 도구부터 시작할 것이다. 명령 행 도구의 주요 장점 중 하나는 최초에 HTTP요청을 빌드한 후 쉽게 이 요청을 다시 실행할 수 있으므로 굳이 마우스를 사용하저나 화면을 탭해 요청을 실행하지 않아도 된다는 것이다. 배치 요청들이 들어간 스크립트를 쉽게 만들어 실행할 수도 있다. 모든 명령 행 도구에서 일어나듯이 GUI 도구에 비해 최초 요청을 수행하는데 보다 많은 시간이 걸릴 수 있지만, 일단 낳은 요청을 수행하고 나면 이전에 작성한 명령을 쉽게 다시 사용해 새 요청을 작성할 수 있다.

curl은 cURL이라고 하며, 쉽게 데이터를 전송할 수 있는 아주 유명한 오픈소스 명령 행 도구이자 라이브러리다. curl 명령행 도구를 사용해서도 HTTP 요청을 쉽게 작성하고 봰며 응답을 점검할 수 있다.

맥OS또는 리눅스에서 작업할 겨웅, 터미널을 열고 명령 행에서 curl을 사요할 수 있다. 모든 위도우 버젼에서 작업할 경우, Cygwin패키지를 설치 옵션에서 curl을 쉽게 설치하고 Cygwin터미널에서 실행할 수 있다.

curl -X GET :8000/games/

위의 명령은 GET http://localhost:8000/games/라는 HTTP 요청을 작성해 보낼 것이다. 이 요청은 views.game_list 함수, 즉 games/views.py 파일 내에서 선선한 game_list 함수를 찾아 실행하므로 레스트 풀 API에서 가장 간단한 경우다. 이 함수는 URL패턴에 매개 변수가 없으므로 request를 매개 변수로 받는다. 요청에 대한 HTTTP동사가 GET이기 때문에 request.method 특성은 'GET'과 같으므로 이 함수는 모든 Game 객체를 얻어 JSON 응담을 생성하는 코드를 실행할 것인데, 이렇게 생성된 응답에는 이들 Game 객체 모두가 직렬화된 상태로 들어 있게 된다.

다음 행은 HTTP 요청에 대한 응답의 예를 보여주는데, 이 json응답에는 3개의 Game 객체가 들어 있다.

curl -ix GET :8000/gaems/



















2018년 7월 20일 금요일

2.4 데이터를 이해를 위한 탐색과 시각화

먼저 테스트 세트를 떼어놓았는지 확인하고 훈련 세트에 대해서만 탐색을 하겠습니다. 또한 훈련 세트가 매우 크면 조작을 간단하고 빠르게 하기 위해 탐색을위한 세트를 별도로 샘플링할 수도 있습니다. 예제에서는 크기가 작으므로 훈련 세트 전체를 사용하겠습니다. 훈련 세트를 손상시키지 않기 위해 복사본을 만들어 사용합니다.

2.4.1 지리적 데이터 시각화
지리 정보(위도와 경도)기 있으니 모든 구역을 산점도로 만들어 데이터를 시각화하는 것은 좋은 생각입니다

이 그림은 캘리포니아 지역을 잘 나타내지만 어떤 특별한 패턴을 찾기는 힘듭니다. alpha옵션을 0.1로 주면 데이터 포인트가 밀집된 영역을 잘 보여줍니다.

일반적으로 우리 뇐ㄴ 그림에서 패턴을 잘 인식해내지만 더 두르러진 패턴을 보려면 매개변수를 다양하게 조절해봐야 합니다.

이제 주택 가격을 나타내보겠씁니다. 원의 반지름은 구역의 인구를 나타내고(매개변수 s), 색칼은 가격을 나타냅니다(매개변수c). 여기서는 미리 정의된 컬러 맵color map 중 파란색(낮은 가격)에서 빨간색(높은 가격)까지 범위를 가지는 jet을 사용합니다(매개변수 cmap).

아마 예상했겠지만 이 그림에서 주택 가격은 지역(예를 들면 바다와 인접한 곳)과 인구 밀도에 관련이 매우 크다는 사실을 알 수 있습니다. 이런 내용은 군집 알고리즘(clustering algorithm)을 사용해 주요 군집 을 찾고 군집의 중심까지의 거리를 재는 특성을 추가할 때 도움이 됩니다. 해안 근접성 특성이 유용할 수도 있지만, 북부 캘리포니아 지역의 해안가는 주택 가격이 그리 높지 않아 간단한 규칙이 적용되기 어렵습니다.

2.4.2 상관관계 조사
데이터셋이 너무 크지 않으므로 모든 특성 간의 표준 상관계수(standard correlation coefficient피어슨 r이라고도 부릅니다)를 corr()메서드를 이용해 쉽게 계산할 수 있습니다.

중간 주택 가격과 다른 특성 사이의 상관관계 크기가 얼마나 되는지 살펴보겠습니다.

상관관계의 범위는 -1 부터 1까지입니다. 1에 가까우면 강한 양의 상관관계를 가진다는 뜻이빈다. 옐르 들어 중간 주택 가격(median_house_value)은 중간 소득(median_income)이 올라갈 때 증가하는 경향이 있습니다. 계수가 -1에 가까우면 강한 음의 상관관계를 나타냅니다. 위도(latitude)와 중간 주택 가격 사이에는 약한 음의 상관관계가 보입니다(즉, 북쪽으로 갈수록 주택 가격이 조금씩 내려가는 경향이 있습니다). 마지막으로 계수가 0에 갂우면 선형적인 상관관계가 없다는 뜻입니다.

CAUTION 상관계수는 선형적인 상관관계만 측정합니다(x가 증가하면 y는 증가하거나 감소합니다). 그래서 비선형적인 관계는 잡을 수 없습니다(예를 들어 x가 0에 가까워지면 y가 증가합니다).

특성 사이의 상관관계를 확인하는 다른 방법은 숫자형 특성 사이에 산점도를 그려주는 판다스의 scatter_matrix 함수를 사용하는 것입니다. 여기서는 숫자형 특성이 11개이므로 총 11(2)= 121개의 그래프가 되어 한 페이지에 모두 나타낼 숭 ㅓㅄ으므로, 중간 주택 가격과 상관관계가 높아 보이는 특성 몇개만 살펴보겠습니다.

대각선 방향(왼쪽 위에서 오른쪽 아래로)은 각 변수 자신에 대한 것이라 그냥 직선이 되므로 유용하지 않습니다. 그래서 판다스는 이곳에 각 특성의 히스토그램을 그립니다(다른 오션도 가능합니다. 자세한 내용은 판다스 문서를 참고하세요).

중간 주택 가격(median_house_value)을 여측하는데 가장 유용할 것 같은 특성은 중간 소득(median_income)이므로 상관관계 산점도를 확대해보겠습니다

이 그래프는 몇 가지 사실을 보여줍니다. 첫째, 상관관계가 매우 강합니다. 위쪽으로 향하는 경향을 볼 수 있으며 포인트들이 너무 널리 퍼져 있지 않습니다. 둘째, 앞서 본 각격 제한 값이 $500,000에서 수평선으로 잘 보입니다. 하지만 이 그래프에서 직선에 가까운 형태를 더 볼 수 있습니다. $450,000 근처에 수형선이 보이고 $350,000와 $280,000에도 있고 그 아래 조금더 보입니다. 알고리즘에 데이터에서 이런 이상한 형태를 학습하지 않도록 해당 구역을 제거한느 것이 좋습니다.

2.4.3 특성 조합으로 실험
앞 절에서 데이터를 탐색하고 통찰을 얻는 여러 방법에 대한 아이디어를 얻었기 바랍니다. 머신러닝 알고리즘에 주입하기 전에 정제해야 할 조금 이상한 테이터를 확인했고, 특성 사이(특히 타깃 속성과의 사이)에서 흥미로운 상관관계를 발견했습니다. 어떤 특성은 코리가 두꺼운 분포라서 데이터 변형해야 할 것입니다(예를 들면 로그 스케일로). 물론 프로젝트마다 처한 사항은 다르겠지만 일반적인 아이디어는 비슷합니다.

머신러닝 알고리즘용 데이터를 실제로 준비하기 전에 마지막으로 해볼 수 있는 것은 여러 특서의 조합을 시도해보는 것입니다. 예를 들어 특정 구역의 방 개수는 얼마나 많은 가구수가 있는지 모른다면 그다지 유용하지 않습니다. 진짜 필요한 것은 가구당 방 개수입니다. 비슷하게 전체 침대 개수도 그 자체로 유용하지 않습니다. 즉, 방 개수와 비교하는 게 낫습니다. 가구당인원도 흥미로운 특성 조합일 것 같습니다. 

새로운 bedrooms_per_room 특성은 전체 방 개수나 침대 개수도다 중간 주택가격과의 상관관계가가 훨씬 높습니다. 확실히 침대/방의 비율이 낮은 집은 더 비싼 경향이 있습니다. 가구당 방 개수도 구역 내 전체 방 개수보다 더 유용합니다. 당연히 더 큰 집이 더 비쌉니다.

이 탐색 단계는 왁벽하지 않습니다. 시작을 잘해서 빨리 통찰을 얻는 것이 처음 프로토타입을 잘 만드는 데 도움이 될 것입니다. 하지만 이는 반복적인 과정입니다. 프로토타입을 만들고 실행한 후 그 결과를 분석해서 더 많은 통찰을 얻고 다시 이 탐색 단계로 돌아오게 됩니다.

2.3 데이터 가져오기

2.3.1 작업환경 만들기

2.3.2 데이터 다운로드

일반적으로 여러분이 다룰 데이터는 관계형 데이터베이스(또는 다른 데이터 저장소)에 들어 있고 여러 테이블, 문서, 파일로 나눠어 있을 것입니다. 이런 데이터에 접근하려면 먼저 보안 자격과 접근 권한이 있어야 하고 그 데이터의 구조를 잘 알고 있어야 합니다. 하지만 이 프로젝트는 간단합니다. 모든 데이터가 들어 있는 CSV(comma-separated value)파일인 housing.csv를 압축한 housing.tgz 파일을 내려받기만 하면 됩니다.

웹 브라우저를 사용해 이 파일을 내려받고 tar xzf housing.tgz 명령을 실행해서 압축을 풀어 CSV 파일을 얻을 수 있지만, 간단한 함수를 만들어 사용하면 더 편합니다. 특히 데이터가 정기적으로 변경되면 최근 데이터가 필요할 때마다 스크립트를 실행하면 되니 유용합니다(또는 스케줄링하여 주기적으로 자동 실행할 수도 있습니다). 데이터를 내려받는일을 자동화하면 여러 기기에 데이터셋을 설치해야 할 때도 편리합니다.

다음 코드가 데이터를 추출하는 함수입니다.

fetch_housing_data() 를 호출하면 작업공간에 datasets/housing 디렉터리를 만들고 housing.tgz 파일을 내려받고 같은 디렉터리에 압축을 풀어 housing.csv 파일을 만듭니다.

이제 판다스를 사용하여 데이터를 읽어 들이겠습니다. 데이터를 읽어 들이는 간단한함수도 하나 만듭니다.

이 함수는 모든 데이터를 담은 판다스의 데이터프레임 객체를 반환합니다.

2.3.3 데이터 구조 흝어보기
DataFrame의 head() 메서드를 사용해 처음 다섯 행을 확인해보겠습니다.

각 행은 하나의 구역을 나타냅니다. 특성은 longitude, latitude, housing_median_age, total_rooms, tatal_bedrooms, population, households, median_incom, median_house_value, ocean_proximity등 10개입니다.

info() 메서드는 데이터에 대한 간략한 설명과 특히 전체 행 수, 각 특성의 테이터 타입과 널null이 아닌 값의 갯수를 확인하는 데 유용합니다.

데이터셋에 20,640개의 샘플이 들어 있습니다. 머신러닝 프로젝트치고는 상당히 작은 편이지만, 처음 시작하기에는 적당한 크기입니다. total_bedrooms 특성은 20,433개만 널 값이 아닙니다. 207개의 구역은 이 특성을 가지고 있지 않다는 것을 뜻합니다. 나중에 이 문제를 적절히 처리하겠습니다.

ocean_proximity 필드만 빼고 모든 특성이 숫자형입니다. ocean_proximity 필드의 데이터 타입이 object이므로 어떤 파이썬 객체도 될 수 있지만, 데이터를 CSV파일에서 읽어 들였기 때문에 텍스트 특성일 것입니다. 처음 다섯 행을 출력했을 때 ocean_proximity 열의 값이 반복되는 것으로 보아서 이 특성은 아마도 범주형(categorical)일 것입니다. 어떤 카테고리가 있고 각 카테고리마다 얼마나 많은 구역이 있을지 value_counts()메서드로 확인합니다.

describe()메소드는 숫자형 특성의 요약 정보를 보여줍니다.

count, mean, min, max 행이 의미하는 바는 쉽게 할 수 있습니다. 널 값이 제외된 것을 볼 수 있습니다(예를 들어 tatal_bedrooms의 count는 20,640dl dkslrh 20,433입니다). std행은 값이 퍼져 있는 정도를 측정하는 표준편차를 나타냅니다. 25%, 50%, 75%행은 백분위수(percentile)를 나타냅니다. 백분위수는 전체 관측값에서 주어진 백분율이 속하는 하위 부분의 값을 나타냅니다. 예를 들어 25%의 구역은 housing)media)Age가 18보다 작고, 50%는 29보다 작고, 75%는 37보다 작습니다. 이를 25번째 백분위수(또는 제1사분위수), 중간값, 75번째 백분위수(또는 제 3사분위수)라고도 합니다.

데이터의 형태를 빠르게 검토하는 다른 방법은 각 숫자형 특성을 히스토그램으로 그려보는 것입니다. 히스토그램은 주어진 값의 범위(수평축)에 속한 샘플 수(수직축)를 나타냅니다. 특성마다 따로 히스토그램을 그릴 수도 있고 전체 데이터셋에 대해 hist()메소드를 호출하면 모든 숫자형 특성에 대한 히스토그램을 출력합니다. 얘를 들어 median_house)value가 약 $100,000인 구역은 800개가 조금 넘는 것을 볼 수 있습니다.

hist()메서드는 맷플로십을 사용하고 결국 화면에 그래프를 그리기 위해 사용자 컴퓨터의 그래픽 백엔드를 필요로 합니다. 그래서 그래프를 그리기 전에 맷를롯립이 사용할 백엔드를 지정해줘야 합니다. 주피터의 매직 명령 %matplotlib inline을 사용하면 편리합니다. 이 명령은 맷플롯립이 주피터 자체의 벡엔드를 사용하도록 설정합니다. 그러면 그래프는 노트븍 안에 그려지게 됩니다. 주피터 노트북에서 그래프를 그릴 때 show()메소드를 호출하는 것은 선택사항입니다. 주피터는 셀이 실행될 때 자동으로 그래프를 그려줍니다.

이 히스토그램에서 몇 가지 사항을 확인할 수 있습니다.

1. 먼저 중간 소득(median income)특성이 US달러로 표현되어 있지 않은 것 같습니다. 데이터를 취합한 팀에 확인해보니 스케일을 조정하고, 상한이 15(실제로는 15.0001), 하한이 0.5(실제로는 0.49999)가 되도록 만들었다고 합니다. 머신러닝에서는 전처리된 데이터를 다루는 경우가 흔하고 이것이 문제가 된지는 않지만 데이터가 어떻게 계산된 것인지 반드시 이해하고 있어야 합니다.

2. 중간 주택 연도(housing median age)와 중간 주택 가격(median house value)역시 최댓값과 최솟값을 한정햇습니다. 중간 주ㅐㄱ 가격의 경우는 타깃 속성(레이블)으로 사용되기 때문에심가한 문제가 될 수 있습니다. 가격이 한곗값을 넘어가지 않도록 머신러닝 알고리즘이 학습될지도 모릅니다. 이것이 문제가 될지 안될지는 클라이언트 팀(이 시스템의 출력을 사용할 팀)과 함께 검토하는 것이 좋습니다. 만약 그 팀에서 $500,000를 넘어가더라도 정확한 예측값이 필요하다고 한다면 우리가 선택할 수 있는 방법은 두가지입니다.

a. 한곗값 밖의 구역에 대한 정확한 레이블을 구합니다.
b. 훈련 세ㅡ에서 이런 구역을 제거합니다($500,000가 넘는 값에 대한 예측은 평가 결과가 매우 나쁠것이므로 테스트 세트에서도 제거합니다).

3. 특성들의 스케일이 서도 많이 다릅니다. 특성 스케일링에 대해서는 이 장의 뒷부분에서 살펴보겠습니다.
4. 마지막으로 많은 히스토그램의 꼬리가 두껍습니다. 가운데에서 왼쪽보다 오른쪽으로 더 멀리 뻗어 있습니다. 이런 형태는 일부 머신러닝 알고리즘에서 패턴을 찾기 어렵게 만듭니다. 나중에 이런 특성들을 좀 더 종 모양의 분포가 되도록 변형시키겠습니다.

이제 우리가 다룰 데이터를 많이 이해하게 되었습니다.
CAUTION_ 데이터를 더 갚게 들여다 보기 전에 테스트 세트를 따로 떠어놓아야 합니다. 그리고 테스트 세트를 절대 들여다보면 안 됩니다.

2.3.4 테스트 세트 만들기
이 단계에서 데이터 일부를 자진해서 떠어놓으라는 것이 이상하게 들리지 모르겟습니다. 지금 까지 데이터를 잠시 살펴봤을 뿐이고 어떤 알고리즘을 사용할지 정하기 전에 전체 데이터를 자세히 파악해야 하지 않을까요? 사실 맞습니다. 하지만 우리 뇌는 매우 과대적합되기  쉬운 엄청난 패턴 감지 시스템입니다. 막약 테스트 세트를 들여다본다면 테스트 세트에서 거으로 드러난 어떤 패턴ㅇ 속아 특정 머신러닝 모델을 선택하게 될지도 모릅니다. 이 테스트 세트로 일반화 오차를 추정하면 매우 낙관적인 추정이 되며 시스템을 론칭했을 때 기대한 성능이 나오지 않을 것입니다. 이를 테이터 스누핑(data snooping)편향이라고 합니다.

테스트 세트를 생성하는 일은 이론적으로 매우 간단합니다. 그냥 무작위로 어떤 샘플을 선택해서 데이터셋의 20%정도를 떼어놓으면 됩니다.

일반적인 해결책은 샘플의 식별자를 사용하여 테스트 세트로 보낼지 말지 정하는 것입니다(샘플이 고유하고 변경 불가능한 식별자를 가지고 있다고 가정합니다). 예를 들어 각 샘플마다 식별자의 해시값을 계산하여 해시의 마지막 바이트 값이 51(256의 20% 정도) 보다 작거나 같은 샘플만 테스트 세트로 보낼 수 있습니다. 이렇게 하면 여러 번 반복 실행되면서 데이터셋이 갱신되더라도 테스트 세트가 동일하게 유지됩니다. 새로운 테스트 세트는 새 샘플의 20%를 갖게 되지만 이전에 훈련 세트에 있던 샘플은 포함시키지 않을 것입니다.

행의 인텍스를 고유 식별자로 사용할 때 새 데이터는 데이터셋의 끝에 추가되어야 하고 어떤 행도 삭제되지 않아야 합니다. 이것이 불가능할 땐 고유 식별자를 만드는데 안전한 특성을 사용해야 합니다. 예를 들어 구역의 위도와 경도는 몇백 년 후까지 안정적이라고 보장할 수 있으므로 두 값을 연결하여 다음과 같이 ID를 만들수 있습니다.

사이킷런은 데이터셋을 여러 서브셋으로 나누는 다양항 방법을 제공합니다. 가장 간단한 함수는 train_test_split으로, 앞서 우리가 만든 split_train_test와 아주 비슷하지만 두 가지 특징이 더 있습니다. 첫째 앞서 설명한 난수 초긱값을 지젖ㅇ할 수 있는 random_state 매개변수가 있고, 둘째 행의 개수가 같은 여러 개의 데이터셋을 넘겨서 같은 인덱스를 기반으로 나눈 수 있습니다(이느 예를 들어 데이터프레임이 레이블에 따라 여러 개로 나눠어 있을 때 매우 유용합니다).

지금까지는 순수한 무작위 샘플링 방식을 보았습니다. 데이터셋이 충분히 크다면 (특히 특성 수에비해)일반적으로 괜찮지만, 그렇지 않다면 샘플링 편향이 생길 가능성이 큽니다. 설문 조사 기관에서 1,000명에게 질문 몇 개를 하려 할때 그냥 전화번호부에서 1,000명을 무작위로 뽑는 것은 아닙니다. 전체 인구를 대표할 수 있는 10,000명을 선택하기 위해 노력합니다. 미국 인구의 51.3%가 여성이고 48.7%가 남성이라면, 잘 구성된 설문조사는 샘플에서도 이 비율을 유지해야 합니다. 즉, 여성은 513명, 남성은 487명이어야 합니다. 이를 계층적 샘플링(stratified sampling)이라고 합니다. 전체 모수 계층(strata)이라는 동질의 그룹으로 나뉘고, 테스트 세트가 전체 모수를 대표하도록 각 계층에서 올바른 수의 샘플을 추출합니다. 기본 무작위 샘플링을 사용하면 49%보다 적거나 54%보다 많은 여성이 테스트 세트에 들어갈 확률이 약 12%입니다. 어느 방법을 사용하든 설문 조사 결과를 크게 편향시키게됩니다.

전문가가 중간 소득이 중간 주택 가격을 예측하는 데 매우 중요하다고 이야기해주었다고 가정합시다. 이 경우 테스트 세트가 전체 데이터셋에 있는 여러 소득 카테고리를 잘 대표해야합니다. 중간 소득이 연속적인 숫자형 특성이므로 소득에 대한 카테고리 특성을 만들어야 합니다. 중간 소득의 히스토그램을 조금 더 자세히 살펴보겠습니다([그림 2-8] 참조). 중간 소득 대분분은 $20,000~$50,000 사이에 모여 있지만 일부는 $60,000를 넘기도 합니다. 계층별로 데이터셋에 충분한 샘플 수가 있어야 합니다. 그렇지 않으면 계층의 중요도를 추정하는 데 편향이 발생할 것입니다. 이 말은 너무 많은 계층으로 나누면 안 된다는 뜻이고 각 계층이 충분히 커야 합니다. 다음 코드는 중간 소득을 1.5로 나누고(소득의 카테고리 수를 제한하기 위해), ceil함수를 사용하여 반올림해서 소득 카테고리를 특성을 만들고(이산적인 카테고리를 만들기 위해), 5보다 큰 카테고리는 5로 합니다.



2018년 7월 17일 화요일

2.2 큰 그림 보기

머신러닝 주택 회사에 오신 것을 환영합니다! 해야 할 일은 캘리포니아 인구조사 데이터를 사용해 캘리포니아의 주택 가격 모델을 만드는 것입니다. 이 데이터는 캘리포니아의 블록 그룹block group마다 인구population, 중간 소득median income, 중간 주택 가격median housing price 등을 담고 있습니다. 블록 그룹은 미국 인구조사국에서 샘플 데이터를 발표하는데 사용하는 최소한의 지리적 단위입니다(하나의 블록 그룹은 보통 600~3,000명의 인구를 나타냅니다). 여기서는 간단하게 구역이라고 부르겠습니다.

이 데이터 모델을 학습시켜서 다른 측정 데이터가 주어졌을 때 구역의 중간 주책 가격을 예측해야 합니다.

TIP 여러분은 잘 훈련된 데이터 과학자이므로 첫 번째로 할 일은 머신러닝 프로젝트 체크리스트를 준비하는 것입니다. 부록 B에 준비한 것을 사용해도 됩니다. 대부분의 머신러닝 프로젝트에 잘 드어맞지만 필요에 따라 수정하는 것이 좋습니다. 이 장에서는 체크리스트에 있는 많은 항목을 다루겠지만 스스로 충분히 이해할 수 있는 항목이나 다음 장에서 논의할 항목은 건너뛰겠습니다.

2.2.1 문제 정의
상사에게 첫 버째로 할 질문은 '비즈니스의 목적이 정확히 무엇인가요? 입니다. 아마도 모델 만들기가 최종 목적은 아닐 것입니다. 회사에서는 이 모델을 어떻게 사용해 이익을 얻으려고 할까요? 이는 문제를 어떻게 구성할지, 어떤 알고리즘을 선택할지, 모델 평가에 어떤 성능 지표를 사용할지, 모델 튜닝을 위해 얼마나 노력을 투여할지 결정하기 때문에 아주 중요한 질문입니다.

상사가 이 모델의 출력(구역의 중간 주택 가격에 대한 예측)이 여러 가지 다른 신호SIGNAL와 함께 다른 머신러닝 시스템에 입력으로 사용된다고 이야기합니다. 뒤따르는 시스템이 해당 지역에 투자할 가치가 있는지 결정합니다. 이 결정이 수익에 직결되기 때문에 올바르게 예측하는 것은 매우 중요합니다.

파이프라인
데이터 처리 컴포넌트component들이 연속되어 있는 것을 데이터 파이프라인pipeline이라고 합니다.
머신러닝 시스템은 데이터를 조작하고 변환할 일이 암ㅎ아 파이프라인을 사용하는 일이 매우 흔합니다.

보통 컴포넌트들은 비동기적으로 동작합니다. 각 컴포넌트는 많은 데이터를 추출해 처리하고 그 결과를 다른 데이터 저장소로 보냅니다. 그러면 일정 시간 후 파이프라인의 다음 컴포넌트가 그 데이터를 추출해 자신의 출력결과를 만드는 식입니다. 각 컴포넌트는 완전히 독립적입니다. 즉, 컴포ㅓㄴ트 사이의 인터페이스는 데이터 저장소뿐입니다. 이는 (데이터 흐름도 덕분에) 시스템을 이해하기 쉽게 만들고,각 팀은 각자의 컴포넌트에 집중할 수 있습니다. 한 컴포넌트가 다운되더라도 하위 컴포넌트는 문제가 생긴 컴포넌트의 마지막 출력을 사용해 (적어도 한동안은) 평상시와 같이 계속 동작할 수 있습니다. 그래서 시스템이 매우 견고해집니다.

한편 모니터일이 적절히 되지 않으면 고장난 컴포넌트를 한동안 모를 수 있습니다. 데이터가 만들어진지 오래 되면 전체시스템의 성능이 떨어집니다.

다음으로 던질 질문은 '현재 솔루션은 어떻게 구성되어 있나요? 입니다(만약 있다면). 이는 문제 해결 방법에 대한 정보는 물론이고 참고 성능으로도 사용할 수 있습니다. 상사가 현재는 구역 주택 가격을 전문가가 수동으로 추정한다고 알려주었습니다. 한 팀이 구역에 관한 최신 정보를 모으고 있는데 중간 주책 가격을 얻을 수 없을 때는 복잡한 규칙을 사용하여 추정을 합니다. 이는 비용과 시간이 많이 들고 추정 결과도 썩 좋지 않습니다. 실제 중간 주택 가격을 구해보면 팀에서 추정한 것이 10%이상 벗어났음을 알게 될 때가 많습니다. 이런 이유로 회사는 구역의 데이터를 기반으로 중간 주택 가격을 예측하는 모델을 훈련시키는 쪽이 유용하다고 생각합니다. 인구 조사 데이터에는 다른 데이터는 물론 수천 개 구역의 중간 주택 가격을 포함하므로 이 작업에 매우 적합한 데이터세으로 보입니다.

좋습니다. 이제 이런 정보들을 가지고 시스템을 설계할 준비가 되었습니다. 먼저 문제를 정의해야 합니다. 이는 지도 학습, 비지도 학습, 강화 학습 중 무엇일까요? 분류나 회귀인가요 아니면 다른 어떤 작업인가요? 배치 학습과 온라인 학습중 어느 것을 사용해야 하나요? 이 책을 계속 읽어 나가기 전에 잠시 멈추고 이 질문들의 답을 찾아보세요.

답을 찾았나요? 같이 한번 보겠습니다. 레이블된 훈련 샘플이 있으니(각 샘플이 개대 출력값, 즉 구역의 중간 주택 가격을 가지고 있습니다) 이는 전형적인 지도 학습 작업입니다. 또한 값을 예측해야 하므로 전형적인 회귀 문제입니다. 좀 더 구체적으로는 예측에 사용할 특성이 여러 개(구역의 인구, 중간 소득 등)이므로 다변량 회귀multivariate regression 문제입니다. 1 장에서는 1인당 GDP 하나의 특성을 기반으로 삶의 만족도를 예측했으므로 단변량 회귀univariate regression 문제 였습니다. 마지막으로 이 시스템으로 들어오는 데이터에 연속적인 흐름이 없으므로 빠르게 변하는 데이터에 적응하지 않아도 되고, 데이터가 메모리에 들어갈 만큼 충분히 작으므로 일반겅니 배치 학습이 적절합니다.

TIP 데이터가 매우 크면(맵리듀스MapReduce 기술을 사용하여) 배치 학습을 여러 서버로 분할하거나, 대신 오라인 학습기법을 사용할 수 있습니다.

2.2.2 성능 측정 지표 선택
다음 단계는 성능 측정 지표를 선택하는 것입니다. 회귀 문제의 전형적인 성능 지표는 평균 제곱근 오차Root Mean Square Error(RMSE)입니다. 오차가 커질수록 이 값은 더욱 커지므로 예측에 얼마나 많은 오류가 있는지 가늠하게 해줍니다. [식2-1]이 RMSE를 계산하는 공식입니다.

표기법
[식2-1]에서 이 책 전체에 걸쳐 사용할 대표적인 머신러닝 분야의 표기법 몇 가지를 볼 수 있습니다.
- m 은 RMSE를 측정할 데이터셋에 있는 샘플 수입니다.
예를 들어 2,000개 구역의 검증 세트에 대해 RMSE를 평가한다면 m = 2,000입니다.

- x(i)는 데이터셋에 있는 i번째 샘플(레이블은 제외한)의 저체 특성값의 벡터이고, y(i)는 해당 레이블(해당 샘플의 기대출력값)입니다.
예를 들어 데이터셋에 있는 첫 번째 구역의 경도 -118.29도, 위도 33.91도에 위치하고, 중간 소득이 $38,372이며, 주민이 1,416명, 중간 주택 가격이 $156,400라면 x(i)과 y(i)은 다음과 같습니다(여기서 다른 특성은 고려하지 않았습니다).
          - 118.29
            33.91
x(i) =      1,416
           38,372

y(i) = 156,400

- X는 데이터셋에 있는 모든 샘플의 모든 특성값(레이블은 제외)을 포함하는 행렬입니다. 샘플 하나가 하나의 행이어서 i번째 행은 x(i)의 전치와 같고 (x(i))T으로 표기합니다.
예를 들어 첫번째 구역이 앞의 예와 같다면 행렬 X는 다음과 같습니다.
      (x(1))T
      (x(2))T
         :                      - 119.29     33.91    1,416    38,372
X =    :                =        :              :           :          :
      (x(1999))T                 :              :           :          :
      (x(2000))T


- h는 시스템의 예측 함수며 가설hypothesis이라고 합니다. 시스템이 하나의 샘플 특성 벡터X(i)를 받으면 그 샘플에 대한 예측값 y(i) = h(x(i))를 출력합니다(y은 y-햇이라고 읽습니다).
예를 들어 시스템이 첫 번째 구역의 중간 주택 가격을 $158,400이라고 예측한다면 y(i) = h(x(i)) = 158,400입니다. 이 구역에 대한 예측 오차는 y(i) = y(1) = 2,000입니다.

- RMSE(X,h)는 가설 h를 사용하여 일련의 샘플을 평가하는 비용 함수입니다.
스칼라 값이나 함수를 나타낼 때는 m이나 y(i)또는 h와 같이 이탤릭체 소문자, 벡터를 나타낼 때는 x(i)와 같이 굵은 소문자, 행렬을 나타낼 때는 X와 같이 굵은 대문자를 사용하겠습니다.

RMSE가 일반적으로 회귀 문제에 선호되는 성능 측정 방법이지만 경우에 따라 다른 함수를 사용할 수도 있습니다. 예를 들어 이상치로 보이는 구역이 많다고 가정합시다. 이런 경우에는 평균 절대 오차Mean Absolute Error(평균 절대 편차Mean Absolute Deviation라고도 합니다)를 고려해볼수 있습니다. [식2-2]를 참조하세요

RME와 MAE 모두 예측값의 벡터와 타깃값의 벡터 사이의 거리를 재는 방법입니다. 거리 측정에는 여러 가지 방법(또는 노름nom)이 가능합니다.

- 제곱항을 합한 것의 접곱근(RMSE) 계산은 유크리디안 노름Euclidan nom에 해당합니다. 우리와 친숙한 거리 개념입니다. 또는 l2 노름이라고 부르며 ||.||2 (또는 그냥 ||.||)로 표시합니다.
- 절댓값의 합을 계산하는 것은 l1노름에 해당하며 ||.||1로 표기합니다. 이는 도시의 구획이 직각으로 나눠어 있을때 이 도시의 두 지점 사이의 거리를 측정하는 것과 같아 맨해튼 노름Manhattan nom이라고 합니다.

- 일반적으로 원소 n개인 벡터v의 lk 노름은 ||v||k = (|v0|k + |v1|k + ..... + |vn|k)1/k 으로 정의합니다. l0은 단순히 벡터에 있는 0이 아닌 원소의 수이고, l무한대 는 벡터에서 가장 큰 절댓값이 됩니다.

- 노름의 지수가 클수록 큰 값의 원소에 치우치며 작은 값은 무시됩니다. 그래서 RMSE가 MAE보다 조금 더 이상치에 민감합니다. 하지만 (종 모양 분포의 양 끝단처럼) 이상치가 매우 드물면 RMSE가 잘 맞아 일반적으로 널리 사용됩니다.

2.2.3 가정 검사
마지막으로 (여러분과 동료들이) 지금까지 만든 가정을 나열하고 검사해보는 것이 좋습니다. 이 과정에서 심각한 문제를 일찍 발견할 수도 있습니다. 예를 들어 시스템이 출력한 구역의 가격이 다음 머신러닝 시스템의 입력으로 들어가게 되는데 이 값이 있는 그대로 사용될 거라 가정했습니다. 하지만 하위 시스템에서 이 값을('저렴', '보통', '고가' 같은) 캍고리고 바꾸고 가격 대신 카테고리를 사용하면 어떻게 될까요? 이럴 때는 정확한 가격을 구하는 것이 전혀 중요하지 않습니다. 올바른 카테고리를 구하는 시스템이 필요합니다. 그렇다면 이제 이 문제는 회귀가 아니라 분류 작업이 됩니다. 몇 달 동안 회귀 시스템을 구축하고 나서야 이런 사실을 깨닫게 되는 것을 아무도 원치 않을 것입니다.
다행이도 하위 시스템을 담당하는 팀과 대화 후 그들이 카테고리가 아니라 실제 가격을 사용한다는 것을 확인했습니다. 훌륭합니다! 모든 것이 준비되었고 출발선에 섰습니다. 이제 코딩을 시작할 수 있습니다.






2018년 7월 10일 화요일

2.1 실제 데이터로 작업하기

머신러닝을 배울 때는 인공적으로 만들어진 데이터셋이 아닌 셀제 데이터로 실험해보는 것이 가장 좋습니다. 다행이 여러 분야에 걸쳐 공개된 데이터셋이 아주 많습니다. 다음 데이터를 구하기 좋은 곳입니다.

- 유명한 공개 데이터 저장소
   - UC 얼바인Irnine 머신러닝 저장소(http://archive.ics.uci.edu/ml/)
   - 캐글Kaggle 데이터셋(http://kaggle.com/datasets)
   - 아마존AWS데이터셋(http://aws.amazon.com/datasets)

- 메타 포털(공개 데이터 저장소가 나열되어 있습니다)
   - http://dataportals.org/
   - http://opendatamonitor.eu/
   - http://quandl.com

- 인기 있는 공개 데이터 저장소가 나열되어 있는 다른 페이지
   - 위키백과 머신러닝 데이터셋 목록(https://goo.gl/SJHN2K)
   - Quora.com 질문(http://goo.gl/zDR78y)
   - 데이터셋 서브레딧subreddit(http://www.reddit.com/r/datasets)

이 장에서는 StatLib 저장소에 있는 캘리포니아 주택 가격Califormia Housing Prices 데이터셋을 사용합니다. 이 데이터셋은 1990년 캘리포니아 인구조사 데이터를 기반으로 합니다. 최근 데이터는 아니지만 학습용으로 아주 좋기 때문에 최근데이터라고 생각하겠습니다. 교육목적으로 사용하기 위해 범주형 특성을 추가하고 몇 개 특성을 제외했습니다.

CHAPTER 2 머신러닝 프로젝트 처음부터 끝까지

이 장에서는 여러분이 부동산 회사에 막 고용된 데이터 과학자라고 가정하고 예제 프로젝트의 처음부터 끝까지 진행해보겠습니다. 진행할 주요 단계는 다음과 같습니다.

1. 큰 그림을 봅니다.
2. 데이터를 구합니다.
3. 데이터로부터 통찰을 얻기 위해 탐색하고 시각화합니다.
4. 머신러닝 알고리즘을 위해 데이터를 준비합니다.
5. 모델을 선택하고 훈련시킵니다.
6. 모델을 상세하게 조정합니다.
7. 솔루션을 제시합니다.
8. 시스템을 론칭하고 모니터링하고 유지 보수합니다.

1.5 테스트와 검증

모델이 새로운 샘플에 얼마나 잘 일반화될지 아는 유일한 방법은 새로운 샘플에 실제로 적용해 보는 것입니다. 이를 위해 실제 서비스에 모델을 넣고 잘 동작하는지 모니터링하는 방법이 있습니다. 이 방법이 괜찮긴 하지만 만약 모델이 아주 나쁘다면 고객이 불만을 토로할 테니 좋은 생각이 아닙니다.

더 나은 방법은 훈련 데이터를 훈련세트와 데스트 세트 두개로 나누는 것입니다. 이름에서도 알 수 있듯이 훈련 세트를 사용해 모델을 훈련시키고 테스트 세트를 사용해 모델을 테스트합니다. 새로운 샘플에 대한 오류 비율을 일반환 오차generalization error(또는 외부 샘플오차out-of-sample error)라고 하며, 테스트 세트에서 모델을 평가함으로써 이 오차에 대한 추정값estimation을 얻습니다. 이 값은 이전에 본 적이 없는 새로운 샘플에 모델이 얼마나 잘 작도할지 알려줍니다.
훈련 오차가 낮지만(즉, 훈련 세트에서 모델의 오차가 적음) 일반화 오차가 높다면 이는 모델이 훈련 데이터에 과대적합되었다는 뜻입니다.

TIP 보통 데이터의 80%를 훈련에 사용하고 20%는 데트트용으로 떠어놓습니다.

모델 평가는 아주 간단합니다. 그냥 테스트 세트를 사용하면 됩니다. 두 모델(선형 모델과 다항 모델)중 어떤 것을 선택할지 갈등하고 있다고 합시다. 어떻게 결정할 수 있을까요? 두 모델 모두 훈련 세트로 훈련시키고 테스트 세트를 사용해 얼마나 잘 일반화되는지 비교해보면 됩니다.

이제 선형 모델이 더 잘 일반화되었다고 가정하고 과대적합을 피하기 위해 규제를 적용하려고 합니다. 이때 하이퍼파라미터 값을 어떻게 선택할까요? 100개의 하이퍼파라미터 값으로 1000개의 다른 모델을 훈련시키는 방법이 있습니다. 일반화 오차가 가장 낮은 모델(5%라고 합시다)을 만드는 최적의 하이퍼파라미터를 찾았다고 가정합시다.

이제 이 모델을 실제 서비스에 투입합니다. 하지만 성능이 예상만큼 좋지 않고 오차를 15%나 만듭니다. 왜 그럴까요?

일반화 오차를 테스트 세트에서 여러 번 측정했으므로 모델과 하이퍼파라미터가 테스트세트에 최적화된 모델을 만들었기 때문입니다. 이는 모델이 새로운 데이터에 잘 작동하지 않을 수 있다는 뜻입니다.

이 문제에 대한 일반적인 해결 방법은 검증 세트validation set라 부르는 두 번째 홀드아웃holdout세트를 만드는 것입니다. 훈련 세트를 사용해 다양한 하이퍼파라미터로 여러 모델을 훈련시키고 검증 세트에서 최상의 성능을 내는 모델과 하이퍼파라미터를 선택합니다. 만족스러운 모델을 찾으면 일반화 오차의 추정값을 얻기 위해 테스트 세트로 단 한번의 최종 테스트를 합니다.

훈련 데이터에서 검증 세트로 너무 많은 양의 데이터를 뺏기지 않기 위해 일반적으로 교차 검증cross-validation 기법을 사용합니다. 훈련 세트르 여러 서브셋subset으로 나누고 각 모델을 이 서브셋의 조합으로 후련시키고 나머지 부분으로 검증합니다. 모델과 하이퍼파라미터가 선택되면 전체 훈련 데이터를 사용하여 선택한 하이퍼파라미터로 최종 모델을 훈련시키고 테스트 세트에서 일반화 오차를 측정합니다.

공짜 점심 없음 이론
모델은 관측한 것을 감소화한 것입니다. 간소화의 의미는 새로운 샘플에 일반적이지 않을 것 같은 불필요한 세부사항을 제거하는 것입니다. 그러나 어떤 데이터를 버리고 어떤 데이터를 남길지 정하기 위해 가정을 해야 합니다. 예를 들어 선형 모델은 데이터가 근본적으로 선형이고 샘플과 직선 사이의 거리는 무시할 수 있는 잡음이라고 가정합니다.

1996년에 발효한 유명한 논문에서 데이비드 월퍼트David Wolperts는 데이터에 관해 완벽하게 어떤 가정도 하지 않으면 한 모델을 다른 모델보다 선호할 근거가 없음을 보였습니다. 이를 공짜 점심 없음No Free Lunch(NFL) 이론이라 합니다. 어떤 데이터셋에서는 선형 모델이 가장 잘 들어잠지만 다른 데이터셋에서는 신경망이 잘 들어맞습니다. 경험하기 전에 더 잘 맞을 거라고 보장할 수 있는 모델은 없습니다(이 이론의이름이 유래된 이유입니다). 어떤 모델이 최선인지 확실히 아는 유일한 방법은 모든 모델을 평가해보는 것뿐입니다. 이것이 불가능하기 때문에 실정에서는 데이터에 관해 타당한 가정을 하고 적절한 모델 몇 가지만 평가합니다. 예를 들어 간단한 작업에서는 규제의 수준이 다양한 선형 모델을 평가하고, 복잡한 문제라면 여러 가지 신경망을 평가합니다.




1.4 머신러닝의 주요 도전 과제

간단하게 말해 우리의 주요 작업은 학습 알고리즘을 선택해서 어떤 데이터에 훈련시키는 것이므로 문제가 될 수 있는 두 가지 '나쁜 알고리즘'과 '나쁜 데이터'입니다. 이 절에서는 이 두가지에 대해 알아봅니다. 나쁜 데이터의 사례부터 알아보겠습니다.

1.4.1 충분하지 않은 양의 훈련 데이터
어린아이에게 사과에 대해 알려주려면 사과를 가리키면서 '사과'라고 말하기만 하면 됩니다(아마도 이 과정을 여러 번 반복해야 합니다). 그러면 아이는 색깔과 모야이 달라도 모든 종류의 사과를 구분할 수 있습니다. 정말 똑똑하지요.

머신러닝은 아직 이렇게까지는 못합니다. 대부분의 머신러닝 알고리즘이 잘 작동하려면 데이터가 많아야 합니다. 아주 간단한 문제에서조차도 수천 개의 데이터가 필요하고 이미지나 음성 인식 같은 복잡한 문제라면 수백만 개가 필요할지도 모릅니다(이미 만들어진 모델을 재사용할 수 없다면 말이죠).

믿을 수 없는 데이터의 효과
2001년에 발표한 유명한 노문에서 마이크로소프트 연구자인 미셀 반코와 에릭 브릴은 아주 간단한 모델을 포함하여 여러 다른 머신러닝 알고리즘에 충분한 데이터가 주어지면 복잡한  자연어 중의성 해소 문제를 거의 비슷하게 잘 처리한다는 것을 보였습니다.

이 논문의 저자들이 말한 것처럼 '이 결과가 제시하는 것은 시간과 돈을 알고리즘 개발에 쓰는것과 말뭉치 개발에 쓰는것 사이트 트레이드오프에 대해 다시 생각해봐야 한다는 것입니다.'

복잡한 문제에서 알고리즘보다 데이터가 더 중요하다는 이 생각은 2009년에 피터 노르빅등이 쓴 'The Unreasonable Effectiveness of Data'논문 때문에 더 유명해졌습니다. 하지만 기억할 점은 작거나 중간 규모의 데이터셋이 여전히 매우 흔하고, 훈련 데이터를 추가로 모으는 것이 항상 쉽거나 저렴한 일은 아니므로, 아직은 알고리즘을 무시하지 말아야 한다는 것입니다.

1.4.2 대표성 없는 훈련 데이터
일반화가 잘되려면 우리가 일반화하기 원하는 새로운 사례를 훈련 데이터가 잘 대표하는 것이 중요합니다. 이는 사례 기반 학습이나 모델 기반 학습 모두 마찬가지입니다.

예를 들어 앞서선형 모델을 훈련시키기 위해 사용한 나라의 집합에는 일부 나라가 빠져있어 대표성이 완벽하지 못합니다. 누락된 나라를 추가했을때 데이터가 어떻게 나타나는지 보여줍니다.

이 데이터에 선형 모델을 훈련시키면 실선으로 된 모델을 얻습니다. 반면 이전 모델은 점선으로 나타나 있습니다. 그림에서 알 수 있듯이 누락된 나라를 추가하면 모델이 크게 변경될 뿐만 아니라 이런 간단한 선형 모델은 잘 작도하지 않는다는 걸 확실히 보여줍니다. 매우 부유한 나라가 중간 정도의 나라보다 행복하지 않고, 반대로 일부 가나한 나라가 부유한 나라보다 행복한 것 같습니다.

대표성 없는 훈련 데이터를 사용했으므로 정확한 예측을 하지 못하는, 특히 매우 가난하거나 부유한 나라에서 잘못 예측하는 모델을 훈련시켰습니다.

일반화하려는 사례들을 대표하는 훈련 세트를 사용하는 것이 매우 중요하지만, 이게 생각보다 어려울 때가 많습니다. 샘플이 작으면 샘플링 잡음sampling noise(즉, 우연에 의한 대표성 없는 데이터)이 생기고, 매우 큰 샘플도 표본 추출 방법이 잘못되면 대표성을 띠지 못할 수 있습니다. 이를 샘플링 편향sampling bias이라고 합니다.

유명한 샘플링 편향 사례
아마도 샘플링 편향에 대한 가장 유명한 사례는 랜던과 루즈벨트가 경쟁했던 1936년 미국 대통령 선거에서 Literary Digest잡지사가 천만 명에게 우편물을 보내 수행한 대규모 여론조사일 것입니다. 240만 명의 응답을 받았고 랜던이 선거에서 57% 득표를 얻을 것이라고 높은 신뢰도로 예측했습니다.

하지만 루즈벨트가 62% 득표로 당선되었습니다. 문제는 Literary Digest의 샘플링 방법에 있었습니다.

- 첫째, 여론조사용 주소를 얻기 위해 전화번호부, 자사의 구독자 명부, 클럽 회원 명부등을 사용했습니다. 이런 명부는 모두 공화당(따라서 랜던)에 투표할 가능성이 높은 부유한 계층에 편중된 경향이 있습니다.

- 둘째, 우편물 수신자 중 25%미만의 사람이 응답했습니다. 이른 정치에 관심 없는 사람, Literary Digest를 싫어하느 사람과 다른 중요한 그룹을 제외시킴으로써 역시 표본을 편형되게 만들었습니다. 특히 이러한 종류의 샘플링 편향을 비응답 편향nonresponse bias이라고 합니다.

다른 예로 펑크 음악 비디올르 분휴하는 시스템을 만든다고 가정합시다. 이를 위한 훈련 세트를 유투브에서 '펑크 음악'을 검색해 마련할 수 있습니다. 하지만 이는 유투브 검색 엔진이 결괏값으로 유투브 내의 모든 펑크 음알그 대표하는 동영상을 반환한다는 가정하는 것입니다. 현실에서는 검색 결과가 인기 음악가들로 편중될 가능성이 큽니다. 그렇다면 어떻게 대량의 훈련 세트를 구할 수 있을까요?

1.4.3 낮은 품질의 데이터
훈련 데이터가 에러, 이상치outlier, 잡음(예를 들면 성능이 낮은 측정 장치 때문에)으로 가득하다면 머신러닝 시스템이 내재된 패턴을 찾기 어려워 잘 작동하지 않을 것입니다. 그렇기 때문에 훈련 데이터 정제에 시간을 투자할 만한 가치는 충분합니다. 사실 대부분의 데이터 과학자가 데이터 정제에 많은 시간을 쓰고 있습니다. 예를 들어보겠습니다.

- 일부 샘플이 이상치라는 게 명학하면 그것히 그긋들을 무시하거나 수종으로 잘못된 것을 고치는 것이 좋습니다.
- 일부 샘플에 특성 몇 개가 빠져있다면(예를 들면 고객 중 5%가 나이를 기록하지 않음), 이 특성을 모두 무시할지, 이 샘플을 무시할지, 빠진 값을 채울지(예를 들면 평균 나이로), 또는 이 특성을 넣은 모델과 제외한 모델을 따로 훈련시킬 것인지 등을 정해야 합니다.

1.4.4 관련 없는 특성
속담에도 있듯이 엉터리가 들어가면 엉터리가 나옵니다garbage in, garbage out. 훈련 데이터에 관련없는 특성이 적고 관련 있는 특성이 충분해야 시스템이 학습할 수 있을 것입니다. 성공적인 머신러닝 프로젝트의 핵심 요소는 훈련에 사용할 좋은 특성들을 찾는 것 입니다. 이 과정을 특성공학feature engineering이라 하며 다음 작업을 포함합니다.

- 특성 선택feature selection: 가지고 있는 특성 중에서 훈련에 가장 유용한 특성을 선택합니다.
- 특성 추출(feature extraction): 특성을 결합하여 더 유용한 특성을 만듭니다(앞서 본 것처럼 차원 축소 알고리즘을 사용할 수 있습니다.)
- 새로운 데이터를 수집해 새 특성을 만듭니다.

지금까지 나쁜 데이터의 사례를 살펴보았고 이제 나쁜 알고리즘의 예를 몇 가지 살펴보겠습니다.

1.4.5 훈련 데이터 과대적합
해외여행 중 택시운전사가 내 물건을 후쳤다고 가정합시다. 아마도 그 나라의 모든 택시운전사를 도둑이라고 생각할 수도 있습니다. 사람은 종종 과도하게 일반화를 하지만 주의하지 않으면 기계도 똑같은 함정에 빠질 수 있습니다. 머신러닝에서는 이를 과대적합(overfitting)이라고 합니다. 이는 모델이 훈현 데이터에 너무 잘 맞지만 일반성이 떨어진다는 뜻입니다.

[그림 1-22]는 고차원의 다항 회귀 모델이 삶의 만족도 훈련 데이터에 크게 과대적합된 사례를 보여줍니다. 간단한 선형 모델보다 이 모델이 훈련 데이터에 더 잘 맞는다라도 실제로 이 예측을 믿기는 힘든니다.

심층 신경망 같은 복잡한 모델은 데이터에서 미묘한 패턴을 감지할 수 있지만, 훈련 세트에 잡음이 많거나 데이터셋이 너무 작으면(샘플링 잡음이 발생하므로) 잡음이 섞인 패턴을 감지하게 됩니다. 당연히 이런 패턴은 새로운 샘플에 일반화되지 못합니다. 예를 들어 삶의 만족도 모델에 나라 이름 같은 관련 없는 특성을 많이 추가한다고 합시다. 이 경우 복잡한 모델이 이름에 'w'가 들어간 나라들의 삶의 만족도가 7보다 크다는 패턴을 감지할지 모릅니다. 뉴질랜드New Zealand(7.3), 노르웨이Norway(7.4), 스웨덴Sweden(7.2), 스위스Switzerland(7.5)가 여기에 속합니다. 이 W-만족도 규칙을 르완다Rewanda나 잠바브웨Zimbabwe에 일반화하면 얼마나 신뢰할 수 있을까요? 확실히 이 패턴은 우연히 훈련 데이터에서 찾은 것이지만 이 패턴이 진짜인지 잡음데이터로 인한 것인지 모델이 구분해낼 방법은 없습니다.

CAUTION_ 과대적합은 훈련 데이터에 있는 잡음의 양에 비해 모델이 너무 복잡할 때 일어납니다. 해결 방법은 다음과 같습니다.
- 파라미터 수가 적은 모델을 선택하거나(예를 들면 고차원 다항 모델보다 선형 모델), 훈련 데이터에 있는 특성 수를 줄이거나, 모델에 제약을 가하여 단순화시킵니다.
- 훈련 데이터를 더 많이 모읍니다.
- 훈련 데이터의 잡음을 줄입니다(예를 들면 오류 데이터 수정과 이상치 제거).

모델을 단순하게 하고 과대적합의 위험을 감소시키기 위해 모델에 제약을 가하는 것을 규제regularization라고 합니다. 예를 들어 앞서 만든 선형 모델은 두 개의 파라미터 @0과 @1을 가지고 있습니다. 이는 훈련 데이터에 모델을 맞추기 위한 두 개의 자유도degree of freedom를 학습 할고리즘에 부여합니다. 모델은 직석읜 절편(@0)과 기울기(@1)를 조절할 수 있습니다. 우리가 @1=0이 되도록 강제하면 알고리즘에 한 개의 자유도만 남게 되고 데이터에 적절하게 맞춰지기 힘들것 입니다. 즉, 할 수 있는 것이 훈련 데이터에 가능한 한 가깝게 되도록 직선을 올리거나 내리는 것이 전부이므로 결국 평균 근거가 됩니다. 진짜 아주 간단한 모델이네요! 알고리즘 @1을 수정하도록 허락되는 작은 값을 유지시키면 학습 알고리즘이 자유도 1과 2 사이의 적절한 어딘가에 위치할 것입니다. 이 자유도 2인 모델보다는 단순하고 자유도 1인 모델보다는 복잡한 모델을 만듭니다. 데이터에 왁변히 맞추는 것과 일반화를 위해 단순한 모델을 유지하는 것 사이의 올바른 균형을 찾는것이 좋습니다.

[그림 1-23]에 세 가지 모델이 있습니다. 점선은 나라 몇 개가 빠진 채로 훈련한 모델이고 파선은 모든 나라를 포함시켜 훈련한 두 번째 모델이며 실선은 첫 번째 모델과 같은 데이터에 규제를 적용해 만든 선형 모델입니다. 규제가 모델의 기울기를 더 작게 만들어 훈련 데이터에는 덜 맞지만 새로운 샘플에는 더 잘 일반화됩니다.

학습하는 동안 적용할 규제의 양은 하이퍼파라미터hyperparameter가 결정합니다. 하이퍼파라미터는 (모델이 아니라) 학습 알고리즘의 파라미터입니다. 그래서 학습 알고리즘으로부터 영향을 받지 않으며, 훈련 전에 미리 지정되고, 훈련하는 동안에는 상수로 남아 있습니다. 규제 하이퍼 파라미터를 매운 큰 값으로 지정하면 (기울기가 0에 가까운) 거의 평면한 모델을 얻게 됩니다. 그러면 학습 알고리즘이 훈련 데이터에 과대적합될 가능성은 거의 없겠지만 좋은 모델을 찾지 못합니다. 머신러닝 시스템을 구축할 때 하이퍼파라미터 튜닝은 매우 중요한 과정입니다(다음 장에서 자세한 예를 보겠습니다).

1.4.6 훈련 데이터 과소적합
이미 짐작했겠지만 과소적합underfitting은 과대적합의 반대입니다. 이는 모델이 너무 단순해서 데이터의 내재된 구조를 학습하지 못할 때 일어납니다. 옐르 들어 삶의 만족도에 대한 선형 모델은 과소적합되기 쉽습니다. 현실은 이 모델보다 더 복잡하므로 훈련 샘플에서조차도 부정확한 예측을 만들 것입니다.

이 문제를 해결하는 주요 기법은 다음과 같습니다.
- 파라미터가 더 많은 강력한 모델을 선택합니다.
- 학습 알고리즘에 더 좋은 특성을 제고합니다(특성 엔지니어링).
- 모델의 제약을 줄입니다(예를 들면 규제 하이퍼라라미터를 감소시킵니다).

1.4.7 한걸음 물러서서
지금까지 여러분은 벌써 머신러닝에 관해 많은 것을 배웠습니다. 그러나 많은 개념을 한꺼번에 다루다 보니 정리가 제대로 되지 않았을 것입니다. 한걸음 물러서서 큰 그림을 보겠습니다.

- 머신러닝은 명시적인 규칙을 코딩하지 않고 기계가 데이터로부터 학습하여 어떤 작업을 더 잘하도록 만드는 것입니다.
- 여러 종륭의 머신러닝 시스템이 있습니다. 지도 학습과 비지도 학습, 배치 학습과 오라인 학습, 사례 기간 학습과 모델 기반 학습 등입니다.
- 머신러닝 프로젝트에서는 훈련 세트에 데이터를 모아 학습 알고리즘에 주입합니다. 학습 알고리즘이 모델 기반이면 훈련 세트에 모델을 맞추기 위해 파라미터를 조정하고(즉, 훈련 세트에서 좋은 예측을 만들기 위해). 새로운 데이터에서도 좋은 예측을 만들거라 기대합니다. 알고리즘이 사례 기반이면 샘플을 기억하는 것이 학습이고 새오룬 샘플에 일반화하기 위해 유사도 측정을 사용합니다.
- 훈련 세트가 너무 작거나, 대표성이 없는 데이터이거나, 잡음이 많고 관련 없는 특성으로 오염되어 있다면 시스템이 잘 작동하지 않습니다(엉터리가 들어가면 엉터리가 나옵니다). 마지막으로, 모델이 너무 단순하거나(과소적합된 경우) 너무 복잡하지 않아야 합니다(과대적합된 경우).

마지막으로 다루어야 할 중요한 주제가 하나 있습니다. 모델을 학습시켰다 해서 새로운 샘플에 일반화되길 그냥 바라기만 해서는 안 됩니다. 모델을 평가하고 필요하면 상세하게 튜닝해야 합니다. 어떻게 하는지 살펴보겠습니다.