반응형

이번에는 머신러닝 프로젝트 4단계에 이어, 5단계를 살펴보도록 하겠습니다.


5. 다양한 모델을 탐색하고 그 중 가장 좋은 모델 찾기

지금까지 문제를 정의하고, 데이터를 얻어서 탐색해 보았습니다. 

그리고 트레이닝 셋과 테스트 셋을 샘플링하고, 자동적으로 머신러닝 알고리즘을 위해 클리닝해서 데이터를 준비하기 위해 변형 파이프라인을 작성했습니다. 

이제 머신러닝 모델을 선택해서 트레이닝시킬 준비가 되었습니다.


트레이닝 셋에서 훈련하고 평가하기

좋은 소식은 이전 1~4단계 덕분에, 이제 생각했던 것보다 상황이 훨씬 간단하게 진행될 것이라는 것입니다. 

우선 선형 회귀 모델을 트레이닝해 봅시다.

>>> from sklearn.linear_model import LinearRegression

>>> lin_reg = LinearRegression()

>>> lin_reg.fit(housing_prepared, housing_labels)


다 했습니다! 

이제 선형 회귀 모델을 사용할 수 있습니다. 

트레이닝 셋으로부터 몇 가지 인스턴스를 시도해 보도록 하겠습니다:

>>> some_data = housing.iloc[:5]

>>> some_labels = housing_labels.iloc[:5]

>>> some_data_prepared = full_pipeline.transform(some_data)

>>> print("Predictions:\t", lin_reg.predict(some_data_prepared))

Predctions:    [    303104.    44800.    308928.    294208.    368704. ]

>>> print("Labels:\t\t", list(some_labels))

Labels:        [359400.0,  69700.0,  302100.0,  301300.0,  351900.0]


예측이 아주 정확하지는 않지만 작동합니다

(두번째 예측이 50% 이상 차이가 있습니다!). 

사이킷런(Scikit-Learn)의 mean_squared_error 함수를 사용해서 전체 트레이닝 셋에 대한 이 회귀 모델의 RMSE를 측정해 봅시다.        

>>> from sklearn.metrics import mean_squared_error>>> housing_predictions = lin_reg.predict(housing_prepared)>>> lin_mse = mean_sqared_error(housing_labels, housing_predictions)
>>> lin_rmse = np.sqrt(lin_mse)
>>> lin_rmse
68628.413493824875

이제, 좀 나아졌습니다. 
하지만 아직 좋은 점수를 받을 정도는 아닙니다: 가장 큰 지구(district)의 median_housing_values의 범위는 120,000 달러에서 265,000달러 사이입니다. 
따라서 68,628달러의 전형적인 예측 에러는 아주 만족스럽지 못합니다.
이것이 트레이닝 데이터의 과소적합(underfitting)된 모델의 사례입니다. 
이런 일이 일어나면, 훌륭한 예측을 만들어 내기 위해 피처들(features)이 충분한 정보를 제공하지 않았다는 것 또는 이 모델이 충분히 파워풀 하지 않다는 것을 의미할 수 있습니다. 
앞의 단계에서 보았듯이, 과소적합(underfitting)을 수정하기 위한 주요 방법은 더욱 강력한 모델을 선택하고, 더 좋은 피쳐들(features)을 가진 트레이닝 알고리즘을 먹이는 것입니다. 또는 이 모델에 대한 제약을 줄이는 것입니다. 이 모델은 정규화되지 않았기 때문에, 이 규칙들은 마지막 옵션에서 제외됩니다. 더 많은 피쳐들(features)을 추가하는 것(예를 들어, 인구의 로그)을 시도해 볼 수 있습니다. 하지만 먼저 보다 복잡한 모델을 시도해 보도록 합시다.
DecisionTreeRegressor을 훈련시켜 봅시다. 
이것은 데이터에서 복잡한 비선형 관계를 찾아낼 수 있는 강력한 모델입니다. 
코드가 이제 친숙해 보여야 할 것입니다:

>>> from sklearn.tree import DecisionTreeRegressor
>>> tree_reg = DecisionTreeRegressor()
>>> tree_reg.fit(housing_prepared, housing_labels)

자, 이 모델이 훈련되었고, 트레이닝 셋에서 이 모델을 평가해 보도록 합시다:

>>> housing_predictions = tree_reg.predict(housing_prepared)
>>> tree_mse = mean_squared_error(housing_labels, housing_predictions)
>>> tree_rmse = np,.sqrt(tree_mse)
>>> tree_rmse
0.0

잠깐, 뭘까요? 아무런 에러도 없었습니다. 정말 이 모델이 완벽한 걸까요? 
물론, 이 모델이 이 데이터에 과적합(overfitting)했을 가능성이 훨씬 높습니다. 
어떻게 확신할 수 있을까요? 
앞에서 보았듯이, 만족스러운 모델을 런칭할 준비가 될 때까지 테스트 셋을 만지는 것을 원하지 않을 것입니다. 
그래서 트레이닝 셋 일부를 훈련용으로 사용하고, 일부는 모델 검증용으로 사용하는 것이 필요합니다.


교차검증(Cross-Validation)을 사용해 더 좋은 평가 만들기

결정트리(Decision Tree) 모델을 평가하기 위한 한가지 방법은 트레이닝 셋을 더 작은 트레이닝 셋과 검증 셋으로 구분하기 위해 train_test_split 함수를 사용하는 것일 겁니다. 그후 더 작은 트레이닝 셋을 모델을 훈련시키고 검증 셋에 대해 검증을 실시합니다. 약간의 작업이 있긴 하지만, 그다지 어렵지 않고 잘 작동할 것입니다.
훌륭한 대안은 사이킷런(Scikit-Learn)의 교차검증(cross-validation) 기능을 이용하는 것입니다. 
다음의 코드는 K-fold 교차검증(cross-validation)을 실행합니다: 무작위로 folds라 불리는 10개 단위의 서브셋으로 트레이닝 셋을 분리합니다. 
그 다음 결정트리(Decision Tree) 모델로 10번 훈련시키고 평가합니다. 
매번 평가를 위해 다른 fold를 골라내고 또 다른 9개 folds를 훈련시킵니다. 
그 결과는 10개의 평가 점수를 포함한 배열로 나타납니다.

>>> from sklearn.model_selection import cross_val_score
>>> scores = cross_val_score(tree_reg, housing_prepared, housing_labels, 
scoring="neg_mean_squared_error", cv=10)
>>> rmse_scores = np.sqrt(-scores)

(주의) 사이킷런(Scikit-Learn)의 교차검증(cross-validation) 기능은 코스트 함수(작을수록 더 좋은)가 아닌 유틸리티 함수(클수록 더 좋은)를 기대합니다. 
그래서 스코어링 함수는 실제로 MSE와 반대(앞의 코드처럼 계산된 이유-제곱근을 계산하기 전의 값)입니다. 

결과를 보도록 합시다:
>>> def display_scores(scores):
...            print("Scores:", scores)
...            print("Mean:", scores.mean())
...            print("Standard deviation:", scores.std())
...
>>> display_scores(tree_rmse_scores)
Scores: [  74678.4916885   64766.2398337    69632.86942005   69166.67693232
71486.76507766  73321.65695983   71860.04741226     71086.32691692
76934.2726093   69060.93319262]
Mean:  71199.4280043
Standard deviation: 3202.70522793

지금 결정트리(Decision Tree)가 앞에서 했던 것보다 좋아보이진 않습니다. 
사실, 선형 회귀 모델보다 더 나쁘게 수행하는 것처럼 보입니다! 
교차검증(cross-validation)이 당신 모델의 성능을 검증하는 것 뿐만 아니라 이 검증이 얼마나 정확한지 측정하는 것(예., 표준편차) 역시 수행하고 있다는 것에 유념하시기 바랍니다. 
이 결정트리(Decision Tree)는 정확히, 일반적으로 ±3,200의 편차를 갖는, 71,200의 점수를 받았습니다. 
하나의 검증 셋만을 사용한다면,  이런 정보를 가질 수 없습니다. 하지만, 교차검증(cross-validation)은 몇 번에 걸쳐 모델을 훈련시키는 비용이 따르기 때문에 항상 가능하지는 않습니다. 
선형 회귀 모델을 확인해 보기 위해 동일한 점수를 계산해 보도록 합시다:

>>> lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels,
...                                                scoring="neg_mean_squared_error", cv=10)
...
>>> lin_rmse_scores = np.sqrt(-lin_scores)
>>> display_scores(lin_rmse_scores)
Scores: [  70423.5893262   65804.84913139   66620.84314068   72510.11362141
66414.74423281   71958.89083606   67624.90198297    67825.36117664
72512.36533141    68028.11688067]
Mean:  68972.377566
Standard deviation: 2493.98819069

맞습니다: 결정트리(Decision Tree) 모델이 과적합(overfitting)되었습니다. 
그래서 슬프게도 선형 회귀 모델보다 안좋은 성능을 발휘합니다. 
이제 마지막 남은 모델을 시도해 보도록 합시다: RandomForestRegressor. 랜덤 포레스트(Random Forests)는 예측값을 평균화하는 대신 피처들(features)의 무작위 서브셋을 많은 결정트리(Decision Trees)로 훈련시켜 작동합니다.  
많은 다른 모델들의 최고 위에 모델을 세우는 것을 앙상블 학습(Ensemble Learning)이라 부릅니다.  ML 알고리즘을 더욱 발전시키기 위한 좋은 방법입니다. 
다른 모델들과 본질적으로 동일하기 때문에, 코드의 대부분을 넘어갈 것입니다:

>>> from sklearn.ensemble import RandomForestRegressor
>>> forest_reg = RandomForestRegressor()
>>> forest_reg.fit(housing_prepared, housing_labels)
>>> [...]
>>> forest_rmse
22542.396440343684
>>> display_scores(forest_rmse_scores)
Scores: [  53789.2879722    50256.19806622   52521.55342602    53237.44937943
52428.82176158    55854.61222549    52158.02291609    50093.66125649
53240.80406125   52761.50852822]
Mean:  52634.1919593
Standard deviation: 1576.20472269

이제 더 좋아졌습니다: 랜덤 포레스트(Random Forests)가 더 유망해 보입니다. 하지만, 검증셋보다 트레이닝 셋의 점수가 여전히 더 낮다는 것에 유념하시기 바랍니다. 
이것은 이 모델이 여전히 트레이닝 셋에 과적합(overfitting)되어 있다는 것을 의미합니다. 
과적합(overfitting)에 있어 가능한 해결책은 모델을 단순화하고, 제약을 가하거나(예, 정규화하는 것), 더 많은 훈련 데이터를 얻는 것입니다.  
하지만, 랜덤 포레스트(Random Forests)에 더 깊이 들어가기 전에, 하이퍼파라미터를 조율하는데 너무 많은 시간을 소비하지 않고, 다양한 범주의 머신러닝 알고리즘으로부터 많은 다른 모델들을 시도해 보아야 할 것입니다. 
목표는 몇 가지(2~5개의) 유망한 모델을 선정하는 것입니다.

5단계는 여기까지입니다. 

머신러닝 프로젝트 실행-1

(1~2단계: 1. 문제정의하고 전체 그림 바라보기 / 2. 데이터 얻기바로가기 


머신러닝 프로젝트 실행-2

(3단계: 3. 인사이트를 찾기 위해 데이터 탐색하기바로가기 


머신러닝 프로젝트 실행-3

(4단계: 4. 기본 데이터 패턴을 머신러닝 알고리즘에 더 잘 노출시킬 수 있도록 데이터 준비하기바로가기


참고)'Hands-On Machine Learning with Scikit-Learn and TensorFlowchapter 2' 

주피터 노트북에서 볼 수 있는 전체 코드 얻기


반응형
반응형

머신러닝 프로젝트 실행 1~3단계에 이어, 4단계를 정리하도록 하겠습니다.

4. 기본 데이터 패턴을 머신러닝 알고리즘에 더 잘 노출할 수 있도록 데이터 준비하기

머신러닝 알고리즘을 위한 데이터를 준비할 시간입니다. 이것을 수동으로 하는 대신에, 자동으로 생성할 함수들을 사용해야 합니다. 

그 이유는 다음과 같습니다.

  • 어떤 데이터셋이든(예, 다음 번에 새로운 데이터셋을 얻게 되었을 때), 이들 변환을 쉽게 재적용할 수 있도록 해줍니다.
  • 미래의 프로젝트에서 재사용할 수 있는 변환 함수 라이브러리를 만들 수 있습니다.
  • 이들 함수들을 알고리즘에 피딩하기 전에 새로운 데이터를 변환하기 위해 실제 사용하는 시스템에서 사용할 수 있습니다. 
  • 다양한 변환을 쉽게 시도하고 어떤 변환 조합이 가장 잘 동작하는지 알 수 있도록 해줍니다.
이제 트레이닝 셋을 클리닝하기 위해 되돌려 봅시다(다시 한번strat_train_set을 복사하면 됩니다). 그리고 불필요하게 예측변수와 목표 값들에 동일한 변환이 적용되기를 원하지 않기 때문에 예측변수와 레이블을 분리하도록 합시다.
(drop()이 데이터 복사본을 만들고, strat_train_set에는 영향을 주지 않는 것에 주의하시기 바랍니다) 

>>> housing = strat_train_set.drop("median_house_value", axis=1)
>>> housing_labels = strat_train_set["median_house_value"].copy()

데이터 클리닝

대부분의 머신러닝 알고리즘은 누락된 값이 있는 피처들(features)로는 작업할 수 없습니다. 따라서, 그것들을 다루기 위한 몇가지 함수들을 만들어 봅시다. 전 단계에서 total_bedrooms 속성에 누락된 값들이 있다는 것을 보았습니다. 이것을 고쳐보도록 합시다. 
다음의 3가지 옵션을 선택할 수 있습니다.

  • 상응하는 지구(districts)를 제거합니다.

  • 전체 속성을 제거합니다.

  • 값들을 특정 값으로 설정합니다(제로, 평균, 중앙값 등).
이것들을 DataFrame의 dropna(), drop(), 그리고 fillna() 메서드를 사용해 쉽게 처리할 수 있습니다.

>>> housing.dropna(subset=["total_bedrooms"]) # 옵션1
>>> housing.drop("total_bedrooms", axis=1) # 옵션2
>>> median = housing["total_bedrooms"].median()
>>> housing["total_bedrooms"].fillna(median) # 옵션3

만약 옵션3을 선택한다면, 트레이닝 셋에 대한 중앙값을 계산해야만 합니다. 그리고 트레이닝 셋의 누락된 값들에 이것을 적용해야 합니다. 
하지만, 위 코드처럼 계산했던 중앙값을 저장하는 것을 잊지 말아야 합니다. 
나중에 시스템을 평가하고 싶을 때, 테스트 셋에서 누락된 값들을 대체하는 것이 필요할 것입니다. 그리고, 시스템을 운영한 후에는 신규 데이터에서 누락된 값들을 즉석에서 대체해야 합니다.

>>> from sklearn.preprocessing import Imputer
>>> imputer = Imputer(strategy="median")

중앙값은 수치 속성들에 대해서만 계산될 수 있기 때문에, 텍스트 속성의 ocean_proximity를 제외한 데이터의 복사본을 생성하는 것이 필요합니다.

>>> housing_num = housing.drop("ocean_proximity", axis=1)

이제 fit() 메서드를 사용해 트레이닝 데이터에 imputer 인스턴스를 적용할 수 있습니다.

>>> imputer.fit(housing_num)

imputer는 간단하게 각 속성의 중앙값을 계산하고, 그 결과를 statistics_ 인스턴스 변수에 저장합니다. 
total_bedrooms 속성만이 누락된 값이 있지만, 이 시스템이 실제 운용되었을 때 신규 데이터에 어떤 누락된 값들이 있을지 확신할 수 없을 것입니다. 
그래서, 모든 수치 속성들에 대해 imputer를 적용하는 것이 더 안전합니다.

>>> imputer.statistics_
array([ -118.51 , 34.26 , 29. , 2119. , 433. , 1164. , 408. , 3.5414])
>>> housing_nim.median().values
array([ -118.51 , 34.26 , 29. , 2119. , 433. , 1164. , 408. , 3.5414]) 

이제 학습된 중앙값으로 누락된 값들을 대체함으로써 트레이닝 셋을 변환하기 위해 "훈련된" imputer를 이용할 수 있습니다. 

>>> X = imputer.transform(housing_num)

결과물은 변환된 피쳐들(features)을 포함한 평이한 넘파이(Numpy) 배열입니다. 
판다스(Pandas) DataFrame으로 다시 넣고 싶다면, 간단히 처리할 수 있습니다.

>>> housing_tr = pd.DataFrame(X, columns=housing_num.columns)


텍스트 다루기와 범주 속성들

앞에서 범주 속성인 ocean_proximity를 제외했습니다. 
그 이유는 텍스트 속성이기 때문에 중앙값을 계산할 수 없기 때문입니다. 
대부분의 머신러닝 알고리즘은 숫자를 가지고 작업하는 것을 선호합니다. 
그렇기 때문에 이들 텍스트 라벨을 숫자로 변환해 봅시다.
사이킷 런(Scikit-Learn)은 이 작업에 필요한 LabelEncoder라 불리는 변환기를 제공합니다.

>>> from sklearn.preprocessing import LabelEncoder
>>> encoder = LabelEncoder()
>>> housing_cat = housing["ocean_proximity"]
>>> housing_cat_encoded = encoder.fit_transform(housing_cat)
>>> housing_cat_encoded
array([1, 1, 4, ..., 1, 0, 3])

이러면 더 좋아집니다: 이제 어떤 ML 알고리즘에서도 이 숫자 데이터를 사용할 수 있습니다. 
이 인코더가 classes_ 속성을 사용하여 학습한 매핑을 볼 수 있습니다("<1H OCEAN"이 0에 매핑되었고, "INLAND"가 1에 매핑되었습니다).

>>> print(encoder.classes_)
['<1H OCEAN' 'INLAND' 'ISLAND' 'NEAR BAY' 'NEAR OCEAN']

위 인코더로 처리한 데이터의 한가지 이슈는 ML 알고리즘이 두개의 근접한 값들이 거리가 있는 두개의 값들보다 더 유사하다고 가정한다는 것입니다. 
분명히 이것은 그런 경우가 아닌데 말입니다(예를 들어, 범주 0과 4가 범주 0과 1보다 더 유사합니다). 
이 이슈를 수정하기 위한, 일반적인 솔루션은 각 범주에 대한 하나의 바이너리 속성을 생성하는 것입니다: 범주가 "<1H OCEAN"일 때 하나의 속성은 1과 같습니다(그렇지 않으면 0). 범주가 "INLAND"일 때 또 다른 속성이 1과 같습니다(그렇지 않으면 0) 등등. 
이것을 원-핫 인코딩이라 부릅니다. 범주들을 원-핫 벡터들로 인코딩합시다. 
fit_transform()은 2D 배열을 기대하지만, housing_cat_encoded는 1D 배열이라는 것을 주의하시기 바랍니다. 
그래서 이것을 재구성하는 것이 필요합니다.

>>> from sklearn.preprocessing import OneHotEncoder
>>> encoder = OneHotEncoder()
>>> housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))
>>> housing_cat_1hot
<16513x5 sparse matrix of type '<class 'numpy.float64'>'
        with 16513 stored elements in Compressed Sparse Row format>

산출물이 NumPy 배열이 아니라 SciPy 스파스(sparse) 매트릭스인 것에 유의하시기 바랍니다. 
이것은 수천 개의 범주들을 가진 범주 속성을 가질 때 매우 유용합니다. 
원-핫 인코딩 후, 수천개의 컬럼을 가진 매트릭스를 얻었습니다. 
그리고 그 매트릭스는 각 열마다 하나의 1을 가진 것 말고는 0으로 가득합니다. 엄청난 메모리를 0을 저장하는데 사용하는 것은 매우 비효율적입니다. 
그래서 대신 스파스(sparse) 매트릭스는 오직 0이 아닌 요소들의 위치만 저장합니다. 대개 일반적인 2D 배열처럼 사용할 수 있습니다. 하지만 정말 NumPy 배열로 전환하고 싶다면, toarray() 메서드를 호출하기만 하면 됩니다. 

>>> housing_cat_1hot.toarray()
array([[ 0.,  1.,  0.,  0.,  0.],
    [0.,  1.,  0.,  0.,  0.],  
    [0.,  0.,  0.,  0.,  1.], 
    [1.,  0.,  0.,  0.,  0.],  
    [0.,  0.,  0.,  1.,  0.]])

  
LabelBinarizer 클래스를 사용해서 (텍스트 범주에서 숫자 범주로 바꾸고, 숫자 범주에서 원-핫 벡터로 바꾸는) 두개의 변환을 한번에 적용할 수 있습니다. 

>>> from sklearn.preprocessing import LabelBinarizer
>>> encoder = LabelBinarizer()
>>> housing_cat_1hot = encoder.fit_transform(housing_cat)
>>> housing_cat_1hot
array([[ 0,  1,  0,  0,  0],
    [0,  1,  0,  0,  0],  
    [0,  0,  0,  0,  1], 
 ...,
    [0,  0,  0,  0,  1], 
    [1,  0,  0,  0,  0],  
    [0,  0,  0,  1,  0]])

기본적으로 고밀도 NumPy 배열을 돌려준다는 것에 유의하시기 바랍니다. 
sparse_output=True를 LabelBinarizer 컨스트럭터(constructor)를 전달함으로써 스파스(sparse) 매트릭스를 얻을 수 있습니다. 

사용자 정의 트랜스포머(Transformers)

사이킷런(Scikit-Learn)이 많은 유용한 트랜스포머(Transformers)를 제공하지만, 사용자 정의 클린업 또는 특정 속성들을 결합하는 것과 같은 작업을 위해 자신만의 트랜스포머를 작성하는 것이 필요할 것입니다. 
사이킷런(Scikit-Learn)의 기능들(pipelines과 같은)을 가지고 완벽하게 작동하는 자신만의 트랜스포머를 원할 수 있습니다. 
사이킷런(Scikit-Learn)이 덕 타이핑(상속이 아닌)에 의존하기 때문에, 클래스를 생성하고 3개의 메서드 구현하는 것이 필요한 전부입니다: fit(), transform(), 그리고 fit_transform(). 간단히 기본 클래스로써 TransformerMixin을 추가함으로써 무료로 하나를 얻을 수 있습니다. 
또한, 기본 클래스로 BaseEstimater를 추가한다면(컨스트럭터(constructor)에 *args와 *kargs를 피하세요), 자동 하이퍼파라미터를 조율하는데 유용한 두개의 특별 메서드(get_params()와 set_params())를 얻을 수 있습니다. 
예를 들어, 아래 코드는 앞에서 논의한 결합된 속성들을 추가하는 작은 트랜스포머(transformer) 클래스입니다. 

컴퓨터 프로그래밍 분야에서 덕 타이핑(duck typing)은 동적 타이핑의 한 종류로, 객체의 변수 및 메소드의 집합이 객체의 타입을 결정하는 것을 말합니다. 

>>> from sklearn.base import BaseEstimator, TransformerMixin
>>> rooms_ix, bedrooms_ix, population_ix, household_ix = 3,4,5,6
>>> class CombineAttributesAdder(BaseEstimator, TransformerMixin):
def __init__(self, add_bedrooms_per_room = True): # *args와 *kargs를 피하세요
self.add_bedrooms_per_room = add_bedrooms_per_room
def fit(self, X, y=None):
return self # 할 일은 없습니다
def transform(self, X, y=None):
rooms_per_household = X[:, rooms_ix] / X[:, household_ix]
population_per_household = X[:, population_ix] / X[:, household_ix]
if self.add_bedrooms_per_room:
bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
return np.c_[X, rooms_per_household, population_per_household, bedrooms_per_room]
else:
return np.c_[X, rooms_per_household, population_per_household]

>>> attr_adder = CombineAttributesAdder(add_bedrooms_per_room=False)
>>> housing_extra_attribs = attr_adder.transform(housing.values)

이 예에서, 트랜스포머(transformer)는 기본적으로 True를 설정한(흔히 민감한 기본값을 제공하는데 도움이 됨)하나의 하이퍼파라미터, add_bedrooms_per_room를 가지고 있습니다. 
이 하이퍼 파라미터는 이 속성을 추가하는 것이 머신러닝 알고리즘에 도움이 되는지 안되는지 쉽게 찾을 수 있도록 해줄 것입니다. 
더 일반적으로, 100% 확신할 수 없는 어떤 데이터를 준비하는 단계에서 점검하기 위한 하이퍼파라미터를 추가할 수 있습니다. 
이들 데이터의 준비 단계를 더 많이 자동화하고, 자동화를 시도할 수 있는 결합을 더 많이 할수록, 매우 좋은 결합을 발견할 가능성이 매우 높아질 것입니다(그리고 많은 시간을 절약하게 해 줄 것입니다).

피처(Feature) 스케일링(Scaling)

데이터에 적용이 필요한 가장 중요한 변환 중 하나는 피처(feature) 스케일링(scaling)입니다. 
몇 가지 예외가 있지만, 머신러닝 알고리즘은 입력 숫치 속성들이 매우 다른 스케일링을 가지면 잘 수행되지 않습니다. 
housing 데이터가 그런 경우입니다: 중간 소득이 0에서 15까지의 범위를 가지는 것에 비해, 전체 방수는 6에서 39,320까지의 범위를 가집니다. 목표 값의 스케일링은 일반적으로 필요하지 않다는 것에 유의하시기 바랍니다.
모든 속성들이 동일한 스케일을 갖도록 하는 두가지 일반적인 방법이 있습니다: min-max scaling과 standardization.
Min-max scaling(많은 사람들이 normalization이라 부릅니다)은 매우 간단합니다: 값들이 이동되어, 결국 0에서 1사의 범위에서 재스케일링 됩니다. 최소값을 뺀 값에, 최대값에서 최소값을 마이너스한 값을 나눔으로써 구합니다. 
사이킷런(Scikit-Learn)에서는 이를 위해 MinMaxScaler라는 트랜스포머를 제공합니다. 
어떤 이유로 0-1의 범위를 원하지 않을 때 범위를 변경할 수 있도록 feature_range라는 하이퍼트랜스포머를 갖습니다.

표준화(Standardization)는 전혀 다릅니다: 먼저 평균값을 구합니다(그래서 표준화된 값은 항상 제로 평균값을 갖습니다). 그런 다음 분산으로 나누어, 그 결과 분포가 단위 분산을 갖도록 합니다. 
min-max scaling과 달리, 표준화(Standardization)는 값을 특정 범위로 한정하지 않습니다. 
이것이 몇몇 알고리즘에서는 문제가 될 수도 있습니다(예를 들어, 뉴럴 네트워크는 흔히 입력 값의 범위를 0에서 1이라고 기대합니다). 하지만, 표준화(Standardization)는 특이값(outliers)에 의한 영향을 훨씬 덜 받습니다. 
예를 들어, 한 지구(district)의 중간 소득이 100과 같다고(실수로) 가정합시다. Min-max scaling은 모든 다른 값들이 0-15에서 0-0.15로 망가트립니다. 반면에 표준화(Standardization)는 별로 영향을 받지 않습니다. 
사이킷런(Scikit-Learn)은 표준화(Standardization)를 위해 StandardScaler라는 트랜스포머를 제공합니다.

변환 파이프라인(Pipeline)

보시다시피, 올바른 순서로 실행해야 하는 데이터 변환의 많은 단계가 있습니다. 
사이킷런(Scikit-Learn)은 그러한 일련의 변환을 도와줄 수 있는 파이프라인(Pipeline) 클래스를 제공합니다.
수치 속성들에 대한 작은 파이프라인 코드를 보시기 바랍니다:

>>> from sklearn.pipeline import Pipeline
>>> from sklearn.preprocessing import StandardScaler
>>> num_pipeline = Pipeline([
('imputer', Imputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),
])
>>> housing_num_tr = num_pipeline.fit_transform(housing_num)

파이프라인(Pipeline) 생성자(constructor)는 일련의 단계를 정의하는 이름(name)/추정량(estimator) 쌍 리스트를 가집니다. 
마지막 추정량을 제외한 모든 것이 트랜스포머여야 합니다(예, 그것들은 fit_transform() 메서드를 가져야만 합니다). 
이름들(names)은 좋아하는 어떤 것으로도 정할 수 있습니다.
파이프라인의 fit() 메서드를 호출할 때, 마지막 추정량에 도달할 때까지, 파라미터로써 각 호출의 결과물을 다음 호출에 전달합니다. 
모든 트랜스포머에 대해 순차적으로 fit_transform()을 호출하며, 이를 위해서 fit() 메서드를 호출하기만 하면 됩니다.
파이프라인은 마지막 추정량으로써 동일한 메서드를 제공합니다. 마지막 추정량은 StandardScaler입니다. 
이것은 트랜스포머이기 때문에 파이프라인은 순차적으로 데이터에 모든 변환을 전달하는 transform() 메서드를 가집니다(또한 fit()과 함께 transform()을 호출하는 대신에 사용했던 fit_transform 메서드를 가집니다).
이제 수치 값들에 대한 파이프라인을 가지고 되었습니다. 그리고, LabelBinarizer를 범주 값들에 대해 적용하는 것이 필요합니다: 이러한 변환을 어떻게 단일 파이프라인에 결합시킬 수 있을까요? 사이킷런(Scikit-Learn)은 이것을 위해 FeatureUnion 클래스를 제공합니다. 변환 리스트를 제공하고(전체 변환 파이프라인이 될 수 있습니다), transform() 메서드가 호출되었을 때 각각의 변환에 대한 transform() 메서드가 병행으로 실행됩니다. 
결과물을 기다린 후, 그것들을 결합하고 결과값으로 돌려줍니다. 
수치와 범주 속성 양쪽을 핸들링하는 전체 파이프라인은 다음처럼 보여질 수 있습니다:

>>> from sklearn.pipeline import FeatureUnion
>>> num_attribs = list(housing_num)
>>> cat_attribs = ["ocean_proximity"]
>>> num_pipeline = Pipeline([
('selector', DataFrameSelector(num_attribs)),
('imputer', Imputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),
])
>>> cat_pipeline = Pipeline([
('selector', DataFrameSelector(cat_attribs)),
('label_binarizer', LabelBinarizer()),
])
>>> full_pipeline = FeatureUnion(transformer_list=[
("num_pipeline", num_pipeline),
("cat_pipeline", cat_pipeline),
])

그리고 전체 파이프라인을 간단하기 실행할 수 있습니다:

>>> housing_prepared = full_pipeline.fit_transform(housing)
>>> housing_prepared
array([[ 0.73225807,  -0.67331551,  0.58426443,  ...,  0.           ,  0.          ,  0.           ],
 [-0.99102923,   1.63234656, -0.92655887,  ...,  0.           ,  0.          ,  0.           ],
 [...]
>>> housing_prepared.shape
(16513, 17)

각 서브 파이프라인은 선택기(selector) 파이프라인으로 시작합니다: 간단히 원하는 속성(수치 또는 범주)을 선택함으로써 데이터를 변환합니다. 
나머지를 버리고, 결과 DataFrame을 넘파이(NumPy) 배열로 전환합니다. 
사이킷런(Scikit-Learn)에서는 판다스(Pandas) DataFrame을 핸들링할 수 없기 때문에, 이 작업을 위해 간단한 사용자 정의 트랜스포머를 작성하는 것이 필요합니다.

>>> from sklearn.base import BaseEstimator, TransformerMixin
>>> class DataFrameSelector(BAseEstimator, TransformerMixin):
def __init__(self, attribute_names):
self.attribute_names = attribute_names
def fit(self, X, y=None):
return self
def transform(self, X):
return X[self.attribute_names].values

4단계는 여기까지입니다. 갈수록 어려워지는 것 같습니다. 다시 앞 단계 내용을 복습하면서 머리속에 정리해 넣어야겠습니다. ㅜㅜ
  

머신러닝 프로젝트 실행-1

(1~2단계: 1. 문제정의하고 전체 그림 바라보기/ 2. 데이터 얻기바로가기 


머신러닝 프로젝트 실행-2

(3단계: 인사이트를 찾기 위해 데이터 탐색하기바로가기 


참고)'Hands-On Machine Learning with Scikit-Learn and TensorFlowchapter 2' 

주피터 노트북에서 볼 수 있는 전체 코드 얻기


반응형
반응형

머신러닝 프로젝트 실행 1~2단계에 이어, 3단계를 정리하도록 하겠습니다.


3. 인사이트를 찾기 위해 데이터 탐색하기

1~2단계에서는 지금까지 처리하는 데이터의 종류에 대한 일반적인 이해를 얻기 위해 데이터를 훑어보았습니다. 

지금부터는 조금 더 깊이 들어가 보도록 하겠습니다.

먼저, 테스트 셋을 별도로 마련해 두었는지 확인하고, 훈련 셋을 탐색해 보도록 합시다. 만약 훈련 셋이 아주 크다면, 쉽고 빠르게 데이터를 다루기 위해 탐색하기 위한 셋을 샘플링하고 싶을지도 모릅니다. 우리 데이터의 경우에는, 훈련 셋이 매우 작기 때문에 전체 셋에서 직접 작업을 할 수 있습니다. 

카피본을 만들어, 훈련 셋에 영향을 주지 않고 작업을 시작해 봅시다.

>>> housing = strat_train_set.copy()


지리 데이터를 시각화하기

지리 정보(위도와 경도)가 있기 때문에, 데이터를 시각화하기 위해 모든 지구의 산포도를 만들어 보는 것은 좋은 아이디어입니다.

>>> housing.plot(kind="scatter", x="longitude", y="latitude")

이것은 캘리포니아처럼 보입니다. 하지만, 어떤 특정 패턴을 보기는 어렵습니다. alpha 옵션(투명도)을 0.1로 설정하면 데이터 포인트가 고밀도인 곳이 어느 지역인지 더 쉽게 시각화할 수 있습니다.

>>> housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.1)

이제 더 좋아 보입니다. 

즉 베이지역과 로스앤젤레스, 샌디에이고 같은 고밀도 지역을 분명하게 구분할 수 있습니다. 샌트럴 밸리 특히 새크래멘토와 프레스노 주변은 상당히 고밀도의 긴 선을 보여주고 있습니다.

일반적으로, 우리 두뇌는 그림에서 얼룩진 패턴을 인식하는데 매우 우수합니다. 하지만, 패턴을 눈에 띄게 만들기 위해 시각화 파라미터를 가지고 더 작업이 필요할 수도 있습니다. 


자, 집값을 보도록 합시다. 

각 원의 반경은 지구의 인구수를 나타냅니다. 그리고 색깔은 가격을 나타냅니다. 여기서는 jet(낮은 값은 푸른색, 높은 값은 붉은색으로 표시)라 불리는 미리 정의된 색깔 맵을 사용할 것입니다. 

>>> housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.4, s=housing["population"]/100, label="population", c="median_house_value", cmap=plt.get_cmap("jet"), colorbar=True)

>>> plt.legend()

이 이미지는 집값이 위치(예를 들자면, 해안과 가까운) 및 인구 밀도와 매우 밀접한 관계가 있다고 알려줍니다. 아마도 중심 클러스터를 탐지하기 위해 클러스터링 알고리즘을 사용하는 것이 유용할 수도 있습니다. 그리고, 클러스터 센터를 더 근접 측정하기 위해 새로운 피처들(features)을 추가할 수도 있습니다. 해안 근접 속성이 유용할 수 있지만, 북부 캘리포니아의 해안 지구 집값이 높지 않기 때문에, 규칙이 간단하지만은 않습니다.


상관관계 찾기

데이터셋이 아주 크지 않기 때문에, 모든 속성 쌍에서 corr() 메소드를 사용해 표준 상관 계수(Pearson's r이라 불림)를 쉽게 계산할 수 있습니다.

>>> corr_matrix = housing.corr()


중간 집 값과 각 속성들이 얼마나 상관관계가 있는 보도록 합시다.

>>> corr_matrix["median_house_value"].sort_values(ascending=False)

median_house_value        1.000000

median_income              0.687170

total_rooms                  0.135231

housing_median_age        0.114220

households                   0.064702

total_bedrooms              0.047865

population                  -0.026699

longitude                   -0.047279

latitude                     -0.142826

Name: median_house_value, dtype: float64


상관 계수의 범위는 -1에서 1사이입니다. 

1에 가깝다는 것은 강한 양의 상관관계가 있다는 것을 의미합니다: 예를 들어, 중간 소득이 올라갈 때, 중간 집 값이 올라가는 경향이 있습니다. 상관관계가 -1에 가깝다는 것은, 강한 음의 상관관계가 있다는 것을 의미합니다: 위도와 중간 집 값은 작은 음의 상관관계가 있다는 것(예, 북쪽으로 갈수록 집 가격이 약간 낮아지는 경향이 있습니다)을 알 수 있습니다. 마지막으로, 계수가 0에 가깝다는 것은 선형 상관관계가 없다는 것을 의미합니다. 

다음의 이미지들은 수평 및 수직 축 사이의 상관 계수에 대한 다양한 플롯을 보여줍니다.

(출처 : 위키피디아, public domain image)


속성들 사이의 상관관계를 점검하는 또 다른 방법은 판다스(Pandas)의 scatter_matrix 함수를 사용하는 것입니다. 이 함수는 모든 수치 속성을 모든 다른 수치 속성들에 대비한 플롯해 보여줍니다. 지금 11개의 수치 속성들이 있기 때문에, 121개의 플롯들을 얻게 될 것입니다. 여기서는 중간 집값과 가장 좋은 상관관계를 보여주는 몇가지 속성들에만 집중하도록 합니다.

>>> from pandas.tools.plotting import scatter_matrix

>>> attributes = ["median_house_value", "median_income", "total_rooms", "housing_median_age"]

>>> scatter_matrix(housing[attributes], figsize=(12, 8))

메인 대각선(왼쪽 위에서 오른쪽 아래)에는 각 속성에 대한 히스토그램을 보여줍니다. 이는 동일 속성에 대해 상관관계를 보여주는 것은 의미가 없기 때문입니다. 

중간 집값과 가장 상관관계가 높은 속성은 중간 소득입니다. 그 상관관계 산포도를 확대해 보도록 합시다.

>>> housing.plot(kind="scatter", x="median_income", y= "median_house_value", alpha=0.1)

이 플롯은 몇가지를 나타냅니다. 

첫째, 실제 상관관계가 매우 강하다는 것입니다; 상향 트렌드를 분명하게 볼 수 있고, 포인트들이 많이 분산되지도 않았습니다. 

둘째, 처음에 알고 있던 가격 상한선이 $500,000 수평선에 분명하게 보입니다. 하지만, 이 플롯은 또 다른 분명한 직선들을 보여줍니다: $450,000 주변 수평선, $350,000 주변 수평선, 아마도 $280,000 주변 수평선과 더 아래의 일부. 

이러한 데이터 단점을 재현하도록 알고리즘이 학습되는 것을 방지하기 위해 대응하는 지구를 제거해야 할지도 모릅니다.


속성 조합으로 시험하기

잘하면 이전 섹션의 몇 가지 방법을 통해 데이터를 탐색한 후 인사이트를 얻을 수 있습니다. 

머신러닝 알고리즘에 데이터를 피딩(feed)하기 전에 청소하고 싶어할 몇가지 데이터 단점을 알게 되었습니다. 그리고 타겟 속성과 다른 속성들 사이의 흥비로운 상관관계를 발견했습니다. 또한 몇몇 속성들은 편향된 분포도를 가지고 있다는 것을 알 수 있었고, 이것들을 변환(예, 그 속성들의 로그를 계산해서 표준 분포로)해야할지도 모릅니다. 물론, 각 프로젝트마다 수고해야 하는 노고는 각기 다릅니다만, 일반적인 아이디어는 유사합니다.


머신러닝 알고리즘을 위해 데이터를 실제 준비하기 전에 해야할 마지막 일은 다양하게 속성 조합을 시도해 보는 것입니다. 

예를 들어, 얼마나 많은 가정이 있는지 알지 못한다면, 각 지구의 전체 방수는 유용하지 않습니다. 정말 원하는 것은 각 가정의 방수입니다. 유사하게, 전체 침실수 자체로는 유용하지 않습니다. 아마도 침실수를 방숫자와 비교하고 싶어할 것입니다. 그리고 각 가정별 인구수 역시 흥미로운 속성 조합처럼 보입니다. 이들 새로운 속성들을 만들어 보도록 합시다.

>>> housing["rooms_per_household"] = housing["total_rooms"]/housing["househods"]

>>> housing["bedrooms_per_room"] = housing["total_bedrooms"]/housing["total_rooms"]

>>> housing["population_per_household"] = housing["population"]/housing["households"]


그런 다음, 다시 상관관계 매트릭스를 보도록 합시다:

>>> corr_matrix = housing.corr()

>>> corr_matrix["median_house_value"].sort_values(ascending=False)

median_house_value            1.000000

median_income                  0.687170

rooms_per_household           0.199343

total_rooms                      0.135231

housing_median_age            0.114220

households                       0.064702

total_bedrooms                  0.047865

population_per_household     -0.021984

population                      -0.026699

longitude                       -0.047279

latitude                         -0.142826

bedrooms_per_room            -0.260070

Name: median_house_value, dtype: float64


새로운 bedrooms_per_room 속성이 중간 집값과 가장 높은 상관관계를 보여줍니다. 분명히 더 적은 침실/전체방 비율을 가진 집들이 더 비싼 경향이 있습니다. 각 가정별 방수 또한 지구내 전체 방수보다 더 많은 정보를 제공합니다- 분명히 집들이 더 클수록 더 비쌉니다.

이 라운드의 탐색은 아주 철저하지 않아도 됩니다; 포인트는 올바른 출발점을 찾는 것이고 합리적으로 좋은 프로토타입을 얻기 위한 인사이트를 빠르게 얻는 것입니다. 하지만 이것은 반복적인 절차입니다: 한번 프로토타입을 얻어서 실행한 후, 더 많은 인사이트를 얻기 위해 그 결과물을 분석할 수 있습니다. 그런 다음 이 탐색 단계로 다시 돌아올 수 있습니다.


생각보다 공부한 것을 정리하는 것이 쉬운 작업이 아니네요. 조금식 꾸준히 하는 것이 한꺼번에 많이 하는 것보다 더 좋다고 생각해서 자주 한 단계씩 정리하도록 할 계획입니다.


"4. 기본 데이터 패턴을 머신러닝 알고리즘에 더 잘 노출할 수 있도록 데이터 준비하기"는 다음 글로 정리하겠습니다.


머신러닝 프로젝트 실행-1

(1~2단계: 1. 문제정의하고 전체 그림 바라보기/ 2. 데이터 얻기바로가기 


참고)'Hands-On Machine Learning with Scikit-Learn and TensorFlowchapter 2' 

주피터 노트북에서 볼 수 있는 전체 코드 얻기


반응형
반응형

요즘 읽고 있는 ML책 중, 예제를 통해 머신러닝 프로젝트 실행 프로세스를 처음부터 끝까지 배우는 부분이 있어, 정리해 봅니다.

머신러닝을 배우는 데 있어 실제 세상의 데이터를 가지고 프로젝트를 수행해 보는 것이 최상일 것입니다. 실제 데이터를 미국에서는 정말 많이 공짜로 제공하고 있습니다. 실 데이터를 가지고 머신러닝 실습을 하게 되면 무척 도움이 많이 될 것입니다.

우선 내용이 길어 글을 나눠서 올리도록 하겠습니다. 

전체 순서는

1. 문제를 정의하고 전체 그림 바라보기

2. 데이터 얻기

3. 인사이트를 찾기 위해 데이터 탐색하기

4. 기본 데이터 패턴을 머신러닝 알고리즘에 더 잘 노출할 수 있도록 데이터 준비하기

5. 다양한 모델을 탐색하고 그 중 가장 좋은 모델을 찾기

6. 모델을 알맞게 튜닝하고 멋진 솔루션으로 통합하기

7. 시스템 런칭, 모니터링과 유지하기

순입니다.


이번에는 1~2번까지 정리해서 올리도록 하겠습니다. 


참고로, 오픈 데이터셋 중에서 가장 인기있는 3곳의 링크는 아래와 같습니다. 

다른 곳도 있지만, 이곳을 가장 많이 애용한다고 하네요.

UC Irvine Machine Learning Repository (http://archive.ics.uci.edu/ml/)

Kaggle datasets (https://www.kaggle.com/datasets)

Amazon's AWS datasets (https://aws.amazon.com/fr/datasets/)


그럼, 지금부터  StatLib repository의 캘리포니아 집값 데이터셋을 가지고 머신러닝 프로젝트를 만들어 볼 것입니다. 

이 데이터셋은 1990년의 캘리포니아 인구조사 데이터를 기반으로 하고 있습니다. 


1. 문제를 정의하고 전체 그림 바라보기

첫번째 해야 할 일은 캘리포니아 인구조사 데이터를 사용해 캘리포니아 집값 모델을 만드는 것입니다. 이 데이터는 캘리포니아의 각 블록 그룹에 대한 인구, 중간 소득, 중간 집값 등 같은 메트릭스로 구성되어 있습니다. 블록 그룹은 미국 인구조사국이 제공하는 가장 작은 지리적 유닛(블록 그룹은 통상 600~3,000명의 사람들로 구성)입니다. 우리는 간단히 '지구(districts)'라고 부를 것입니다. 

그럼 이제 정확하게 이 데이터를 가지고 무엇을 하려고 하는 것인지를 분명히 하는 것으로 시작해야 합니다. 문제를 어떻게 정하느냐에 따라 문제를 정의하고 어떤 알고리즘을 선택해서, 성능을 측정할지가 정해지기 때문입니다. 

여기서는 주어진 데이터를 통해 지구별 가격을 예측해서, 어느 곳에 투자하는 것이 이익을 가장 많이 얻을 수 있는지 알아보는 것을 문제로 정했습니다.

그 다음 확인할 사항은 현재 실행되고 있는 해결 방안이 어떻게 이루어지고 있는지 파악하는 것입니다. 

여기서는 현재 지역별 집값을 전문가들이 직접 측정하고 있다고 합니다: 지구별 최신 정보를 팀이 모으고, 측정을 위해 복잡한 규칙을 적용합니다. 이것은 비용과 시간이 소모되고, 측정값이 훌륭하지도 않습니다; 통상 에러율이 약 15% 수준입니다.

이 모든 정보를 가지고 이제 시스템 디자인을 시작합니다. 

첫째, 문제에 대한 프레임이 필요합니다: 지도학습, 비지도학습, 또는 강화학습? 분류 업무, 회귀 업무, 또는 그 밖에 다른? 배치 학습 또는 온라인 러닝 기술을 사용해야 하는지? 등을 결정해야 합니다.

이것은 분명히 라벨링된 훈련 예제들(각 인스턴스들은 기대되는 아웃풋과 함께 제시, 예를 들어 각 지구의 중간 집값)이 주어진 통상적인 지도학습 업무입니다. 게다가, 값을 예측해야 하기 때문에 전형적인 회귀 업무입니다. 더 특별하게는, 이 시스템이 다양한 피처들(features)을 사용해서 예측을 만들기 때문에 다변량 회귀 문제입니다(지역별 인구, 중간 수익값 등을 사용할 것입니다). 마지막으로 빠르게 변화하는 데이터를 반영할 필요가 없고, 데이터도 메모리에서 처리가 적당할 정도로 적기 때문에, 배치 러닝을 사용하는 것으로도 충분할 것입니다. 

* 데이터가 크다면, 멀티 서버를 통해 배치 러닝 작업을 분할할 수도 있습니다(예를 들어 MapReduce 기술을 사용). 또는 온라인 러닝 기술을 사용할 수도 있습니다.

다음 단계는 성능 측정을 선택하는 것입니다. 회귀 문제에 대한 전형적인 성능 측정은 시스템이 만든 예측값들의 에러들에 대한 표준 편차를 측정하는 RMSE (Root Mean Square Error)를 사용합니다. 예를 들어, RMSE가 50,000과 같다는 것은 시스템이 예측한 68%가 실제 50,000에 포함된다는 의미입니다. 그리고 예측값의 95%가 실제 100,000 값들에 포함된다는 것입니다. 

RMSE가 일반적으로 회귀 업무에 있어 선호되는 성능 측정이긴 하지만, 몇몇 문맥에서 다른 기능을 사용하는 것을 선호할 수 도 있습니다. 예를 들어, 특이값을 갖는 지구가 많이 있다고 가정해 보자. 그러한 경우에, Mean Absolute Error(MAE)를 사용하는 것도 고려해 볼 수 있습니다. 

RMSE와 MAE 모두 두개의 벡터값 거리를 측정하는 방법들입니다: 예측 벡터와 타겟값 벡터. 다양한 거리 측정, 또는 규칙들이 가능합니다.

마지막으로, 지금까지 만들어진 가정들을 리스트하고 검증합니다. 이를 통해 심각한 이슈사항을 찾을 수 있습니다. 예를 들어, 시스템 아웃풋인 각 지구별 값을 정확하게 예측하는 것보다 카테고리(예, 싼, 중간, 비싼)로 가격을 변환해서 처리하는 것이 더 나을 수도 있습니다. 이럴 경우, 이 문제는 회귀 업무가 아니라, 분류 업무로 재정의되어야 할 것입니다. 몇 달 동안 회귀 시스템 구축을 추진한 후에 분류 업무로 처리해야 한다는 말을 듣고 싶지는 않을 것입니다. 그렇기 때문에, 초기에 피드백을 받는 것이 중요합니다.


2. 데이터 얻기

자, 이제 손을 사용해 봅시다. 

바로 랩탑을 열고 Jupyter 노트북에서 다음의 코드 예제를 열어봅시다.

먼저, 파이썬을 인스톨해야 합니다(이미 다 했겠죠?). 

다음으로는 머신러닝 코드와 데이터셋을 위한 작업 디렉토리가 필요합니다. 더 필요한 라이브러리들은 다음과 같습니다: Jupyter, Numpy, Pandas, Matplotlib, Scikit-Learn.  여기서는 이미 다 설치했다고 가정하겠습니다.

(Jupyter notebook 환경에서 실행하는 것을 가정했습니다. 다른 환경에서는 아래 코드가 제대로 실행되지 않을 수 있습니다)

>>> import os

>>> import tarfile

>>> from six.moves import urllib

>>> DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handon-ml/master/"

>>> HOUSING_PATH = "datasets/housing"

>>> HOUSING_URL = DOWNLOAD_ROOT + HOUSING_PATH + "/housing.tgz"

>>> def fetch_housing_data(housing_url=HOUSING_URL, housing_path=HOUSING_PATH):

if not os.path.isdir(housing_path):

    os.makedirs(housing_path)

tgz_path = os.path.join(housing_path, "housing.tgz")

urllib.request.urlretrieve(housing_url, tgz_path)

housing_tgz = tarfile.open(tgz_path)

housing_tgz.extractall(path=housing_path)

housing_tgz.close()

함수 fetch_housing_data()를 불러오면, 작업 디렉토리에 datasets/housing 폴더를 만들고, housing.tgz파일을 다운로드 합니다. 그리고, 이 디렉토리에 housing.csv파일을 압축해제 합니다.

그 다음에 pandas를 사용해 데이타를 로드해 봅시다. 데이터를 불러오기 위해 다음 코드를 씁니다

>>> import pandas as pd

>>> def load_housing_data(housing_path=HOUSING_PATH):

csv_path = os.path.join(housing_path, "housing.csv")

return pd.read_csv(csv_path)

위 함수는 모든 데이터를 포함한 pandas 데이타프레임을 불러옵니다.

그 다음에 데이터 구조를 빠르게 살펴봅니다.

>>> housing = load_housing_data()

>>> housing.head()

각 열은 하나의 특정 속성을 나타냅니다. 여기에는 10개의 속성들이 있습니다 : longitude, latitude, housing_median_age, total_rooms, total_bedrooms, population, households, median_income, median_house_value, ocean_proximity.

>>> housing.info()

 이 데이터셋에는 총 20,640개의 레코드들이 있습니다. 머신러닝 표준에서는 매우 적은 수의 데이터들입니다. 그러나, 처음 시작하기에는 완벽합니다. 

info()로 확인한 바에 의하면, bedrooms 속성이 20,433개의 non-null 값을 가지고 있습니다. 이것은 207개의 레코드의 값이 없다는 것을 의미합니다. 그리고 모든 속성들이 숫자들입니다. ocean_proximity를 제외하구요. ocean_proximity의 type은 object로 파이썬의 어떤 object라도 담을 수 있습니다. 위 head()를 잘 살펴보았다면, ocena_proximity가 text속성을 가지고 잇다는 것을 알 수 있을 것입니다. 또한, 이 컬럼의 값이 반복된다는 것을 알아챘다면, value_counts() 메소드를 통해 각 카테고리에 얼마나 많은 값들이 있는지 알아낼 수 있을 것입니다.

>>> housing["ocean_proximity"].value_counts()

<1H OCEAN        9136

INLAND              6551

NEAR OCEAN     2658

NEAR BAY          2290

ISLAND                     5

Name: ocean_proximity, dtype: int64

다른 필드를 봅시다. 

describe() 메서드는 숫자 속성들의 요약을 보여줍니다.

>>> housing.describe()

count, mean, min과 max 열은 자명합니다. null 값들이 무시된 것을 주의하시기 바랍니다(예를 들어, total_bedrooms의 count는 20,640이 아니라 20,433입니다). 25%, 50%와 75% 열은 해당 백분위 수를 보여줍니다. 예를 들어, housing_median_age의 25%는 18보다 작다는 것을 알려줍니다. 50%는 29보다, 75%는 37보다 작습니다. 이것은 흔히 25th 백분위(또는 1  분위수), 중앙값, 그리고 75th 백분위(3 분위수)로 불립니다.

지금 다루고 있는 데이터 형태를 느끼기 위한 빠른 다른 방법은 각 숫자 속성에 대한 히스토그램을 그리는 것입니다. 

>>> %matplotlib inline # 주피터 노트북에서만 가능합니다.

>>> import matplotlib.pyplot as plt

>>> housing.hist(bins=50, figsize=(20,15))

>>> plt.show()

 (주피터 노트북에서 위의 명령어를 입력하고 실행해야 Matplotlib를 백그라운드에서 쉽게 이용할 수 있습니다) 

위 히스토그램에서, median_income 속성은 미국달러로 표현된 것처럼 보이지 않습니다. 머신러닝에서는 보통 전처리된 속성들로 작업합니다. 그래서, 문제가 되지는 않습니다만, 데이터가 어떻게 계산되었는지 파악하려는 노력을 해야만 합니다.

housing_median_age와 median_house_value값 모두 상한선이 있습니다. 이것이 문제가 될지 안될지 점검하는 것이 필요합니다. 

많은 히스토그램들의 꼬리가 깁니다. 중앙값보다 오른쪽으로 더 많이 깁니다. 이것은 몇몇 머신러닝 알고리즘에 있어 패턴을 인식하는데 더 어려움을 줄 수 있습니다. 나중에 이들 속성들이 종 모양의 분포를 가질 수 있도록 속성들에 변형을 주도록 할 것입니다.


테스트 셋 만들기

이론적으로 테스트 셋을 만드는 것은 매우 간단합니다 : 랜덤하게 몇몇 인스턴스를 선택합니다. 통상 데이터셋의 20%를 선택해서, 따로 설정해 둡니다.

>>> import numpy as np

>>> def split_train_test(data, test_ratio):

            shuffled_indices = np.random.permutation(len(data))

test_set_size = int(len(data) * test_ratio)

test_indices = shuffled_indices[:test_set_size]

train_indices = shuffled_indices[test_set_size:]

return data.iloc[train_indices], data.iloc[test_indices]


위 함수를 아래처럼 사용합니다:

>>> train_set, test_set = split_train_test(housing, 0.2)

>>> print (len(train_set), "train +", len(test_set), "test")

16512 train + 4128 test

이것이 동작하긴 하지만, 완벽하지는 않습니다. 만약 이 프로그램을 다시 작동시키면, 이것은 다른 테스트 셋을 생성할 것입니다! 하나의 해결책은 처음 작동시에 테스트 셋을 저장하는 것입니다. 다른 옵션은 np.random.permutation()을 호출하기 전에 랜덤 숫자 제너레이터 씨드를 설정(예를 들어, np.random.seed(42))하는 것입니다. 그러면 항상 동일한 수치들은 생성할 것입니다.

위 두개의 해결책은 다음 번 업데이트된 데이터셋을 패치할 때 깨질 것입니다. 일반적인 해결책은 테스트 셋에 가야할 지 말아야 할지를 결정하기 위해 각 인스턴스의 식별자(인스턴스들이 고유하고 불변의 식별자를 가지고 있다고 가정)를 사용하는 것입니다. 예를 들어, 각 인스턴스의 식별자의 해시를 계산하고, 그 해시의 마지막 바이트만 간직합니다. 그리고 그 값이 51(256의 20%)보다 작거나 같다면 테스트 셋에 그 인스턴스를 집어넣습니다. 이렇게 하면 여러번 실행되는 동안, 데이타셋을 리프레시할 때 조차도 테스트 셋이 일관성을 유지하게 됩니다. 새로운 테스트 셋은 새로운 인스턴스의 20%를 포함할 것입니다. 하지만, 이전 트레이닝 셋의 어떤 인스턴스도 포함하지 않습니다. 아래 실행 가능한 코드가 있습니다.

>>> import hashlib

>>> def test_set_check(identifier, test_ratio, hash):

return hash(np.int64(identifier)).digest()[-1] < 256 * test_ratio

>>> def split_train_test_by_id(data, test_ratio, id_column, hash=hashlib.md5):

ids = data[id_column]

in_test_set = ids.apply(lamda id_: test_set_check(id_, test_ratio, hash))

return data.loc[~in_test_set], data.loc[in_test_set] 

불행히도, housing 데이타 셋은 식별자 컬럼을 가지고 있지 않습니다. 간단한 해결책은 ID로써 열 인덱스를 사용하는 것입니다.

>>> housing_with_id = housing.reset_index() # '인덱스'컬럼 추가

>>> train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")

만약 고유한 식별자로 열 인덱스를 사용한다면, 새로운 데이터가 데이터 셋 마지막에 추가되는 것을 확인하는 것이 필요합니다. 그리고 어떤 열도 삭제되지 않아야 합니다. 이것이 불가능하다면, 고유한 식별자를 만들기 위해 가장 안정적인 피처를 사용하도록 해봐야 할 것입니다. 예를 들어, 지구(district)의 위도와 경도가 수백년 동안 안정된 것으로 보장될 것입니다. 그래서 이 둘을 ID로써 병합할 수 있습니다. 

>>> housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]

>>> train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")

사이킷런(Scikit-Learn)은 다양한 방법으로 데이터셋을 여러 개의 서브 셋으로 나누는 함수들을 제공합니다. 가장 간단한 함수가 train_test_split입니다. 앞에서 정의한 split_train_test 함수와 매우 많은 부분이 동일합니다. 

>>> from sklearn.model_selection import train_test_split

>>> train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)

지금까지 순수 랜덤 샘플링 메서드를 살펴보았습니다. 이것은 데이터셋이 충분히 크다면(특히 속성들의 수와 비례해서), 일반적으로 훌륭합니다. 그러나 그렇지 않은 경우에는 상당한 샘플링 편차가 발생할 수 있습니다. 설문조사 회사가 1,000명에게 설문을 돌릴 때, 단순히 전화번호부에서 무작위로 1,000명을 뽑지는 않습니다. 예를 들어, 미국 인구는 51.3%의 여성과 48.7%의 남성으로 구성되어 있습니다. 그래서 미국에서 잘 만든 설문조사는 샘플에서 이 비율(513명의 여성과 487명의 남성)을 유지하려고 할 것입니다. 이것을 층화(stratified) 샘플링이라 부릅니다: 인구는 지층이라 불리는 균질한 하위 집단으로 나뉘어 집니다 . 테스트 셋이 전체 모집단을 대표할 수 있도록 하기 위해 각 계층에서 올바른 숫자의 인스턴스들이 샘플링됩니다. 만약 순수 랜덤 샘플링 메서드를 사용했다면, 49% 여성보다 더 적거나 54% 여성보다 더 많은 편향된 테스트 셋을 샘플링할 확률이 12%나 됩니다. 어느 쪽이든, 설문조사 결과가 상당히 편향될 것입니다. 

전문가들이 중간(median) 집값을 예측하기 위해 중간(median) 소득이 매우 중요한 속성이라고 말했다고 가정해 봅시다. 테스트 셋이 전체 데이터셋의 다양한 카테고리의 소득들을 대표하는 것이 확실하기를 원할 것입니다. 중간(median) 소득이 연속된 숫자 속성이기 때문에, 최초에 수입 카테고리 속성을 만드는 것이 필요합니다. 중간(median) 소득 히스토그램을 더 자세히 들여다 봅시다.

대부분의 중간 소득 값은 2-5 사이입니다. 하지만 몇몇 중간 소득들은 6을 넘습니다. 데이터 집합에 각 계층별 충분한 수의 인스턴스들을 갖는 것이 중요합니다. 그렇지 않으면 지층별 중요성에 대한 추정치가 편향될 수도 있습니다. 

다음 코드는 1.5로 나눈 소득 카테고리 속성을 만듭니다. 그리고 ceil을 사용해 반올림(이산 카테고리를 갖게 됩니다)합니다. 그 다음에 5보다 큰 모든 카테고리를 카테고리 5로 합칩니다.

>>> housing["income_cat"] = np.ceil(housing["median_income"] / 1.5)

>>> housing["income_cat"].where(housing["income_cat"] < 5, 5.0, inplace=True)

이제 소득 카테고리에 기반한 층화(stratified) 샘플링을 할 준비가 되었습니다. 사이킷런(Scikit-Learn)의 StratifiedShuffleSplit 클래스를 사용할 수 있습니다.

>>> from sklearn.model_selection import StratifiedShuffleSplit

>>> split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)

>>> for train_index, test_index in split.split(housing, housing["income_cat"]):

strat_train_set = housing.loc[train_index]

strat_test_set = housing.loc[test_index]

기대한대로 동작하는지 봅시다. 먼저 전체 housing 데이터셋에서 소득 카테고리 비중을 봅시다.

>>> housing["income_cat"].value_counts() / len(housing)

3.0    0.350581

2.0    0.318847

4.0    0.176308

5.0    0.114438

1.0    0.039826

name: income_cat, dtype: float64

위와 유사한 코드로 테스트 셋의 소득 카테고리 비중을 측정할 수 있습니다. 아래 표에서 전체 데이터셋과 층화(stratified) 샘플링으로 생성된 테스트 셋, 순수 랜덤 샘플링으로 생성된 테스트 셋의 소득 카테고리 비중을 비교할 수 있습니다. 층화(stratified) 샘플링으로 생성된 테스트 셋의 비중이 전체 데이터셋의 비중과 거의 동일한 것을 알 수 있습니다.

이제 데이터를 원래 상태로 되돌리기 위해 income_cat 속성을 제거해야 합니다.

>>> for set in (strat_train_set, strat_test_set):

set.drop(["income_cat"], axis=1, inplace=True)

테스트 셋을 생성하는 것에 대해서 알아보았습니다. 이 부분은 머신러닝 프로젝트에서 흔히 간과되지만 중요한 부분입니다. 게다가 이들 많은 아이디어들은 앞으로 교차 검증을 논의할 때 유용할 것입니다. 


참고) 'Hands-On Machine Learning with Scikit-Learn and TensorFlow, chapter 2' 

주피터 노트북에서 볼 수 있는 전체 코드 얻기

반응형
반응형

Scrapy 기초

Scrapy는 2008년에 처음 0.7버전으로 공개되었다고 합니다. 

그만큼 오랫동안 사용되어 왔고, 안정성을 갖추고 있다고 알려져 있습니다. 웹 스크래핑을 본격적으로 하는 경우에 있어, Scrapy를 사용하는 것이 성능상 매우 큰 이점을 갖게 될 것입니다.

빠르고 안정적인 성능 외에도 스크래피의 장점을 들어보자면, 일단 깨진 HTML을 이해합니다.

Scrapy에서 직접 BeautifulSoup 또는 lxml을 사용할 수 있습니다. 하지만 스크래피는 XPath 인터페이스인 selectors를 제공하고, 이것으로 깨진 HTML 코드와 혼란스러운 인코딩을 효율적으로 수행할 수 있게 해줍니다.

Scrapy는 본래 웹 스크래핑을 위해 디자인 되었지만, API를 사용해서 데이터를 추출하거나 일반적인 웹 크롤러 목적으로도 사용할 수 있습니다.

* 보통 크롤링과 스크래핑을 혼용하지만, 웹 크롤링은 모든 데이터를 가져오는 것으로, 웹 스크래핑은 특정 데이터만 추출하는 것으로 구분할 수 있습니다.

Scrapy 라이브러리를 제대로 배우기 위해서는 많은 시간과 노력이 필요할 것입니다. 이번 정리에서는 기본적으로 크롤링을 하는 방법을 알아보도록 할 것입니다.  

1) 새로운 Scrapy 프로젝트 생성

2) 추출할 아이템 정의

3) 사이트를 크롤링하고 아이템을 추출하기 위해 스파이더 작성

4) 추출된 아이템을 저장하기 위해 아이템 파이프라인 작성

(이미 Scrapy가 설치되어 있다고 가정하겠습니다. 전 제 맥에서 pip install scrapy로 설치했습니다)


1. 새로운 Scrapy 프로젝트 생성

스크래핑을 시작하기 전에, 가장 먼저 해야 할 일은 새로운 Scrapy 프로젝트를 설정하는 것입니다. 

파이썬을 실행하고, 아래 코드를 입력합니다.

>>> scrapy start project tutorial

이것은 다음 콘텐츠를 가진 tutorial 디렉토리를 생성할 것입니다.


tutorial/

    scrapy.cfg # 설정 파일을 배치


    tutorial/ # 프로젝트의 파이썬 모듈, 여기에서 당신 코드를 임포트할 것입니다.

        __init__.py


        items.py # 프로젝트 아이템 파일

        pipelines.py # 프로젝트 파이프라인 파일

        settings.py # 프로젝트 설정 파일

        spiders/ # 나중에 당신의 스파이더들이 들어갈 디렉토리

        __init__.py

        ...


2. 아이템 정의

아이템은 스크랩한 데이터를 싫은 콘테이너라고 할 수 있습니다. 이것은 파이썬 딕셔너리처럼 동작합니다. 아이템은 scrapy.item 클래스를 생성함으로써 선언됩니다. 그리고 ORM과 매우 유사하게 scrapy.Field 오브젝트로 속성이 정의됩니다. 

예를 들자면, 우리가 어떤 사이트의 타이틀, url, 사이트 설명을 캡처하고 싶다면, 우리는 이 세가지 데이터를 위해 필드를 각각 정의해야 합니다. 

앞에서 생성된 items.py 파일을 다음과 같이 에디트합니다.

import scrape


class DmozItem(scrape.Item):

    title = scrape.Field()

    link = scrape.Field()

    desc = scrapy.Field()


3. 스파이더 작성

스파이더는 작성자가 직접 정의하는 클래스입니다. 

Scrapy는 도메인 또는 그룹 도메인에서 정보를 스크랩하기 위해 사용합니다. 

제일 먼저 다운로드할 URLs의 첫번째 리스트를 정의합니다. 

그리고 아이템을 추출하기 위해 어떻게 페이지 콘텐츠를 읽어들일지 정의합니다.

그럼 스파이더를 생성해 보도록 하겠습니다.

import scrapy


class Dmozspider(scrapy.Spider):

    name = “dmoz”

    allowed_domains = [“dmoz.org”]

    start_urls = [

http://www.dmoz.org/Computers/Programming/Languages/Python/Books/“,

http://www.domz.org/Computers/Programming/Languages/Python/Resources/“

    ]

    

    def parse(self, response):

        filename = response.url.split(“/“)[-2] + ‘.html’

        with open(filename, ‘wb’) as f:

            f.write(response.body)

위의 예를 보면, scrapy.Spider를 서브 클래스로 받았습니다. 그리고 몇가지 속성을 정의했다는 것을 알 수 있습니다.

- name : 스파이더를 알아보게 하는 용도로 씁니다. 그래서 고유한 이름을 써야 합니다. 즉, 다른 스파이더와 동일한 이름을 설정할 수 없습니다.

- start_urls : 스파이더가 크롤링을 시작하는 URLs입니다. 다운로드한 첫번째 페이지는 여기부터 시작합니다. 하위 URLs는 여기 시작 URLs에 포함된 데이터에서 생성될 것입니다.

- parse() : 각 시작 URL의 다운로드한 Response 객체와 함께 불러오는 스파이더의 메서드입니다. 

그럼, 위에 작성한 스파이더를 동작시키기 위해, 프로젝트 최상위 디렉토리에서 다음 명령어를 실행합니다. 

>>> scrapy crawl dmoz

위 명령어는 우리가 추가한 dmoz 이름을 가진 스파이더를 실행합니다. 이것은 'dmoz.org' 도메인에 몇가지 request를 보낼 것입니다. 아래와 유사한 output을 받게 될 것입니다.

2014-01-23 18:13:07-0400 [scrapy] INFO: Scrapy started (bot: tutorial)

2014-01-23 18:13:07-0400 [scrapy] INFO: Optional features available: ...

2014-01-23 18:13:07-0400 [scrapy] INFO: Overridden settings: {}

2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled extensions: ...

2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled downloader middlewares: ...

2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled spider middlewares: ...

2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled item pipelines: ...

2014-01-23 18:13:07-0400 [scrapy] INFO: Spider opened

2014-01-23 18:13:08-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/2014-01-23 18:13:09-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/2014-01-23 18:13:09-0400 [scrapy] INFO: Closing spider (finished)

 

3-1. Selectors 소개 

웹 페이지에서 데이터를 추출하기 위해서는 몇가지 방법이 있습니다. 

Scrapy에서는 Scrapy Selectors라고 불리는 XPath 또는 CSS에 기반한 메커니즘을 사용합니다. 

XPath 표현과 그 의미를 다음 사례를 통해 기본적으로 이해할 수 있습니다.

  • /html/head/title: HTML 문서의 <head> 요소 안의 <title>요소를 선택하세요.
  • /html/head/title/text(): 앞에서 언급한 <title> 내부의 텍스트를 선택하세요.
  • //td: 모든 <td> 요소를 선택하세요.
  • //div[@class=“mine”]: class=“mine”속성을 포함한 모든 div요소를 선택하세요.

위 사례는 XPath의 간단한 예제일 뿐입니다. 실제로 더 파워풀하게 사용이 가능합니다. 

CSS와 XPath로 작업하기 위해, Scrapy는 Selector 클래스를 제공하고 response로부터 무엇을 선택할 것이지에 대한 편리한 단축키를 제공합니다.

- Selectors는 4가지 기본 메서드를 가지고 있습니다.

  • xpath() :  인수로 주어진 xpath에서 선택된 각 노드를 대표하는 selectors 리스트를 돌려줍니다
  • css() : 인수로 주어진 CSS에서 선택된 각 노드를 대표하는 selectors 리스트를 돌려줍니다
  • extract() : 선택된 데이터와 함께 유니코드 string을 돌려줍니다
  • re() : 인수로 주어진 정규 표현식을 적용해서 추출한 유니코드 string을 돌려줍니다

4. 스크랩한 데이터 저장 

스크랩한 데이터를 저장하는 가장 간단한 방법은 크롤링할 때 저장형태를 포함한 다음 명령어를 쓰는 것입니다. 

>>> scrapy crawl dmoz -o dmoz.json

위 명령어는 모든 스크랩한 데이터들을 포함한 json 형태의 dmoz.json파일을 생성합니다.

Scrapy는 기존 파일을 덮어쓰지 않고, 파일에 데이터를 추가합니다. 


위 예제 및 설명은 www.scrapy.org의 공식 문서를 참고해서 작성했습니다.



반응형

'파이썬으로 할 수 있는 일 > 크롤링' 카테고리의 다른 글

최근의 웹 스크래핑에 대해  (0) 2023.10.20
반응형

BeautifulSoup으로 웹 크롤링


웹 페이지 html은 태그, 요소, 속성 등의 구성요소를 사용해 구조적으로 웹페이지를 표시합니다. 

이런 구조화된 문서를 효율적으로 파싱해서 원하는 정보를 찾을 수 있는데, 파이썬 기본 모듈을 사용해도 되지만, Beautifulsoup을 사용하면 더욱 손쉽게 파싱할 수 있습니다.   


1. BeautifulSoup4 설치

파이썬에 기본 설치되는 라이브러리가 아니기 때문에, BeautifulSoup은 별도로 설치해야 합니다.

명령어는 다음과 같습니다.

(리눅스) sudo apt-get install python-bs4

(맥) pip install beautifulsoup4 또는 easy_install beautifulsoup4


2. virtualenv 환경에 대해

라이브러리를 프로젝트 단위로 구분해서 관리하면, 나중에 환경폴더 전체를 다른 컴퓨터로 옮기기가 매우 쉽습니다. 따라서, virtualenv 가상 환경을 사용하는 것이 매우 편리하다는 점을 언급하고 싶습니다. 

pyenv와 virtualenv는 구글링해 보면 설정하는 법이 나와 있으니 참고하면 될 것 입니다.


3. 기본 사용 예제

>>> from urllib.request import urlopen

>>> from bs4 import BeautifulSoup

>>> html = urlopen("http://www.pythonscraping.com/pages/warandpeace.html")

>>> soup = BeautifulSoup(html, 'html.parser')

>>> nameList = soup.find_all('span', {'class':'green'})

>>> for name in nameList:

             print(name.get_text())

jupyter notebook에서 실행해본 결과!


위 사용 예제(참조:파이썬으로 웹 크롤러 만들기)에서, 몇가지 주석을 달아봅니다. 

먼저, {'class':'green'}은 class_ = 'green'으로 변경해서 사용할 수 있습니다. class가 예약어이기 때문에, class_로 표시해야 제대로 처리됩니다.

get_text()는 현재 문서에서 모든 태그를 제거하고 텍스트만 들어 있는 문자열을 반환합니다. 

예를 들어, 하이퍼링크, 문단, 기타 태그가 여럿 들어 있는 텍스트 블록에 사용하면 태그 없는 텍스트만 남기 때문에, 가장 마지막에 원하는 텍스트를 얻을 때 사용하는 것이 좋습니다.

BeautifulSoup를 사용해서 웹페이지를 크롤링하다 보니, re도 많이 사용해야 하더군요. 그리고, 웹페이지 구조를 파악하는 것도 익숙해져야 하구요. 


가끔 웹페이지 크롤링한 예제를 올려보도록 하겠습니다. 

참고) https://www.crummy.com/software/BeautifulSoup/bs4/doc/




반응형
반응형

파이썬에서 CSV파일 읽고 쓰기

파이썬에서 파일로 된 데이터를 처리하는 일은 매우 빈번하게 일어난다. 그 중에서 CSV형태가 가장 일반적일 것이다.  

파이썬 공식 문서에는 CSV(Comma Separated Values)라 불리는 포멧은 스프레드시트와 데이터베이스에서 가장 많이 임포트(import)하고 엑스포트(export)하는 포멧이라고 정의되어 있다.

공식 문서에 나와 있는 CSV에 대해 간략히 정리해 보면 아래와 같다.

물론 앞으로 계속 발전하면서 업데이트 되겠지만 말이다.

'CSV 표준'이라는 것은 사실 없다고 할 수 있다. 즉, 이 포멧은 서로 다른 어플리케이션에서 읽고 쓰는 방식을 통해 기능적으로 미묘한 차이를 갖는다고 할 수 있다. 

이러한 차이점들은 다양한 소스들로부터 CSV 파일을 처리하는 것을 어렵게?(귀찮게) 만든다. 

하지만, 경계기호(delimiters)와 인용 부호가 다르더라도, 전체 포멧은 단일 모듈로 쓰거나 읽을 때 충분히 유사하게 동작하기 때문에 기본적으로 읽고 쓰는 것은 매우 유사하다고 생각한다. 

csv 모듈은 CSV 포멧에서 표 데이터를 읽고 쓰기 위한 클래스를 실행한다. 쉽게 말하자면, 엑셀에서 사용하는 스프레드시트 형태의 포멧으로 처리된다고 생각하면 된다.

csv 모듈의 reader와 writer 객체는 순차적으로 읽기와 쓰기를 행한다. 

또한, DictReader와 DictWriter 클래스를 사용해 딕셔너리 폼에서 데이터를 읽고 쓸 수 있다. 

그럼, reader와 writer, DictReader, DictWriter의 기본적인 사용법을 정리해 보자. (아래 예시는 파이썬 3.5이상을 기준으로 함)


1. csv.reader

>>> import csv

>>> with open('file.csv', 'r') as f:

               reader_csv = csv.reader(f, delimiter=',')

               for row in reader_csv:

                    print (row)


2. csv.writer

>>> import csv

>>> with open('file.csv', 'w') as f:

               writer = csv.writer(f, delimiter=',')

               writer.writerow(['love']*3 + ['banana'])

               writer.writerow(['Spam', 'Lovely Spam', 'Wonderful Spam'])


3. csv.DictReader

>>> import csv

>>> with open('names.csv') as f:

               reader = csv.DictReader(f)

               for row in reader:

                    print(row['first_name'], row['last_name'])


4. csv.DictWriter

>>> import csv

>>> with open('names.csv, 'w') as f:

               fieldnames =['first_name', 'last_name']

               writer = csv.DictWriter(f, fieldnames=fieldnames)

               writer.writeheader()

               writer.writerow({'first_name': 'Baked', 'last_name': 'Beans'})

               writer.writerow({'first_name': 'Lovely', 'last_name': 'Spam'})

               writer.writerow({'first_name': 'Wonderful', 'last_name': 'Spam'})


참고) https://docs.python.org/3/library/csv.html


udacity에서 분석해 보았던 titanic-data.csv 파일, 읽고 쓰는게 문제가 아니라 각 필드별로 분석하는게 노가다... 





반응형
반응형

머신러닝(Machine Learning) 기본 개념(Basci Concept)

머신러닝은 주어진 데이터를 훈련시켜(training), 훈련된 지식을 기반으로 새로운 입력(test input)에 대해 적절한 답(test output)을 찾고자 하는 일련의 과정이라고 말할 수 있습니다. 

이때 훈련시키는 데이터가 질문(training input)과 정답(training output)이 모두 주어진 경우가 있고, 질문만 주어진 경우가 있습니다. 전자의 경우를 라벨링(Labeling)이 되어 있다고 말합니다.


1. 지도학습(Supervised Learning)

훈련 데이터에 라벨링이 되어 있는 경우. 

즉, 각 질문(input)에 대해 무엇이 정답(output)인지 훈련데이터가 알고 있는 경우입니다. 

예를 들면 (2, 4) -> (8), (4, 3) -> (12), 이런 식으로 훈련데이터가 주어져 있다는 것입니다.

ex) regression - (x,y)글의 데이터를 가지고 모함수 y=f(x)의 관계를 예측하는 방법

Classification - 각 그룹별 특징을 학습하여 새로운 데이터가 어느 그룹에 속해야 하는지 판단하는 방법


2. 비지도학습(Unsupervised Learning)

데이터만 잔뜩 주어집니다. 

즉 문제는 있는데 답은 없습니다. 

우리가 할 수 있는 건, 문제 유형 정도를 구분할 수 있겠습니다. 

이를 clustering이라고 합니다.  

ex) clustering - 어떠한 데이터끼리 군집으로 묶을 수 있는지 판단하는 방법

Dimension reduction - 독립적인 특징(축)들을 추출해 이를 중심으로 데이터를 압축하는 저차원으로 압축하는 방법


★ 요즘 비지도학습에 PCA(Principle Component Analysis, 주성분 분석) 알고리즘을 적용하고 딥러닝(Deep Learning)+오토엔코더(Autoencoder)로 확대되면서 인공지능 분야를 가장 핫한 분야가 되게 하였습니다.  이 부분은 나중에 정리해 보도록 하겠습니다.


3. 강화학습(Reinforcement Learning)

미리 정답이 주어져 있진 않고, 시행마다 잘한 정도를 보상(reward)하여 줍니다(이는 동물들이 학습하는 방법과 유사).

ex) 엘레베이터 스케쥴링, 컴퓨터 체스, 복잡한 로봇의 제어 등등


4. 그 외 : 일부만 라벨링이 되어 있는 경우도 있고, 정보가 점진적으로(incremental) 주어지는 경우도 있고, 데이터가 너무 많은 경우, 적은 경우 등 다양한 기계학습 문제가 존재합니다.



아래 그림은 scikit-learn(파이썬에서 많이 사용하고 있는 머신러닝 라이브러리) 관련 사이트에서 머신러닝 알고리즘을 한 눈에 볼 수 있도록 맵으로 제시하고 있습니다. 


머신 러닝에 대한 전체 윤곽을 그려볼 수 있어서 앞으로 머신러닝을 배워나가는 길잡이로 알고 있으면 좋습니다. 


원본 맵 보기(http://scikit-learn.org/stable/tutorial/machine_learning_map/)


반응형

+ Recent posts