관문 4까지 다룬 것은 모두 수학적 대상이었습니다. 확률변수는 함수였고 분포는 측도였으며, 표본은 이론적으로 뽑힌 것이었습니다.
이제 실제 자료를 만집니다. 그리고 실제 자료에는 이론에 없던 것들이 있습니다.
| 이론에서 | 실제 자료에서 |
|---|---|
| 실수 x\in\mathbb | 비트 부동소수점, 유효숫자 자리 |
| 정수 n\in\mathbb | 비트에서 비트, 범위를 넘으면 되감김 |
| 값이 있다 | 결측이 있고 표현 방법이 여럿입니다 |
| 범주 | 수치로 넣는 순간 없던 순서가 생깁니다 |
| 표본 | 표에 담기고 다른 표와 연결됩니다 |
첫 강의가 타입과 스키마인 이유가 여기 있습니다. 자료를 어떤 타입으로 담았는지가 뒤의 모든 계산을 조용히 바꿉니다. 133강 문제 1에서 본 파국적 상쇄가 이론의 문제가 아니라 실무에서 매일 일어나는 일입니다.
문제. 정수와 실수 타입을 조사합니다.
(1) 정수 타입별 범위와 크기를 재세요.
(2) 범위를 넘으면 어떻게 되는지 확인하세요.
(3)float32와float64의 정밀도를 견주세요.
생각의 실마리. 타입은 비트 수를 정하는 일입니다. 비트가 적으면 공간을 아끼지만 담을 수 있는 값의 범위와 정밀도가 줄어듭니다.
풀이. (1) 검산 결과입니다.
| 타입 | 바이트 | 최솟값 | 최댓값 |
|---|---|---|---|
int8 |
|||
int16 |
|||
int32 |
|||
int64 |
만 개를 담으면 int8이 MB, int64가 MB입니다. 값이 작다는 것을 안다면 여덟 배를 아낍니다.
(2) 범위를 넘겨 봅니다.
| 계산 | 결과 |
|---|---|
int8로 |
|
int32로 |
예외가 아니라 되감김입니다. 오류가 나지 않고 조용히 틀린 값이 나오며, 이것이 가장 위험한 종류의 실패입니다.
(3) 실수 정밀도입니다.
| 값 | float32로 담은 값 |
float64로 담은 값 |
|---|---|---|
float32에서 이 과 같아집니다. 유효숫자가 자리뿐이기 때문입니다.
**정확히 담을 수 있는 정수의 한계가 **이며, 그 위의 홀수는 표현되지 않습니다.
이 문제에서 배우는 것: 타입은 저장과 정확도의 거래입니다.
정수 타입의 범위. 비트 부호 있는 정수는 을 담습니다.
부동소수점의 정밀도. 가수부 비트 수가 유효숫자를 정하며, 정확한 정수의 한계가 입니다.
| 형식 | 전체 비트 | 가수부 | 유효숫자 | 정확한 정수 한계 |
|---|---|---|---|---|
float16 |
약 자리 | |||
float32 |
약 자리 | |||
float64 |
약 자리 | 9\times10^ |
첫째 줄이 딥러닝에서 매일 쓰입니다. 240강의 혼합정밀도 학습이 float16으로 계산하고 float32로 누적하는 방식이며, 누적을 낮은 정밀도로 하면 값이 사라지기 때문입니다.
타입을 좁게 잡는 것이 언제나 이득은 아닙니다. 나중에 값이 커질 여지가 있으면 되감김이 조용히 자료를 망칩니다.
진단은 경계 확인입니다. 자료를 넣을 때 최댓값과 최솟값이 타입 범위 안에 있는지 검사하며, 문제 5의 스키마 제약이 그 자리입니다.
바로 확인 1.
확인 1-1. 비트 부호 있는 정수의 범위를 쓰세요.
답. 입니다.
확인 1-2. 정수 범위를 넘으면 무엇이 일어나는지 쓰세요.
답. 예외가 아니라 되감김이 일어나 조용히 틀린 값이 나옵니다.
확인 1-3. float32가 정확히 담는 정수의 한계를 쓰세요.
답. 입니다.
문제. 십진 소수를 이진으로 담을 때 무엇이 일어나는지 봅니다.
(1) 인지 확인하세요.
(2) 을 여러 번 더해 오차를 재세요.
(3) 결합법칙이 성립하는지 판정하세요.
생각의 실마리. 은 이진법으로 유한하게 적을 수 없습니다. 을 십진법으로 적을 수 없는 것과 같은 이유입니다.
풀이. (1) 검산 결과입니다.
| 항목 | 값 |
|---|---|
0.1 + 0.2 == 0.3 |
거짓 |
| 차이 | 5.551\times10^ |
| 의 실제 값 |
이 애초에 이 아닙니다. 가장 가까운 이진 분수로 반올림된 값이 저장됩니다.
(2) 을 반복해서 더합니다.
| 더한 횟수 | 단순 합 | 정확한 값 | 절대오차 |
|---|---|---|---|
| 1.11\times10^ | |||
| 1.41\times10^ | |||
| 10^ | 1.88\times10^ | ||
| 10^ | 1.33\times10^ |
오차가 쌓입니다. 만 건이면 백만분의 이며, 금액이라면 무시할 수 없습니다.
(3) 결합법칙을 확인합니다.
| 식 | 결과 |
|---|---|
| (1+10^{16})-10^ | |
괄호 위치만 바꿨는데 답이 다릅니다. 이 으로 반올림되어 이 사라졌기 때문입니다.
이 문제에서 배우는 것: 금액은 정수로 담습니다.
| 항목 | 값 |
|---|---|
| 부터 만까지 인 개수 | 개 |
| 을 만 번 더한 값 | |
| 정수로 세면 | 정확히 |
개가 원래 값으로 돌아오지 않습니다. 나누고 곱하는 왕복만으로 값이 바뀝니다.
금액은 최소 단위 정수로 담습니다. 원 단위면 원으로, 센트가 필요하면 센트로 담고 표시할 때만 나눕니다.
| 대상 | 권하는 타입 | 이유 |
|---|---|---|
| 금액 | 정수(최소 단위) 또는 십진수 | 반올림이 예측 가능합니다 |
| 개수 | 정수 | 원래 정수입니다 |
| 물리 측정값 | float64 |
원래 근사값입니다 |
| 비율, 확률 | float64 |
십진 정확도가 필요 없습니다 |
| 식별자 | 문자열 또는 정수 | 산술을 하지 않습니다 |
다섯째 줄이 흔한 실수입니다. 주민번호나 전화번호를 정수로 담으면 앞의 이 사라지고, 우편번호를 실수로 담으면 정밀도를 잃습니다. 산술하지 않는 것은 수가 아닙니다.
133강 문제 1의 파국적 상쇄와 같은 뿌리입니다. 크기가 아주 다른 수를 더하면 작은 쪽이 사라지며, 92강에서 이미 본 현상입니다.
대책이 셋입니다. 정수로 담거나, 정렬해서 작은 것부터 더하거나, 144강 심화 4의 웰포드처럼 수치적으로 안정한 알고리즘을 씁니다.
바로 확인 2.
확인 2-1. 이 정확히 저장되지 않는 이유를 쓰세요.
답. 이진법으로 유한하게 적을 수 없어 가장 가까운 값으로 반올림되기 때문입니다.
확인 2-2. 부동소수점 덧셈에서 결합법칙이 성립하는지 쓰세요.
답. 성립하지 않으며 괄호 위치가 답을 바꿉니다.
확인 2-3. 금액을 담는 권장 방법을 쓰세요.
답. 최소 단위 정수나 십진수로 담고 표시할 때만 나눕니다.
문제. 측정 척도를 구분하고 범주 인코딩을 조사합니다.
(1) 네 가지 척도와 허용되는 연산을 쓰세요.
(2) 요일을 부터 으로 넣으면 무엇이 깨지는지 보세요.
(3) 순환하는 범주를 어떻게 넣을지 판정하세요.
생각의 실마리. 수치로 넣는 순간 덧셈과 크기 비교가 가능해집니다. 원래 그 연산이 뜻을 갖지 않는 자료라면 없던 구조를 만들어 넣는 것입니다.
풀이. (1) 네 척도입니다.
| 척도 | 허용되는 연산 | 예 |
|---|---|---|
| 명목 | 같다, 다르다 | 혈액형, 요일 이름 |
| 순서 | 같다, 다르다, 크다, 작다 | 만족도 단계 |
| 구간 | 덧셈, 뺄셈 | 섭씨 온도, 연도 |
| 비율 | 덧셈, 뺄셈, 곱셈, 나눗셈 | 길이, 무게, 개수 |
섭씨 도가 도의 두 배가 아닙니다. 영점이 임의로 정해진 구간척도라 비가 뜻을 갖지 않으며, 켈빈으로 바꾸면 비율척도가 됩니다.
(2) 요일을 부터 으로 넣습니다.
| 두 요일 | 라벨 차이 | 실제 인접성 |
|---|---|---|
| (월, 화) | ||
| (토, 일) | ||
| (일, 월) | ||
| (월, 목) |
**일요일과 월요일이 붙어 있는데 라벨 차이가 **입니다. 거리 구조가 뒤집혔습니다.
(3) 사인과 코사인 두 개로 넣습니다.
| 요일 | 월요일과의 거리 | ||
|---|---|---|---|
| 월 | |||
| 화 | |||
| 토 | |||
| 일 |
월요일과 일요일의 거리가 화요일과의 거리와 같아집니다. 순환이 복원되었습니다.
이 문제에서 배우는 것: 인코딩이 구조를 정합니다.
| 범주의 성격 | 인코딩 | 주의 |
|---|---|---|
| 순서 없음, 개수 적음 | 원핫 | 열이 개 늘어납니다 |
| 순서 없음, 개수 많음 | 임베딩, 목표 인코딩 | 누출에 주의합니다 |
| 순서 있음 | 정수 라벨 | 간격을 같다고 가정합니다 |
| 순환 | 사인과 코사인 | 두 열을 함께 씁니다 |
| 계층 | 수준별로 나눠 인코딩 | 상위 수준도 함께 |
첫째 줄에서 열을 개로 할지 개로 할지가 갈립니다. 절편이 있는 선형모형에서는 개를 다 넣으면 완전 공선성이 생기므로 하나를 뺍니다. 153강 문제 4의 다중공선성이 그 이유입니다.
셋째 줄에 숨은 가정이 있습니다. 만족도 부터 를 그대로 넣으면 "매우 불만"과 "불만" 사이가 "보통"과 "만족" 사이와 같다고 가정하는 것입니다. 그것이 맞는지는 자료가 아니라 도메인이 정합니다.
둘째 줄의 목표 인코딩이 위험합니다. 범주별 목표 평균을 특징으로 쓰면 자료를 두 번 쓰게 되어 훈련 성능이 부풀며, 152강 심화 3의 해킹과 같은 종류의 문제입니다.
바로 확인 3.
확인 3-1. 네 가지 측정 척도를 쓰세요.
답. 명목, 순서, 구간, 비율입니다.
확인 3-2. 요일을 정수 라벨로 넣으면 무엇이 깨지는지 쓰세요.
답. 일요일과 월요일의 거리가 이 되어 순환 구조가 사라집니다.
확인 3-3. 순환 범주의 인코딩을 쓰세요.
답. 사인과 코사인 두 열로 넣습니다.
문제. 결측 표현 방법을 견줍니다.
(1)NaN의 성질을 확인하세요.
(2) 정수 타입에 결측을 담을 수 있는지 판정하세요.
(3) 표식 값이 무엇을 망치는지 재세요.
생각의 실마리. 결측은 값이 아니라 상태입니다. 그런데 저장 형식은 값만 담을 수 있으므로 어딘가에 상태를 끼워 넣어야 합니다.
풀이. (1) NaN의 성질입니다.
| 항목 | 결과 |
|---|---|
np.nan == np.nan |
거짓 |
NaN이 섞인 배열의 합 |
nan |
NaN이 섞인 배열의 평균 |
nan |
| 결측을 빼고 계산한 합과 평균 | 과 (관측 개) |
자기 자신과 같지 않습니다. 그래서 x == np.nan으로는 결측을 찾을 수 없고 전용 함수를 써야 합니다.
전파가 기본 동작입니다. 하나만 섞여도 전체 결과가 nan이 되며, 이것은 결함이 아니라 안전장치입니다. 조용히 무시하는 것보다 낫습니다.
(2) 정수 배열에 NaN을 넣으면 오류가 납니다. NaN은 부동소수점 규격의 값이라 정수 표현에 자리가 없습니다.
(3) 표식 값을 씁니다. 참 평균이 이고 결측이 입니다.
| 방법 | 평균 | 참 평균과의 차이 |
|---|---|---|
| 결측 없는 원자료 | ||
| 를 그대로 계산 | ||
NaN으로 두고 제외 |
가 평균을 만큼 끌어내렸습니다. 결측이 뿐인데 평균의 부호가 바뀌었습니다.
이 문제에서 배우는 것: 결측은 값이 아닙니다.
| 표현 방법 | 장점 | 위험 |
|---|---|---|
NaN |
전파해서 눈에 띕니다 | 정수에 못 씁니다 |
| 표식 값 | 어떤 타입에나 됩니다 | 계산에 조용히 섞입니다 |
| 별도 마스크 열 | 명시적입니다 | 열이 늘어납니다 |
| 널 허용 타입 | 타입 안전 | 지원이 필요합니다 |
둘째 줄이 실무에서 가장 많은 사고를 냅니다. , , , 빈 문자열이 모두 표식으로 쓰이는데 누군가는 그것을 값으로 읽습니다.
결측의 원인을 함께 기록합니다. 응답 거부인지, 해당 없음인지, 측정 실패인지가 뒤의 처리를 정합니다.
161강에서 결측 처리 방법을 본격적으로 다룹니다. 삭제할지 대체할지, 대체한다면 무엇으로 할지는 결측이 무작위인지에 달려 있으며, 177강 관측 데이터의 함정에서 그 조건을 다시 봅니다.
142강 심화 5의 생존 편향과 이어집니다. 결측이 값에 의존하면 남은 자료의 평균이 참 평균이 아니며, 표본을 늘려도 고쳐지지 않습니다.
바로 확인 4.
확인 4-1. NaN의 비교 성질을 쓰세요.
답. 자기 자신과도 같지 않아 등호로 찾을 수 없습니다.
확인 4-2. 표식 값의 위험을 쓰세요.
답. 계산에 조용히 섞여 결과를 오염시킵니다.
확인 4-3. 검산에서 가 평균을 얼마나 바꿨는지 쓰세요.
답. 결측 에서 평균을 만큼 끌어내렸습니다.
문제. 주문 표를 만들고 제약을 검사합니다.
(1) 네 가지 제약 위반을 찾으세요.
(2) 각 제약이 무엇을 막는지 쓰세요.
(3) 중복 저장이 왜 위험한지 보이세요.
생각의 실마리. 스키마는 열 이름과 타입의 목록이 아닙니다. 값이 만족해야 할 조건과 표 사이의 관계까지 포함합니다.
풀이. (1) 행 개, 열 개, 한 행이 바이트인 표입니다.
| 제약 | 위반 건수 | 위반한 값 |
|---|---|---|
기본키 유일성 order_id |
||
| 금액은 음수가 아닐 것 | ||
| 요일은 부터 사이 | ||
외래키 user_id 참조 무결성 |
네 가지를 모두 잡았습니다. 타입 검사만으로는 하나도 잡지 못합니다. 도 도 도 모두 올바른 int64이기 때문입니다.
(2) 각 제약이 막는 것입니다.
| 제약 | 막는 것 |
|---|---|
| 기본키 | 같은 주문이 두 번 세어지는 것 |
| 값 범위 | 물리적으로 불가능한 값 |
| 열거 제약 | 정의되지 않은 코드 |
| 외래키 | 존재하지 않는 대상 참조 |
| 널 허용 여부 | 필수 항목의 누락 |
(3) 중복 저장을 봅니다. 사용자 의 등급이 주문 세 건에 A, A, B로 적혀 있습니다.
한 곳만 고치면 서로 어긋납니다. 같은 사실이 여러 곳에 적혀 있으면 고칠 곳도 여러 곳이고, 하나라도 놓치면 자료가 자기 자신과 모순됩니다.
이 문제에서 배우는 것: 스키마와 정규화.
스키마. 열의 이름과 타입, 값의 제약, 표 사이의 관계를 함께 적은 것입니다.
| 구성 요소 | 예 |
|---|---|
| 열 이름과 타입 | amount int64 |
| 널 허용 여부 | user_id NOT NULL |
| 값 제약 | amount >= 0 |
| 유일성 | PRIMARY KEY (order_id) |
| 참조 무결성 | FOREIGN KEY (user_id) |
| 기본값 | created_at DEFAULT now() |
정규화. 같은 사실을 한 곳에만 두는 설계입니다.
| 단계 | 없애는 것 |
|---|---|
| 제정규형 | 한 칸에 여러 값이 들어가는 것 |
| 제정규형 | 기본키의 일부에만 의존하는 열 |
| 제정규형 | 다른 일반 열에 의존하는 열 |
갱신 이상이 정규화의 이유입니다. 사용자 등급을 주문 표에 넣으면 등급이 바뀔 때 그 사용자의 모든 주문 행을 고쳐야 하며, 한 건이라도 빠지면 모순이 생깁니다.
정규화가 언제나 옳은 것은 아닙니다. 분석용 저장소에서는 일부러 비정규화해 조인을 줄이기도 하며, 160강에서 그 거래를 다룹니다.
스키마는 문서가 아니라 실행되는 계약입니다. 적어만 두고 검사하지 않으면 없는 것과 같으며, 162강의 파이프라인이 그 검사를 자동화합니다.
바로 확인 5.
확인 5-1. 스키마가 타입 외에 포함하는 것을 쓰세요.
답. 값의 제약과 표 사이의 관계입니다.
확인 5-2. 타입 검사만으로 잡지 못하는 것을 예로 쓰세요.
답. 음수 금액이나 존재하지 않는 외래키처럼 타입은 맞고 뜻이 틀린 값입니다.
확인 5-3. 정규화가 없애는 문제를 쓰세요.
답. 같은 사실이 여러 곳에 적혀 생기는 갱신 이상입니다.
| 개념 | 내용 |
|---|---|
| 비트 정수 범위 | |
| 정수 오버플로 | 예외가 아니라 되감김 |
float32 정확 정수 한계 |
|
float64 유효숫자 |
약 자리 |
| 의 실제 값 | |
| 결합법칙 | 성립하지 않습니다 |
| 금액 | 최소 단위 정수로 담습니다 |
| 측정 척도 | 명목, 순서, 구간, 비율 |
| 순환 범주 | 사인과 코사인 두 열 |
NaN |
자기 자신과 같지 않고 전파합니다 |
| 표식 값 | 계산에 조용히 섞입니다 |
| 스키마 | 타입 + 제약 + 관계 |
| 정규화 | 같은 사실을 한 곳에만 |
| 대상 | 권하는 타입 |
|---|---|
| 금액 | 정수(최소 단위) |
| 개수 | 정수 |
| 물리 측정값 | float64 |
| 확률, 비율 | float64 |
| 식별자 | 문자열 |
| 자주 하는 실수 | 바로잡기 |
|---|---|
| 금액을 실수로 담습니다 | 최소 단위 정수로 담습니다 |
| 좁은 정수 타입에 큰 값을 넣습니다 | 조용히 되감깁니다 |
| 범주를 정수 라벨로 넣습니다 | 없던 순서와 거리가 생깁니다 |
| 순환 범주를 정수로 넣습니다 | 일요일과 월요일이 멀어집니다 |
| 결측을 로 표시합니다 | 계산에 섞여 평균을 오염시킵니다 |
x == NaN으로 결측을 찾습니다 |
언제나 거짓입니다 |
| 스키마를 문서로만 둡니다 | 검사하지 않으면 없는 것입니다 |
| 같은 사실을 여러 표에 적습니다 | 갱신 이상이 생깁니다 |
문제 6. 비트 부호 있는 정수의 범위를 쓰세요.
답. 입니다.
문제 7. 정수 범위를 넘겼을 때 무엇이 일어나는지 쓰세요.
답. 예외가 아니라 되감김이 일어나 조용히 틀린 값이 나옵니다.
문제 8.
float32가 정확히 담는 정수의 한계와 그 근거를 쓰세요.
답. 이며 가수부가 비트이기 때문입니다.
문제 9. 이 정확히 저장되지 않는 이유를 쓰세요.
답. 이진법으로 유한하게 적을 수 없어 반올림되기 때문입니다.
문제 10. 부동소수점 덧셈의 결합법칙을 판정하고 예를 드세요.
답. 성립하지 않으며 이고 입니다.
문제 11. 금액을 담는 권장 방법과 이유를 쓰세요.
답. 최소 단위 정수로 담으며 반올림이 예측 가능하기 때문입니다.
문제 12. 네 가지 측정 척도와 각각 허용되는 연산을 쓰세요.
답. 명목은 같다 다르다, 순서는 크기 비교까지, 구간은 덧셈 뺄셈, 비율은 곱셈 나눗셈까지입니다.
문제 13. 섭씨 도가 도의 두 배가 아닌 이유를 쓰세요.
답. 영점이 임의로 정해진 구간척도라 비가 뜻을 갖지 않기 때문입니다.
문제 14. 요일을 정수 라벨로 넣을 때의 문제와 대안을 쓰세요.
답. 일요일과 월요일의 거리가 이 되며 사인과 코사인 두 열로 넣습니다.
문제 15. 원핫 인코딩에서 열을 개로 하는 이유를 쓰세요.
답. 절편이 있으면 개를 다 넣을 때 완전 공선성이 생기기 때문입니다.
문제 16.
NaN의 두 성질을 쓰세요.
답. 자기 자신과 같지 않고 계산에 전파됩니다.
문제 17. 표식 값이 위험한 이유와 검산의 수치를 쓰세요.
답. 계산에 조용히 섞이며 결측 에서 평균을 만큼 끌어내렸습니다.
문제 18. 스키마의 구성 요소와 정규화가 없애는 문제를 쓰세요.
답. 타입과 제약과 관계이며 정규화는 갱신 이상을 없앱니다.
심화 1. 부동소수점 규격을 정리하세요.
| 형식 | 부호 | 지수 | 가수 | 최대 상대오차 |
|---|---|---|---|---|
float16 |
4.88\times10^ | |||
bfloat16 |
3.91\times10^ | |||
float32 |
5.96\times10^ | |||
float64 |
1.11\times10^ |
둘째 줄이 딥러닝을 위해 만들어졌습니다. float16과 비트 수는 같은데 지수를 float32와 맞춰 범위를 넓히고 정밀도를 희생했습니다. 기울기가 아주 작아지는 상황에서 범위가 정밀도보다 중요하기 때문입니다.
| 특수한 값 | 뜻 |
|---|---|
inf, -inf |
넘침 또는 으로 나눔 |
nan |
정의되지 않음 |
-0.0 |
음의 영, 과 같다고 비교됨 |
| 비정규수 | 아주 작은 값, 정밀도가 줄어듭니다 |
넷째 줄이 성능 문제를 냅니다. 비정규수 연산이 훨씬 느린 하드웨어가 있어, 학습 중 값이 그 영역에 들어가면 갑자기 느려집니다.
심화 2. 시간을 어떻게 담는지 정리하세요.
시간은 타입 문제가 가장 많이 생기는 자리입니다.
| 항목 | 주의 |
|---|---|
| 시간대 | 저장은 UTC, 표시만 지역시 |
| 서머타임 | 하루가 시간이거나 시간입니다 |
| 윤초 | 어떤 분은 초입니다 |
| 해상도 | 초, 밀리초, 나노초 |
| 범위 | 나노초 정수는 서기 년에 넘칩니다 |
| 달력 | 월의 길이가 다르고 윤년이 있습니다 |
둘째 줄이 실제 사고를 냅니다. 서머타임 전환일에 같은 지역시가 두 번 나타나므로, 지역시로 저장하면 순서를 복원할 수 없습니다.
다섯째 줄이 문제 1과 같은 이야기입니다. 비트 나노초 정수의 범위가 약 년이라, 기준점이 년이면 년에 되감깁니다.
날짜 차이 계산에서 "한 달 뒤"가 모호합니다. 월 일의 한 달 뒤는 월 일이 없으므로 규칙을 정해야 하며, 그 규칙이 라이브러리마다 다릅니다.
심화 3. 문자열과 인코딩을 정리하세요.
| 항목 | 내용 |
|---|---|
| 바이트와 문자 | UTF-에서 한글은 바이트입니다 |
| 길이 | 바이트 길이와 문자 길이가 다릅니다 |
| 정규화 | 같은 글자를 여러 방식으로 적을 수 있습니다 |
| 대소문자 | 언어마다 규칙이 다릅니다 |
| 정렬 | 사전 순서가 코드포인트 순서와 다릅니다 |
| 공백 | 보이지 않는 문자가 섞입니다 |
셋째 줄이 조인을 깨뜨립니다. 자모를 결합해 적은 한글과 완성형으로 적은 한글이 눈으로는 같은데 바이트가 다릅니다. 정규화하지 않으면 같은 값이 다른 값으로 취급됩니다.
여섯째 줄이 가장 흔합니다. 앞뒤 공백, 줄바꿈, 비파괴 공백이 섞여 있으면 그룹이 갈라지며, 161강의 정제에서 첫 번째로 하는 일이 이것입니다.
심화 4. 저장 형식을 정리하세요.
| 형식 | 성격 | 적합한 자리 |
|---|---|---|
| CSV | 사람이 읽습니다 | 교환, 소량 |
| JSON | 중첩을 담습니다 | 로그, 설정 |
| Parquet | 열 지향, 압축 | 분석 |
| Arrow | 메모리 표준 | 프로세스 사이 |
| 데이터베이스 | 제약과 트랜잭션 | 운영 |
첫째 줄에 타입이 없습니다. CSV는 모든 것이 문자열이라 읽을 때 타입을 추론해야 하고, 그 추론이 자주 틀립니다. 앞의 이 사라지고 날짜가 숫자가 되며 큰 정수가 실수가 됩니다.
셋째 줄이 분석의 기본값입니다. 열 지향이라 필요한 열만 읽을 수 있고, 스키마를 파일 안에 담아 타입 추론이 필요 없습니다.
타입을 명시적으로 지정하는 것이 언제나 낫습니다. 추론에 맡기면 파일마다 다른 타입이 나와 이어 붙일 때 깨집니다.
심화 5. 타입과 스키마에서 흔히 저지르는 실수를 정리하세요.
| 실수 | 결과 |
|---|---|
| CSV 타입 추론에 맡깁니다 | 파일마다 타입이 달라집니다 |
| 식별자를 정수로 담습니다 | 앞의 이 사라집니다 |
| 금액을 실수로 담습니다 | 반올림 오차가 쌓입니다 |
| 범주를 라벨 정수로 넣습니다 | 없던 순서가 생깁니다 |
| 결측을 으로 채웁니다 | 값과 구별되지 않습니다 |
| 시간을 지역시로 저장합니다 | 서머타임에 순서가 깨집니다 |
| 스키마 검사를 생략합니다 | 오염이 하류로 퍼집니다 |
| 열 이름에 뜻을 담지 않습니다 | 단위와 정의가 사라집니다 |
다섯째 줄이 가장 조용합니다. 매출이 인 것과 매출을 모르는 것은 전혀 다른데 한번 채우면 되돌릴 수 없습니다.
여덟째 줄이 시간이 지나면 치명적입니다. amount가 원인지 달러인지, 세금 포함인지, 어느 시점 기준인지가 열 이름에 없으면 몇 달 뒤 아무도 모릅니다. 데이터 사전을 스키마와 함께 관리해야 합니다.
진단은 계약 검사입니다. 자료가 들어올 때마다 타입, 범위, 유일성, 참조, 결측률을 검사하고 위반이 있으면 멈춥니다. 162강에서 그 파이프라인을 세웁니다.
심화 6. 기계학습에서 타입이 문제를 일으키는 자리를 정리하세요.
| 자리 | 무엇이 문제인가 | 관련 강의 |
|---|---|---|
| 혼합정밀도 학습 | 낮은 정밀도로 누적하면 값이 사라집니다 | 240강 |
| 손실의 로그 | 이 -inf가 됩니다 |
249강 |
| 소프트맥스 | 지수가 넘칩니다 | 249강 |
| 배치 정규화 | 분산이 이면 으로 나눕니다 | 244강 |
| 특징 스케일 | 크기가 다르면 조건수가 나빠집니다 | 216강 |
| 범주 인코딩 | 없던 순서가 학습됩니다 | 216강 |
| 결측 처리 | 대체 방법이 편향을 만듭니다 | 161강 |
| 양자화 | 비트로 줄일 때 범위 설정 | 340강 |
둘째와 셋째 줄이 같은 대책을 씁니다. 로그와 지수를 함께 다룰 때는 최댓값을 빼고 계산하며, 149강 문제 2에서 우도를 로그로 다룬 것과 같은 이유입니다.
첫째 줄이 문제 1의 직접적 응용입니다. float16으로 기울기를 계산하되 가중치 갱신은 float32로 누적하는데, 그렇게 하지 않으면 작은 갱신이 문제 2의 처럼 사라집니다.
여덟째 줄이 문제 1의 정수 타입 선택과 같은 거래입니다. 비트로 줄이면 메모리가 네 배 줄지만 범위와 정밀도를 잃으며, 어느 범위를 담을지 정하는 일이 양자화 설계의 전부입니다.
float32가 정확히 담는 정수의 한계를 쓰세요.NaN의 두 성질을 쓰세요.정답.
| 기호 | 읽는 법 | 뜻 |
|---|---|---|
int8 ~ int64 |
정수 타입 | 비트 수가 범위를 정합니다 |
float32, float64 |
부동소수점 | 가수부가 정밀도를 정합니다 |
bfloat16 |
브레인 플로트 | 지수를 넓힌 비트입니다 |
| 되감김 | overflow wraparound | 범위를 넘으면 반대쪽으로 갑니다 |
| 가수부 | mantissa | 유효숫자를 담는 비트입니다 |
NaN |
not a number | 정의되지 않은 값입니다 |
| 표식 값 | sentinel value | 결측을 나타내는 특정 값입니다 |
| 명목척도 | nominal scale | 같다 다르다만 뜻이 있습니다 |
| 구간척도 | interval scale | 영점이 임의입니다 |
| 비율척도 | ratio scale | 영점이 절대적입니다 |
| 원핫 인코딩 | one-hot encoding | 범주마다 열 하나입니다 |
| 스키마 | schema | 타입과 제약과 관계입니다 |
| 기본키 | primary key | 행을 유일하게 정합니다 |
| 외래키 | foreign key | 다른 표의 행을 가리킵니다 |
| 참조 무결성 | referential integrity | 가리키는 대상이 존재합니다 |
| 정규화 | normalization | 같은 사실을 한 곳에만 둡니다 |
| 갱신 이상 | update anomaly | 중복 저장이 낳는 모순입니다 |
다음은 159강 관계형 질의의 논리입니다. 이 강의가 자료를 표에 담았고, 그 표에서 원하는 것을 꺼내는 일이 다음 주제입니다.
선택, 사영, 조인 세 연산이 질의의 뼈대이며, 그 논리가 26강의 집합 연산과 22강의 술어 논리입니다. 조건을 AND로 이을지 OR로 이을지, NOT을 어디에 붙일지가 곧 자료를 어떻게 자르는지를 정합니다.
그리고 NULL이 논리를 삼값으로 바꿉니다. 문제 4에서 본 결측이 질의에서는 참도 거짓도 아닌 세 번째 값이 되며, 그것을 모르면 조건이 조용히 행을 빠뜨립니다.
import numpy as np
rng = np.random.default_rng(20260826)
# --- 문제 1: 타입이 무엇을 바꾸는가 -------------------------------------
print(" 같은 정수 100 을 여러 타입으로 담아 봅니다")
print(" 타입 바이트 최솟값 최댓값")
for t in [np.int8, np.int16, np.int32, np.int64]:
i = np.iinfo(t)
print(" %-10s %6d %20d %20d" % (np.dtype(t).name, np.dtype(t).itemsize, i.min, i.max))
print(" 100 만 개를 담을 때 int8 은 %.2f MB, int64 는 %.2f MB 입니다"
% (1e6 * 1 / 1e6, 1e6 * 8 / 1e6))
print(" 범위를 넘으면 조용히 넘어갑니다. 예외가 아니라 되감기입니다")
a = np.array([127], dtype=np.int8)
b = np.array([1], dtype=np.int8)
print(" int8 로 127 + 1 = %d" % int((a + b)[0]))
c = np.array([2000000000], dtype=np.int32)
print(" int32 로 2000000000 + 2000000000 = %d" % int((c + c)[0]))
print(" 실수도 정밀도가 갈립니다. 유효숫자가 float32 는 7 자리, float64 는 16 자리입니다")
print(" 값 float32 로 담은 값 float64 로 담은 값")
for v in [0.1, 1e8 + 1, 16777217.0]:
print(" %-16.1f %22.10f %22.10f" % (v, np.float32(v), np.float64(v)))
print(" float32 에서 1e8 + 1 이 1e8 과 같은가: %s"
% (np.float32(1e8 + 1) == np.float32(1e8)))
print(" float32 가 정확히 담는 정수의 한계는 2^24 = %d 입니다" % (2 ** 24))
print(" 타입을 고르는 일은 저장 공간과 정확도를 맞바꾸는 일입니다")
# --- 문제 2: 돈을 실수로 다루면 -----------------------------------------
print(" 0.1 + 0.2 가 0.3 과 같은지 봅니다")
print(" 0.1 + 0.2 == 0.3 : %s, 차이 %.20f"
% (0.1 + 0.2 == 0.3, abs((0.1 + 0.2) - 0.3)))
print(" 0.1 을 float64 로 담은 실제 값 %.20f" % 0.1)
print(" 0.1 을 여러 번 더하면 오차가 쌓입니다")
print(" 더한 횟수 단순 합 정확한 값 절대오차")
for n in [10, 1000, 100000, 1000000]:
s = 0.0
for _ in range(n):
s += 0.1
print(" %13d %18.8f %16.1f %14.2e" % (n, s, n * 0.1, abs(s - n * 0.1)))
print(" 덧셈 순서를 바꾸면 결과가 달라집니다. 결합법칙이 깨집니다")
print(" (1 + 1e16) - 1e16 = %.1f, 1 + (1e16 - 1e16) = %.1f"
% ((1.0 + 1e16) - 1e16, 1.0 + (1e16 - 1e16)))
print(" 돈은 정수로 담습니다. 원 단위 대신 최소 단위 정수로 바꿉니다")
bad = sum(1 for v in range(1, 100001) if (v / 100.0) * 100.0 != float(v))
print(" 1 부터 10 만까지 v 를 100 으로 나눴다 곱하면 %d 개가 원래 값과 다릅니다" % bad)
tot = 0.0
for _ in range(1000000):
tot += 0.01
print(" 0.01 을 100 만 번 더하면 %.8f 이고 정수로 세면 정확히 %d 입니다"
% (tot, 1000000))
print(" 92강 파국적 상쇄와 같은 뿌리입니다. 크기가 다른 수를 더하면 작은 쪽이 사라집니다")
# --- 문제 3: 범주를 수치로 넣으면 ---------------------------------------
print(" 측정 척도 네 가지를 구분합니다")
print(" 척도 허용되는 연산 예")
rows = [("명목", "같다 다르다", "혈액형, 요일 이름"),
("순서", "같다 다르다 크다 작다", "만족도 5 단계"),
("구간", "덧셈 뺄셈", "섭씨 온도, 연도"),
("비율", "덧셈 뺄셈 곱셈 나눗셈", "길이, 무게, 개수")]
for a1, b1, c1 in rows:
print(" %-8s %-36s %s" % (a1, b1, c1))
print(" 요일을 0 부터 6 으로 넣으면 무엇이 깨지는지 봅니다")
names = ["월", "화", "수", "목", "금", "토", "일"]
print(" 두 요일 라벨 차이 실제 인접성")
for i, j in [(0, 1), (5, 6), (6, 0), (0, 3)]:
cyc = min(abs(i - j), 7 - abs(i - j))
print(" (%s, %s) %10d %14d" % (names[i], names[j], abs(i - j), cyc))
print(" 일요일과 월요일은 붙어 있는데 라벨 차이는 6 입니다. 거리가 뒤집힙니다")
print(" 순환하는 범주는 사인과 코사인 두 개로 넣습니다")
print(" 요일 sin(2 pi k/7) cos(2 pi k/7) 월요일과의 거리")
for k in [0, 1, 5, 6]:
s1, c1 = np.sin(2 * np.pi * k / 7), np.cos(2 * np.pi * k / 7)
d = np.hypot(s1 - np.sin(0.0), c1 - np.cos(0.0))
print(" %s %14.6f %16.6f %16.6f" % (names[k], s1, c1, d))
print(" 월요일과 일요일의 거리가 화요일과의 거리와 같아집니다. 순환이 복원됩니다")
print(" 순서 없는 범주는 원핫으로 넣습니다. 범주 k 개면 열이 k 개 또는 k-1 개입니다")
# --- 문제 4: 결측을 어떻게 표현하는가 -----------------------------------
print(" 결측을 나타내는 세 방법을 견줍니다")
print(" NaN 은 자기 자신과 같지 않습니다: np.nan == np.nan 은 %s" % (np.nan == np.nan))
v = np.array([1.0, 2.0, np.nan, 4.0])
print(" NaN 이 하나 섞이면 합이 %s, 평균이 %s 가 됩니다"
% (str(np.sum(v)), str(np.mean(v))))
print(" 결측을 빼고 계산하면 합 %.1f, 평균 %.6f (관측 %d 개)"
% (float(np.nansum(v)), float(np.nanmean(v)), int(np.sum(~np.isnan(v)))))
print(" 정수 타입에는 NaN 을 담을 수 없습니다")
try:
np.array([1, 2, np.nan], dtype=np.int64)
print(" 정수 배열에 NaN 을 넣었습니다")
except ValueError:
print(" 정수 배열에 NaN 을 넣으면 ValueError 가 납니다")
print(" 그래서 -999 같은 표식을 쓰는 관행이 생겼습니다. 그것이 무엇을 망치는지 봅니다")
n4 = 10000
true = rng.normal(50.0, 5.0, n4)
miss = rng.random(n4) < 0.05
print(" 방법 평균 참 평균과의 차이")
print(" %-18s %10.6f %16.6f" % ("결측 없는 원자료", float(true.mean()), 0.0))
sent = true.copy()
sent[miss] = -999.0
print(" %-18s %10.6f %16.6f"
% ("-999 를 그대로 계산", float(sent.mean()), abs(float(sent.mean()) - float(true.mean()))))
nanv = true.copy()
nanv[miss] = np.nan
print(" %-18s %10.6f %16.6f"
% ("NaN 으로 두고 제외", float(np.nanmean(nanv)), abs(float(np.nanmean(nanv)) - float(true.mean()))))
print(" 결측 비율 %.4f, -999 가 평균을 %.2f 만큼 끌어내렸습니다"
% (float(miss.mean()), float(true.mean()) - float(sent.mean())))
print(" 표식은 계산에 조용히 섞입니다. 결측은 값이 아니라 상태로 표현해야 합니다")
# --- 문제 5: 스키마가 무엇을 보장하는가 ---------------------------------
print(" 주문 표를 만들고 제약을 검사합니다")
dt = np.dtype([("order_id", "i8"), ("user_id", "i8"), ("amount", "i8"), ("day", "i4")])
rows5 = [(1, 101, 15000, 3), (2, 102, 22000, 5), (3, 101, -500, 2),
(4, 103, 8000, 9), (2, 104, 12000, 1), (6, 999, 30000, 4)]
T = np.array(rows5, dtype=dt)
users = np.array([101, 102, 103, 104], dtype=np.int64)
print(" 행 %d 개, 열 %d 개, 한 행의 바이트 %d" % (len(T), len(dt.names), dt.itemsize))
u, cnt = np.unique(T["order_id"], return_counts=True)
dup = u[cnt > 1]
print(" 제약 위반 건수 위반한 값")
print(" %-28s %10d %s" % ("기본키 유일성 order_id", int(cnt[cnt > 1].sum() - len(dup)),
", ".join(str(int(z)) for z in dup)))
neg = T["amount"] < 0
print(" %-28s %10d %s" % ("금액은 음수가 아닐 것", int(neg.sum()),
", ".join(str(int(z)) for z in T["amount"][neg])))
bad_day = (T["day"] < 1) | (T["day"] > 7)
print(" %-28s %10d %s" % ("요일은 1 부터 7 사이", int(bad_day.sum()),
", ".join(str(int(z)) for z in T["day"][bad_day])))
orphan = ~np.isin(T["user_id"], users)
print(" %-28s %10d %s" % ("외래키 user_id 참조 무결성", int(orphan.sum()),
", ".join(str(int(z)) for z in T["user_id"][orphan])))
print(" 네 가지 위반을 모두 잡았습니다. 스키마는 타입만이 아니라 제약을 포함합니다")
print(" 중복 저장이 왜 위험한지 봅니다. 사용자 등급을 주문 표에 함께 넣은 경우입니다")
g = np.array(["A", "A", "B", "A"], dtype="U1")
print(" 사용자 101 의 등급이 주문 세 건에 A, A, B 로 적혀 있습니다")
print(" 한 곳만 고치면 %s 처럼 서로 어긋납니다. 이것이 갱신 이상입니다"
% ", ".join(g[:3]))
print(" 등급을 사용자 표에 한 번만 두면 고칠 곳이 한 곳입니다. 이것이 정규화입니다")
print(" 스키마는 문서가 아니라 실행되는 계약입니다. 검사하지 않으면 없는 것과 같습니다")
# 같은 정수 100 을 여러 타입으로 담아 봅니다
# 타입 바이트 최솟값 최댓값
# int8 1 -128 127
# int16 2 -32768 32767
# int32 4 -2147483648 2147483647
# int64 8 -9223372036854775808 9223372036854775807
# 100 만 개를 담을 때 int8 은 1.00 MB, int64 는 8.00 MB 입니다
# 범위를 넘으면 조용히 넘어갑니다. 예외가 아니라 되감기입니다
# int8 로 127 + 1 = -128
# int32 로 2000000000 + 2000000000 = -294967296
# 실수도 정밀도가 갈립니다. 유효숫자가 float32 는 7 자리, float64 는 16 자리입니다
# 값 float32 로 담은 값 float64 로 담은 값
# 0.1 0.1000000015 0.1000000000
# 100000001.0 100000000.0000000000 100000001.0000000000
# 16777217.0 16777216.0000000000 16777217.0000000000
# float32 에서 1e8 + 1 이 1e8 과 같은가: True
# float32 가 정확히 담는 정수의 한계는 2^24 = 16777216 입니다
# 타입을 고르는 일은 저장 공간과 정확도를 맞바꾸는 일입니다
# 0.1 + 0.2 가 0.3 과 같은지 봅니다
# 0.1 + 0.2 == 0.3 : False, 차이 0.00000000000000005551
# 0.1 을 float64 로 담은 실제 값 0.10000000000000000555
# 0.1 을 여러 번 더하면 오차가 쌓입니다
# 더한 횟수 단순 합 정확한 값 절대오차
# 10 1.00000000 1.0 1.11e-16
# 1000 100.00000000 100.0 1.41e-12
# 100000 10000.00000002 10000.0 1.88e-08
# 1000000 100000.00000133 100000.0 1.33e-06
# 덧셈 순서를 바꾸면 결과가 달라집니다. 결합법칙이 깨집니다
# (1 + 1e16) - 1e16 = 0.0, 1 + (1e16 - 1e16) = 1.0
# 돈은 정수로 담습니다. 원 단위 대신 최소 단위 정수로 바꿉니다
# 1 부터 10 만까지 v 를 100 으로 나눴다 곱하면 9175 개가 원래 값과 다릅니다
# 0.01 을 100 만 번 더하면 10000.00000017 이고 정수로 세면 정확히 1000000 입니다
# 92강 파국적 상쇄와 같은 뿌리입니다. 크기가 다른 수를 더하면 작은 쪽이 사라집니다
# 측정 척도 네 가지를 구분합니다
# 척도 허용되는 연산 예
# 명목 같다 다르다 혈액형, 요일 이름
# 순서 같다 다르다 크다 작다 만족도 5 단계
# 구간 덧셈 뺄셈 섭씨 온도, 연도
# 비율 덧셈 뺄셈 곱셈 나눗셈 길이, 무게, 개수
# 요일을 0 부터 6 으로 넣으면 무엇이 깨지는지 봅니다
# 두 요일 라벨 차이 실제 인접성
# (월, 화) 1 1
# (토, 일) 1 1
# (일, 월) 6 1
# (월, 목) 3 3
# 일요일과 월요일은 붙어 있는데 라벨 차이는 6 입니다. 거리가 뒤집힙니다
# 순환하는 범주는 사인과 코사인 두 개로 넣습니다
# 요일 sin(2 pi k/7) cos(2 pi k/7) 월요일과의 거리
# 월 0.000000 1.000000 0.000000
# 화 0.781831 0.623490 0.867767
# 토 -0.974928 -0.222521 1.563663
# 일 -0.781831 0.623490 0.867767
# 월요일과 일요일의 거리가 화요일과의 거리와 같아집니다. 순환이 복원됩니다
# 순서 없는 범주는 원핫으로 넣습니다. 범주 k 개면 열이 k 개 또는 k-1 개입니다
# 결측을 나타내는 세 방법을 견줍니다
# NaN 은 자기 자신과 같지 않습니다: np.nan == np.nan 은 False
# NaN 이 하나 섞이면 합이 nan, 평균이 nan 가 됩니다
# 결측을 빼고 계산하면 합 7.0, 평균 2.333333 (관측 3 개)
# 정수 타입에는 NaN 을 담을 수 없습니다
# 정수 배열에 NaN 을 넣으면 ValueError 가 납니다
# 그래서 -999 같은 표식을 쓰는 관행이 생겼습니다. 그것이 무엇을 망치는지 봅니다
# 방법 평균 참 평균과의 차이
# 결측 없는 원자료 50.036047 0.000000
# -999 를 그대로 계산 -1.373240 51.409287
# NaN 으로 두고 제외 50.029190 0.006857
# 결측 비율 0.0490, -999 가 평균을 51.41 만큼 끌어내렸습니다
# 표식은 계산에 조용히 섞입니다. 결측은 값이 아니라 상태로 표현해야 합니다
# 주문 표를 만들고 제약을 검사합니다
# 행 6 개, 열 4 개, 한 행의 바이트 28
# 제약 위반 건수 위반한 값
# 기본키 유일성 order_id 1 2
# 금액은 음수가 아닐 것 1 -500
# 요일은 1 부터 7 사이 1 9
# 외래키 user_id 참조 무결성 1 999
# 네 가지 위반을 모두 잡았습니다. 스키마는 타입만이 아니라 제약을 포함합니다
# 중복 저장이 왜 위험한지 봅니다. 사용자 등급을 주문 표에 함께 넣은 경우입니다
# 사용자 101 의 등급이 주문 세 건에 A, A, B 로 적혀 있습니다
# 한 곳만 고치면 A, A, B 처럼 서로 어긋납니다. 이것이 갱신 이상입니다
# 등급을 사용자 표에 한 번만 두면 고칠 곳이 한 곳입니다. 이것이 정규화입니다
# 스키마는 문서가 아니라 실행되는 계약입니다. 검사하지 않으면 없는 것과 같습니다