728x90
2021.03.10 - [기록 note] - 2021-03-10(제조공정불량실습)
아래 내용은 머신러닝 강의 교육 중 실습한 내용의 일부분입니다
좋은 참고가 되었으면 합니다
개요
의류직물 생산 시 찍힘, 스크래치, 뜯어짐 같은 불량을 인공지능을 통해 구분하는 프로그램을 만드는 내용입니다.
*사용된 모델: inception
*데이터 출처: AITEX FABRIC IMAGE DATABASE
원본 데이터를 보면 몇몇 의류직물의 패턴이 다른 걸로 보아서 여러 모델의 의류직물이 섞여 있는 것 같습니다
또한 불량 이미지 중에는 사람이 봐도 구분이 가지 않는 불량품이 있음을 확인했습니다.
이제 이 데이터를 가지고 딥러닝을 통해 불량 검출을 해보겠습니다
Import
import glob
import os
from datetime import datetime
import time
import tensorflow as tf
import tensorflow_addons as tfa
import matplotlib.pyplot as plt
import cv2
from tensorflow.keras.layers import Conv2D, MaxPool2D, Concatenate, Flatten, Dense
작업 경로 및 파라미터 설정
DATASET_OK_PATTERN = 'dataset/3/OK/*.png'
DATASET_FAIL_PATTERN = 'dataset/3/FAIL/*.png'
TFRECORD_PATH = 'tfrecords/'
IMAGE_PER_TFRECORD = 100 #100개이미지를 하나의 TFRECORD로 묶음
- 이미지를 TFRecord로 변환 시 저장할 위치를 지정합니다
- 100개 이미지를 하나의 TFRecord로 생성합니다
데이터 전처리
ok_list = glob.glob(DATASET_OK_PATTERN)
fail_list = glob.glob(DATASET_FAIL_PATTERN)
#오버샘플링을 위해 데이터 수를 저장 해준다.
num_ok = len(ok_list)
num_fail = len(fail_list)
#fail데이터를 여러번 반복해서 ok데이터 사이즈와 맞춘다
fail_list_new= list()
for _ in range(num_ok//num_fail):
fail_list_new += fail_list
#나머지 부분을 채워주기 위해서 따로 작업해준다
fail_list_new += fail_list[: num_ok % num_fail]
fail_list =fail_list_new
#레이블 생성
ok_label = [0]* len(ok_list)
fail_label = [1]* len(fail_list)
#파일과 레이블을 각각 묶어주기
file_list = ok_list +fail_list
label_list = ok_label +fail_label
- 불량 데이터가 적으므로 오버샘플링을 사용해서 정상 데이터만큼 늘려줍니다
- num_ok//num_fail를 통해 차이가 나는 것만큼 채워주고, 나머지는 num_ok%num_fail로 해결해 줍니다
- 정상은 0번 클래스, 불량은 1번 클래스로 레이블 생성합니다
- 이미지 파일은 파일대로, 레이블은 레이블대로 묶습니다(TFRecord 만들기 위해서 따로 구분)
TFRecord 생성
1. TFRecord 함수
# 함수출처: https://www.tensorflow.org/tutorials/load_data/tfrecord
def _bytes_feature(value):
"""Returns a bytes_list from a string / byte."""
if isinstance(value, type(tf.constant(0))):
value = value.numpy() # BytesList won't unpack a string from an EagerTensor.
return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
def _int64_feature(value):
"""Returns an int64_list from a bool / enum / int / uint."""
return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
def image_example(image_string, label):
image_shape = tf.image.decode_image(image_string).shape
feature = {
'height': _int64_feature(image_shape[0]),
'width': _int64_feature(image_shape[1]),
'depth': _int64_feature(image_shape[2]),
'label': _int64_feature(label),
'image_raw': _bytes_feature(image_string),
}
return tf.train.Example(features=tf.train.Features(feature=feature))
2. TFRecord 저장
#데이터를 원하는 크기 만큼슬라이싱 해서 저장
#tfrecord 저장 폴더
if os.path.exists(TFRECORD_PATH) is False:
os.mkdir(TFRECORD_PATH)
# +1: 딱 나누 떨어지지 않고 나머지가 존재하기 때문
num_tfrecords = len(file_list) // IMAGE_PER_TFRECORD #TFRecord 갯수
if len(file_list) // IMAGE_PER_TFRECORD !=0:
num_tfrecords+=1
for idx in range(num_tfrecords):
idx0= idx*IMAGE_PER_TFRECORD
idx1= idx0+IMAGE_PER_TFRECORD #끝나는점
record_file =TFRECORD_PATH+'%05d.tfrecords'% idx
with tf.io.TFRecordWriter(record_file) as writer:
for filename, label in zip(file_list[idx0:idx1], #0~99,100~99 100개씩
label_list[idx0:idx1]):
image_string = open(filename, 'rb').read() #rb 바이너리로 읽기
tf_example = image_example(image_string, label) #파일과 레이블 , 바이너리로 저징
writer.write(tf_example.SerializeToString()) #저장
- num_tfrecord: tfrecord파일의 개수를 계산합니다
- 나머지 부분은 if문으로 따로 처리합니다
- idx와 IMAGE_PER_TFRRCORD를 사용해서 100개씩 이미지와 레이블을 저장합니다
- 위에서 정의한 image_example 함수를 통해 이미지와 레이블의 저장 타입을 설정할 수 있습니다
- 각 파일에는 100개 이미지와 100개 레이블 정보가 이진 형태로 함께 저장되어 있습니다
- 이 tfrecord 데이터로 모델 학습을 진행합니다
하이퍼라마미터 설정
EPOCHS = 1000
RESULT_SAVE_PATH = 'results/'
모델 정의
def Model():
#padding=same을 해줘야 다른 필터들을 적용한 것과 Concatenate가 가능하다
def inception(filters):
def subnetwork(x):
h1 = Conv2D(filters, (1,1), padding='same',activation='relu')(x)
h1 = MaxPool2D()(h1)
h2 = Conv2D(filters//2, (1,1), padding='same',activation='relu')(x)
h2 = Conv2D(filters, (3,3), padding='same',activation='relu')(h2)
h2 = MaxPool2D()(h2)
h3 = Conv2D(filters//2, (1,1), padding='same',activation='relu')(x)
h3 = Conv2D(filters, (5,5), padding='same',activation='relu')(h3)
h3 = MaxPool2D()(h3)
return Concatenate()([h1,h2,h3])
return subnetwork
#입력 x 설정
x= tf.keras.Input(shape=(256,256,3)) #입력층을 아는 경우에는 지정하는게 연산과정에서 이득
h= inception(16)(x)
h= inception(32)(h)
h= inception(32)(h)
h= inception(32)(h)
h= inception(32)(h)
h=Flatten()(h)
h=Dense(1024, activation='relu')(h)
#출력
y=Dense(1, activation='sigmoid')(h)
return tf.keras.Model(inputs=x, outputs=y)
이미지 데이터 타입 변환을 위한 함수 정의
def preprocess(img):
return tf.image.convert_image_dtype(img, tf.float32)
데이터 Augmentation 함수 정의
def augmentation(img, label):
def flip(x):
x= tf.image.random_flip_left_right(x)
x= tf.image.random_flip_up_down(x)
return x
def rotate(x):
x = tf.cond(tf.random.uniform(shape=[], minval=0.0, maxval=1.0,dtype=tf.float32)>0.5,
lambda: tfa.image.rotate(x, tf.random.uniform(shape=[], minval=0.0, maxval=360.0, dtype=tf.float32),
interpolation='BILINEAR'),
lambda: x)
return x
def translation(x):
dx= tf.random.uniform(shape=[], minval=-10.0, maxval=10.0,dtype=tf.float32)
dy= tf.random.uniform(shape=[], minval=-10.0, maxval=10.0,dtype=tf.float32)
x = tf.cond(tf.random.uniform(shape=[], minval=0.0, maxval=1.0, dtype=tf.float32) >0.5,
lambda: tfa.image.transform(x, [0,0,dx, 0,0,dy, 0,0], #끝에 두 0은 스케일팩터
interpolation='BILINEAR'),
lambda: x)
return x
img = flip(img)
img = rotate(img)
img = translation(img)
return img, label
- [0,0, dx, 0,0, dy]에서 0,0은 각각 x, y좌표인 것 같습니다 x, y의 변화량은 0이고 위치만 dx, dy만큼 변경한다는 내용(추측입니다)
TFRecords 불러오기
tffiles = glob.glob('tfrecords/*')
raw_image_dataset = tf.data.TFRecordDataset(tffiles)
image_feature_description = {
'height': tf.io.FixedLenFeature([], tf.int64),
'width': tf.io.FixedLenFeature([], tf.int64),
'depth': tf.io.FixedLenFeature([], tf.int64),
'label': tf.io.FixedLenFeature([], tf.int64),
'image_raw': tf.io.FixedLenFeature([], tf.string),
}
def _parse_image_function(example_proto):
return tf.io.parse_single_example(example_proto, image_feature_description)
def _parse_image_label(parsed_dataset):
return preprocess(tf.image.decode_png(parsed_dataset['image_raw'])), parsed_dataset['label']
parsed_image_dataset = raw_image_dataset.map(_parse_image_function) #각 파일을 image_feature_description를 적용
dataset = parsed_image_dataset.map(_parse_image_label) #레이블은 그대로 출력하고, 이미지는 float32로 타입 변환 후 출력
- 이미지와 레이블 데이터를 불러올 때 이미지 데이터만 preprocess를 통해 float32로 변환해준다
데이터셋 나누기
ds_size=0
for _ in dataset:
ds_size+=1
train_size = int(ds_size*0.7)
ds = dataset.shuffle(ds_size)
#배치다음에 map함수가 들어가야 addons함수가 작동이 됨(데이터 증식과 관련됨)
ds_train =ds.take(train_size).shuffle(1024, reshuffle_each_iteration=True).prefetch(1024).batch(32) #.map(augmentation)
ds_valid =ds.skip(train_size).prefetch(1024).batch(32)
모델 생성
model = Model()
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
모델 학습
earlystopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=30, verbose=1)
history = model.fit(ds_train,
validation_data=ds_valid,
epochs=EPOCHS,
callbacks=[earlystopping])
학습 결과 Plot
loss = history.history['loss']
val_loss = history.history['val_loss']
plt.figure()
plt.plot(loss, 'ro-')
plt.plot(val_loss, 'bo-')
plt.ylabel('Cross Entropy')
plt.xlabel('Epoch')
plt.title('Training and Validation Loss')
plt.show()
- 180 epoch에서 큰 폭의 감소가 이뤄졌으며 과적합 현상 없이 잘 학습이 되었습니다
모델 저장
model.save('model/inception_model.h5')
- 저장된 모델을 사용해서 배치형 동작 프로그램을 생성합니다
*배치형 동작 프로그램 생성하기
배치형 프로그램은 대량의 데이터를 일괄적으로 특정 시간에 처리하는 것을 의미합니다
이번 실습 내용에서는 특정 시간이 되면 자동으로 동작하도록 하는 내용은 없음을 미리 알려드립니다
하이퍼 파라미터/ Path 설정
THRES_LEVEL = 0.5
INPUT_PATH = 'data/input_data/'
CSV_PATH = 'data/output_csv/'
- 0과 1 클래스를 판별하는 기준은 보통 0.5로 사용합니다.
- 0.5 이하면 0번 클래스(양품), 크다면 1번 클래스(부적합품)로 구분합니다
- 이 실습의 고객 요구 사항중 하나는 불량품을 정상품으로 오분류를 절대 허용하지 않아야 한다는 점입니다
- 즉, 조금이라도 의심이 되면 불량으로 처리해달라는 의미 이므로 THRES_LEVEL를 내리는 방법도 생각해 볼 수 있습니다
모델 불러오기
model = tf.keras.models.load_models('model/inception_model.h5')
- 학습한 모델을 불러와서 새로운 이미지 데이터를 예측할
입력 데이터 전처리를 위한 함수 정의
def preprocess(file_name):
img = tf.io.read_file(file_name)
img = tf.image.decode_image(img)
return tf.image.convert_image_dtype(img, dtype.float32)
- 불러들인 이미지의 타입을 float32로 변경하는 함수를 정의합니다
- 이때 이미지는 png가 아니므로 decode_image를 사용합니다
입력 데이터 불러오기
file_list =glob.glob(INPUT_PATH+'*.png') # 파일이름
dataset = tf.data.Dataset.list_files(file_list).map(preprocess) #이미지
알고리즘 구동 및 CSV 결과 저장
now = datetime.now().strftime('%Y%m%d%_%H%M%S')
with open(CSV_PATH+now+'.csv','w') as f: #파일을 열고 작업
for image, filename in zip(dataset, file_list):
image= image[tf.newaxis,...] # HWC -> batch,HWC
a= time.time()
predict = model.predict(image)[0][0]
print('Inference Time:', time.time()-a)
if predict > THRES_LEVEL:
label = 'FAIL'
else:
label ='OK'
f.write(','.join([filename, label, str(predict)]))
- 해당 모델을 학습 시 배치가 있었으므로 축을 하나 더 생성하여 형식을 맞춰줍니다 [tf.newaxis,...]
- 만일 축을 뒤쪽에 생성한다면 [..., tf.newaxis]
- C 셀을 보면 예측값이 1과 가까운 값이므로 1번 클래스인 FAIL로 되어 있지만
- A셀에 정답 레이블을 보면 OK인 경우가 다수 존재했음을 알 수가 있습니다
728x90
'실습 note' 카테고리의 다른 글
자전거 수요예측 실습 (0) | 2021.04.16 |
---|---|
bitcoin 예측 실습 (0) | 2021.04.14 |
OpenCV_12(딥러닝2) (0) | 2021.03.05 |
OpenCV_11(딥러닝) (0) | 2021.03.04 |
OpenCV_10(머신러닝) (0) | 2021.03.03 |