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
plt.rc('font', family='NanumBarunGothic') 대봉감일까, 단감일까?
대봉감과 단감을 구분하는 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).
urls = search_images('대봉감', max_images=1)
urls[0]Searching for '대봉감'
'http://www.food123.kr/gamimg/aa6.jpg'
download_url로 url의 이미지를 dest에 다운로드합니다.Image.open으로 경로에 위치한 데이터를 열 수 있습니다.
from fastdownload import download_url
dest = '대봉감.jpg'
download_url(urls[0], dest, show_progress=False)
from fastai.vision.all import *
im = Image.open(dest)
im.to_thumb(256,256)
단감에도 동일하게 진행하겠습니다.
download_url(search_images('단감', max_images=1)[0], '단감.jpg', show_progress=False)
Image.open('단감.jpg').to_thumb(256,256)Searching for '단감'

정확한 사진을 불러온 것 같습니다. 이제는 여러 대봉감, 단감 이미지를 다운로드 받고 각각의 폴더에 저장하겠습니다.
searches = '대봉감', '단감'from pathlib import Pathpath = Path('daebong_or_dangam')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:
dest = (path/o)
dest.mkdir(exist_ok=True, parents=True)
download_images(dest, urls=search_images(f'{o}'))
sleep(10)
resize_images(path/o, max_size=400, dest=path/o)Searching for '대봉감'
Searching for '단감'
Step 2: 모델 훈련시키기
verify_images는 열리지 않는 이미지를 확인합니다.
failed = verify_images(get_image_files(path))failed에 존재하는 경로, 즉 열리지 않는 이미지들에 대해 map을 통해 Path.unlink를 각각 적용합니다.
Path.unlink는 삭제와 동일합니다.
failed.map(Path.unlink)
len(failed)1
모델을 훈련시키기 위해서는 Dataloaders가 필요합니다. 이를 위해서는 Fast.ai의 중수준 API인 DataBlock을 사용하거나 데이터 타입별로 미리 구축된 고수준 API를 사용할 수 있습니다.
아래 DataBlock에 사용하는 인자는 blocks, get_items, splitter, get_y, item_tfms 입니다.
dls = DataBlock(
blocks=(ImageBlock, CategoryBlock),
get_items=get_image_files,
splitter=RandomSplitter(valid_pct=0.2, seed=42),
get_y=parent_label,
item_tfms=[Resize(192, method='squish')]
).dataloaders(path, bs=32)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')
dls.show_batch(max_n=6)
데이터의 준비는 모두 끝났습니다. 이제 learner를 정의하고, 훈련시키면 됩니다.
learner에는 dataloaders와 architecture 지정이 필수적입니다.
데이터 특성별 learner가 존재하는데, 이미지를 위한 것은 vision_learner입니다.
learner = vision_learner(dls, resnet18, metrics=error_rate)/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는 라벨을 맞추는 정확도로 생각해주시면 되겠습니다.
learner.fine_tune(epochs=10)| 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: 모델을 사용해봅니다
img = PILImage.create('단감.jpg')img.to_thumb(400)
predict는 세 가지 값을 tuple로 산출합니다. 각각 예측되는 레이블, Loss function의 값(정확히 이해하지 못했습니다), 예측 확신도입니다.
is_dangam, _, probs = learner.predict(img)
print(f'이 사진은 {is_dangam} 입니다.')
print(f'{probs[0]:.4f}의 확률로 그렇습니다.')이 사진은 단감 입니다.
1.0000의 확률로 그렇습니다.