ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Chapter 5: Digging Deeper into Turi Create
    Raywenderlich/Machine Learning by Tutorials 2020. 5. 12. 20:33

    SqueezeNet 기본 모델(base model)을 사용하여, 간식(snacks) 분류기(classifier)를 교육(train)한 다음, 결과를 평가(evaluate)하는 더 많은 방법을 살펴본다.

    또한 반복(iterations) 횟수를 늘린 다음, 기본 Turi Create 소스 코드를 일부 변경(tweaking)하여 모델(model)의 정확도(accuracy)를 개선(improve)해 본다. SqueezeNet 모델(model)은 VisionFeaturePrint_Screen보다 교육 정확도(training accuracy )가 훨씬 낮으므로 개선 사항을 쉽게 확인할 수 있다.

    또한 Netron 도구(tool)를 사용하여 모델(model)을 살펴볼 수 있다. SqueezeNet 기반 모델(model)은 이전의 Create ML 보다 훨씬 많은 계층을 내장하고 있다.

     

    Getting started

    이전에 생성한 turienv 환경(environment), Jupyter 노트북(notebook), 간식(snacks) 데이터 세트(dataset)를 계속 사용하거나, 시작(start) 폴더(folder)에 있는 DiggingDeeper_starter 노트북(notebook)으로 새로 시작할 수도 있다.

    이전 Chapter 4, “Getting Started with Python & Turi Create”를 생략한 경우, turienv 환경(environment)을 설정하는 가장 빠른 방법은 터미널(Terminal) 창(window)에서 다음 명령(commands)을 수행(perform)하는 것이다 : 

    $ cd /path/to/chapter/resources
    $ conda env create --file=starter/turienv.yaml
    $ conda activate turienv
    $ jupyter notebook

    열린 웹 브라우저(browser) 창(window)에서 starter/notebook 폴더(folder)로 이동하여 DiggingDeeper_starter.ipynb를 연다.

    이전 장에서 간식(snacks) 데이터 세트(dataset)를 다운로드한 경우, starter/notebook에 복사한다. 또는 starter/notebook/snacks-download-link.webloc을 더블 클릭(double-click)하여 간식(snacks) 데이터 세트(dataset)를 다운로드(download)하고, 압축을 푼(unzip) 다음 starter/notebook 으로 붙여넣는다.

    여기서는 Turi Create 버전(version) 5.6을 사용한다. 다른 버전(version)에서는 다른 결과(results)가 나오거나 오류(errors)가 발생할 수도 있다. 따라서 함께 제공되는 turienv를 사용하는 것이 좋다.

     

    Transfer learning with SqueezeNet

    이전의 노트북(notebook)을 계속 이어서 진행하는 것이 아니라면, 다음 셀(cell)을 하나씩 실행한다.

    이전의 노트북(notebook)을 계속 이어서 진행하더라도, 그 사이 Jupyter를 종료(shut down)한 경우에도 이 셀(cell)을 다시 실행(e-run)해야 한다. Jupyter는 Python 상태(state)를 자동으로(automatically) 복원(restore)하지 않는다. 데이터 탐색(exploration) 셀(cell)은 건너 뛸 수(skip) 있다.

    1. 필요한 Python 모듈(module)을 가져온다(import) : 

    import turicreate as tc
    import matplotlib.pyplot as plt

    2. 교육(training) 및 테스트(testing) 데이터를 로드하고, 데이터가 있는 지 확인한다 : 

    train_data = tc.image_analysis.load_images("snacks/train", with_path=True)
    len(train_data)
    test_data = tc.image_analysis.load_images("snacks/test", with_path=True)
    len(test_data)

    3. 이미지 경로(path)에서 레이블(label)을 추출(extract)하고 레이블(label)의 수을 표시한다 : 

    import os
    train_data["label"] = train_data["path"].apply(lambda path: 
                                                   os.path.basename(os.path.split(path)[0]))
    test_data["label"] = test_data["path"].apply(lambda path: 
                                                 os.path.basename(os.path.split(path)[0]))
    train_data["label"].value_counts().print_rows(num_rows=20)
    test_data["label"].value_counts().print_rows(num_rows=20)

    이제 데이터 세트(dataset)가 로드되었으므로, 이미지 분류기(image classifier)를 생성할 수 있다. Mac에서 이 모델의 학습이 끝날때까지 기다리지 않으려면 starter/notebook 폴더(folder)에서 사전 훈련 된 모델(pre-trained model)을 대신 로드한다 :

    model = tc.load_model("MultiSnacks.model")

    시간적인 여유가 있다면 모델(model)을 자유롭게 훈련(train)할 수 있다. SqueezeNet 특징 추출기(feature extractor)를 사용하기 위해, 매개변수(arguments) model="squeezenet_v1.1"을 사용한다는 점을 제외하면, 이전과 동일하다 : 

    model = tc.image_classifier.create(train_data, target="label", 
                                       model="squeezenet_v1.1",
                                       verbose=True, max_iterations=100)

    이 셀(cell)을 실행하면 이전 장(chapter)에서 생성한 모델(model)에 비해, 특징 추출(feature extraction)이 놀랄 정도로 빠르다는 걸 알 수 있다. VisionFeaturePrint_Screen이 299x299 이미지에서 2,048개의 특징(features)을 추출(extracts)하는 것에 비해, SqueezeNet은 227x227 픽셀(pixel) 이미지에서 1,000개의 특징(features)만 추출(extracts)하기 때문이다.

    그러나 교육(training) 및 검증(validation) 정확도(accuracies)는 실망스러울 수 있다 : 

    위와 약간 다른 훈련(training) 결과를 얻을 수 있다. 로지스틱 회귀(logistic regression) 부분은 훈련되지 않은 모델(untrained models)이므로, 난수(random numbers)로 초기화(initialized)된다. 이로 인해 다른 훈련(training) 실행(runs)간에 차이가 발생할 수 있다. 훈련 정확도(training accuracy)가 65% 미만이라면 다시 시도하는 것이 좋다. 기계 학습(machine learning)의 고급 사용자는 실제 훈련(training) 실행(runs) 간의 이런 차이점을 활용하여, 여러 모델(model)을 하나의 큰 앙상블(ensemble)로 결합하고, 보다 정확한 예측을 제공한다.

    Create ML과 마찬가지로, Turi Create는 임의로(randomly) 교육 데이터(training data)의 5%를 검증 데이터(validation data)로 선택하므로 검증 정확도(validation accuracies)는 교육(training) 실행(runs)마다 상당히 다를 수 있다. 이 장의 뒷부분에서, 대규모 고정 검증 데이터 세트(validation dataset)를 직접 선택하여 모델(model)을 더 효과적으로 개선한다.

    모델(model)을 평가(evaluate)하고 몇 가지 분석 결과(metrics)을 표시한다 : 

    metrics = model.evaluate(test_data)
    print("Accuracy: ", metrics["accuracy"])
    print("Precision: ", metrics["precision"])
    print("Recall: ", metrics["recall"])

    정확도(accuracy)는 검증 정확도(validation accuracy)와 거의 비슷하다 : 

    Accuracy: 0.6470588235294118
    Precision: 0.6441343963604582
    Recall: 0.6445289115646259

     

    Getting individual predictions

    지금까지는 이전 장(chapter)에서 배웠던 것을 반복했다. evaluate()은 기본적으로 모델(model)의 전체(overall) 정확도(accuracy)를 보여주지만, 개별 예측(individual predictions)에 대한 더 많은 정보를 얻을 수도 있다. 특히, 모델(model)의 신뢰도(confidence)가 매우 높지만, 결과적으로 잘못 예측한 경우를 살펴보아야 한다. 어디서 모델(model)이 잘못 예측하는 지를 알면 훈련 데이터 세트(training dataset)를 개선하는 데 도움이 될 수 있다.

     

    더 자세한 상황을 알아보려면 다음 셀(cell)을 실행한다 : 

    그러면 정확도(accuracy)와 기타 분석 결과(metrics)를 시각적으로(visually) 확인할 수 있는 새 창(window)이 열린다(Mac 전용). 이는 정확하게 분류된 예시와, 잘못 분류된 예시를 보여주기때문에 매우 편리하다.

     

    Predicting and classifying

    Turi Create 모델(model)에는 evaluation() 외에도 사용할 수 있는 다른 함수가 있다. 다음 셀(cell)에서 명령(commands)을 입력하고, 실행 한 후 잠시 기다린다 : 

    model.predict(test_data)

    교육 세트(test set)의 각 개별 이미지에 대한 실제 예측값(prediction)을 표시한다 :

    ['apple', 'grape', 'orange', 'orange', 'orange', 'apple', 'orange', 'apple', 'candy', 'apple', 'grape', 'apple', ’strawberry', 'apple', 'apple', 'carrot', 'candy', 'ice cream', 'apple', 'apple', 'apple', ...

    첫 번째 예측(prediction)은 test_data[0]의 이미지, 두 번째 예측(prediction)은 test_data [1]의 이미지에 해당한다. 처음 50개의 테스트(test) 이미지는 모두 사과(apples)이지만, 이 모델(model)은 두 번째 이미지를 "포도(grape)"로 분류 했다. 이미지를 살펴보려면, 다음 셀(cell)에서 명령(command)을 입력하고 실행한다 : 

     

    두 번째 이미지를 보여준다.

    이런 경우, 모델(model)의 첫 번째 범주(class)에 대한 신뢰도 점수가 낮을 수도 있다. 다음 명령(commands)을 입력하고 실행(run) 한 후 잠시 기다린다 : 

    output = model.classify(test_data) 
    output

    classify() 함수(function)는 확률이 가장 높게(highest-probability) 예측(predicts)된 범주(class)에 대한 모델(model)의 신뢰도(confidence)를 제공한다.

     

    따라서 이 모델(model)은 두 번째 이미지가 "포도(grape)"라고 69.96% 확신한다. 네 번째 이미지는 93%로 “오렌지(orange)”라고 확신한다. 그러나 "오렌지(orange)"라고 예측한 다른 이미지들에 대해서는 50%도 신뢰(confident)하지 않는다.

    각각의 예측(prediction)에 해당하는 이미지를 보는 것이 도움이 된다. 다음 명령(commands)을 입력(enter)하고 실행(run)한다 : 

    imgs_with_pred = test_data.add_columns(output) 
    imgs_with_pred.explore()

    첫 번째 명령(command)은 test_data에 출력(output) 열(columns)을 추가한다. 그런 다음 explore()를 사용하여 병합된(merged) SFrame을 표시한다.

    레이블(label) 열(column)은 올바른 범주(class)이며, 범주(class) 열(column)은 모델의 최고 신뢰도(highest-confidence) 예측(prediction)이다 : 

     

    가장 관심있게 봐야할 이미지는 두 레이블(label)이 일치하지 않지만, 확률(probability)이 90% 이상으로 매우 높은 행(rows)이다. 다음 명령(commands)을 입력(enter)한다 :

    imgs_filtered = imgs_with_pred[(imgs_with_pred["probability"] > 0.9) & 
                                   (imgs_with_pred["label"] != imgs_with_pred["class"] )]
    imgs_filtered.explore()

    이 명령(command)은 SFrame을 필터링(filters)하여, 예측 확률은 높지만 예측(predictions)이 잘못된 경우의 행(rows)을 가져온다. 첫 번째 항(term)은 확률(probability) 열(column)의 값이 90%보다 큰 행(rows)을 선택하고, 두 번째 항(term)은 label과 class 열(columns)이 동일하지 않은 행(rows)을 선택한다.

    일치하는 행(rows)의 하위 세트(subset)가 새 SFrame에 저장되어 다음과 같이 표시된다 : 

     

    강조 표시된(highlighted) 이미지의 실제 레이블(label)은 "딸기(strawberry)"이지만, 우유잔이 딸기보다 훨씬 크기 때문에 모델(model)은 97% 확률로 "주스(juice)"라고 확신한다. 

    신뢰도가 높지만 틀린 예측(confident-but-wrong predictions)을 살펴보면, 모델(model)이 세상을 어떻게 보는지 확인할 수 있다 : 때로는 모델(model)이 완전히 틀리지만, 레이블(label)이 공식적으로 완벽한 정답이 아니므로 때로는 틀리더라도 예측이 실제로 상당히 합리적(reasonable)인 경우도 있다. 

    이미지에 음료와 딸기가 함께 포함된(contains) 예와 같이 둘 이상의 객체(object)가 포함된 경우, 훈련(training) 레이블(label)이 실제로 잘못되었거나 적어도 오해의 소지가 있다(misleading)고 주장 할 수 있다.

     

    Sorting the prediction probabilities

    Turi Create의 predict() 메서드(method)는 각 이미지에 대한 확률 분포(probability distribution)를 제공 할 수도 있다. 다음을 실행한 다음, 잠시 기다린다 :

    predictions = model.predict(test_data, output_type="probability_vector")

    매개변수(argument) output_type을 추가하여 각 이미지의 확률 벡터(probability vector, 20개 범주에 대한 각각의 예측 확률)를 가져온다. 그런 다음 두 번째 이미지를 다시 살펴 본다. 이제 가장 높은 확률(probabilities)뿐 아니라 모든 확률을 표시한다 :

    print("Probabilities for 2nd image", predictions[1])

    출력(output)은 다음과 같다 : 

    array('d', [0.20337662077520557, 0.010500386379535839, 2.8464920324200633e-07, 0.0034932724790819624, 0.0013391166287066811, 0.0005122369124003818, 5.118841868115829e-06, 0.699598450277612, 2.0208374302686123e-07, 7.164497444549948e-07, 2.584012081941193e-06, 5.5645094234565224e-08, 0.08066298157942492, 0.00021689939485918623, 2.30074608705137e-06, 3.6511378835730773e-10, 5.345215832976188e-05, 9.897270575019545e-06, 2.1477438456101293e-08, 0.00022540187389448156])

    확률(probabilities)은 교육(training) 세트의 범주(class) 이름별로 정렬되므로, 첫 번째 값은 "apple", 두 번째 값은 "banana", 세 번째 값은 "cake"이다. 이를 유용하게 사용하려면 범주(class) 레이블(label)을 추가해야 한다. 다음을 입력(enter)하고 실행(run)한다 : 

    labels = test_data["label"].unique().sort()
    preds = tc.SArray(predictions[1])
    tc.SFrame({"preds": preds, "labels": labels}).sort([("preds", False)])

    먼저 test_data SFrame에서 레이블(label) 집합(sets)을 가져와 확률 벡터(probability vector)의 순서(order)와 일치하도록 정렬(sort)한 다음, 결과를 Turi Create 배열(Array) SArray인 labels 변수에 저장한다. 그런 다음 두 번째 이미지의 확률 벡터(probability vector)에서 또 다른 SArray를 생성한다. 마지막으로 두 SArray를 SFrame로 병합(merge)한 다음 preds 열(column)에서 내림차순(descending order)으로 정렬한다(오름차순(ascending) = False).

    이 출력(ouput)의 상위 5 행(tows)은 다음과 같다 :

     

    따라서 이 모델(model)은 적어도 20%의 신뢰도(confidence)로 “apple”을 예측한다. 상위 3개 또는 5개의 정확도(accuracy)가 이미지에 여러 객체(objects)가 포함될 수 있는 데이터 세트(dataset)에 대한 더 공정한(fairer) 분석 결과(metric)이다.

     

    Using a fixed validation set

    Turi Create는 임의로(randomly) 교육(training) 데이터 세트(dataset)의 5%를 검증 데이터 세트(validation dataset)로 추출한다. 소규모 임의 검증 세트(validation set)를 사용하는 것의 문제점은 때때로 훌륭한 결과를 얻는다는 것이다. 검증 데이터 세트(validation dataset)가 유별나게 잘 들어맞는 경우가 있을 수 있다.

    예를 들어, 모델(model)이 "와플" 범주(class)를 잘 예측(predicting)하고, 검증 세트(validation set)가 대부분 와플 이미지인 경우, 검증 정확도(validation accuracy)는 모델(model)의 실제 성능보다 더 높을 것이다. 이는 모델(model)을 과대 평가(overestimate)할 수 있기 때문에 좋지 않다.

    모델(model) 교육(training)을 여러 번 반복(repeat)하다보면, 검증 정확도(validation accuracy)가 크게 달라지는 것을 볼 수 있다. 때로는 이전의 67%보다 나아질 수도 있고, 훨씬 더 나빠질 수도 있다. 서로 다른 실행(runs)간에 이렇게 많은 차이가 나면, 모델(model)이 얼마나 잘 작동하는지 이해하기 어렵다.

    정확도(accuracy)에 대한 보다 신뢰할(reliable) 수 있는 추정치(estimates)를 얻으려면, Turi Create가 검증 세트(validation set)를 무작위로(randomly) 선택하는 대신, 자체 검증 세트(own validation set)를 사용해야 한다. 항상 동일한 검증(validation) 이미지 모음(collection)을 사용하면, 보다 효과적으로 제어(control)하고 재현 가능한(reproducible) 결과를 얻을 수 있다. 하이퍼 파라미터(hyperparameters)라고 하는 몇 가지 다른 구성(configuration) 설정(settings)을 사용해, 모델(model)을 교육(train)하고 결과를 비교하여(compare) 가장 적합한 설정(settings)을 결정할 수 있다. 매번 다른 검증 세트(validation set)를 사용하는 경우, 선택한 이미지가 계속 달라지므로 변경된 하이퍼 파라미터(hyperparameter)의 효과가 모호해(obscure)질 수 있다.

    간식(snacks) 데이터 세트(dataset)에는 이미 이러한 목적의 이미지가 포함된 val 폴더(folder)가 있다. 이전과 동일한 코드를 사용하여 이러한 이미지를 SFrame에 로드한다 :

    val_data = tc.image_analysis.load_images("snacks/val", with_path=True)
    val_data["label"] = val_data["path"].apply(lambda path:
                                               os.path.basename(os.path.split(path)[0])) 
    len(val_data)

    955가 출력되야 한다. 이는 test_data와 이미지 수가 거의 동일하며, train_data 이미지의(4,838) 5% 이상이다.

    자체 검증 세트(validation set)로 모델(model)을 교육(train)하려면 다음과 같이 작성한다 : 

    model = tc.image_classifier.create(train_data, target="label", 
                                       model="squeezenet_v1.1",
                                       verbose=True,
                                       max_iterations=100,
                                       validation_set=val_data)

    이제 교육(training)의 반복(repeat)에 상관없이, 항상 동일한 검증 정확도(validation accuracy, 약 63%)를 얻는다. 큰 변동(fluctuations)이 사라졌다.

    훈련 시작시, 모델(model)이 무작위(random) 숫자로 초기화(initialized)되기 때문에, 각 훈련 실행(run) 간에는 여전히 작은 차이가 있다. 매번 정확히 동일한 결과를 얻으려면, 시드(seed) 매개변수(argument)를 tc.image_classifier.create()에 전달하여, 난수 생성기(random number generator)의 시드(seed)를 수정하면 된다(예 : seed = 1234).

    이 고정된(fixed) 검증 세트(validation set)를 사용하면, 검증 정확도(validation accuracy)를 계산하는 데 상당한 시간이 걸리기 때문에 학습(training)이 약간 느려진다. 이전에 Turi는 이 훈련 세트(training set)의 5%(약 240개)의 이미지만 사용했다. 이제 955개의 이미지를 사용하므로 검증 점수(validation score)를 계산하는 데 약4 배의 시간이 걸린다. 그러나 더 신뢰할 만한(trustworthy) 결과를 얻으므로 더 기다릴 가치가 있다.

     

    Increasing max iterations

    63%의 검증 정확도(validation accuracy)는 결코 좋은 편이 아니다. Turi Create는 이를 알고 있다. 교육(training)이 끝나면 다음을 출력한다 : 

    This model may not be optimal. To improve it, consider increasing `max_iterations`.

    Turi Create는이 모델(model)이 여전히 몇 가지 문제가 있음을 인식한다(이 메시지(message)가 표시되지 않을 수도 있다. 이는 Turi Create 버전(versions)에 따라 달라진다 ).

    이번에는 반복(iterations)횟수를 100번에서 200번으로 늘려 다시 학습(train)해 본다 :

    model = tc.image_classifier.create(train_data, target="label", 
                                       model="squeezenet_v1.1",
                                       verbose=True,
                                       max_iterations=200,
                                       validation_set=val_data)
    Create ML과 마찬가지로, Turi Create는 특징(features)을 다시 추출(extract)해야 한다. 특징 벡터(feature vectors)를 그대로 유지하지 않는다. 만약 그렇다면 모델(model)을 다시 훈련(training)시키는 것이 훨씬 빠르다. Mac에서 100번을 반복(iterations)하는데 오랜 시간이 걸렸다면, starter/notebook에서 사전 훈련된(pre-trained) 모델을 로드(load)하는 것이 좋다. 
    model = tc.load_model("MultiSnacks_200.model")

    반복(iterations) 횟수는 하이퍼 파라미터(hyperparameter)의 한 예이다. 이것은 단순히 모델(model)의 구성 설정(configuration settings)이다. 모델(model)이 교육 데이터(training data)로부터 배우는 것을 "매개 변수(parameters)"또는 학습된 매개 변수(earned parameters)라고 한다. 따라서 교육(training)으로 변경되지 않는, 직접 설정 값을 구성(configure)하는 것을 따로 "하이퍼 파라미터(hyperparameters)"라고 한다. 하이퍼 파라미터(hyperparameters)는 모델(model)에게 학습 방법(how to learn)을 알려주고, 교육 데이터(training data)는 모델(model)에게 학습 대상(what to learn)을 알려주며, 매개 변수(parameters)는 실제로 학습된 것(actually been learned)을 설명한다.

    max_iterations 설정(setting)에 따라 모델(model)의 교육 기간이 결정된다. 모든 하이퍼 파라미터(hyperparameters)와 마찬가지로, 좋은 값(value)으로 설정하는 것이 중요하다. 그렇지 않으면 결과 모델(model)이 원하는만큼 좋지 않을 수 있다. 훈련 기간(training time)이 너무 짧으면, 모델(model)은 모든 것을 배울 기회가 없었을 것이고, 훈련 기간(training time)이 너무 길면 모델(model)이 과적합(overfit)된다.

    200 회 반복(iterations) 훈련(training) 후 최종 점수는 다음과 같다 : 

     

    훈련 정확도(training accuracy)는 이제 90%이다. 이것은 4,582개의 예제가 있는 훈련 세트(training set)에서 20%(훈련 정확도(training accuracy)가 약 80% 인 경우)가 아닌  10%만 잘못 판단했음을 의미한다.

    꽤 괜찮은 결과로 보이지만, 훈련 정확도(training accuracy) 자체를 지나치게 신뢰해서는 안된다. 더 중요한 것은 검증 정확도(validation accuracy)이다. 보시다시피, 위에서 확인할 수 있듯이, 검증 정확도(validation accuracy)는 잠시 동안 상승하다가 다시 감소한다. 이 모델(model)의 정점(sweet spot)은 검증 정확도(validation accuracy)가 63.7%인, 약 150회 반복(iterations)할 때로 보인다. 더 오래 훈련하면 훈련 정확도(training accuracy)가 서서히 향상되지만, 검증 정확도(validation accuracy)가 떨어지기 시작하면서 모델(model)은 더 나빠진다. 이는 전형적인 과적합(overfitting) 신호이다. 과적합(overfitting)은 모델(model)을 교육(training)할 때 반드시 겪게되는 문제이다. 그러나 과적합(overfitting)은 모델(model)이 여전히 더 많은 것을 배울 수 있다는 것을 의미하기 때문에 반드시 나쁜 것만은 아니다. 과접합(overfitting)은 잘못된 것을 배운 것 뿐이며, 정규화(regularization)와 같은 기술을 적용해 모델(model)이 올바른 학습을 하도록 할 수 있다(이 장(chapter)의 뒷부분에서 정규화(regularization)에 대해 더 자세히 배운다).

    불행히도 Turi Create는 최상의 검증 정확도(validation accuracy)로 모델(model) 반복(iteration)을 저장하지 않고 가장 마지막 반복(iteration)만 저장하기 때문에, 가능한 최상의 결과를 얻으려면 max_iterations = 150으로 다시 훈련(train)해야 한다.

    다음 코드를 실행하여 테스트 세트(test set)로 모델(model)을 평가하고 분석 결과(metrics)를 확인한다 : 

    metrics = model.evaluate(test_data) 
    print("Accuracy: ", metrics["accuracy"]) 
    print("Precision: ", metrics["precision"]) 
    print("Recall: ", metrics["recall"])

    테스트 데이터 세트(test dataset)로 이 모델(model)을 평가하면(evaluating), 약 65%의 결과가 나오며 이는 이전보다 약간 높은 수준이다 :

    Accuracy: 0.6554621848739496
    Precision: 0.6535792163681828
    Recall: 0.6510697278911566

    반복 횟수(iterations)를 늘리는 것이 조금 도움이 되었으므로, 100번의 초기 반복(iterations) 횟수는 너무 낮았던 것으로 보인다. 팁(Tip) : 최상의 결과를 얻으려면 먼저 반복(iterations)이 많은 모델(model)을 학습(train)하고 검증 정확도(validation accuracy)가 나빠지기 시작하는 반복(iteration) 횟수를 확인한다. 그 지점이 정점(sweet spot)이다. 이후, 그 반복(iteration) 횟수로 다시 훈련(train)하고 모델(model)을 저장한다.

     

    Confusing apples with oranges?

     

    백문이 불여일견이라는 말이 있듯이(A picture says more than a thousand numbers), 분류 결과표(confusion matrix)는 모델(model)이 얼마나 잘 수행되었는지를 시각화(visualization)하는 유용한 방법이다. 이 행렬(matrix)은 예측 범주(class)와 이미지의 실제 범주(class) 레이블(labels)을 표시하므로, 모델(model)이 어디에서 실수하는지 위치를 확인할 수 있다.

    이전 장에서 아래와 같은 명령(command)을 실행했다 : 

    print("Confusion Matrix:\n", metrics["confusion_matrix"])

    이는 표(table)를 출력한다 :

    target_label 열(column)에는 실제 범주(class)가 표시되며, predicted_labe는 예측된(predicted) 범주(class)를 나타낸다. count는 잘못 판단한 횟수이다.

    표(table)는 이미지가 실제로 "머핀"일 때, "도넛"이라 7번 잘못 예측 했고, 이미지가 실제로 "케이크"일 때 "아이스크림"이라고 두 번 잘못 예측 한 것을 보여준다.

    그러나 이러한 방식으로 표시하면, 분류 결과표(confusion matrix)는 행렬(matrix)처럼 보이지 않으며 더 나은 시각화 방법이 있다.

    먼저 다음 코드(code)를 입력하고(entering) 실행한다(running) : 

    import numpy as np
    import seaborn as sns
    
    def compute_confusion_matrix(metrics, labels):
        num_labels = len(labels)
        label_to_index = {l:i for i,l in enumerate(labels)}
        
        conf = np.zeros((num_labels, num_labels), dtype=np.int)
        for row in metrics["confusion_matrix"]:
            true_label = label_to_index[row["target_label"]] 
            pred_label = label_to_index[row["predicted_label"]] 
            conf[true_label, pred_label] = row["count"]
            
        return conf
    
    def plot_confusion_matrix(conf, labels, figsize=(8, 8)):
        fig = plt.figure(figsize=figsize)
        heatmap = sns.heatmap(conf, annot=True, fmt="d")
        heatmap.xaxis.set_ticklabels(labels, rotation=45, ha="right", fontsize=12)
        heatmap.yaxis.set_ticklabels(labels, rotation=0, ha="right", fontsize=12)
        plt.xlabel("Predicted label", fontsize=12)
        plt.ylabel("True label", fontsize=12)
        plt.show()

    분류 결과표(confusion matrix)를 계산하기 위한 함수(functions)와 이를 표시하는 두 가지 새로운 함수(functions)를 정의한다 :

    compute_confusion_matrix()는 metrics[ "confusion_matrix"] 표(table)의 모든 행(rows)을 살펴보고, 각 레이블(labels) 쌍(pair)의 개수로 2D 배열(2D-array)을 채운다. 이는 NumPy 패키지(package)를 사용한다.

    그런 다음, plot_confusion_matrix()는 이 NumPy 배열(array)을 가져 와서, 여러 가지 차트(plots) 유형(types)을 Matplotlib에 추가하는 플로팅(plotting) 패키지(package)인 Seaborn을 사용하여 히트맵(heatmap)을 그린다. 이전 장(chapter)에서 turienv 환경(environment)을 만들 때, Seaborn을 설치(installed)했다.

    이제 다음 명령(commands)을 입력(enter)하고 실행(run)하여 함수(functions)를 호출한다 : 

    conf = compute_confusion_matrix(metrics, labels) 
    plot_confusion_matrix(conf, labels, figsize=(16, 16))

    출력은 다음과 같다.

     

    히트 맵(heatmap)은 작은 값은 "어두운(cool)" 색(검은색 또는 진한 자주색)으로, 큰 값은 "밝은(warm)" 색(빨간색에서 주황색, 흰색)으로 표시한다. 값(values)이 클수록 밝아진다. 분류 결과표(confusion matrix)에서는 대각선(diagonal)에서 높은 값이 많이 표시되는데, 이는 올바른 일치 값이기 때문이다.

    예를 들어, "프레첼(pretzel)" 범주(class)의 행(row)에는 14개의 정확한(correct) 일치 항목과 11개의 잘못된(wrong) 일치 항목이 표시된다. 잘못된 예측은 "사과" 1개, "쿠키" 2개, "도넛" 1개, "핫도그" 7개 이다. 사과는 종종 오렌지로 오인되며, 쿠키, 도넛, 머핀도 자주 혼동된다.

    분류 결과표(confusion matrix)는 모델(model)의 잠재적 문제 영역을 보여주므로 매우 유용하다. 대각선(diagonal)이 두드러지게 나타나기 때문에, 모델(model)이 이미 많은 것을 배웠다는 것은 분명하지만, 여전히 완벽하지는 않다. 이상적으로는 대각선(diagonal)을 제외한 모든 것이 0이 되기를 원한다. 해당 히트 맵을 언뜻보기면 실수가 그리 많지 않은 것으로 보이기 때문에, 약간 오해할 수 있다. 그러나 어두운 사각형의 숫자의 합은 340으로, 이는 총 952개의 이미지 중 잘못 분류(misclassified)된 것이 36%라는 의미이다.

    일부 범주(categories)에는 다른 범주보다 더 많은 이미지가 있다. 예를 들어 프레첼(pretzel)은 테스트 세트(test set)에 25개의 이미지만 있는 반면, 대부분의 다른 범주(class)는 50개의 이미지가 있으므로 절대적으로 올바른 일치 항목이 많을 수 없다. 하지만, 25점 만점 중 14 점(56 %)에 불과하므로, 전체적으로 프레첼의 정확도는 낮다고 할 수 있다.

     

    Computing recall for each class

    Turi Create의 evaluation() 함수(function)는 전체(overall) 테스트 데이터(test) 세트(dataset)의 정확도(accuracy)를 제공하지만, 첫 장의 AI Ethics 섹션(section)에서 언급한 것처럼, 데이터 세트(dataset)의 특정 하위 집합(subsets)에 대해 정확도(accuracy)가 훨씬 낮거나 더 높을 수 있다. 약간의 코드(code)를 추가하면, 분류 결과표(confusion matrix)에서 개별 범주(classes)의 정확도(accuracies)를 얻을 수 있다 : 

    for i, label in enumerate(labels):
        correct = conf[i, i]
        images_per_class = conf[i].sum()
        print("%10s %.1f%%" % (label, 100. * correct/images_per_class))

    신뢰도(confidence) 행렬(matrix)의 각 행(row)에 대해 대각선(diagonal)의 숫자는 모델(model)이 올바르게(correctly) 예측한(predicted) 범주(class)의 이미지 수이다. 이 숫자를 해당 행(row)의 합계(해당 범주의 총 테스트 이미지 수)로 나눈다.

    이렇게 하면, 모델(model)이 올바르게 분류한(classified) 각 범주(class)의 백분율(percentage)을 알 수 있다(예 : 모델이 총 "사과" 이미지에서 몇 개의 "사과"이미지 를 찾았는가?). 이것이 각 범주(class)의 재현율(recall) 지표(metric)이다 :

    apple 64.0% 
    banana 68.0%
    cake 54.0%
    candy 58.0%
    carrot 66.0%
    cookie 56.0%
    doughnut 62.0%
    grape 84.0%
    hot dog 76.0%
    ice cream 44.0%
    juice 74.0%
    muffin 50.0%
    orange 74.0%
    pineapple 67.5%
    popcorn 62.5%
    pretzel 56.0%
    salad 72.0%
    strawberry 67.3%
    waffle 62.0%
    watermelon 64.0%

    재현율이 가장 높은 범주(classes)는 포도(84%)와 핫도그(76%)이다. 74%인 주스와 오렌지도 좋은 편이다. 최악의 범주(classes)는 아이스크림(44%), 머핀(50%), 케이크(54%), 프레첼(56%)이다. 모델(model)을 개선하기 위해, 주의를 기울여야 할 범주(classes)이다(예 : 이러한 범주(classes)에 대해 더 많거나(more) 나은(better) 교육(training) 이미지 수집).

    항상 그렇듯이 이 모델(model)에서 얻은 숫자는 고유한 버전(version)마다 약간 다를 수 있다. 이는 반복(iterations) 횟수와 같은 하이퍼 파라미터(hyperparameters)가 다를 수 있기 때문이다. 또한 훈련되지 않은(untrained) 모델(models)은 임의의(random) 숫자로 초기화되므로, 임의의(random) 시드(seed)를 동일한(fixed) 숫자로 설정하지 않는 한 두 개의 훈련된(trained) 모델(model)은 정확히 동일하지 않다.

     

    Training the classifier with regularization

    기계 학습(machine learning) 실무자들이 일반적으로 자주 사용하는 하이퍼 파라미터(hyperparameter)는 정규화(regularization)이다. 정규화(regularization)는 과적합(overfitting)을 방지하는 데 도움이 된다. 과적합(overfitting)은 모델(model)에 문제가 있다는 의미이므로 , 정규화(regularization) 설정을 사용하는 것이 좋다.

    다음 코드를 입력(enter)하고 실행(run)한다 : 

    model = tc.image_classifier.create(train_data, 
                                       target="label", 
                                       model="squeezenet_v1.1",
                                       verbose=True,
                                       max_iterations=200,
                                       validation_set=val_data,
                                       l2_penalty=10.0,
                                       l1_penalty=0.0,
                                       convergence_threshold=1e-8)

    l2_penalty, l1_penalty, convergence_threshold의 세 가지 인수(arguments)를 추가했다. convergence_threshold를 매우 작은 값으로 설정하면 200번의 반복(iterations)이 모두 완료될 때까지, 교육(training)이 중단되지 않는다.

    l2_penalty와 l1_penalty는 과적합(overfitting)을 줄이기 위해 정규화(regularization)를 추가하는 하이퍼 파라미터(hyperparameters)이다.

    모델(model)은 특징(feature) 값(values)을 결합하기(combining) 위해, 가중치(weights) 또는 계수(coefficients)라고도 하는 매개 변수(parameters)를 학습하여, 올바르게 분류(classifies)하는 학습 데이터(training data) 항목 수를 최대화한다. 모델(model)이 일부 특징(feature)에 너무 큰 가중치(coefficients)를 부여하여 가중치(weight)가 커지면, 과적합(overfitting)이 발생할 수 있다. l2_penalty를 0보다 크게 설정하면, 큰 계수(coefficients)에 불이익을 주어(penalizes), 모델이 더 작은 계수(coefficients)를 학습하도록 유도(encouraging)한다. l2_penalty 값이 클수록, 계수(coefficients)의 크기는 줄어들지만 훈련 정확도(training accuracy)는 떨어질 수 있다.

    l1_penalty 또한 0보다 크게 설정하면, 큰 계수(coefficients)가 불이익을 받는다(penalizes). 또한 계수(coefficients)를 0으로 설정하면, 계수가 매우 작은 특징(features)을 버린다. 일반적으로, 동일한 훈련(training) 섹션(session)에서 l2_penalty와 l1_penalty 중 하나를 사용하며 동시에 사용되지는 않는다.

     

    정규화 결과, 모델(model)의 과적합(overfitting)이 해소 된다 : 

     

    훈련 정확도(training accuracy)는 더 이상 100%에 가까워지지 않고, 약 79%가 최고치이다. 중요한 것은 반복 횟수(iterations)가 많아져도 검증 정확도(validation accuracy)가 나빠지지 않는다는 것이다. 훈련 정확도(training accuracy)가 검증 정확도(validation accuracy)보다 높은 것이 일반적이다. 검증 정확도(validation accuracy)가 떨어지는 경우가 나쁜 경우이며, 위의 결과는 괜찮은 편이다.

    l2_penalty = 10.0이 최선의 설정(setting)인지 생각해 봐야 한다. 이를 확인하기 위해, l2_penalty와 l1_penalty의 값을 변경하면서 분류기(classifier)를 여러 번 훈련(train)시킬 수 있다. 이것을 하이퍼 파라미터(hyperparameter) 튜닝(tuning)이라고 한다.

    훈련(training)에 맞는 하이퍼 파라미터(hyperparameters)를 선택하는 것이, 모델(model)의 품질에 큰 차이를 만들 수 있다. 검증 정확도(validation accuracy)는 이러한 하이퍼 파라미터(hyperparameters)의 영향을 나타낸다. 그렇기 때문에 동일한(fixed) 검증 세트(validation set)를 사용한 결과의 변화가 우연이 아닌, 하이퍼 파라미터(hyperparameters)의 변화에 ​​의한 것임을 확인할 수 있다.

    하이퍼 파라미터 튜닝(hyperparameter tuning)은 시행 착오가 많으므로, 여러 설정을 사용하여 모델(model)에 미치는 영향(affect)을 파악해야 한다. l2_penalty를 100으로 설정하면, 모델(model)에 너무 심하게 불이익을 주므로, 훈련 정확도(training accuracy)가 65%를 넘지 않는다.

    불행히도, Turi Create는 모델(model)을 훈련(train)시킬 때마다 모든 교육(training)과 검증(validation) 이미지에서 반복해서 특징(features)를 추출(extract)한다. 이는 하이퍼 파라미터(hyperparameter) 튜닝(tuning)을 매우 느리게 만드므로, 수정이 필요하다.

     

    Wrangling Turi Create code

     

    Turi Create의 매력적인 점 중 하나는 일단 SFrame에 데이터를 저장하면, 모델(model)을 훈련(train)시키는 데 단 한 줄의 코드(code)만 있으면 된다는 것이다. 단점(downside)은 Turi Create API를 사용해 교육(training) 과정(process)을 제한적으로만(limited) 제어(control) 할 수 있다는 것이다. 다행히 Turi Create는 오픈 소스(open source)이므로, 어떻게 작동하는지 코드를 확인하거나 일부 제한(limitations) 사항을 수정할 수도 있다.

    tc.image_classifier.create()에 대한 코드는 GitHub 저장소(repo) github.com/apple/turicreate 의turicreate/src/python/turicreate/toolkits/image_classifier/image_classifier.py 파일에 있다. 해당 코드 중 일부를 노트북(notebook)에 복사하여 붙여넣고 하이퍼 파라미터(hyperparameters)를 수정하여 사용하면 된다.

     

    Saving the extracted features

    훈련(training) 단계(phase)에서 시간을 절약하고, SqueezeNet에서 추출한 특징(features)을 지속적으로 재생성(regenerate)할 수 있는 방법이 있다면 좋을 것이다. 여기서는 도중에(intermediate) SFrame을 디스크(disk)에 저장하고, 분류기(classifier)로 실험하기 전 다시 가져(reload)오는 방법에 대해 알아 본다.

    특징(feature) 추출(extraction)을 기다리지 않으려면, starter/notebook 폴더(folder)에서 특징(features)을 불러온다.
    extracted_train_features = tc.SFrame("extracted_train_features.sframe")
    extracted_val_features = tc.SFrame("extracted_val_features.sframe")

     

    먼저 사전 훈련된(pre-trained) SqueezeNet 모델(model)을 불러오고, 특징(feature) 추출기(extractor)를 가져온다 : 

    from turicreate.toolkits import _pre_trained_models
    from turicreate.toolkits import _image_feature_extractor
    
    ptModel = _pre_trained_models.MODELS["squeezenet_v1.1"]()
    feature_extractor = _image_feature_extractor.MXFeatureExtractor(ptModel)

    MXFeatureExtractor는 Turi Create가 구축된 MXNet 기계 학습(machine learning) 프레임 워크(framework)의 객체(object)이다. Python에서 밑줄(underscore)로 시작하는 변수명은 private으로 간주되지만, 여전히 가져올 수(import) 있다. 다음 코드를 입력(enter)하고 실행(run)한다 : 

     train_features = feature_extractor.extract_features(train_data, "image", verbose=True)

    MXFeatureExtractor 객체(object)를 사용하여, 교육(training) 데이터 세트(dataset)에서 SqueezeNet 특징(features)을 추출(extract)한다. tc.image_classifier.create()를 실행할 때 가장 많은 시간이 소요된다. 이를 별도로 실행하면, 분류기(classifier)를 훈련(train)시킬 때마다 특징(feature) 추출(extraction)을 기다릴 필요가 없다. 다음 코드를 입력(enter)하고, 실행(run)한다 :

    extracted_train_features = tc.SFrame({ 
        "label": train_data["label"], 
        "__image_features__": train_features, 
    })

    여기서는 각 이미지의 특징(features)을 각각의(respective) 레이블(label)과 결합하여 새로운 SFrame을 만든다. 이를 나중에 사용할 수 있도록 저장(save)한다. 다음 코드를 입력(enter)하고, 실행(run)한다 :

    extracted_train_features.save("extracted_train_features.sframe")

    extract_train_features를 파일(file)로 저장한다. 다음에 이와 동일한 특징(features)을 사용하여 더 많은 훈련(training)을 수행할 때, 간단히 SFrame을 다시 불러(load)올 수 있으며, 특징(features)을 추출(extract)하는 데 극히 적은 시간만 소요된다 :

    # Run this tomorrow or next week
    extracted_train_features = tc.SFrame("extracted_train_features.sframe")

     

    Inspecting the extracted features

    이러한 특징(features)이 실제로 어떻게 표시되는지 확인해 보려면, 다음 명령(command)을 입력(enter)하고 실행(run)한다 : 

    extracted_train_features.head()

     

    각 행(row)에는 하나의 교육(training) 이미지에 대해 추출된(extracted) 특징(features)이 있다. __image_features__ 열(column)에는 숫자가 포함된 목록이 있고, label 열(column)에는 이 행(row)에 해당하는 범주(class) 이름이 있다. 다음 명령(command)을 입력(enter)하고 실행(run)한다 : 

    extracted_train_features[0]["__image_features__"]

    특징(feature) 벡터(vector)가 어떻게 보이는지 보여주며, 다음과 같이 출력된다 :

    array('d', [6.1337385177612305, 10.12844181060791, 13.025101661682129, 7.931194305419922, 12.03809928894043, 15.103202819824219, 12.722893714904785, 10.930903434753418, 12.778315544128418, 14.208030700683594, 16.8399658203125, 11.781684875488281, ...

    이것은 1,000 개의 숫자 목록(list)이다. 이를 확인(verify)하려면 len() 함수(function)를 사용한다. 그것들은 모두 0에서 30사이의 숫자이다.

     

    이들이 나타내는 것을 정확히 이해할 수는 없지만, 이는 SqueezeNet이 객체(onject)가 얼마나 긴지(long), 둥근지(round), 사각형인지(square), 주황색(orange)인지 등 중요하다고 판단한 특징(features)이다. 중요한 것은 이러한 특징(features)들로 부터 배울수 있도록, 로지스틱(logistic) 분류기(classifier)를 훈련(train)시킬 수 있다는 것이다.

    같은 방법으로 검증(validation) 데이터 세트(dataset)에서 이미지의 특징(features)을 추출하고, SFrame을 파일(file)에 저장(save)한다 :

    val_features = feature_extractor.extract_features(val_data, "image", verbose=True)
    extracted_val_features = tc.SFrame({ 
        "label": val_data["label"], 
        '__image_features__': val_features,
    })
    extracted_val_features.save("extracted_val_features.sframe")

     

    Training the classifier

    이제 분류기(classifier)를 훈련(train)시킬 준비가 되었다. 다음을 입력(enter)하고 실행(run)한다

    lr_model = tc.logistic_classifier.create(extracted_train_features,
                                             features=["__image_features__"], 
                                             target="label",
                                             validation_set=extracted_val_features,
                                             max_iterations=200,
                                             seed=None,
                                             verbose=True,
                                             l2_penalty=10.0,
                                             l1_penalty=0.0,
                                             convergence_threshold=1e-8)

    추출된 extract_train_features SFrame을 입력(input) 데이터(data)로, extract_val_features를 검증(validation) 데이터(data)로 사용하여 로지스틱(logistic) 회귀(regression) 모델(model)을 작성하고 훈련(trains)하는 Turi Create 코드이다.

    이 하이퍼 파라미터(hyperparameters)에 다른 값을 사용하고 싶다면, 위의 셀(cell)에서 변경하고 다시 실행(run)하면 된다. 특징(feature) 추출(extraction) 단계를 건너 뛰기(skipped) 때문에, 훨씬 빠르게 실행된다.

    feature_rescaling, solver, step_size, lbfgs_memory_level과 같이 설정할 수 있는 몇 가지 다른 하이퍼 파라미터(hyperparameters)가 있다. 이에 대해 더 자세히 알아보려면, 새 셀(cell)에 다음을 입력하거나, Turi Create 소스 코드에서 주석(comments)을 확인한다.

    tc.logistic_classifier.create?

    정규화(regularization)를 사용해 400회 정도 반복(iterations) 교육 하면, 이 모델(model)의 검증(validation) 점수(score)는 65​​%로 천천히 향상된다. 조금씩 개선되지만 도움이 된다(every little bit helps). 지금까지의 모델 개선 방법은 하이퍼 파라미터(hyperparameters)를 실험하는 것이었다. 이 모델(model)에서 더 나은 하이퍼 파라미터(hyperparameters)를 찾을 수도 있을 것이다.

    검증(validation) 점수(score)가 실제 모델(model)의 성능(performance)을 나타내는지 확인하려면, 테스트 세트(test set)의 정확도(accuracy)도 확인해야 한다.

    먼저, 모델(model)을 유효한 ImageClassifier 객체(object)로 변환한다 : 

    from turicreate.toolkits.image_classifier import ImageClassifier
    
    state = {
        'classifier': lr_model,
        'model': ptModel.name,
        'max_iterations': lr_model.max_iterations, 
        'feature_extractor': feature_extractor, 
        'input_image_shape': ptModel.input_image_shape, 
        'target': lr_model.target,
        'feature': "image",
        'num_features': 1,
        'num_classes': lr_model.num_classes,
        'classes': lr_model.classes,
        'num_examples': lr_model.num_examples, 
        'training_time': lr_model.training_time, 
        'training_loss': lr_model.training_loss,
    }
    
    model = ImageClassifier(state)

    기본 모델(model)과 훈련(trained)시킨 분류기(classifier)를 상태(state) 구조(structure)로 결합(combines)해서, ImageClassifier 객체(object)를 만든다.

    이전과 같이 테스트 세트(test set)의 분석 결과(metrics)를 계산한다 : 

    metrics = model.evaluate(test_data) 
    print("Accuracy: ", metrics["accuracy"]) 
    print("Precision: ", metrics["precision"]) 
    print("Recall: ", metrics["recall"])

    출력은 다음과 같다 : 

    Accuracy: 0.6712184873949579
    Precision: 0.6755916486674352
    Recall: 0.6698818027210884

    정규화(regularization)와 하이퍼 파라미터(hyperparameter) 튜닝(tuning)이 항상 기적을 일으키지는 않지만, 조금이라도 모델(model)을 개선한다.

    model.evaluate()는 실행(run)할 때마다, 테스트 세트(test set)에서 특징(feature) 추출(extraction)을 수행(performs)한다. 이미 추출된(extracted) 특징(features)이 있는 SFrame을 사용해서 evaluate()를 실행할 수 있는지 확인하려면, Turi Create 소스 코드를 자세히 살펴 본다. 새 셀(cell)에서 model.evaluate?? 를 입력해, 메서드(method)의 소스 코드(source code)를 확인해 볼 수 있다.

     

    Saving the model

    해당 모델(model)을 Turi Create 모델(model)로 저장(save)할 수 있다 : 

    model.save("MultiSnacks_regularized.model")

    또는 Core ML 모델(model)로 내보낼(export) 수 있다 :

    model.export_coreml("MultiSnacks_regularized.mlmodel")

    모델(model)에 대한 자세한 내용을 보려면 다음을 실행(run)한다 :

    model

    모델(model)과 모델의 교육(training)에 대한 고급(high-level) 정보(information)가 표시된다 : 

    Class : ImageClassifier

    Schema
    ------
    Number of classes : 20
    Number of feature columns : 1
    Input image shape : (3, 227, 227)

    Training summary
    ----------------
    Number of examples : 4838
    Training loss : 3952.4993
    Training time (sec) : 59.2703

    하이퍼 파라미터(hyperparameters)를 변경하면 훈련 손실(Training loss, 교육(training) 데이터 세트(dataset)에 대한 전체 오류)이 달라진다. 조금 더 자세한 정보를 보려면 다음과 같이 입력(enter)하고 실행(run)한다 :

    model.classifier

    이는 모델(model)의 분류기(classifier) ​​부분에 대한 정보를 보여준다 : 

    Class : LogisticClassifier

    Schema
    ------
    Number of coefficients : 19019
    Number of examples : 4838
    Number of classes : 20
    Number of feature columns : 1
    Number of unpacked features : 1000

    Hyperparameters
    ---------------
    L1 penalty : 0.0
    L2 penalty : 10.0

    Training Summary
    ----------------
    Solver : lbfgs
    Solver iterations : 200
    Solver status : Completed (Iteration limit reached).
    Training time (sec) : 59.2703

    Settings
    --------
    Log-likelihood : 3952.4993

    Highest Positive Coefficients
    -----------------------------
    (intercept) : 1.8933
    (intercept) : 1.4506 
    (intercept) : 0.6717 
    (intercept) : 0.5232 
    (intercept) : 0.4072 

    Lowest Negative Coefficients
    ----------------------------
    (intercept) : -1.6521
    (intercept) : -1.5588 
    (intercept) : -1.4143 
    (intercept) : -0.8959 
    (intercept) : -0.5863

    이 정보는 주로 문제 해결(troubleshooting)이나 로지스틱 회귀 분류기(logistic regression classifier)가 어떻게 작동하는지 궁금할 때 유용하다.

    주목할 만한 것은 간식(snacks) 이미지를 20개의 가능한 범주(categories)로 분류(classify)하기 위해 이 모델(model)이 학습한 매개 변수(parameters)의 수인 19,019이다. 각 입력 특징 벡터(input feature vector)는 1,000개의 숫자와, 20개의 가능한 출력(outputs)이 있으므로 1,000 × 20 = 20,000개가 된다. 여기에서 각 출력(output)에 대해 20개의 "편향(bias)"값을 더하여 20,020개의 계수(coefficients)가 만들어진다.

    그러나 20개의 가능한 범주(classes)가 있다면, 실제로는 19,019개의 계수(coefficients)를 제공하는 19개의 범주(classes)만 배우면 된다. 예측(prediction)이 이 19개 범주(classes) 중 어느 것도 아니라면, 20번째 범주(class)가 된다. 흥미롭게도, Core ML의 .mlmodel 파일에서 로지스틱 회귀 계층(logistic regression layer)은 20,020개의 매개 변수(parameters)가 있다. Netron을 사용하면, 이를 직접 확인해 볼 수 있다.

    Settings의 로그 우도(Log-likelihood)는 훈련 손실(Training loss)의 수학적인 용어이다. 이 아래에는 최고(highest) 및 최저(lowest) 계수(coefficients)가 있다. 정규화(regularization) 하이퍼 파라미터(hyperparameter)의 목적은 계수(coefficients)의 크기를 줄이는 것이다.

    비 정규화(no-regularization) 모델(model)의 계수(coefficients)와 비교하려면 다음을 입력(enter)하고 실행(run)한다 :

    no_reg_model = tc.load_model("MultiSnacks.model") 
    no_reg_model.classifier

    미리 훈련된 모델(pre-trained model)을 다시 불러오고(starter/notebook 폴더(folder) 참조), 분류기(classifier)를 검사한다. 이 모델(model)은 훈련 정확도(training accuracy)가 높았으므로 로그 우도(Log-likelihood, 훈련 손실(Training loss))은 2,400보다 낮다. 그리고 예상대로 최고(highest) 및 최저(lowest) 계수(coefficients)는 정규화(regularization) 모델보다 절대값(absolute value)이 더 크다 : 

    Settings
    --------
    Log-likelihood : 2400.3284

    Highest Positive Coefficients
    -----------------------------
    (intercept) : 0.3808
    (intercept) : 0.3799
    (intercept) : 0.1918
    __image_features__[839] : 0.1864
    (intercept) : 0.15

    Lowest Negative Coefficients
    ----------------------------
    (intercept) : -0.3996
    (intercept) : -0.3856
    (intercept) : -0.3353
    (intercept) : -0.2783
    __image_features__[820] : -0.1423

    다음 장(chapter)에서 이 모든 것이 무엇을 의미하는지에 더 자세히 설명한다. 자신만의 로지스틱 회귀(logistic regression)를 훈련하기 위해 처음부터(from scratch) 코드를 작성하게 될 뿐만 아니라, Turi Create의 SqueezeNet 기반 모델(model)을 능가하는 완전한 신경망(neural network)을 구축한다.

     

    A peek behind the curtain 

    SqueezeNet과 VisionFeaturePrint_Screen은 모두 컨볼루션 신경망(convolutional neural networks)이다. 다음 장(chapters)에서 이러한 네트워크(networks)의 내부(internally) 작동 방식에 대해 자세히 알아보고, 처음부터(from scratch) 네트워크(networks)를 구축하는 방법을 설명한다. 그 전에 Core ML 모델(model)의 안을 들여다 볼 수 있는 방법이 있다.

    모델 아키텍처(model architecture)를 멋지게 시각화하는 Netron(github.com/lutzroeder/Netron)이라는 멋진 무료 도구가 있다. GitHub 페이지의 설치(Install)로 이동 한 후, macOS 다운로드(Download) 링크를 클릭한다. 다음 페이지에서 Netron-x.x.x.dmg 링크를 클릭 한 다음, 이 파일을 실행(run)하여 Netron을 설치(install)한다.

    Netron에서 .mlmodel 파일을 연다. 모델(model)의 파이프 라인(pipeline)에 들어가는 모든 변환(transformation) 단계가 표시된다.

    입력 이미지가 맨 위에 있고 컨볼루션(convolutions), 활성화(activations), 풀링(pooling) 등이 있다. 이들은 이런 종류의 신경망(neural network)에서 사용되는 다른 유형의 변환(transformations), 즉 계층(layers)의 이름이다.

    이 파이프 라인(pipeline)이 때때로 분기되었다가 다시 연결되는 방식에 주목해야 한다. 이것이 SqueezeNet의 이름에 영향을 준 "squeeze" 기능(feature)이다.

    구성(configuration), 입력(inputs), 출력(output)에 대한 자세한 내용을 보려면, 구성 요소(building blocks) 중 하나를 클릭한다.

     

    파이프 라인(pipeline)의 가장 끝에는 innerProduct 계층(layer)이 있고, 그 뒤에 softmax가 있다. 이 두 블록(blocks)은 로지스틱 분류기(logistic classifier)를 구성한다. flatten 블록(block)까지 모든 것이 SqueezeNet 특징(feature) 추출기(extractor)이다.

    차후에 이런 다양한 종류의 계층(layer)이 무엇을 하는지에 대해 배운다. 지금은 Netron으로 이 모델(model)의 내부가 어떻게 구성되어 있는지 이해하는 것으로 충분하다.

    Netron은 다른 많은 기계 학습(machine learning) 프레임 워크(frameworks)의 모델(model)뿐만 아니라, 모든 Core ML 모델(model)에서도 작동한다. 이전 장(chapter)의 Apple 웹 사이트에서 다운로드한 모델(model)을 살펴볼 수도 있다.

    모든 신경망(neural networks)의 핵심부(core)는 매우 비슷하기 때문에, 위의 사진 구조와 유사하게 보일 것이다. 다른것은 계층(layers) 수와 분기 구조(branching structure)이다.

    VisionFeaturePrint_Screen과 같은 Apple 자체 모델(models)은 iOS 12에 포함되어 있으며, .mlmodel 파일의 번들로(bundled) 제공되지 않는다. .mlmodel 파일 자체에는 VisionFeaturePrint_Screen 계층(layers)이 포함되어 있지 않다. 이러한 내장(built-in) 특징(feature) 추출기(extractors)를 기반으로 하는 맞춤형(customized) 모델(models)의 경우, Netron은 Xcode의 설명에 나오는 입력(inputs), 출력(outputs), 메타 데이터(metadata) 이상의 것을 보여 줄 수 없다. 따라서 Netron은 이러한 모델(model)의 내부(internal) 구조(architecture)를 알수 없다.

     

    Key points

    • Turi Create로 자신만의 Core ML 모델(model)을 교육(training)했다. 사실, 2장 "Getting Started with Image Classification"에서 사용한 모델(models)들은 정확히 이렇게 훈련(trained)되었다.
    • Turi Create는 특히 Jupyter 노트북(notebook)에서 매우 사용하기 쉽다. 약간의 Python 코드만 있으면 된다. 그러나 매우 정확한(accurate) 모델(model)을 만들 수는 없는데 이는 부분적으로 제한된 데이터 세트(dataset) 때문이다.
    • 이미지가 많을수록 좋다. 여기서는 4,800개의 이미지를 사용했지만 48,000개 였으면 좋았을 것이고, 480만 개 였다면 더 좋았을 것이다. 그러나 교육(training) 이미지를 찾고 주석(annotating)을 다는 작업에는 비용이 발생하며, 대부분의 프로젝트(project)의 경우 범주(class) 당 수백 개 또는 최대 수천 개가 감당할 수 있는 최대치일 수도 있다. 수집한 자료를 최대한 사용해야 한다. 더 많은 교육(training) 데이터(data)를 수집하고(collected), 나중에 언제든지 모델(model)을 다시 교육할 수 있다. 기계 학습(machine learning) 분야에서 데이터가 가장 중요하며, 대체로 많은 데이터가 더 나은 모델(model)로 귀결된다.
    • Turi Create의 모델(model)이 최선이 아닌 또 다른 이유는 SqueezeNet이 소규모의 특징(feature) 추출기(extractor)이기 때문이다. 빠르고(fast) 메모리 친화적(memory-friendly)이지만, 대규모 모델(model)만큼 정확하지 않은 단점(cost)이 있다. 그러나 이는 SqueezeNet의 책임(fault)은 아닙니다. SqueezeNet의 추출된(extracted) 특징(features)을 기반으로, 기본 로지스틱 회귀를 훈련하는 대신, 보다 강력한 분류기(classifiers)를 만드는 것도 가능하다.
    • Turi Create를 사용하여 몇 가지 하이퍼 파라미터(hyperparameters)를 조정(tweak)할 수 있다. 정규화(regularization)를 사용하면 과적합(overfitting)을 피할 수 있다. 그러나 Turi Create를 사용하면, 특징(feature) 추출기(extractor)를 미세 조정(fine-tune)하거나 데이터 보강(augmentation)을 사용할 수 없다. 이러한 고급(advanced) 기능(features)을 사용하면, 훈련이 오래 걸리지만 더 나은 모델(model)을 만들 수 있다.

    다음 장(chapter)에서는 이미지 분류기(classifier)를 훈련할 때 발생하는 문제를 해결하는 방법을 Keras를 사용해 다시 살펴 본다. 또한 이러한 신경망(neural networks)의 모든 구성 요소(building blocks)들을 알아보고, 왜 그것들을 사용하는지 배운다.

     

     

     

    //노트북 주석 여기도 코드에 넣어 주는 게 좋을 듯.

Designed by Tistory.