from sys import platform
if platform == 'linux':
!sudo apt-get update
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
import matplotlib.pyplot as plt
'font', family='NanumBarunGothic') plt.rc(
대봉감과 단감을 구분하는 AI를 구축하겠습니다.
탄닌은 떫은 맛을 나게 하는 성분으로, 와인에도 함유되어 있습니다.
대봉감은 홍시를 만들어 먹는 감으로, 탄닌 수용성이 커 딱딱한 상태에서 먹을 때 굉장히 떫은 맛이 납니다. 그에 비해 단감은 탄닌 수용성이 낮아 딱딱한 상태에서도 단맛이 주를 이룹니다.
이를 구분할 수 있는 AI를 만든다면 떫은 감은 홍시로, 단감은 단단한 상태에서 맛있게 먹을 수 있지 않을까요?
우선 한글 사용을 위해 나눔폰트를 다운로드하고 기본 폰트로 설정합니다. (참고 링크)
::: {#cell-4 .cell _kg_hide-input=‘true’ _kg_hide-output=‘true’ tags=‘[]’ execution_count=2}
!pip install -Uqq fastai duckduckgo_search
:::
Step 1: 대봉감과 단감의 사진 다운로드하기
::: {#cell-6 .cell _kg_hide-input=‘true’ tags=‘[]’ execution_count=3}
from duckduckgo_search import ddg_images
from fastcore.all import *
def search_images(term, max_images=30):
print(f"Searching for '{term}'")
return L(ddg_images(term, max_results=max_images)).itemgot('image')
:::
search_images
는 duckduckgo에서 이미지를 찾는 함수입니다.
#NB: `search_images` depends on duckduckgo.com, which doesn't always return correct responses.
# If you get a JSON error, just try running it again (it may take a couple of tries).
= search_images('대봉감', max_images=1)
urls 0] urls[
Searching for '대봉감'
'http://www.food123.kr/gamimg/aa6.jpg'
download_url
로 url의 이미지를 dest에 다운로드합니다.Image.open
으로 경로에 위치한 데이터를 열 수 있습니다.
from fastdownload import download_url
= '대봉감.jpg'
dest 0], dest, show_progress=False)
download_url(urls[
from fastai.vision.all import *
= Image.open(dest)
im 256,256) im.to_thumb(
단감에도 동일하게 진행하겠습니다.
'단감', max_images=1)[0], '단감.jpg', show_progress=False)
download_url(search_images(open('단감.jpg').to_thumb(256,256) Image.
Searching for '단감'
정확한 사진을 불러온 것 같습니다. 이제는 여러 대봉감, 단감 이미지를 다운로드 받고 각각의 폴더에 저장하겠습니다.
= '대봉감', '단감' searches
from pathlib import Path
= Path('daebong_or_dangam') path
from fastai.vision.all import *
from time import sleep
아래 for문은 다음과 같이 수행됩니다. - dest
에 키워드 Path를 지정합니다. - dest.mkdir
로 폴더를 만듭니다. - download_images
로 이미지를 다운로드 합니다. urls
의 개수는 위에서 만든 search_images
의 기본 max_images
인 30입니다. - resize_images
는 해당 폴더에 존재하는 사진에 대해 반복적으로 수행됩니다.
for o in searches:
= (path/o)
dest =True, parents=True)
dest.mkdir(exist_ok=search_images(f'{o}'))
download_images(dest, urls10)
sleep(/o, max_size=400, dest=path/o) resize_images(path
Searching for '대봉감'
Searching for '단감'
Step 2: 모델 훈련시키기
verify_images
는 열리지 않는 이미지를 확인합니다.
= verify_images(get_image_files(path)) failed
failed
에 존재하는 경로, 즉 열리지 않는 이미지들에 대해 map
을 통해 Path.unlink
를 각각 적용합니다.
Path.unlink
는 삭제와 동일합니다.
map(Path.unlink)
failed.len(failed)
1
모델을 훈련시키기 위해서는 Dataloaders가 필요합니다. 이를 위해서는 Fast.ai의 중수준 API인 DataBlock을 사용하거나 데이터 타입별로 미리 구축된 고수준 API를 사용할 수 있습니다.
아래 DataBlock에 사용하는 인자는 blocks, get_items, splitter, get_y, item_tfms 입니다.
= DataBlock(
dls =(ImageBlock, CategoryBlock),
blocks=get_image_files,
get_items=RandomSplitter(valid_pct=0.2, seed=42),
splitter=parent_label,
get_y=[Resize(192, method='squish')]
item_tfms=32) ).dataloaders(path, bs
blocks
는 어떤 input과 output을 가지고 있는지 명시해주는 것입니다. 예제에서는 input을 Image, output을 Category로 가지고 있기 때문에 ImageBlock
과 CategoryBlock
을 사용합니다.
get_items
는 아이템을 불러오는 함수를 지정합니다.
splitter
는 훈련용, 검증용 데이터셋을 나눌 기준을 설정합니다. RandomSplitter
외에도 다양한 Splitter들이 존재합니다.
get_y
는 목적변수의 값을 어디에서 가져올지 지정하는 것입니다. 위에서 사용된 parent_label
은 상위 폴더의 이름으로 가져오는 것을 의미합니다.
item_tfms
는 아이템 변환의 내용을 지정합니다. 각각 이미지를 동일하게 192 사이즈로 리사이징하고, 방법은 ’squish’를 사용했습니다. 아래는 사용 가능한 method입니다.
ResizeMethod()
fastcore.basics.ResizeMethod(Squish='squish', Crop='crop', Pad='pad')
=6) dls.show_batch(max_n
데이터의 준비는 모두 끝났습니다. 이제 learner를 정의하고, 훈련시키면 됩니다.
learner에는 dataloaders와 architecture 지정이 필수적입니다.
데이터 특성별 learner가 존재하는데, 이미지를 위한 것은 vision_learner
입니다.
= vision_learner(dls, resnet18, metrics=error_rate) learner
/usr/local/lib/python3.9/dist-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and will be removed in 0.15, please use 'weights' instead.
warnings.warn(
/usr/local/lib/python3.9/dist-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and will be removed in 0.15. The current behavior is equivalent to passing `weights=ResNet18_Weights.IMAGENET1K_V1`. You can also use `weights=ResNet18_Weights.DEFAULT` to get the most up-to-date weights.
warnings.warn(msg)
정확도를 측정하기 위한 메트릭으로 error_rate
를 사용하겠습니다. error_rate
는 라벨을 맞추는 정확도로 생각해주시면 되겠습니다.
=10) learner.fine_tune(epochs
epoch | train_loss | valid_loss | error_rate | time |
---|---|---|---|---|
0 | 1.326514 | 1.511976 | 0.589744 | 00:03 |
epoch | train_loss | valid_loss | error_rate | time |
---|---|---|---|---|
0 | 0.375253 | 0.329849 | 0.153846 | 00:01 |
1 | 0.335464 | 0.031661 | 0.000000 | 00:01 |
2 | 0.240976 | 0.003346 | 0.000000 | 00:01 |
3 | 0.185915 | 0.000432 | 0.000000 | 00:01 |
4 | 0.143643 | 0.000189 | 0.000000 | 00:01 |
5 | 0.115001 | 0.000153 | 0.000000 | 00:01 |
6 | 0.095365 | 0.000119 | 0.000000 | 00:01 |
7 | 0.079920 | 0.000103 | 0.000000 | 00:01 |
8 | 0.068021 | 0.000119 | 0.000000 | 00:01 |
9 | 0.058917 | 0.000138 | 0.000000 | 00:01 |
fine_tune
은 사전 학습된 architecture를 학습시키기 위한 함수입니다. 이 함수는 사전 학습된 부분은 많이 학습하지 않고, 새로 추가된 출력 계층은 많이 학습합니다. 자세한 내용은 추후에 배웁니다.
Step 3: 모델을 사용해봅니다
= PILImage.create('단감.jpg') img
400) img.to_thumb(
predict
는 세 가지 값을 tuple로 산출합니다. 각각 예측되는 레이블, Loss function의 값(정확히 이해하지 못했습니다), 예측 확신도입니다.
= learner.predict(img)
is_dangam, _, probs print(f'이 사진은 {is_dangam} 입니다.')
print(f'{probs[0]:.4f}의 확률로 그렇습니다.')
이 사진은 단감 입니다.
1.0000의 확률로 그렇습니다.