1. 대회 설명
https://kbig.kr/portal/kbig/keybiz/creatorcamp.page
데이터 크리에이터 캠프(Data Creator Camp, 이하 DCC) 대회는 고등학생과 대학생을 대상으로 인공지능에 대해 학습할 수 있는 부트캠프형 대회입니다.
대회 특징으로는 대회를 하는데 필요한 기본적인 지식을 얻을 수 있는 강의 영상을 제공해줄 뿐더러 대회 예선이 진행되는 약 4주동안 총 3회의 전문가와의 멘토링을 지원해주어 인공지능과 컴퓨터 비전에 대해 잘 모르는 사람도 쉽게 접근할 수 있다는 장점이 있다.
저의 경우도 인공지능 중 컴퓨터 비전 분야는 연구실에서 조금 해본 것 이외의 대회나 프로젝트 경험은 크게 없기에 배우자는 목적으로 참가하게 되었습니다.
군대에서 자기계발을 해야 하는데 시간은 없고, 막상 할 것도 없어 찾고 있던 도중 같은 대대 후임이 이 대회를 찾았고 선임 두명과 후임 한명과 함께 대회에 참가하게 되었습니다. 4명 모두 CV 분야에 큰 경험은 없었던 터라 이 대회와 멘토링을 통해 많은 것을 배우자는 목적으로 참가하게 되었습니다.
2. 데이터셋 (Dataset)
데이터셋에는 총 25,503개의 이미지 데이터가 포함되어 있으며 20개의 클래스로 나뉘어있습니다. 전체 데이터의 크기는 2.5GB 정도였습니다.
여기서 이 데이터셋의 어려운 점이 한 클래스가 반드시 한 종류를 나타내는 것은 아닙니다. 또한, 데이터셋에는 일러스트 이미지 말고 쓰레기 데이터인 실사진 데이터도 섞여있습니다.
3. 1주차 문제 (Problems)
1주차에는 EDA를 통해 데이터를 분석하고, 클래스 간 데이터 불균형을 해결하기 위해 Data Balancing 작업을 하는 것입니다.
2주차에는 섞여있는 실사진 이미지 데이터를 인공지능 기술을 이용해 쓰레기 데이터를 제거하는 (즉, 일러스트 이미지와 실사진 이미지를 Classify(분류)하는) 것입니다.
3~4주차는 인공지능 기술을 이용해 데이터를 20개의 클래스로 분류하는 분류기를 만들면 됩니다.
따라서, 1주차에는 EDA(탐색적 데이터 분석) 작업과 데이터셋의 카테고리별 데이터 수의 Unbalancing을 해결하기 위한 Data Balancing 작업, 이 작업을 하기 위한 Data Augmentation 작업을 하였습니다.
4. EDA (Exploratory Data Analysis)
EDA는 탐색적 데이터 분석으로 본격적으로 모델을 짜고 데이터를 학습시키기 전에 데이터를 분석해보고 지속적으로 해당 데이터에 대한 ‘탐색과 이해’를 기본으로 가져가기 위해 탐색해보는 것을 의미합니다.
처음 기획 단계에서 데이터 날 것(raw data) 그대로에 대해 분석해야 데이터 수를 맞추거나(balancing) augmentation을 해서 늘리거나, 또한 각 데이터의 관계 속에서 숨겨져 있던 중요한 데이터의 새로운 특성(feature)을 발견해낼 수도 있기 때문입니다. 때론 EDA 과정을 통해 뽑아낸 새로운 특성들이 데이터 학습에 있어 핵심 역할을 해내기도 하므로 매우 중요한 과정이라고 생각합니다.
EDA를 수행하며 데이터셋의 특성들을 다루기 편하도록 Python Pandas 라이브러리의 데이터 형태인 Data Frame으로 구축도 함께 하였으니 참고 바랍니다.
명시적 정보 분류 및 분석
먼저, 명시적으로 알 수 있는 데이터셋의 정보부터 정리합니다. 괄호 안은 attribute의 이름입니다.
- 이미지 이름(
img
) - 레이블(
label
) - 파일 확장자(
ftype
) - color map(
cmap
) - channel 차원 (
channel
) - 이미지 크기 용량(
fsize
) - 이미지 마지막 수정 날짜(
ftime
) - 이미지 너비(
width
) - 이미지 높이(
height
)
data = []
# make list which contains information of each image data
for label in data_list.keys():
for img_dir in data_list[label]:
img_path = base_dir + "/" + label + "/" + img_dir
# load image data
img = Image.open(img_path)
# PIL.Image to np.array
imgarr = np.array(img)
if len(imgarr.shape) == 2:
w, h= imgarr.shape
c = 1
else:
w, h, c = imgarr.shape # get shape
fsize = (os.path.getsize(img_path) / 1024.0)
fmtime = time.ctime(os.path.getmtime(img_path))
data.append([img_dir[:-4], label, img_dir[-3:], img.mode, fsize, fmtime, w, h, c])
print(f"{cnt}/{numdata}")
df = pd.DataFrame(data, columns=['img','label','ftype', 'cmap', 'fsize',\
'fmtime', 'width', 'height', 'channel'])
df = df.astype({'img':'string', 'label':'string', 'ftype':'string', 'cmap':'string',\
'fsize':'float', 'fmtime':'datetime64[ns]','width':'int', 'height':'int',\
'channel':'int'})
df.head()
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 25503 entries, 0 to 25502
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 img 25503 non-null string
1 label 25503 non-null string
2 ftype 25503 non-null string
3 cmap 25503 non-null string
4 fsize 25503 non-null float64
5 fmtime 25503 non-null datetime64[ns]
6 width 25503 non-null int64
7 height 25503 non-null int64
8 channel 25503 non-null int64
dtypes: datetime64[ns](1), float64(1), int64(3), string(4)
memory usage: 1.8 MB
위처럼 총 25,503개의 데이터에 대한 모든 정보들이 Data Frame으로 생성되었습니다.
모든 데이터의 특성에는 null 값이 없습니다.
Data Frame을 생성할 때 이미지를 불러온 후 각 이미지에 대한 정보를 읽어 해당 열을 Data Frame에 삽입하였습니다.
마지막에는 각 특성의 type을 설정해주었습니다.
이 데이터셋에는 클래스가 총 20개 존재합니다. 각 클래스에는 한 가지 카테고리만 있는 것이 아니라 여러 가지 카테고리가 포함되어 있습니다. 여기서는 subclass라고 부르겠습니다.
예를 들면, L2_10 클래스 안에는 telephone, phone, router 이처럼 3개의 서브 클래스가 포함되어 있게 되는 것입니다.
각 클래스별 데이터의 개수는 위 그래프와 같습니다. 생각보다 데이터간의 unbalancing이 심한 것을 알 수 있습니다.
따라서 1주차에 Data Augmentation을 사용한 Oversampling(데이터 수 늘리기)와 Undersampling(데이터 수 줄이기) 작업을 진행해주어야 합니다.
이제 본격적으로 데이터셋에 대한 EDA를 진행해보겠습니다. 먼저 파일의 형식의 차이입니다.
sns.countplot(y='ftype',data=df)
plt.show()
또한, 이미지마다 색상 처리 방식이 다를 수 있으므로 JPG와 PNG 형식의 각 색상 맵(color map)의 비율과 종류는 아래와 같습니다.
df.groupby('ftype')['cmap'].value_counts()
plt.xlim(0,1200)
sns.countplot(y='ftype',hue='cmap',data=df)
plt.show()
ftype cmap
jpg RGB 23944
CMYK 42
L 6
png RGBA 1127
RGB 374
P 10
Name: cmap, dtype: int64
이미지의 너비(width
)와 높이(height
)값을 scatter plot으로 표현하면 아래와 같습니다.
여기서 알아낼 수 있는 점은 높이와 너비가 한 쪽에 모여있고 일부 그룹은 높이와 너비가 각각 일정한 그룹이 있다는 것을 발견했습니다.
명시적이지 않은 숨겨진 특성 추출 및 분석
각 일러스트 이미지가 무엇을 표현하는지 명시되어 있지 않으므로 subclass라는 특성(attribute)을 만들어 Data Frame에 추가했습니다. 25,503개의 데이터에 하나하나 이름을 부여하는 것은 불가능하므로 subclass별로 폴더를 생성해 유사한 이미지를 직접 분류하여 폴더별로 정리했습니다. 이후 폴더 디렉토리를 읽어 subclass 라벨링 작업을 했습니다.
EDA를 통해 알아낸 데이터셋의 특성
1) 이미지 파일 형식
df['ftype'].value_counts()
jpg 23992
png 1511
Name: ftype, dtype: Int64
2) 이미지 색상맵 및 채널 수
df.groupby('ftype')['cmap'].value_counts()
ftype cmap
jpg RGB 23944
CMYK 42
L 6
png RGBA 1127
RGB 374
P 10
Name: cmap, dtype: int64
3) 이미지 너비와 높이(규격)
sns.scatterplot(x='width',y='height',data=df)
plt.show()
이후 3주차에서 위에 나열한 문제점을 해결하기 위하여 전처리(Preprocessing) 작업을 통해 이미지의 파일 형식(확장자), 컬러맵, 규격 등을 통일해주는 작업을 해야 한다.
5. Data Augmentation
Data Augmentation은 고려대학교 DMQA 연구실의 PT 자료를 참고하였습니다.
출처 : http://dmqm.korea.ac.kr/activity/seminar/307
https://www.youtube.com/watch?v=BmD4HJcNtHc
Data Augmentation이란 Crop, Rotate, Invert 등의 물리적 변환과 밝기, 감마값, 색감, 온도값 등을 조절하여 대상이 변하지 않는 한에서 사진을 복제하는 것입니다.
Computer Vision과 같은 머신러닝 알고리즘은 매우 많은 양의 데이터셋을 필요로 하기 때문에 데이터가 부족하거나, 여러 카테고리 사이에 Data가 Unbalance(불균형) 하다면 Data Augmentation을 통해 데이터를 늘려주게 됩니다.
여기서 중요한 점은, 데이터가 완전히 비슷하게 복제가 된다면 학습 과정에서 Over Fitting될 수 있으므로 너무 비슷하게 복제되는 것은 막아야 합니다.
# import libaries
import os
import cv2 as cv
import numpy as np
import random,math
from PIL import Image, ImageEnhance, ImageChops
import matplotlib.pyplot as plt
%matplotlib inline
def cutout(img): #input: image, output: ndarray
img = np.array(img)
h, w, _ = img.shape
center = (h//2,w//2)
res_img = img
y = np.random.randint(h)
x = np.random.randint(w)
y1 = np.random.randint(0,h//2)
y2 = y1+h//2
x1 = np.random.randint(0,w//2)
x2 = x1+w//2
res_img[y1:y2,x1:x2,:] = 0
return res_img
CutOut 알고리즘은 사진에서 $\frac{1}{2}\times$ width, $\frac{1}{2}\times$ height 만큼의 부분을 0 또는 1과 같은 의미 없는 값으로 채워 넣는 것입니다.
# mix up two images
def mixup(img1arr, img2arr, img1_p=0.5, img2_p=0.5): #input: ndarray, output: ndarray
img3 = img1arr*img1_p+img2arr*img2_p
img3 = img3.astype(np.int64)
return img3
# mix up three images
def mixup3(img1arr, img2arr, img3arr, img1_p=0.33, img2_p=0.33, img3_p=0.33): #input: ndarray, output: ndarray
img4 = img1arr*img1_p+img2arr*img2_p+img3arr*img3_p
img4 = img4.astype(np.int64)
return img4
Mixup은 카테고리가 다른 두 사진을 0.5, 0.5 비율로 투명도를 주어 섞는 것입니다. 해당 사진의 label 값은 [0.5, 0.5]
이 됩니다. 여기서 저희는 세 개의 사진을 [0.33, 0.33, 0.33]
의 비율로 섞어주는 함수도 만들었습니다.
def cutmix(img1,img2): #input: image, output: ndarray
img1 = np.array(img1)
img2 = np.array(img2)
h, w, _ = img1.shape
lamda = random.uniform(0,1)
rx = random.uniform(0,w)
ry = random.uniform(0,h)
rw = w*math.sqrt(1-lamda)
rh = h*math.sqrt(1-lamda)
x1 = max(0,round(rx - rw / 2))
x2 = min(round(rx + rw / 2),w)
y1 = max(0,round(ry - rh / 2))
y2 = min(round(ry + rh / 2),h)
img3 = img1.copy()
img3[x1:x2,y1:y2,:] = img2[x1:x2,y1:y2,:]
return (img3, round(100*lamda,2),round(100*(1-lamda),2))
CutMix는 카테고리가 다른 두 개의 사진을 일정 비율로 잘라서 섞는 것입니다. 여기서 위에 Mixup과 다른점은 비율이 반드시 [0.5, 0.5]
가 되지 않는 것입니다. 또한, 투명도를 주어 섞는 것이 아닌 사각형 모양으로 잘라내어 섞는 것입니다.
이 방법 또한 다른 방법보다 우수한 성능을 보여주었다고 합니다.
AugMix는 여러개의 Augmented된 이미지를 생성한 후 투명도를 주어 섞는 것입니다.
여기서 각 샘플의 가중치의 경우 0.5처럼 고정된 값이 아닌 랜덤 값으로 결정됩니다.
AugMix를 하면 노이즈에 대한 저항성이 증가하고 너무 확실하게 추정하는 것을 방지해주어 성능을 증가시킨다고 합니다.
def bright(img):
imgcopy = img.copy()
enhancer = ImageEnhance.Brightness(imgcopy)
brightness_image = enhancer.enhance(1.8)
return brightness_image
사진의 밝기를 랜덤으로 조정하는 함수입니다.
def enhance(img):
imgcopy = img.copy()
curr_col = ImageEnhance.Color(imgcopy)
new_col = 2.5
img_colored = curr_col.enhance(new_col)
return img_colored
사진의 채도를 랜덤으로 조정하는 함수입니다.
def contrast(img):
imgcopy = img.copy()
curr_con = ImageEnhance.Contrast(imgcopy)
new_con = 0.3
img_contrasted = curr_con.enhance(new_con)
return img_contrasted
사진의 대비값을 랜덤으로 조정하는 함수입니다.
def move_h(img):
imgcopy = img.copy()
width, height = imgcopy.size
shift = random.randint(0, round(width * 0.2))
horizonal_shift_image = ImageChops.offset(imgcopy, shift, 0)
horizonal_shift_image.paste((0), (0, 0, shift, height))
return horizonal_shift_image
def move_v(img):
imgcopy = img.copy()
width, height = imgcopy.size
shift = random.randint(0, round(height * 0.2))
vertical_shift_image = ImageChops.offset(imgcopy, 0, shift)
vertical_shift_image.paste((0), (0, 0, width, shift))
return vertical_shift_image
사진을 랜덤 값만큼 좌우/상하로 미는(Shift) 함수입니다.
def rotate(img):
imgcopy = img.copy()
rotate_image = imgcopy.rotate(random.randint(-30, 30))
return rotate_image
사진을 랜덤 각도만큼 회전시키는 함수입니다. (-30°에서 30°)
def shear_v(img):
imgcopy = img.copy()
cx, cy = 0, random.uniform(0.0, 0.3)
shear_image = imgcopy.transform(
imgcopy.size,
method=Image.AFFINE,
data=[1, cx, 0,
cy, 1, 0,])
return shear_image
def shear_h(img):
imgcopy = img.copy()
cx, cy = random.uniform(0.0, 0.3), 0
shear_image = imgcopy.transform(
imgcopy.size,
method=Image.AFFINE,
data=[1, cx, 0,
cy, 1, 0,])
return shear_image
사진을 기울이는 함수입니다. 좌우(Horizontal)로 기울이거나, 상하(Vertical)로 기울일 수 있습니다.
def zoomimg(img):
imgcopy = img.copy()
zoom = random.uniform(0.7, 1.3) #0.7 ~ 1.3
width, height = imgcopy.size
x = width / 2
y = height / 2
crop_image = imgcopy.crop((x - (width / 2 / zoom), y - (height / 2 / zoom), \
x + (width / 2 / zoom), y + (height / 2 / zoom)))
zoom_image = crop_image.resize((width, height), Image.LANCZOS)
return zoom_image
사진을 랜덤 비율만큼 확대하여 잘라(crop)내거나 축소하여 주변을 빈 값으로 채우는 함수입니다.
def unif():
u = random.uniform(0,1)
return (u,1-u)
def unif3():
u1 = random.uniform(0,1)
u2 = random.uniform(0,1)
u3 = random.uniform(0,1)
s = u1+u2+u3
return (u1/s,u2/s,u3/s)
def unif_select():
r = random.uniform(0,3)
if 0<=r<1:
return 0
elif 1<=r<2:
return 1
else:
return 2
위 함수는 Uniform하게 난수값을 뽑아내는 함수입니다. rand 함수의 경우 uniform한 난수값이 아니기 때문에 random
모듈의 uniform 함수를 사용했습니다.
def aug_aux(img,lst,n): #input: image, output: image
res = img.copy()
for i in range(n+1):
res = func_list[lst[i]](res)
return res
def augmix(img): #input: image, output: ndarray
aug_order = np.array([0,1,2,3,4,5,6,7,8])
random.shuffle(aug_order)
aug_order = aug_order.reshape(3,3)
img1 = img.copy()
img1 = aug_aux(img1,aug_order[0],unif_select())
img2 = img.copy()
img2 = aug_aux(img2,aug_order[1],unif_select())
img3 = img.copy()
img3 = aug_aux(img3,aug_order[2],unif_select())
u1,u2,u3 = unif3()
mixed = mixup3(np.array(img1),np.array(img2),np.array(img3),u1,u2,u3)
unf = unif()
return mixup(np.array(img),mixed,unf[0],unf[1])
AugMix 함수는 이미지를 받아 랜덤으로 적용할 함수를 선택하고, 원본 이미지에 선택된 3개의 함수를 각각 적용한 후 세개를 Uniform하게 선택된 랜덤값을 각각 투명도에 적용시켜 합치게 됩니다.
위 aug_aux
함수는 AugMix의 방법과는 다르게 한 이미지에 연속적으로 변화를 적용하는 함수입니다.
Puzzle Mix의 경우 적용해보려고 시도했으나, 구현 방법이 매우 복잡했습니다.
사진에서 중요도(Saliency)를 계산하고 중요한 부분이 사라지지 않도록 Transport하여 사진을 합치는 방식이었고, 특히 저희 데이터셋의 경우 ImageNet 데이터와는 다르게 일러스트 물체와 주변에 빈 배경이 있어 적용하기에 적합하지 않다고 생각하였습니다.
저희는 위에 나열한 Augmentation 방법들을 적용하기 위해 Auto Augment(AA)와 RandAugment (RA) 방법을 사용했습니다.
6. Data OverSampling and UnderSampling (by RA and AA)
아래 작성한 코드를 사용해 데이터의 밸런스를 맞추었습니다.
매주 진행했던 멘토링에서 담당 멘토님께서 데이터의 수를 늘려서 맞추는 것 보다는 데이터의 수를 줄이는게 낫다고 하셨습니다. 따라서 전체 데이터를 각 클래스 당 1,000개(총 20,000개)로 줄일 생각이었습니다.
다만, 간단한 테스트 결과 학습률이 너무 떨어져서 Augmentation을 통해 각 클래스 당 3,000개(총 데이터 60,000개)로 늘리기로 결정했습니다.
특히, 저희가 받은 데이터셋에는 실사진과 일러스트 사진이 섞여있습니다. 위 그래프에서 주황색은 실사진 이미지로 실사진 이미지를 제거하고 나면 일러스트 데이터는 아주 적게 남는 클래스들도 있습니다.
심지어, 위는 수작업으로 라벨링을 하여 정확히 나눈 것이지만 저희는 인공지능 기술을 이용해서 실사진 이미지와 일러스트를 구분해야 하므로 인공지능 기술의 정확도에 따라 남은 일러스트 이미지가 더 적어질 수도 있다는 문제가 있습니다.
데이터 수가 매우 많은 클래스인 L2_25와 L_33와 같은 데이터 수가 1000개를 넘어가는 클래스의 데이터를 먼저 제거하고 나머지 데이터수가 부족한 일부 클래스의 데이터를 Augmentation을 통해 증강하도록 했습니다.
알고리즘은 아래와 같습니다.
1-1. 데이터 수 1000개 초과 클래스
서브클래스 클러스터링을 통해 비슷한 사진들을 우선으로 제거합니다. (K-Means 알고리즘 사용)
먼저 클러스터의 개수를 지정하지 않고 클러스터링을 돌립니다. 우리는 1,000개의 이미지만 남겨야 합니다. $K$개의 클러스터가 결과로 나온 경우 각 클러스터(군집)을 $C_1, C_2, ..., C_N$이라고 합니다.
이때 모든 군집의 개수를 최대한 동일하게 남겨두기 위해서 아래와 같은 규칙을 따릅니다.
1. 클러스터 중 원소의 개수가 가장 많은 클러스터의 원소 수를 a라고 한다.
2. 전체 데이터 개수가 1000개보다 작거나 같을 때까지 반복한다.
2-i. 각 클러스터 중 원소 수가 a개와 같은 클러스터에서 랜덤으로 한 개의 원소를 제거한다.
2-ii. a = a - 1
위 의사결정코드(pseudo-code)를 따르게 되면 개수가 가장 많은 클러스터부터 1개씩 제거를 시작합니다.
한 번 while문을 돌 때마다 개수가 많은 클러스터에서 원소 한 개씩을 제거하게 되는 것입니다.
따라서 1000개가 남을 때까지 많은 클러스터 위주로 제거하게 됩니다.
원소 수가 작은 클러스터는 비교적 희귀한 원소들을 의미하고, 원소수가 큰 클러스터는 비교적 흔한 원소들을 의미합니다. 위 알고리즘에 따르면 랜덤한 불운에 의해 희귀한 원소들이 제거되는 불상사를 막을 수 있고, 흔한 원소들 위주로 지워나가서 한 클래스 내에서도 균형을 맞출 수 있다는 장점이 있습니다.
이때, 사진은 모두 (28, 28)
사이즈로 변환하여 클러스터링을 진행하며, 각 클러스터를 정렬하고 골고루 1000개만 남기는 것입니다.
1-2. 데이터 수 1000개 미만 클래스
아래의 Simple Augmentation을 통해 1000개까지 늘립니다.
- 밝기 Brightness
- 채도 Saturation
- 대비 Contrast
- 좌우 이동
- 상하 이동
- 회전
- 기울기 상하
- 기울기 좌우
- 확대 또는 축소
위 알고리즘을 통해 데이터를 1000개까지 복제합니다.
이때, 적용할 대상 사진과 함수는 랜덤으로 선택합니다. 함수를 리스트에 담은 후 함수를 shuffle하여 함수를 택합니다.
2. Augmentation
위 1-1, 1-2. 과정을 거치면 모든 클래스의 데이터의 개수가 1000개로 맞추어졌습니다. 이후 Augmentation을 통해 모든 클래스의 이미지 데이터의 개수를 각각 3000개로 늘립니다.
이때 데이터를 1000개에서 3000개까지 늘릴 때 어떤 Augmentation 알고리즘을 사용하는 지에 따라 학습률이 많이 달라질 수 있습니다. 이는 3~4주차 과정에서 다루도록 하겠습니다.
Data OverSampling과 UnderSampling 코드는 아래와 같습니다.
먼저 필요한 라이브러리를 불러옵니다.
import os
import cv2
import pandas as pd
import random
import math
from time import time
import numpy as np
import matplotlib.pyplot as plt
op = os.path.join
from PIL import Image, ImageEnhance, ImageChops
from sklearn.cluster import KMeans
from sklearn.metrics import accuracy_score
from sklearn import decomposition
from sklearn.manifold import TSNE
from sklearn.cluster import DBSCAN
import warnings
warnings.filterwarnings(action='ignore')
base_dir = "/home/Desktop/DCC2022/data"
pdir = "/home/Desktop/DCC2022/pdata3"
tdir = "/home/Desktop/DCC2022/tdata"
adir = "/home/Desktop/DCC2022/adata_mix3"
print("adir setted:", adir)
dsize = (384, 384)
def reset_seed():
np.random.seed(int(time()))
random.seed(int(time()))
아래는 저장된 CSV 파일을 통해 Data Frame을 불러오는 코드입니다.
# load class list from directory
cls_list = os.listdir(base_dir)
cls_list
df = pd.read_csv("/home/Desktop/DCC2022/processed_df2.csv", index_col=0)
df = df.astype({'img':'string',
'label':'string',
'ftype':'string',
'cmap':'string',
'fsize':'float',
'fmtime':'datetime64[ns]',
'width':'int',
'height':'int',
'channel':'int',
'subclass':'string',
'mode_rate':'float',
'numcluster':'int',
'backcount':'int',
'isillust':'int',
'isback':'int',
'annotation':'string',
'mode_rate3':'float',
'mode_rate_jw':'float',
'week2':'int'})
df_label = pd.DataFrame({'class':[], 'img':[], 'label':[], 'type':[]})
df_label = df_label.astype({'class':'string', 'img':'string', 'label':'string', 'type':'string'})
df_label.info()
assert len(df_label) == 0
아래는 Augmented된 이미지를 저장할 디렉토리를 생성하는 코드입니다.
print("making directories")
init_dict = dict()
if not os.path.isdir(adir):
os.system(f'mkdir {adir}')
for cls in cls_list:
imglist = os.listdir(op(pdir, cls))
print(cls, len(imglist))
init_dict[cls] = len(imglist)
if not os.path.isdir(op(adir, cls)):
os.system(f'mkdir {op(adir, cls)}')
print("done")
아래는 Perspective Transformation 함수로, 이미지 변형 방법 중 한 가지입니다. Data Over&UnderSampling 과정에서 필요하여 따로 추가하였습니다.
from random import uniform
def pt(img):
w, h = img.size
img_rgb = np.array(img.convert('RGB'))
w_, h_ = w // 10, h // 10
dw, dh = [], []
for i in range(4):
dw.append(uniform(-w_, +w_))
dh.append(uniform(-h_, +h_))
src_pts = np.array([[0, 0], [0, h - 1], [w - 1, 0], [w - 1, h - 1]], dtype='float32')
dst_pts = np.array([[dw[0], dh[0]], [dw[1], h - 1 + dh[1]], [w - 1 + dw[2], dh[2]], [w - 1 + dw[3], h - 1 + dh[3]]], dtype='float32')
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
return cv2.warpPerspective(img_rgb, M, dsize, borderMode=cv2.BORDER_CONSTANT, borderValue=(255, 255, 255))
또한, 저희 팀에서 새롭게 만든 Augmentation 방식인 Slice Mix입니다.
이는 사진의 중심점을 지나도록 랜덤한 각도(0 ~ 180도)로 두 사진을 잘라 붙여서 사진을 증강합니다. 중심점을 지나도록 자르므로 데이터의 레이블은 [0.5, 0.5]
가 됩니다.
def SliceMix(img1, img2):
w, h = img1.size
img1_np = np.array(img1)
img2_np = np.array(img2)
mask = np.zeros((h, w), dtype='uint8')
alpha = uniform(0, np.pi)
for i in range(h):
for j in range(w):
if np.sin(alpha) * (i - (h - 1) // 2) > np.cos(alpha) * (j - (w - 1) // 2):
mask[i, j] = 255
img1_mask1 = cv2.bitwise_and(img1_np, mask)
img2_mask2 = cv2.bitwise_and(img2_np, ~mask)
return img1_mask1 + img2_mask2#, img1_mask2 + img2_mask1
i) Data UnderSampling
먼저 데이터 개수가 1000개를 초과하는 클래스에 대해 데이터를 제거합니다.
아래 코드는 K-Means 알고리즘을 통해 한 클래스 내에서 Clustering을 한 후, 같은 그룹(클러스터)로 묶인 그룹 내에서 같은 개수가 남겨질 때까지 지우는 방식입니다.
가로 줄이 같은 카레토리고 분류된 클러스터입니다.
클러스터링한 그룹 이미지들을 시각화(Visualization) 하는 함수입니다.
def viz_img(y_pred, n, k):
fig = plt.figure(1)
box_index = 1
for cluster in range(0, k):
result = np.where(y_pred == cluster)
for i in np.random.choice(result[0].tolist(), n, replace=False):
ax = fig.add_subplot(n, n, box_index)
temp = X[i].reshape(dsize)
temp = cv2.resize(temp * 255, (28, 28)).astype(np.uint8)
temp = cv2.cvtColor(temp, cv2.COLOR_GRAY2RGB)
plt.imshow(temp)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
box_index += 1
plt.show()
데이터 수가 1000개 초과인 클래스와 1000개 미만(이하)인 클래스를 나누는 코드입니다.
reset_seed()
over1000 = dict()
under1000 = dict()
for cls, num in init_dict.items():
if num > 1000:
over1000[cls] = num
else:
under1000[cls] = num
print("over1000", over1000, len(over1000), "classes")
print("under1000", under1000, len(under1000), "classes")
K-Means 비지도학습 인공지능 알고리즘을 통해 한 클래스 내의 이미지 데이터를 작은 그룹(클러스터)로 나눈 후 위 알고리즘에 따라 제거하여 1000개로 맞추는 코드입니다.
df_list = []
for cls, numimg in over1000.items():
print("="*30)
print(cls, numimg)
X = []
y = []
### Load Images
imglist = os.listdir(op(pdir, cls))
for i, imgdir in enumerate(imglist):
try:
img = Image.open(op(pdir, cls, imgdir))
img = np.array(img.convert('L'))
img = cv2.resize(img, dsize)
X.append(img)
y.append([cls, imgdir])
except Exception as e:
print(e)
print("load done.")
### Clustering
X = np.array(X)
X = X.astype(np.float64) / 255.
X = X.reshape(len(X), -1)
print("clustering", X.shape, end=' ')
model = TSNE(learning_rate=300)
transformed = model.fit_transform(X)
model = DBSCAN(eps=2.4, min_samples=8)
predict = model.fit(transformed)
y_pred = predict.labels_
clusters, ncluster = np.unique(y_pred, return_counts=True)
print(clusters)
print(ncluster)
n = min(ncluster)
print(f"{len(clusters)} clusters created.")
### Selecting Data with Priority Queue
cluster_pq = []
count_pq = []
for i in range(1, len(clusters) - 1):
cluster_pq.append([clusters[i], ncluster[i]])
count_pq.append([clusters[i], ncluster[i]])
cluster_pq.sort(key=lambda x : -x[1])
count_pq.sort(key=lambda x : -x[1])
count = sum(ncluster[1:])
left_count = ncluster[0]
print(count, left_count)
# print(count_pq)
n_select = 1000 - ncluster[0]
print(f"select {n_select} data from clusters, {ncluster[0]} from \"-1\"")
while True:
count_pq[0][1] -= 1
count_pq.sort(key=lambda x : -x[1])
if sum(list(map(lambda x : x[1], count_pq))) == n_select:
break
count_pq.sort(key=lambda x : x[0])
cluster_pq.sort(key=lambda x : x[0])
print(sum(list(map(lambda x : x[1], count_pq))))
cnt1 = 0
### Copying data from processed dataset
for i in range(len(count_pq)):
c, n = count_pq[i]
result = np.where(y_pred == c)[0].tolist()
np.random.shuffle(result)
idxlist = result[:n]
for idx in idxlist:
imgdir = y[idx][1]
img = Image.open(op(pdir, cls, imgdir))
img.save(str(op(adir, cls, imgdir)))
df_list.append([cls, imgdir, f"{cls}:1", 'original'])
cnt1 += 1
print(cnt1, "data copied.")
print(len(os.listdir(op(adir, cls))), "files are in directory.")
assert len(os.listdir(op(adir, cls))) == cnt1
idxlist = np.where(y_pred == -1)[0].tolist()
cnt2 = 0
for idx in idxlist:
imgdir = y[idx][1]
img = Image.open(op(pdir, cls, imgdir))
img.save(str(op(adir, cls, imgdir)))
df_list.append([cls, imgdir, f"{cls}:1", 'original'])
cnt2 += 1
print(cnt2, "data copied from remaining.")
print(len(os.listdir(op(adir, cls))), "files are in directory.")
assert cnt1 + cnt2 == 1000
print("1000 data selected successfully.")
실행 결과 :
==============================
L2_15 1371
load done.
clustering (1371, 147456) [-1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44]
[700 14 16 15 65 11 14 17 17 29 37 31 11 23 11 8 22 8
19 19 8 15 16 13 11 20 8 8 19 8 8 9 10 10 19 13
10 12 7 8 10 8 7 8 9 10]
46 clusters created.
671 700
select 300 data from clusters, 700 from "-1"
300
300 data copied.
300 files are in directory.
700 data copied from remaining.
1000 files are in directory.
1000 data selected successfully.
==============================
L2_10 1865
load done.
clustering (1865, 147456) [-1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
47 48 49 50 51 52 53 54 55 56]
[660 29 22 11 93 20 38 24 35 54 52 27 24 19 57 32 11 13
30 14 17 13 25 11 44 16 38 27 11 21 20 9 25 8 13 13
16 9 14 15 9 13 11 11 8 26 18 24 8 34 14 12 8 8
8 8 6 9]
58 clusters created.
1205 660
select 340 data from clusters, 660 from "-1"
340
340 data copied.
340 files are in directory.
660 data copied from remaining.
1000 files are in directory.
1000 data selected successfully.
==============================
L2_33 6202
load done.
clustering (6202, 147456) [ -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
161]
[448 75 10 31 68 12 103 68 231 82 38 27 23 42 37 25 37 13
202 43 62 18 25 12 62 57 51 28 36 72 104 85 37 103 18 63
23 25 29 55 80 16 64 8 74 25 16 19 15 18 57 72 13 77
73 9 36 24 123 70 43 20 20 72 20 19 22 58 49 57 35 25
18 40 94 24 18 11 36 18 17 37 80 72 86 60 40 14 22 54
13 54 33 21 13 18 36 12 22 20 15 24 37 32 14 47 14 12
47 51 18 21 15 14 46 10 20 16 18 39 22 8 11 9 110 22
12 26 13 25 43 15 5 8 24 33 36 15 17 33 20 20 25 10
25 13 18 12 25 11 14 14 9 16 17 10 5 8 9 9 8 11
4]
163 clusters created.
5754 448
select 552 data from clusters, 448 from "-1"
552
552 data copied.
552 files are in directory.
448 data copied from remaining.
1000 files are in directory.
1000 data selected successfully.
==============================
L2_46 2211
load done.
clustering (2211, 147456) [-1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
71]
[912 21 21 51 70 31 8 50 22 20 16 28 23 24 48 10 32 10
35 9 43 13 11 18 32 19 15 32 8 16 26 16 15 14 9 50
16 15 11 9 15 16 15 9 17 15 9 15 10 8 26 14 8 16
11 8 11 9 10 15 15 10 8 9 13 14 6 8 8 8 9 8
9]
73 clusters created.
1299 912
select 88 data from clusters, 912 from "-1"
88
88 data copied.
88 files are in directory.
912 data copied from remaining.
1000 files are in directory.
1000 data selected successfully.
==============================
L2_25 6167
load done.
clustering (6167, 147456) [ -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140]
[781 29 55 19 147 51 75 79 106 151 9 153 177 93 22 16 59 8
67 34 302 135 32 225 12 8 86 120 13 49 6 78 10 16 60 236
62 104 13 13 24 77 66 20 38 144 94 13 80 35 97 144 13 14
52 59 52 12 47 26 26 16 29 54 26 39 57 15 9 26 42 40
10 53 8 25 17 33 20 20 12 36 9 16 18 12 15 17 14 12
12 9 17 12 8 10 23 10 19 14 12 10 17 9 18 17 30 11
8 12 19 13 9 26 12 8 12 13 13 12 9 11 8 16 10 9
14 12 11 8 5 10 9 9 8 13 8 9 8 6 10 5]
142 clusters created.
5386 781
select 219 data from clusters, 781 from "-1"
219
219 data copied.
219 files are in directory.
781 data copied from remaining.
1000 files are in directory.
1000 data selected successfully.
ii) Oversampling
Simple Augmentation을 통해 1000개 미만인 클래스의 개수를 1000개까지 늘리는 코드입니다.
for cls, numimg in under1000.items():
print("="*30)
print(cls, numimg)
cnt = 0
imglist = os.listdir(op(pdir, cls))
for imgdir in imglist:
img = Image.open(op(pdir, cls, imgdir))
img.save(op(adir, cls, imgdir))
df_list.append([cls, imgdir, f"{cls}:1", 'original'])
cnt += 1
print(f"{cnt} original data copied.")
imglist = np.random.choice(imglist, 1000 - numimg, replace=True)
print(len(imglist), len(imglist) + numimg)
for imgdir in imglist:
img = Image.open(op(pdir, cls, imgdir))
pimg = pt(img)
newimgdir = f"a{cnt}_{imgdir}"
cv2.imwrite(op(adir, cls, newimgdir), pimg)
df_list.append([cls, newimgdir, f"{cls}:1", 'perspective'])
cnt += 1
print(cnt, "data created")
실행 결과 :
==============================
L2_21 409
409 original data copied.
591 1000
1000 data created
==============================
L2_24 415
415 original data copied.
585 1000
1000 data created
==============================
L2_27 426
426 original data copied.
574 1000
1000 data created
==============================
L2_30 363
363 original data copied.
637 1000
1000 data created
==============================
L2_50 405
405 original data copied.
595 1000
1000 data created
==============================
L2_40 180
180 original data copied.
820 1000
1000 data created
==============================
L2_39 434
434 original data copied.
566 1000
1000 data created
==============================
L2_20 402
402 original data copied.
598 1000
1000 data created
==============================
L2_45 629
629 original data copied.
371 1000
1000 data created
==============================
L2_3 195
195 original data copied.
805 1000
1000 data created
==============================
L2_12 121
121 original data copied.
879 1000
1000 data created
==============================
L2_41 198
198 original data copied.
802 1000
1000 data created
==============================
L2_34 419
419 original data copied.
581 1000
1000 data created
==============================
L2_52 381
381 original data copied.
619 1000
1000 data created
==============================
L2_44 543
543 original data copied.
457 1000
1000 data created
지금까지 생성된 데이터의 개수를 확인합니다.
cnt = 0
for cls in cls_list:
print(cls, len(os.listdir(op(adir, cls))))
cnt += len(os.listdir(op(adir, cls)))
print("total count:", cnt)
print("len of df", len(df_list))
실행 결과 :
L2_21 1000
L2_24 1000
L2_15 1000
L2_27 1000
L2_30 1000
L2_50 1000
L2_40 1000
L2_39 1000
L2_20 1000
L2_45 1000
L2_3 1000
L2_12 1000
L2_41 1000
L2_10 1000
L2_34 1000
L2_52 1000
L2_33 1000
L2_46 1000
L2_44 1000
L2_25 1000
total count: 20000
len of df 20000
각 클래스의 데이터 수가 모두 1000개가 된 것을 확인했습니다.
지금까지 생성된 Augmentation된 이미지를 포함하는 Data Frame을 CSV 파일로 중간 저장합니다.
class_df_list = list(map(lambda x : x[0], df_list))
img_df_list = list(map(lambda x : x[1], df_list))
label_df_list = list(map(lambda x : x[2], df_list))
type_df_list = list(map(lambda x : x[3], df_list))
tempdf = pd.DataFrame({"class":class_df_list, "img":img_df_list, "label":label_df_list, 'type':type_df_list})
df_label = pd.concat([df_label, tempdf])
print(df_label)
df_label.to_csv('adata_mix_df3', mode='w')
iii) Data Augmentation
이제 본격적으로 데이터를 3000개까지 각 클래스별로 2000개 분량을 증강합니다.
cnt = 0
N = 2000
for j, cls in enumerate(cls_list):
print("="*30)
print(f"{j+1}/20 {cls}")
cls_df = df_label[df_label['class'] == cls]
for i in range(N):
print(f"{i+1}/{N}", end=' ')
while True:
cls1, imgdir1, label1, augtype1 = cls_df.sample(1, replace=False).values.tolist()[0]
cls2, imgdir2, label2, augtype2 = df_label.sample(1, replace=False).values.tolist()[0]
if imgdir1 != imgdir2:
break
print(f"{cls1} {imgdir1} / {cls2} {imgdir2}", end=' ')
img1 = Image.open(op(adir, cls1, imgdir1)).convert('L')
img2 = Image.open(op(adir, cls2, imgdir2)).convert('L')
pimg = SliceMix(img1, img2)
newimgdir = f"s{cnt}_{imgdir1}"
print(f"-> {newimgdir}", end=' ')
cv2.imwrite(op(adir, cls, newimgdir), pimg)
df_list.append([cls, newimgdir, f"{cls1}:0.5,{cls2}:0.5", 'slicemix'])
cnt += 1
print("saved.")
위 코드에서는 Slice Mix만 적용한 코드입니다.
추가적으로 여러 Augmentation 기법에 따라 학습률을 비교하기 위해 아래 코드를 작성했습니다.
# mode = "NO_MIX"
# mode = "DEFAULT"
mode = "MIX"
cnt = 0
while True:
label = pq[0][1]
if mode == "DEFAULT" and pq[0][0] > 2000:
break
elif pq[0][0] > 3000:
break
imglist = os.listdir(op(pdir, label))
targetimg = np.random.choice(imglist)
print(cnt, label, targetimg, end=' ')
newimgname = f"augmentedimg{len(os.listdir(op(adir, label))) + 1}.jpg"
# 함수 결정
"""fidx = random.randrange(rrange)
if mode == "DEFAULT":
print(func_name_list[fidx], end=' ')
else:
print(f_list[fidx], end=' ')
if mode == "DEFAULT":
img = Image.open(op(pdir, label, targetimg))
for i in range(random.randrange(4) + 2):
img = func_list[r](img)
img = np.array(img, dtype=np.uint8)
cv2.imwrite(op(adir, label, newimgname), img)
tempdf = pd.DataFrame({"class":[label], "img":[newimgname], "label":[f"{label}:1"], 'type':['defaultmix']})
df_label = pd.concat([df_label, tempdf])
pq[0][0] += 1
# 2-1 클래스 하나만 씀
elif fidx == 0:
# augmix
img = Image.open(op(pdir, label, targetimg))
img = augmix(img)
cv2.imwrite(op(adir, label, newimgname), img)
pq[0][0] += 1
f_cnt_dict[label][fidx] += 1
tempdf = pd.DataFrame({"class":[label], "img":[newimgname], "label":[f"{label}:1"], 'type':['augmix']})
df_label = pd.concat([df_label, tempdf])
elif fidx == 1:
# cutout
img = Image.open(op(pdir, label, targetimg))
img = cutout(img)
cv2.imwrite(op(adir, label, newimgname), img)
pq[0][0] += 1
f_cnt_dict[label][fidx] += 1
tempdf = pd.DataFrame({"class":[label], "img":[newimgname], "label":[f"{label}:1"], 'type':['cutout']})
df_label = pd.concat([df_label, tempdf])
# 2-2 클래스 두 개 씀
elif fidx == 2:
cls = random.randrange(20)
imglist2 = os.listdir(op(pdir, pq[cls][1]))
targetimg2 = np.random.choice(imglist2)
img = Image.open(op(pdir, label, targetimg))
img = np.array(img).astype(np.uint8)
img2 = Image.open(op(pdir, pq[cls][1], targetimg2))
img2 = np.array(img2).astype(np.uint8)
img = mixup(img, img2)
cv2.imwrite(op(adir, label, newimgname), img)
pq[0][0] += 0.5
pq[cls][0] += 0.5
f_cnt_dict[label][fidx] += 1
f_cnt_dict[pq[cls][1]][fidx] += 1
tempdf = pd.DataFrame({"class":[label], "img":[newimgname], "label":[f"{label}:0.5,{pq[cls][1]}:0.5"], 'type':['mixup']})
df_label = pd.concat([df_label, tempdf])
elif fidx == 3:
cls = random.randrange(20)
imglist2 = os.listdir(op(pdir, pq[cls][1]))
targetimg2 = np.random.choice(imglist2)
img = Image.open(op(pdir, label, targetimg))
img2 = Image.open(op(pdir, pq[cls][1], targetimg2))
img, r1, r2 = cutmix(img, img2)
cv2.imwrite(op(adir, label, newimgname), img)
pq[0][0] += r1
pq[cls][0] += r2
f_cnt_dict[label][fidx] += 1
f_cnt_dict[pq[cls][1]][fidx] += 1
tempdf = pd.DataFrame({"class":[label], "img":[newimgname], "label":[f"{label}:{r1},{pq[cls][1]}:{r2}"], 'type':['cutmix']})
df_label = pd.concat([df_label, tempdf])
"""
### augmentation
cls = random.randrange(20)
imglist2 = os.listdir(op(pdir, pq[cls][1]))
targetimg2 = np.random.choice(imglist2)
img = Image.open(op(pdir, label, targetimg))
img2 = Image.open(op(pdir, pq[cls][1], targetimg2))
img = SliceMix(img, img2)
cv2.imwrite(op(adir, label, newimgname), img)
pq[0][0] += 0.5
pq[cls][0] += 0.5
# f_cnt_dict[label][fidx] += 1
# f_cnt_dict[pq[cls][1]][fidx] += 1
if label == pq[cls][1]:
# tempdf = pd.DataFrame({"class":[label], "img":[newimgname], "label":[f"{label}:1.0"], 'type':['sandwich']})
class_df_list.append(label)
img_df_list.append(newimgname)
label_df_list.append(f"{label}:1.0")
type_df_list.append('slicemix')
else:
# tempdf = pd.DataFrame({"class":[label], "img":[newimgname], "label":[f"{label}:0.5,{pq[cls][1]}:0.5"], 'type':['sandwich']})
class_df_list.append(label)
img_df_list.append(newimgname)
label_df_list.append(f"{label}:0.5,{pq[cls][1]}:0.5")
type_df_list.append('slicemix')
# df_label = pd.concat([df_label, tempdf])
pq.sort()
cnt += 1
print()
위 코드는 여러 Augmentation 알고리즘을 사용한 코드입니다.
마지막으로 Augmentation 된 결과를 확인합니다.
cnt = 0
for cls in cls_list:
print(cls, len(os.listdir(op(adir, cls))))
cnt += len(os.listdir(op(adir, cls)))
print("total count:", cnt)
print("len of df", len(df_list))
실행 결과 :
L2_21 3000
L2_24 3000
L2_15 3000
L2_27 3000
L2_30 3000
L2_50 3000
L2_40 3000
L2_39 3000
L2_20 3000
L2_45 3000
L2_3 3000
L2_12 3000
L2_41 3000
L2_10 3000
L2_34 3000
L2_52 3000
L2_33 3000
L2_46 3000
L2_44 3000
L2_25 3000
total count: 60000
len of df 60000
모든 클래스가 3000개씩 총 20개 클래스에 대해 6만개의 데이터가 생성된 것을 확인했습니다.
이제, 이 데이터를 Data Frame을 저장하고 CSV 파일로 저장합니다. 이는 나중에 학습 전 데이터를 불러올 때 사용됩니다. (Mix 알고리즘의 경우 레이블이 One-Hot Encoding이 아니라 0.5와 같은 값이 섞여있으므로 필요합니다)
df_label = df_label.reset_index(drop=True)
display(df_label)
print(len(df_label))
if mode == "NO_MIX":
df_label.to_csv('adata_no_mix_df3.csv', mode='w')
print('dataframe saved to adata_no_mix_df3.csv')
elif mode == "MIX":
df_label.to_csv('adata_mix_df3.csv', mode='w')
print('dataframe saved to adata_mix_df3.csv')
elif mode == "DEFAULT":
df_label.to_csv('adata_default_df3.csv', mode='w')
print('dataframe saved to adata_default_df3.csv')
이렇게 되면 Data Augmentation을 사용한 Data Balancing 작업(Over&UnderSampling) 작업을 마쳤습니다.
위와 같이 1주차에는 데이터셋 EDA와 여러 기법의 Data Augmentation 함수를 사용한 Data Balancing을 해보았습니다.