[ML] 혼공머닝 4편 교차검증, 그리드서치와 트리 앙상블(랜덤 포레스트, 엑스트라 트리, 그래디언트 부스팅, 히스토그램 부스팅)
카테고리: ML
태그: k-최근접이웃, ML, RandomForestClassifier, RandomForestRegressor, 그리드서치
💡 ‘혼자 공부하는 머신러닝 딥러닝(박해선 저)’ 책을 읽고 공부한 내용을 요약한 페이지입니다.
책에 나오는 코드를 그대로 쓰지 않고, 코드와 파라미터를 변형하고 조정해가며 다른 결과를 출력하며 공부했습니다.
05 트리 알고리즘
05-1 결정 트리
와인 겉면에 적힌 알코올 도수, 당도, pH로 와인 종류를 구별하는 프로그램을 만들려 한다. 이것을 로지스틱 회귀로 분류해보자.
#데이터 준비
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
wine.info()
#info 메서드는 데이터프라임의 각 열의 데이터 타입과 누락된 데이터를 확인하는 데 쓰인다.
wine.describe()
#describe 메서드는 열에 대한 간략한 통계를 출력해준다.
data = wine[['alcohol','sugar','pH']].to_numpy() #넘파이 배열로 전환
target = wine[['class']].to_numpy()
#훈련 세트와 테스트 세트 나누기
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)
#훈련세트 전처리
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
#로지스틱 회귀모델 훈련
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))
#결과값
0.7808350971714451
0.7776923076923077
#다소 낮게 나옴.
print(lr.coef_, lr.intercept_)
#결과값
[[ 0.51270274 1.6733911 -0.68767781]] [1.81777902]
이 모델을 머신러닝을 잘 모르는 이에게 설명한다고 가정해보자.
이 모델은 알코올 도수 값에 0.51270274를 곱하고 당도에 1.6733911을 곱하고 … 이 값이 0보다 크면 화이트 와인, 작으면 레드 와인인데, 현재 약 77% 정도를 정확히 화이트 화인으로 분류했습니다.
영 어지러운 소리가 아닐 수 없다. 하지만 순서도처럼 잘 도식화되어 머신러닝 문외한이더라도 쉽게 이해할 수 있는 프로그램이 있으니, 바로 결정 트리(Decision Tree)이다.
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target)) #0.9969
print(dt.score(test_scaled, test_target)) #0.8592
#과대적합된 모델.
#사이킷런은 plot_tree()함수를 통해 결정 트리를 그림으로 보여준다.
#맨 위 노드를 루트노드, 맨 아래 노드를 리프 노드라 부른다
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize=(10,7))
plot_tree(dt, max_depth=1,filled=True,feature_names=['alcohol','sugar', 'pH'])
#max_depth매개변수를 1로 설정하면 루트노드를 제외하고 하나의 노드를 확장해서 그린다.
plt.show()
루트 노드를 보자. 당도가 -0.239이하인지 질문을 한다. 만약 이하라면 왼쪽 가지로, 초과라면 오른쪽 가지로 이동한다. 총 샘플 수는 5197개이며, 그 중 음성 클래스(레드 와인)는 1,258개, 양성 클래스(화이트 와인)는 3,939개임을 알 수 있다.
그 밑 노드를 보면 오른쪽 노드가 더 색깔이 짙음을 알 수 있다. plot_tree()
함수에서 filled=True
로 지정하면 클래스마다 색깔을 부여하고, 특정 클래스의 비율이 높아지면 점점 진한색으로 표시한다.
결정 트리에서는 리프 노드에서 가장 많은 클래스가 예측 클래스가 된다. 두번째 노드가 리프 노드라면, 두 노드 모두 양성 클래스가 과반수이므로 양성으로 예측할 것이다.
- 불순도(impurity): 결정 트리 모델이 트리를 성장시키는 기준. 결정 트리 모델은 부모 노드와 자식 노드의 불순도 차이가 가능한 크도록 트리를 성장시킨다. 대표적으로 지니 불순도(Gini impurity)가 쓰인다.
지니 불순도 = 1 -(음성 클래스 비율^2 + 양성 클래스 비율^2)
루트 노드를 예로 들면 1,528개가 음성이고 3,939개가 양성이었으므로 지니 불순도는 1- ((1259/5197)^2 + (3937/5197)^2) = 0.367이다. 만약 클래스의 비율이 정확히 반반이라면 지니 불순도는 0.5가 되어 최악이 된다.
만약 노드에 하나의 클래스만 있다면 지니 불순도는 0이 되고, 이런 노드를 순수 노드라고 부른다.
부모 노드와 자식 노드 간의 불순도 차이를 정보 이득(information gain) 이라고 부르고, 결정 트리는 정보 이득을 최대화하는 방식으로 학습한다.
무한정 자라나는 트리는 과대적합을 만들기 마련이다. 따라서 해줄 필요가 있다.
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol','sugar','pH'])
plt.show()
결정 트리의 또다른 장점은 클래스별 비율을 가지고 불순도를 계산하기에, 특성값의 스케일을 전처리할 필요가 없다는 것이다.
#어떤 특성이 가장 유용한지 나타내는 특성 중요도
print(dt.feature_importances_)
05-1 핵심 키워드
- 결정 트리: 예/아니오에 대한 질문을 이어나가면서 정답을 찾아 학습하는 알고리즘
- 불순도(impurity): 결정 트리가 최적의 질문을 찾기 위한 기준. 지니 불순도와 엔트로피 불순도가 있다.
- 정보 이득: 부모노드와 자식노드의 불순도 차이
05-2 핵심 패키지와 함수
- pandas
info()
: 데이터프레임의 요약된 정보 출력describe()
: 데이터프레임 열의 통계값을 요약
- scikit-learn
DesicionTreeClassifier
: 결정 트리 분류 클래스criterion
매개변수는 불순도를 지정하며 기본값은 gini(지니불순도)이다.max_depth
매개변수는 트리가 성장할 최대 깊이를 지정한다.
- plot_tree()는 결정 트리 모델을 시각화하는 함수이다.
max_depth
매개변수로 나타낼 트리의 깊이를 지정한다feature_names
매개변수로 특성의 이름을 지정한다filled
매개변수를 True로 하면 타깃값에 따라 노드의 색을 채운다.
05-2 교차 검증과 그리드 서치
테스트 세트를 사용해 자꾸 성능을 확인하다보면 점점 테스트 세트에 모델을 맞추게 된다. 일반화 성능을 정확히 예측하려면 가능한 한 테스트 세트를 사용하지 말아야 한다. 모델을 만들고 나서 마지막에 딱 한 번만 사용하는 것이 좋다. 그래서 활용할 세트가 바로 검증세트이다.
- 검증 세트(validation set): 훈련 세트에서 모델을 평가한 후 검증 세트로 모델을 평가한다. 테스트하고 싶은 매개변수를 바꿔가며 가장 좋은 모델을 고른다. 매개변수를 정했다면 마지막에 훈련세트와 검증세트를 합쳐 다시 훈련을 하고, 이 때 테스트 세트에서 최종 점수를 평가한다. 보통 훈련:검증:테스트의 비율은 6:2:2이다.
#검증 세트 만들어보기
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
data = wine[['alcohol','sugar','pH']].to_numpy()
target = wine['class'].to_numpy()
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)
#여기까진 동일하지만, 이번엔 train_input과 train_target을 다시 train_test_split()에 넣는다.
sub_input, val_input, sub_target, val_target = train_test_split(train_input, train_target, test_size=0.2, random_state=42)
#test_size매개변수를 0.2로 하여 train_input의 약 20%를 val_input으로 만든다.
이 때 교차검증(cross validation) 을 활용하면 더 안정적인 검증 점수를 얻을 수 있다. 다음 그림을 보면 교차검증을 쉽게 이해할 수 있다.
from sklearn.model_selection import cross_validate #교차 검증 함수
scores = cross_validate(dt, train_input, train_target)
#앞에서처럼 직접 데이터를 떼어낼 필요 없이 그냥 변수로 전달만 하면 됨
print(scores)
#이 함수는 fit_time, score_tim, test_score 키를 가진 딕셔너리를 반환한다.
import numpy as np
print(np.mean(scores['test_score'])) #각각의 5개 폴드의 검증점수의 평균
한 가지 주의할 점은 원래 train_test_split()
을 사용할 땐 전체 데이터를 섞은 후 훈련 세트를 준비했지만, cross_validation()
의 경우엔 훈련 세트를 섞으려면 분할기(splitter
)를 지정해야 한다.
splitter = StratifiedKFold(n_splits=10, shuffle=True,random_state=42)
scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))
하이퍼 파라미터는 사용자가 직접 지정하는 파라미터라고 했다. 만약 결정 트리 모델에서 최적의 max_depth
값을 찾았다고 가정해보자. 이 때 min_samples_split
를 바꿔가며 최적의 값을 찾는다면, 옳은 방법일까? 안타깝게도 아니다. max_depth
의 최적값은 min_samples_split
이 바뀜에 따라 함께 달라진다.
이렇게 골머리를 앓으며 하이퍼파라미터를 튜닝할 필욘 없다. 사이킷런에서 제공하는 그리드서치를 이용하면 되기 때문이다. GridSearchCV
클래스는 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행해준다.
from sklearn.model_selection import GridSearchCV
params = {'min_impurity_decrease':[0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}
# 탐색할 매개변수와 탐색할 값의 리스트를 딕셔너리로 넣어준다.
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
#GridSearchCV클래스에 탐색 대상 모델과 params변수를 전달하여 객체를 만든다
#n_jobs매개변수는 병렬 실행에 사용할 cpu코어 수를 지정한다. -1로 지정하면 시스템의 모든 코어를 사용한다.
gs.fit(train_input, train_target)
#매개변수 값은 5개이고 5폴드교차검증을 하니 총 25개의 모델을 훈련한다.
dt = gs.best_estimator_ #훈련이 끝나면 25개 모델 중 검증 점수가 가장 높은 모델의 매개변수 조합을 반환해준다.
print(dt.score(train_input, train_target))
print(gs.best_params_) #최적의 매개변수
#print(gs.cv_results_['mean_test_score']) #각 매개변수에서 수행한 교차검증의 평균 점수
#좀 더 복잡한 경우
params = {'min_impurity_decrease':np.arange(0.0001, 0.001, 0.0001),
'max_depth':range(5,20,1),
'min_samples_split':range(2,100,10)}
#첫번째 매개변수는 9개, 두번째는 15개, 세번째는 10개로 수행할 교차검증 횟수는 1,350번!
gs = GridSearchCV(DecisionTreeClassifier(random_state=42),params,n_jobs=-1)
gs.fit(train_input, train_target)
print(gs.best_params_)
dt = gs.best_estimator_
print(dt.score(train_input,train_target))
print(np.max(gs.cv_results_['mean_test_score']))
매개변수의 값이 수치일 때나 너무 많은 매개변수 조건이 있어 그리드 서치 수행 시간이 오래 걸릴 때에는 랜덤 서치를 사용하면 좋다. 랜덤 서치에는 매개변수 값의 목록을 전달하지 않고 매개변수를 샘플링할 수 있는 확률 분포객체를 전달한다.
from scipy.stats import uniform, randint
rgen = randint(0,10) #0에서 10 사이의 정수값 무작위로 뽑기
rgen.rvs(10) #10개 뽑기
ugen=uniform(0,1) #0에서 10 사이의 실수값 무작위로 뽑기
ugen.rvs(10) #10개 뽑기
params = {'min_impurity_decrease':uniform(0.0001, 0.001),
'max_depth':randint(20,50),
'min_samples_split':randint(2,25),
'min_samples_leaf':randint(1,25),
}
#탐색할 매개변수 범위를 위와 같이 설정
from sklearn.model_selection import RandomizedSearchCV
gs = RandomizedSearchCV(DecisionTreeClassifier(random_state=42),params,n_iter=100, n_jobs=-1, random_state=42)
#n_iter매개변수에 샘플링 횟수를 지정한다.
gs.fit(train_input, train_target)
print(gs.best_params_)
이렇게 하면 params
에 정의된 매개변수 범위에서 총 100번을 샘플링 하여 교차검증을 수행하고 최적의 매개변수 조합을 얻는다. 앞서 그리드 서치는 1350번(91510)에 걸친 것을 랜덤서치는 100번만에 수행했다. 즉, 훨씬 교차 검증 수를 줄이면서 넓은 영역을 효과적으로 탐색할 수 있다.
05-2 핵심 키워드
- 검증 세트(validation set): 하이퍼 파라미터 튜닝을 위해 모델을 평가할 때 테스트 세트를 사용하지 않기 위해 훈련 세트에서 다시 떼어 낸 데이터 세트
- 교차 검증(cross validation): 훈련 세트를 여러 폴드로 나눈 다음 한 폴드가 검증 세트의 역할을 하고 나머지 폴드에서 모델을 훈련하는 방식
- 그리드 서치: 탐색할 매개변수를 나열하면 교차검증을 수행하여 가장 좋은 검증 점수의 매개변수 조합을 반환해준다.
- 랜덤 서치: 연속된 매개변수값의 범위를 설정하면 교차검증을 수행하여 가장 좋은 검증 점수의 매개변수 조합을 반환해준다.
05-2 핵심 패키지와 함수
- scikit-learn
cross_validation()
: 교차 검증을 수행하는 함수- 첫번째 매개변수에 교차검증을 수행할 모델 객체 전달
- 두번째, 세번째 매개변수에 특성과 타깃 데이터 전달
cv
매개변수에 교차검증 폴드 수나 스플리터 객체를 지정.n_jobs
매개변수에 사용할 cpu코어 수를 지정. -1이면 시스템의 모든 코어 사용
GridSearchCV()
: 교차 검증으로 하이퍼파라미터 탐색을 수행- 첫번째 매개변수로 그리드 서치를 수행할 모델 객체를 전달
- 두번째 매개변수로 탐색할 모델의 매개변수를 전달
RandomizedSearchCV
: 교차 검증으로 랜덤한 하이퍼파라미터 탐색을 수행- 첫번째 매개변수로 랜덤 서치를 수행할 모델 객체를 전달
- 두번째 매개변수로 탐색할 모델의 매개변수와 확률분포 객체 전달
05-3 트리의 앙상블
정형 데이터를 다루는 데 가장 뛰어난 성과를 내는 알고리즘이 앙상블 학습(ensemble learning) 이다. 앙상블 학습은 대부분 결정 트리를 기반으로 만들어져 있다.
- 랜덤 포레스트(random forest): 앙상블 학습의 대표주자 중 하나로 안정적인 성능 덕분에 널리 사용되고 있다. 부트스트랩 샘플을 사용하고 랜덤하게 일부 특성을 선택하여 결정 트리를 만든다.
1,000개가 들어있는 가방에서 100개씩 샘플을 뽑는다면 먼저 1개를 뽑고 뽑은 한 개를 가방에 다시 넣고 다음 샘플을 뽑는다. 이렇게 중복을 허용하는 샘플을 부트스트랩 샘플(bootstrap sample) 이라고 한다. 기본적으로 샘플 개수는 훈련 세트와 같게 한다.
또한 각 노드를 분할 할 때 전체 특성 중 일부를 무작위로 고른다. RandomForestClassifier
는 기본적으로 전체 특성 개수의 제곱근만큼의 특성을 선택한다. 즉 4개의 특성이 있다면 노드마다 2개를 랜덤하게 선택한다. 이 2개 중에서 최적의 분할을 찾아나간다.
#데이터 준비
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
wine = pd.read_csv('https://bit.ly/wine_csv_data')
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine[['class']].to_numpy()
train_input, test_input, train_target, test_target = train_test_split(data, target,test_size=0.2, random_state=42)
#교차 검증 수행
from sklearn.model_selection import cross_validate
#랜덤 포레스트 분류 클래스 import
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_jobs=-1, random_state=42)
#RandomForestClassifier는 기본적으로 100개의 트리를 사용하므로 n_jobs를 -1로 하여 cpu를 최대한 많이 사용하는 것이 좋다.
scores = cross_validate(rf, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
#결과값
0.9973541965122431 0.8905151032797809
rf.fit(train_input, train_target)
#랜덤포레스트로 훈련을 시키고 특성의 중요도를 확인해보자.
print(rf.feature_importances_)
#결과값 [0.23167441 0.50039841 0.26792718]
랜덤포레스트에서의 특성 중요도와 결정트리에서의 특성 중요도가 다른 것으로 나타냈다. 이는 랜덤포레스트가 특성의 일부를 랜덤하게 선택하여 결정 트리를 훈련하기 때문이다. 그 결과 좀 더 많은 특성이 훈련에 기여할 기회를 얻고, 일반화 성능을 높인다.
부트스트랩 샘플에 포함되지 않고 남은 샘플을 OOB(out of bag)샘플이라고 한다. 이 샘플을 사용하여 부트스트랩 샘플로 훈련한 결정 트리를 평가할 수 있다! 마치 검증세트의 역할을 수행하는 것이다. 고로, 굳이 검증세트를 수행해서 사용할 샘플을 낭비할 필요가 없다.
rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score_)
- 엑스트라 트리(Extra Trees): 랜덤 포레스트와 유사하나, 부트스트랩 샘플을 사용하지 않고 전체 훈련세트를 사용한다. 대신 노드를 분할할 때 가장 좋은 분할을 찾는 것이 아니라 무작위로 분할한다. 이는
spliter
매개변수를 random으로 지정한 것과 같은 효과를 준다. 이러한 무작위 분할은 계산 속도를 빠르게 해준다.
from sklearn.ensemble import ExtraTreesClassifier
et = ExtraTreesClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(et, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
#결과값
0.9974503966084433 0.8887848893166506
- 그레이디언트 부스팅(Gradient boosting): 깊이가 얕은 결정 트리를 사용하여 이전 트리의 오차를 보완하는 방식으로 앙상블하는 방법. 여러 분류기가 순차적으로 학습하되, 앞에서 학습한 분류기가 예측이 틀린 데이터에 대해 올바르게 예측하게끔, 다음 분류기에 가중치를 부여한다.
앞선 트리의 오차를 보완하기에 랜덤 포레스트보다 좋은 성능을 낼 수 있다. 그러나 순서대로 트리를 추가하기 때문에(종속적이기 때문에) 훈련속도가 느리다. 순서대로 트리를 추가한다는 것은 병렬 수행이 불가능함을 의미하고, 따라서 n_jobs
매개변수도 존재하지 않는다.
from sklearn.ensemble import GradientBoostingClassifier
#GradientBoostingClassifier은 max_depth가 3인 결정 트리를 100개 사용한다.
gb = GradientBoostingClassifier(random_state=42)
scores=cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
gb = GradientBoostingClassifier(n_estimators=500, learning_rate=0.2, random_state=42)
scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
- 히스토그램 기반 그레이디언트 부스팅(Histogram-based Gradient Boosting): 정형 데이터를 다루는 알고리즘 중 가장 인기가 많은 알고리즘이다. 입력 특성을 256개 구간으로 나누기 때문에 최적의 분할을 매우 빠르게 찾을 수 있다. 또한 256개의 구간 중에서 하나를 떼어 놓고 누락된 값을 위해 사용한다.
from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier
hgb = HistGradientBoostingClassifier(random_state=42)
scores = cross_validate(hgb, train_input, train_target, return_train_score=True)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
히스토그램 기반 그레이디언트 부스팅의 특성 중요도를 계산하는 함수는 permutation_importance()
이다. 이 함수는 특성을 하나씩 랜덤하게 섞어 모델의 성능이 변화하는지를 관찰하여 어떤 특성이 중요한지 계산한다.
from sklearn.inspection import permutation_importance
hgb.fit(train_input, train_target)
result=permutation_importance(hgb, train_input, train_target, n_repeats=10, random_state=42, n_jobs=-1)
#n_repeats매개변수는 랜덤하게 섞을 횟수를 지정한다.
print(result.importances_mean)
#결과값(중요도, 평균, 표준편차)
[0.08876275 0.23438522 0.08027708]
hgb.score(test_input, test_target)
#결과값
0.8723076923076923
#이 값은 단일 결정 트리보다 높은 값.
05-3 핵심 키워드
- 앙상블 학습: 더 좋은 예측을 위해 여러 개의 트리를 훈련하는 알고리즘
- 랜덤 포레스트: 부트스트랩 샘플을 사용하고 랜덤하게 일부 특성을 선택하여 트리를 만드는 알고리즘
- 엑스트라 트리: 부트스트랩 샘플을 사용하지 않고 랜덤하게 노드를 분할해 빠른 속도가 장점인 알고리즘
- 그레이디언트 부스팅: 트리를 연속적으로 추가하여 손실 함수를 최소화하는 알고리즘. 훈련 속도는 좀 느리지만 가장 좋은 성능을 기대할 수 있다. 대표적인 것이 히스토그램 기반 그레이디언트 부스팅이다.
05-3 핵심 패키지와 함수
- scikit-learn
RandomForestClassifier
: 랜덤 포레스트 분류 클래스n_estimators
매개변수는 트리의 개수를 지정. 기본값은 100criterion
매개변수는 불순도를 지정. 기본값은 ginimax_depth
매개변수는 트리가 성장할 최대 깊이min_samples_split
는 노드를 나누기 위한 최소 샘플 개수max_features
매개변수는 최적의 분할을 위해 탐색할 특성의 개수. 기본값은 특성 개수의 제곱근.oob_score
매개변수는 OOB샘플을 사용할지 지정
ExtraTreesClassifier
: 엑스트라 트리 분류 클래스- 랜덤 포레스트와 대부분 동일
GradientBoostingClassifier
: 그레이디언트 부스팅 분류 클래스loss
매개변수는 손실함수를 지정learning_rate
매개변수는 트리가 앙상블에 기여하는 정도를 조절. 기본값은 0.1subsamples
매개변수는 사용할 훈련세트의 샘플 비율을 지정max_depth
매개변수의 기본값은 3
HistGradientBoostingClassifier
: 히스토그램 기반 그레이디언트 부스팅 분류 클래스max_iter
매개변수는 부스팅 단계를 수행하는 트리의 개수max_bins
는 입력 데이터를 나눌 구간의 개수. 기본값은 255.