LG U+ Why Not SW CAMP 7기 - 17주차 회고

God menu·2025년 9월 5일

<9/1 77일차>

오늘은 2차 프로젝트가 끝나고 첫 이론으로 인공신경망과 딥러닝에 대해서 배우는 시간이었다.

인간의 뉴런(neuron)

  • 시냅스(synapse)를 통해 뉴런간 신호를 전달
  • 각 뉴런은 수상돌기(dendrite)를 통해 입력 신호를 받음
  • 입력 신호가 특정크기(threshold) 이상인 경우에만 활성화되어 축삭돌기(axon)을 통해 다음 뉴런으로 전달

인공 뉴런(노드:node)

  • 각 노드는 가중치가 있는 입력 신호를 받음
  • 입력신호는 모두 더한 후, 활성화 함수(activation function)를 적용함
  • 활성화 함수의 값이 특정 값 이상인 경우에만 다음 노드의 입력값으로 전달

신경망의 출력은 입력에 가중치를 곱하여 더해준다.

비선형 문제를 해결해야 할 경우면 더 깊은 신경망이 필요하다. 은닉층이 여러 개로 구성된 인공 신경망을 다층신경망(MultiLayered Perceptron)이라고 부른다.

심층 신경망(DNN)은 입력층과 출력층 사이에 다수의 은닉층을 포함하는 인공 신경망이다. 다수의 은닉층을 두었기 때문에 다양한 비선형적 관계를 학습할 수 있는 장점이 있지만, 학습을 위한 연산량이 많고 기울기 소멸 문제 등이 발생할 수 있다. 이러한 문제를 해결하는 방법으로는 드롭아웃, 렐루 함수, 배치 정규화 등이 있다.

이후 오늘은 구글 코랩(Google Colab)에서 신경망의 구조를 이해하기 위한 코드를 연습하였다. iris 데이터를 사용하였고, train용, test용으로 나누어서 신경망 모델을 만들었다.

from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
y = iris.target

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)

W1 = tf.Variable(tf.random_normal([4, 50]))
b1 = tf.Variable(tf.zeros([50]))
h1 = tf.nn.sigmoid(tf.matmul(X, W1)+b1)

W2 = tf.Variable(tf.random_normal([50, 30]))
b2 = tf.Variable(tf.zeros([30]))
h2 = tf.nn.sigmoid(tf.matmul(h1, W2)+b2)

W3 = tf.Variable(tf.random_normal([30, 3]))
b3 = tf.Variable(tf.zeros([3]))
h3 = tf.nn.softmax(tf.matmul(h2, W3)+b3)

W는 가중치이고, b는 편향(bias)이다. 첫 번째 layer는 50node, 두 번째 layer는 30node, 세 번째 layer는 출력층이다. 이후 keras, Pytorch로 신경망을 구성해보는 코드를 작성해보았다. 신경망 구조를 생성하고 드롭아웃, 활성화함수 지정해보았다. 그리고 가중치 초기화도 해보고, 배치정규화도 해보며 각각의 정확도를 구해봤는데, 가중치 초기화의 경우가 정확도가 가장 높게 나왔다.

단순히 신경망을 만드는 것보다 신경망이 어떻게 하면 더 잘 배울 수 있을지 도와주는 게 중요하다는 걸 알게 되었다. 또한, 신경망이 처음 학습을 시작할 때 좋은 출발선(가중치 초기화)을 주는 게 중요하다는 것도 알 수 있었다. iris 데이터뿐만 아니라 이미지, 글자 같은 다양한 데이터도 신경망 모델로 구현해볼 수 있을 거라는 기대감이 생겼다.


<9/2 78일차>

오늘 오전에 학원 노트북 사양을 확인해보고 WSL설치, Tensorflow설치, Git연동 등을 하는 데 생각보다 시간이 오래 걸렸다. 설치를 다 완료하고 VSCode로 WSL연결하여 딥러닝에 대해 코딩 연습을 하도록 하였다.

딥러닝은 인공지능의 하위 분야인 머신러닝의 한 종류로, 여러 층의 인공 신경망을 사용하여 데이터로부터 패턴을 학습하는 방법입니다.

딥러닝의 강점

  • 함수를 근사하는 능력이 뛰어나다.
  • 특징을 자동으로 추출한다.
  • 모델의 확장성이 뛰어나다.
  • 기존 머신러닝보다 훨씬 좋은 성능을 보인다.

딥러닝의 어려움

  • 딥러닝 모델은 학습 데이터가 많이 필요하다.
  • 딥러닝 모델은 훈련을 위한 시간과 비용이 많이 든다.
  • 딥러닝 모델의 최적 모델과 훈련 방법을 찾으려면 상당히 많은 검색과 튜닝이 필요하다.
  • 인공 신경망 모델은 오류를 파악하거나 디버깅하기 어렵다.
  • 지도 학습에서는 타깃 데이터를 만드는 비용이 만만치 않다.

CNN(합성곱 신경망)의 구조에 대해 배우는 실습을 진행하였다. 숫자 이미지(MNIST)를 보고 숫자를 맞히는 신경망 모델을 작성했다.

model = Sequential(
    [
    layers.Input(shape=(28,28,1)), # 3차원 이미지 1장의 입력
    layers.Conv2D(filters=32, kernel_size=(3,3), activation='relu' ),
    layers.MaxPooling2D(), # default 2*2
    layers.Dropout(0.1),
    
    layers.Conv2D(filters=64, kernel_size=(3,3), activation='relu' ), 
    layers.MaxPooling2D(),
    layers.Dropout(0.25),
    
    layers.Flatten(),
    layers.Dense(128, activation='relu'), # 분류 계층
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax') # 출력 계층
    ]

)

이미지 속 특징을 찾고, 줄이고(MaxPooling), 일부 드롭아웃 후 다시 특징 찾는 과정을 반복하였다. 학습시킨 후 평가하였더니 accuracy가 약 0.9924로 높게 나왔다.

이후에는 ResNet50라는 이미지 분류 신경망 모델을 작성했다. 'YellowLabradorLooking_new.jpg'라는 강아지 이미지를 저장 후 ResNet50모델을 불러오는데 imagenet라는 가중치를 사용하였다. 그 결과 가장 가능성 높은 3가지 정답을 알려주는데 ('Labrador_retriever', 'boxer', 'Chesapeake_Bay_retriever')가 출력되었다.

CNN을 이용해 MNIST 데이터를 학습시켰을 때 높은 정확도가 나온 것을 보면서 딥러닝의 성능을 직접 체감할 수 있었고, 이론으로만 배운 합성곱과 풀링, 드롭아웃이 실제로 어떻게 동작하는지 이해가 더 잘 되었다. 특히 ResNet50을 활용해 강아지 이미지를 분류하는 실습에서는 사전 학습된 모델이 얼마나 강력한지를 확인할 수 있었다. 내가 준비하지 않은 데이터임에도 불구하고 적절한 결과를 내는 것이 인상적이었다. 다만 딥러닝 모델을 만들고 튜닝하는 과정은 여전히 복잡하고 많은 자원이 필요하다는 점도 실감했다.


<9/3 79일차>

컨볼루션(convolution)과 선형결합(linear combination)은 모두 입력 값들을 가중치와 곱해 더한다는 공통점이 있지만, 구조와 적용 방식이 다르다.

선형결합 (Linear Combination)

  • 정의 : 모든 입력 xi 에 대응하는 가중치 wi를 곱해서 한꺼번에 더하는 것.
  • 모든 입력과 연결
  • 각 가중치가 독립적으로 학습된다
  • 공간적 구조를 고려하지 않는다
  • 파라미터 수가 입력 크기만큼 커진다

컨볼루션 (Convolution)

  • 정의 : 입력 데이터(이미지, 시계열 등)에 작은 필터(커널) 를 슬라이딩하면서, 해당 영역과 필터를 곱해 합한 값을 출력으로 만듦
  • 하나의 필터(같은 가중치 집합)를 전체 입력에 반복적으로 적용한다
  • 이미지의 공간적 구조(인접한 픽셀의 패턴)를 반영한다
  • 파라미터 수가 선형결합보다 훨씬 적다

합성곱은 하나의 함수와 다른 함수를 반전(reverse)하고 이동(shift)한 값을 곱한 후, 각 구간을 적분하여 새로운 함수를 만드는 연산자이다.
👉 CNN은 입력 데이터의 특정 구간에 필터를 적용하여 새로운 결과(합성곱 된 결과)를 생성한다.

오늘은 Roboflow에서 모델을 가져와 YOLO로 객체 탐지와 학습을 해보는 실습이 진행되었다.

고라니 이미지를 불러와서 YOLOv8 모델로 동물을 찾은 뒤, 찾은 동물의 위치에 초록색 네모 박스와 이름표를 그려서 화면에 보여주고 'detect_result.jpg' 파일로 저장해보았다.

model = YOLO('yolov8n.pt')
result = model('sample.png')
result = model('gorani.jpg')
img = cv2.imread('gorani.jpg')

for box in result[0].boxes:
    class_id = box.cls[0]
    class_name = model.names[class_id.item()]
    
    # 신뢰도 점수
    conf = float(box.conf[0])
    
    # 바운딩 박스
    coords = box.xyxy[0].tolist()
    x1, y1, x2, y2 = map(int, coords)
    cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
    cv2.putText(img, f'{class_name} {conf:.4f}',
                (x1, y1-10), cv2.FONT_HERSHEY_PLAIN,
                1, (0, 255, 0), 1)

# end-for
cv2.imshow('yolov8 object detection', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('detect_result.jpg', img)

코드가 실행되면 이처럼 나오는데 OpenCV가 이처럼 그려준다. 그러나 결과가 sheep으로 나왔다. 😅

YOLO로 3가지 Task(Detection, Segmentation, Pose) 실습을 각각 다른 이미지들로 연습해보았다. 그 결과 pose가 인물의 눈, 코, 귀, 팔, 등을 파악한다는 것이 가장 신기했다.

YOLO 모델을 활용하여 이미지에서 객체를 자동으로 탐지하고 바운딩 박스를 그리는 실습을 직접 해본 것이 유익했다. 우리 팀이 프로젝트에서 쓰려는 모델이 YOLO여서 프로젝트에 도움이 될 것 같다. 그러나 객체 탐지할 때 왜 고라니 이미지를 양으로 잘못 예측했는지에 대한 원인 분석이 필요할 것 같고, 모델을 개선하는 방법에 대해 배우면 좋을 것 같다.


<9/4 80일차>

워드 임베딩(Word Embedding)

  • 단어를 컴퓨터가 이해할 수 있도록 벡터(숫자들의 배열)로 변환하는 기법
  • 자연어(텍스트)를 수치화하여 기계 학습 모델이 처리할 수 있도록 만드는 자연어 처리(NLP)의 핵심 기술 중 하나

오늘은 워드 임베딩 실습을 진행하였다. TF(단어 빈도)를 세어서 딕셔너리 형태로 저장해보았다. 텍스트에 있는 마침표(.)를 제거하고, 띄어쓰기를 기준으로 문자열을 나눠서 단어 목록으로 만든다.

text = "John likes to watch movies. Mary likes movies too. \
    Mary also likes to watch football games."
words = text.replace('.', '').split()
word_count = np.unique(words, return_counts = True)

word_to_cnt = {}
for word, cnt in zip(*word_count):
    word_to_cnt[word] = cnt

word_to_cnt

결과 :
{np.str('John'): np.int64(1),
np.str
('Mary'): np.int64(2),
np.str('also'): np.int64(1),
np.str
('football'): np.int64(1),
np.str('games'): np.int64(1),
np.str
('likes'): np.int64(3),
np.str('movies'): np.int64(2),
np.str
('to'): np.int64(2),
np.str('too'): np.int64(1),
np.str
('watch'): np.int64(2)}

단어 배열과 빈도수 배열을 묶어 딕셔너리에 저장된다.

이후 TDM(단어의 빈도수를 계산해서 행렬로 만들어 놓은 것)과 TF-IDF(전체 문서에서 용어가 얼마나 자주 발생하는지 정의)

  • .vocabulary_의 단어 순사가 사전으로 정의된 단어 순서임을 알 수 있었고, 정의된 단어 순서대로 문장마다 몇 번 나타나는지 한눈에 볼 수 있어서 편한 것 같다.
  • 각 단어가 다른 단어들과 얼마나 유사한지 유사도를 볼 수 있어서 편리한 것 같다. 그 기준이 해당 단어가 문장에 속해있는지 아닌지에 따라 크가 달라지는 것 같다.

단순히 문장을 단어 단위로 나누고 빈도를 세어 딕셔너리로 저장하는 과정만으로도 텍스트가 수치화된다는 점이 신기했다. 자연어 처리가 단순히 텍스트를 다루는 것이 아니라, 이를 벡터화하여 기계가 이해할 수 있도록 만드는 과정임을 이해할 수 있었던 것 같다. RNN으로 영화평 분류하는 실습도 진행했었는데, 옵티마이저를 'adam', 'sgd'로 바꿔가고, 영화평의 길이도 바꿔가고, truncating과 padding을 'post', 'pre'로 바꿔가보며 정확도(accuracy) 비교도 해보았다.


<9/5 81일차>

트랜스포머(Transformer)는 자연어 처리와 같은 순차적인 데이터를 처리하기 위해 설계된 딥러닝 모델이다. 트랜스포머는 인코더와 디코더오 구성되어있다.

  • 인코더 : 입력 문장을 받아 어텐션을 통해 의미 있는 벡터로 변환

  • 디코더 : 인코더에서 얻은 벡터를 받아 새로운 문장을 생성하거나, 번역 작업 등을 수행

  • 입력 처리 : 문장을 단어 단위로 쪼개어 각 단어를 수치로 변환(임베딩)한다. 이 변환된 숫자 벡터는 모델이 이해할 수 있는 형태로, 각 단어의 의미를 담고 있음

  • 병렬 처리 : 트랜스포머는 모든 단어를 한 번에 처리할 수 있어 학습이 빠르다. 이전 단어들을 순차적으로 보지 않아도 각 단어의 중요도를 빠르게 계산할 수 있음

트랜스포머 모델의 기본 구조를 익히는 코드 실습이 진행되었다.

# 토큰 임베딩
inputs = layers.Input(shape=(80, ))
input_embedding = layers.Embedding(input_dim=1000, output_dim=32)(inputs)

# 위치 임베딩
positions = tf.range(start=0, limit=80)
pos_embedding = layers.Embedding(input_dim=80, output_dim=32)(positions)
pos_enc_output = pos_embedding + input_embedding

길이 80인 문장을 입력받아, 각 단어를 32차원 벡터(숫자 묶음)로 바꾸고 단어가 문장에서 몇 번째인지 위치 정보도 준다.

# K, V를 받아주는 구조
attention_output = layers.MultiHeadAttention(num_heads=3, key_dim=32)(pos_enc_output, pos_enc_output)   # output이 그대로 들어가야하므로 32개로 통일

문장 속 단어들이 서로를 참고한다.

x = layers.add([pos_enc_output, attention_output])
x = layers.BatchNormalization()(x)   # 정규화

from tensorflow.keras.models import Sequential
ffnn = Sequential(
    [
        layers.Dense(64, activation='relu'),
        layers.Dense(32, activation='relu')
    ]
)(x)

x = layers.add([ffnn, x])
x = layers.BatchNormalization()(x)

원래 입력과 어텐션 결과를 더해서 안정적으로 학습하게 하고, 정규화시킨다. 안정적으로 학습하기 위해 이를 반복한다.

x = layers.GlobalAveragePooling1D()(x)
x = layers.Dropout(0, 1)(x)
x = layers.Dense(64, activation='relu')(x)
x = layers.Dropout(0, 1)(x)
outputs = layers.Dense(2, activation='softmax')(x)

model = Model(inputs=inputs, outputs=outputs)
model.summary()

# 손실함수와, 옵티마이저 지정
model.compile(loss='sparse_categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

아래 트랜스포머 구조를 위 코드로 구현해본 실습이다.

이후에는 허깅페이스에서 여러 모델들을 가져와 사용해보았다.

허깅페이스(Hugging Face) : 자연어 처리와 관련된 다양한 기술과 리소스를 제공하는 회사 및 온라인 플랫폼

허깅페이스에는 다양한 사전 훈련된 모델들을 포함하고 있으며, 다양한 NLP 작업에 대해 미세 조정이 가능하다.

text-classification, tabularisai/multilingual-sentiment-analysis, bert-base-multilingual-cased, question-answering-roberta-base-s-v2, distilbert-NER, text_summarization, translation, blip-image-captioning-large 모델들을 사용해보았다. 각 모델들을 사용해보면서 긍정/부정 분류, 감정 분석, 질문-답변, 개체명 인식, 문장 요약, 언어 번역, 이미지 설명 등 다양한 모델을 연습해본 것이 흥미로웠다.

트랜스포머가 단순히 복잡한 모델이 아니라, 실제로 어떻게 구조화되어 있는지 감이 잡히기 시작했던 것 같다. 이론으로만 접했을 때는 추상적으로 느껴졌는데, 코드를 직접 작성해보니 각각의 블록이 어떤 역할을 하는지 연결 지어 이해할 수 있었다.

1개의 댓글

comment-user-thumbnail
2025년 9월 12일

좋은 내용이네여

답글 달기