AI와 빅데이터를 활용한 경제정보처리

유튜브 뉴스 썸네일을 이용하여 카테고리 분류하기

나다꼬 2022. 6. 5. 17:49

이번 포스팅에서는 pytorch 기반으로 만들어진 fast.ai라는 딥러닝 라이브러리를 이용해 빠르게 CNN 분류모델을 적용해보는 것이 이번  포스팅의 주요 내용입니다. 분류 목적은 유튜브 뉴스 썸네일을 이용하여 카테고리(스포츠, 경제, 정치)를 예측하는 것입니다. 카테고리별로 유튜브 재생목록을 만들어 샘플을 각각 50여개씩 수집했습니다.

 

코드 실행 환경은 아래와 같습니다.

언어: Python

편집기: Google Colab(무료 버전, 하드웨어 가속기: GPU)

파일 경로 같은 것들이 모두 Google Colab을 가정하고 작성되어 Local에서는 실행이 어려우실 수 있습니다.

 

소스코드: https://drive.google.com/file/d/1k3m4yQ3DNU1epmOxa9Se8gtmQ0iAvuKp/view?usp=sharing 

 

과제3_모델2적용.ipynb

 

drive.google.com

 

1. Install and Import Libraries

# upgrade fastai to the most recent version (v. 2.5.3)

%%capture
!pip install fastai --upgrade
import fastai
print(fastai.__version__)

from fastai.vision.all import *
#from fastai.text.all import *
#from fastai.collab import *
#from fastai.tabular.all import * 

from matplotlib.pyplot import imshow
%%capture
!pip install -Uqq fastbook
from fastbook import *
from google.colab import files
!pip install pytube
from pytube import YouTube
import time
import pandas as pd
import urllib.request

필요한 파이썬 library를 모두 받습니다.

 

2. Dowonload news video Information from Youtube Playlist

그리고 pytube의 Playlist 함수를 사용하여 유튜브 플레이 리스트에 들어있는 동영상의 정보를 받아옵니다.

플레이 리스트는 제가 미리 직접 샘플링하여 생성한 각 뉴스 카테고리별 플레이 리스트입니다.

from pytube import Playlist
sports_list = Playlist('https://www.youtube.com/playlist?list=PLzelvdcOWMK-K7VQJ5IRGBh6ZgVMxTjM0')
economics_list = Playlist('https://www.youtube.com/watch?v=2mf0YbA33q8&list=PLzelvdcOWMK_DFdH3pdUnuscRCOpXvPTT&ab_channel=MBCNEWS')
politics_list = Playlist('https://www.youtube.com/watch?v=0Y9xcsG18eI&list=PLzelvdcOWMK_Yl6TlXoXJMKBzYAzmWmwM&ab_channel=SBS%EB%89%B4%EC%8A%A4')

 

각각의 뉴스 동영상에 대한 정보를 카테고리별로 데이터프레임화 시킵니다. 이 과정에서 'label'을 추가함으로써 동영상이 어떤 카테고리에 속하는지 정답값을 달아줍니다.

# 스포츠 뉴스 리스트 정보 생성
video_link= []
for video in sports_list.video_urls:
  url = video
  yt = YouTube(url)

  video_info = {
      'YouTubeURL' : url,
      'title' : yt.title,
      'length' : yt.length,
      'uploader' : yt.author,
      'upload_date' : yt.publish_date,
      'views' : yt.views,
      'keywords' : yt.keywords,
      'explanation' : yt.description,
      'thumbnail_url' : yt.thumbnail_url,
      'label' : 'sports'
  } 
  video_link.append(video_info)
  time.sleep(3)

df_sports = pd.DataFrame(video_link)
# 경제 뉴스 리스트 정보 생성
video_link= []
for video in economics_list.video_urls:
  url = video
  yt = YouTube(url)

  video_info = {
      'YouTubeURL' : url,
      'title' : yt.title,
      'length' : yt.length,
      'uploader' : yt.author,
      'upload_date' : yt.publish_date,
      'views' : yt.views,
      'keywords' : yt.keywords,
      'explanation' : yt.description,
      'thumbnail_url' : yt.thumbnail_url,
      'label' : 'economics'
  } 
  video_link.append(video_info)
  time.sleep(3)

df_economics = pd.DataFrame(video_link)
# 정치 뉴스 리스트 정보 생성
video_link= []
for video in politics_list.video_urls:
  url = video
  yt = YouTube(url)

  video_info = {
      'YouTubeURL' : url,
      'title' : yt.title,
      'length' : yt.length,
      'uploader' : yt.author,
      'upload_date' : yt.publish_date,
      'views' : yt.views,
      'keywords' : yt.keywords,
      'explanation' : yt.description,
      'thumbnail_url' : yt.thumbnail_url,
      'label' : 'politics'
  } 
  video_link.append(video_info)
  time.sleep(3)

df_politics = pd.DataFrame(video_link)
new_df = df_sports.append(df_economics).append(df_politics)
new_df # 인덱스가 꽤 중요한 역할을 하는데, 3개의 데이터프레임을 append로 잇다보니 각각의 label마다 인덱스가 따로따로 생기는 문제가 있음

그런데 각각의 dataframe을 append로 연결하니 sports 인덱스 0~50, economics 인덱스 0~50, politics 인덱스 0~50 이런 식으로 각각의 카테고리별 인덱스가 따로따로 붙어져 있습니다. 인덱스에 접근해서 정렬되게 부여할 수도 있지만, 데이터프레임을 로컬에 다운도 받을 겸, 인덱스도 이 과정에서 자동으로 정렬되도록 데이터프레임을 csv 파일로 한번 내보내고 그걸 다시 불러와줍니다. 그러면 인덱스가 제대로 정렬되어 label에 상관없이 인덱스가 0~150 이런 식으로 정렬되어 있습니다.

new_df.to_csv('/content/img.csv', index=False, encoding='utf-8-sig') # 그래서 데이터프레임을 csv 파일로 한번 저장하고
new_df = pd.read_csv('/content/img.csv') # 다시 불러들여보니
new_df # 통일된 index로 정렬돼 있음

 

 

3. Set folder structure and Dowonload news thumbnail

썸네일 이미지를 받아와야 하는데, 무작정 받아오는 것아 아니라 뉴스 카테고리별로 다운을 받아야 편합니다. 그래야만 폴더명을 label로 이용할 수 있기 때문입니다. 그래서 폴더 구조를 미리 만들어줍니다.

# 추출한 이미지를 저장할 폴더를 생성하는 함수
def createFolder(directory):
    import os
    try:
        if not os.path.exists(directory):
            os.makedirs(directory)
    except OSError:
        print ('Error: Creating directory. ' +  directory)
# /content/image 아래의 폴더명은 각 뉴스 썸네일의 label, 즉, 정답값으로 사용됩니다.
createFolder('/content/image')
createFolder('/content/image/sports')
createFolder('/content/image/economics')
createFolder('/content/image/politics')

폴더구조가 완성되었으면, 썸네일 URL을 이용해서 각 카테고리 폴더에 뉴스의 썸네일을 다운받습니다.

# urllib.request.urlretrieve 함수를 사용하여 각 뉴스 카테고리별로 생성된 폴더에 해당하는 썸네일을 다운로드합니다.

for i in range(0, len(new_df)) :
  image_path = '/content/image/'+ new_df['label'][i]
  urllib.request.urlretrieve(new_df['thumbnail_url'][i], image_path + '/' + new_df['title'][i] + ".jpg")

4. Apply CNN Model

# glob함수를 사용하여 각 이미지의 파일명과 경로명을 담은 데이터프레임을 생성합니다.

import glob
img_full_path = pd.Series(glob.glob('/content/image/*/*.jpg'), name='my_file_path')
img_nm = pd.Series(img_full_path.str.split(pat="/").str[4], name='file_name')

df_img = pd.concat([img_full_path, img_nm], axis=1)
df_img.head(10)

 

# /content/image 아래에 있는 모든 이미지 리스트 접근

# location of images
from fastai import *
from fastai.vision import *

path = Path('/content/image')

# get list of image files
file_names = get_image_files(path)
file_names
 
 
 
# show an example image

im = Image.open(file_names[0])
print(im.shape) # 이미지를 보여줘
imshow(im)

 

# 잘못된 형태의 이미지를 찾아 제거

corrupt_images = verify_images(file_names)

corrupt_images.map(Path.unlink);

corrupt_images

(#0) []

 

# Fast AI library의 DataBlock 함수를 이용하여 train, validation dataset 분리
# data block settings

image_size = 128 # 이미지 사이즈를 지정하는 변수
valid_set_share = 0.3 # validation 비율 지정
my_random_seed = 42 # 시드 설정

my_dblock = DataBlock(
    blocks=(ImageBlock, CategoryBlock), 
    get_items=get_image_files,
    splitter=RandomSplitter(valid_pct=valid_set_share, seed=my_random_seed),
    get_y=parent_label, # y라는 게 보통 이미지의 정답값이다. 파일이 속한 폴더의 이름을 가져와라. 이미지의 정답값이 무엇이냐??
    item_tfms=Resize(image_size))
# prepare dataloaders
dls = my_dblock.dataloaders(path) # 이미지를 계속 불러오면서 이미지의 정답값을 매치하며 기계학습을 준비
# show image examples
dls.train.show_batch(max_n=10, nrows=2) # 이미지 example 확인. training data set에 정답값을 붙여줘

 

len(dls.train_ds), len(dls.valid_ds)

(105, 44)

 

아래에서 cnn_learner함수를 이용해 모델을 생성하고, fine_tune 함수를 이용해 모델을 학습시킵니다. resnet34라는 미리 학습된 모델을 가져다가 쓰고, accuracy를 metric으로 삼는 모델을 생성했습니다.

그리고 100번 동안 training dataset을 모델이 학습하면서 valid loss라는 지표를 관찰하는데, 최고 성능인 epoch에서 valid loss가 더 이상 개선되지(줄어들지) 않는 횟수가 10번 이상 지속되면, 더 이상 개선의 여지가 없는 최고의 상태라고 보고 학습을 종료하는 early stopping을 적용했습니다.

#learn = 'change metric to accuracy'

trained_model = cnn_learner(dls, resnet34, metrics=accuracy).to_fp16() # resnet 18, 34, 50, 101, 152
trained_model.fine_tune(100, cbs=[EarlyStoppingCallback(monitor='valid_loss', patience=10),SaveModelCallback(monitor='valid_loss')]) # early stopping

학습결과는 accuracy 70%로서 나쁘지 않은 수준입니다. 이는 유튜브 썸네일로 뉴스가 스포츠, 경제, 정치에 관련된 것인지 어느정도 잘 예측할 수 있음을 의미합니다. 즉, 언론사 유튜브 담당자들이 뉴스의 썸네일을 어느정도 뉴스 내용을 예측할 수 있도록 신경써서 올린다고도 볼 수 있습니다. 이런 간단한 category classification이 아니라, 썸네일로 어느 수준의 정보를 추출할 수 있는지도 연구의 대상이 될 수 있을 것 같습니다.

 

 

학습된 모델로 테스트 데이터를 예측해본 예시입니다. 위가 label이고, 아래가 예측값입니다. 

trained_model.show_results()

 

trained_model.validate()

(#2) [0.9042053818702698,0.6818181872367859]