728x90
SMALL
- 오늘은 역전파, 워드 임베딩에 이어 관련된 Word2Vec 모델을 구축해보는 작업까지 실습해보았습니다.
⚽ 역전파에 대해서 더 자세히 알아보잡!
- 지금까지 배운 내용을 정리해보면, 신경망 학습에서의 핵심은 입력값이 들어와서 은닉층, 그리고 출력층을 거쳐 최종 예측값을 내고,
- 이 예측값이 실제값과 얼마나 차이(오차)가 있는지를 계산한 뒤,
- 이 오차를 줄이기 위해 가중치를 계속 업데이트해나가는 과정이라고 할 수 있습니다.
✨ 역전파를 준비하기 위한 순전파
- 순전파에 대해서는 전에도 간단히 배운 적이 있었지만, 몇 가지 예를 들어보며 더 깊게 살펴보았습니다.
- 가중합을 계산하기
- 먼저, 입력층($x1, \, x2$)에서 은닉층($h1, \, h2$)으로 이동하면서, $z_1 = x_1 w_1 + x_2 w_2, \, z_2 = x_1 w_3 + x_2 w_4$과 같이 은닉층 각 뉴런의 $z$값(가중합)을 구해줍니다.
- 은닉층에서 활성화 함수 적용하기
- 구한 $z1, \, z2$에 시그모이드 함수를 적용해서 $h_1 = \sigma(z_1), \, h_2 = \sigma(z_2)$처럼 은닉층 뉴런의 최종 출력값을 얻습니다.
- 출력층으로 천천히 이동하기
- 은닉층에서 나온 $h1, \, h2$를 가지고, 출력층의 가중합을 구합니다.
- 예를 들어, $z_3 = h_1 w_5 + h_2 w_6, \, z_4 = h_1 w_7 + h_2 w_8$처럼 계산합니다.
- 출력층에서도 활성화 함수 적용하기
- 마찬가지로 $z3, \, z4$에 시그모이드 함수를 적용하면, $o_1 = \sigma(z_3), \, o_2 = \sigma(z_4)$ 이렇게 두 개의 출력값을 얻습니다.
- 오차를 계산하기
- 실제값(0.4, 0.6 등)과 $o_1, \, o_2$ 사이의 차이를 이용해 오차를 계산합니다.
- 위 예제에서는 단순화해서 각 출력 뉴런마다 예측값과 실제값의 차이를 살펴보지만,
- 일반적으로는 전에 배운 평균제곱오차(MSE)나 교차엔트로피(CE) 등을 사용할 수 있습니다.
- 결국 순전파는 입력값을 시작으로, 가중치와 활성화 함수를 거쳐, 최종 예측값을 내는 과정이며, 마지막에는 오차를 확인하게 됩니다.
✨ 역전파 1단계!
- 역전파는?
- 오차가 계산되면, 이 오차가 어디에서 어떻게 발생했는지 역으로 추적해나가면서 각 가중치가 오차에 얼마나 기여했는지(미분값, 기울기)를 계산합니다.
- 이렇게 계산된 기울기를 활용해서 가중치를 업데이트하는 것이 역전파의 핵심입니다.
- 그럼 출력층의 가중치($w_5, w_6, w_7, w_8$)는 어떻게 업데이트하나?
- 먼저, 출력층 바로 앞쪽인 은닉층에서 출력층 사이의 가중치들에 대해, 오차를 줄이는 방향(경사 하강법)으로 조정합니다.
- 시그모이드 함수를 썼기 때문에, 미분(편미분) 시에는 시그모이드의 도함수 $\sigma'(z) = \sigma(z)(1 - \sigma(z))$가 포함됩니다.
- 학습률은 어떻게 설정할까?
- 보통은 얼마나 크게 업데이트할지를 결정하는 학습률($α$)을 곱해서 가중치를 한 번에 너무 크게 바꾸지 않도록 조절합니다.
- 이렇게 각 가중치를 오차의 기울기에 따라 조금씩 바꿉니다.
- 정리해보면 역전파 1단계에서는 출력층과 은닉층 사이의 가중치부터 먼저 수정합니다.
✨ 역전파 2단계! 이번엔 은닉층!
- 오차가 은닉층으로 전달되어 오는데...
- 출력층에서 오차 기여도를 계산해보면, 은닉층 각각의 뉴런($h_1, h_2$)이 그 오차에 얼마나 영향을 미쳤는지도 알 수 있습니다.
- 이 정보를 은닉층으로 역으로 전달해줍니다.
- 은닉층에서도 가중치($w_1, w_2, w_3, w_4$) 업데이트하기!
- 은닉층의 가중치 역시, 출력층에서 전달된 기울기를 이용해 같은 방식(경사 하강법)으로 업데이트합니다.
- 은닉층이 여러 개면?
- 위 예제를 배울 때는 은닉층이 1개뿐이라 2단계에서 끝나지만, 만약 은닉층이 여러 개라면 한 층씩 거꾸로 가면서 같은 과정을 반복합니다.
- 즉, 역전파 2단계는 은닉층의 가중치를 업데이트하는 과정입니다.
✨ 결과를 확인해보기
- 업데이트된 가중치로 다시 순전파 진행하기
- 가중치를 바꿨다면, 이제 새로운 가중치로 다시 한 번 순전파를 진행합니다.
- 그 결과로 새 예측값이 나오고, 실제값과 비교해 오차가 어떻게 변했는지 확인합니다.
- 오차가 감소되었는지 확인하기
- 일반적으로 제대로 된 학습률과 계산을 적용했다면, 오차가 조금이라도 줄어들어야 합니다.
- 오차가 줄었다면 방향은 맞는 것이고, 계속 같은 과정(순전파와 역전파를 왔다갔다)을 반복합니다.
- 반복적으로 학습하기
- 오차가 어느 정도 이하로 떨어질 때까지, 혹은 정해진 반복 횟수(epoch)가 될 때까지 학습을 진행해줍니다.
🛡️ 과적합을 어떻게 하면 막을 수 있을까?
✨ 데이터의 양을 늘려보기
- 과적합은 종종 모델이 데이터에 있는 사소한 패턴(노이즈)까지 외워버리는 현상인데, 데이터가 많아질수록 이러한 문제는 완화됩니다.
- 모델이 충분히 많은 데이터를 접할수록, 데이터에 포함된 일반적인 패턴을 학습하게 되고, 특정 노이즈나 특이값을 암기하는 정도가 줄어듭니다.
- 그래서 보통 이미지나 텍스트 데이터에선 데이터 증강을 진행하는데,
- 이미지에서는 이미지를 회전, 이동, 자르기, 노이즈 추가 등으로 변형해서 새로운 학습 데이터를 만들어내기도 하고,
- 텍스트에서는 역번역(Back Translation) 등을 통해서 새로운 텍스트 데이터를 생성해냅니다.
- 모델이 다양한 패턴을 접함으로써, 훈련 데이터에 과하게 맞춰지는 대신 일반화 능력(새로운 데이터에 대한 대응)이 높아집니다.
✨ 모델의 복잡도를 줄여보기
- 은닉층(hidden layer)의 수, 뉴런(노드)의 수, 그리고 가중치(파라미터)의 개수가 많을수록 모델의 복잡도가 올라갑니다.
- 모델의 복잡도가 높을수록 데이터의 세세한 부분까지 학습하게 되어서 과적합될 가능성이 큽니다.
- 그래서 은닉층의 수를 줄이거나, 각 층의 뉴런 수를 줄여서 모델의 파라미터 수를 축소합니다.
- 보통 지나치게 복잡한 모델보다 단순한 모델이 과적합을 방지하는 데 유리하긴 하지만,
- 너무 단순하게 만들면 과소적합(Underfitting)이 일어날 수 있으므로, 데이터와 문제 난이도에 맞춰서 잘 조정해야 합니다.
✨ 가중치에 대한 규제를 적용하기
- 위에서도 언급했듯이 모델이 복잡해질수록(가중치가 많을수록) 과적합 위험이 커지는데, 이것을 규제(Regularization) 기법으로 완화할 수 있습니다.
- 일반적으로 L1 규제와 L2 규제가 많이 사용됩니다.
💡 L1 규제
- 가중치 $w$의 절대값을 비용 함수(손실 함수)에 추가로 포함시킵니다.
- 모델이 자연스럽게 가중치들을 0에 가깝거나 아예 0으로 만들어버려서 희소성(Sparsity)을 유도합니다.
- 이 L1 규제는 어떤 특성이 모델에 중요한지 골라내는 데 유리합니다.
💡 L2 규제
- 가중치 $w$의 제곱합을 비용 함수에 포함합니다.
- 가중치가 완전히 0이 되기보다는, 전체적으로 작아지도록(0에 가깝게) 만듭니다. 그래서 가중치 감쇠(Weight Decay)라고도 부릅니다.
- 경험적으로 L2 규제가 더 자주 쓰이며, 성능도 안정적으로 나오는 편입니다.
- 가중치가 너무 크거나 편향되는 것을 막아줘서 모델을 조금 더 단순화해주는 효과가 있습니다.
- 그래서 L2 규제는 과적합을 완화하고 일반화 성능을 높이는 데 도움이 됩니다.
✨ 드롭아웃해버리기
- 드롭아웃은 학습 과정에서 일부 뉴런을 임의로 사용하지 않는 방식입니다.
- 예를 들어서 드롭아웃 비율이 0.5라면, 학습할 때마다 랜덤하게 절반의 뉴런만 사용하고, 나머지 절반은 꺼두는 방식입니다.
- 드롭아웃의 장점은 특정 뉴런이나 특정 조합에 모델이 지나치게 의존하는 것을 방지하고,
- 매번 다른 뉴런 조합으로 학습하기 때문에 여러 다른 신경망을 앙상블하는 것과 유사한 효과가 나타납니다.
- 주의해야 할 점이 있다면 예측 단계에서는 모든 뉴런을 사용해야 모델의 전체 성능을 발휘할 수 있기 때문에
- 훈련 시에만 드롭아웃을 적용하는 것이 일반적입니다.
model = Sequential()
model.add(Dense(256, input_shape=(max_words,), activation='relu'))
model.add(Dropout(0.5)) # 드롭아웃 비율 0.5
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))
- 위 코드처럼 Dropout 레이어를 중간중간에 삽입해서 쉽게 적용할 수 있습니다.
🤯 기울기가 소실되는 건 알겠는데 폭주도 한다고?
- 기울기 소실은 예전에도 언급된 바 있지만, 역전파 과정에서 입력층 방향으로 갈수록 기울기가 점점 작아져서 결국 가중치가 제대로 업데이트되지 않는 문제를 말합니다.
- 시그모이드 함수나 하이퍼볼릭 탄젠트 함수를 깊은 신경망에 사용할 경우 특히 심해집니다.
- 그리고 이와는 반대로 기울기가 점차 커지면서 가중치가 비정상적으로 커지고, 모델이 발산하는 문제를 "기울기가 폭주한다"고 말합니다. 보통 나중에 배울 순환 신경망(RNN) 등에서 자주 발생한다고 합니다.
✨ 기울기 소실은 어떻게 막았었지?
- 이전에 얘기드렸던 ReLU(Rectified Linear Unit) 함수는 음수 입력에 대해서 0, 양수 입력에 대해서는 선형으로 동작하기 때문에 기울기 소실 문제를 완화해줍니다.
- Leaky ReLU, PReLU, ELU 등의 ReLU의 변형 함수들도 존재하고, 이 변형 함수들은 ReLU의 단점(음수 영역에서 뉴런이 죽는 문제)을 보완해줄 수 있다고 합니다.
- 은닉층 활성화 함수로는 ReLU 계열 함수(ReLU, Leaky ReLU 등)를 사용하는 것이 권장됩니다.
✨ 기울기 폭주는 어떻게 막지?
- 기울기가 임계값을 넘어서는 경우, 해당 임계값으로 잘라내(클리핑) 기울기의 폭주를 방지하는 방법입니다.
- 특히 이 방법은 RNN에서 유용하게 쓰일 수 있는데, RNN은 시점(Time step)을 거슬러 올라가면서 역전파를 진행하기 때문에 기울기가 매우 커질 위험이 높기 때문입니다.
from tensorflow.keras import optimizers
# clipnorm=1.0: L2 노름 기준으로 기울기가 1을 넘지 않도록 클리핑
Adam = optimizers.Adam(lr=0.0001, clipnorm=1.0)
- 그래서 위와 같이 옵티마이저에 클리핑 임계값을 정해주게 되면, 기울기 폭주로 인한 발산을 방지하고, 학습의 안정성을 높일 수 있습니다.
✨ 가중치를 잘 초기화하는 것도 하나의 방법
- 같은 모델이라도 초기 가중치 설정에 따라서 학습 결과가 크게 달라질 수 있습니다.
- 적절하게 가중치를 초기화해주면 기울기 소실, 폭주 문제를 어느 정도 완화해준다고 합니다.
💡 세이비어 초기화(Xavier/Glorot Initialization)
- 2010년 글로럿(Glorot)과 벤지오(Bengio)의 논문에서 제안된 초기화 방법으로 이전 층과 다음 층의 뉴런 개수를 고려하여, 가중치가 지나치게 크거나 작아지지 않도록 균등/정규 분포 범위를 설정하는 방법입니다.
- 균등 분포(Uniform) 버전은 다음과 같은 범위로 가중치가 설정되고,
- 정규 분포(Normal) 버전은 아래와 같이 설정됩니다.
- 시그모이드나 하이퍼볼릭 탄젠트 등의 S자 형태의 활성화 함수와 함께 사용할 경우에 좋은 성능을 보인다고 합니다.
💡 He 초기화(He Initialization)
- 2015년 Kaiming He 등 연구진의 논문에서 제안된 방법으로, ReLU 계열 함수는 음수 영역이 0이 되므로, 세이비어 초기화와 달리 다음 층 뉴런 개수는 고려하지 않고 이전 층 뉴런 개수만 고려해서 범위를 설정하는 방법입니다.
- 마찬가지로 균등 분포 버전은 다음과 같고,
- 정규 분포 버전은 아래와 같습니다.
- 당연하게도 ReLU 계열(ReLU, Leaky ReLU 등) 활성화 함수와 함께 사용할 경우 효과적이고,
- 실무에서는 일반적으로 ReLU와 He 초기화 조합이 매우 보편적으로 쓰인다고 합니다.
✨ 배치 정규화는 뭘까?
- 배치 정규화는 미니 배치(Mini-batch) 단위로 입력값의 평균과 분산을 구하여 정규화한 뒤, 학습 가능한 두 파라미터(스케일 $\gamma$와 시프트 $\beta$)를 통해 다시 조정(Scale & Shift)하는 기법입니다.
- 일반적으로 배치 정규화는 각 층의 활성화 함수 전후(주로 전)에 적용합니다.
$$ {\small \hat{x}^{(i)} = \frac{x^{(i)} - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}}, \quad y^{(i)} = \gamma \hat{x}^{(i)} + \beta} $$
- 위 정규화 수식에서
- $\mu_B, \, \sigma_B^2$는 해당 미니 배치에 대한 평균과 분산을 뜻하고,
- $\gamma$는 학습 가능한 스케일 파라미터,
- $\beta$는 학습 가능한 시프트 파라미터,
- 마지막으로 $\epsilon$은 분모가 0이 되는 것을 방지하기 위한 작은 양의 상수를 의미합니다.
💡 내부 공변량 변화(Internal Covariate Shift)는 뭘까?
- 학습 과정에서 이전 층의 가중치가 변경됨에 따라, 현재 층에 전달되는 입력 분포가 계속 바뀌는 현상입니다.
- 배치 정규화가 제안된 논문에서는 이러한 분포 변화가 기울기 소실이나 폭주 등을 야기한다고 주장했고, 이 문제를 줄이기 위해서 배치 정규화를 도입한 것으로 보여집니다.
- 이후에는 다른 반론도 제기되었지만, 배치 정규화가 학습을 안정화하고 성능을 높이는 사실 자체는 분명하게 증명되어져있다고 합니다.
💡 어떤 장점이 있을까?
- 시그모이드나 tanh 같은 S자 형태 활성화 함수를 사용할 때도, BN(배치 정규화)을 적용하면 기울기 소실 문제가 완화됩니다.
- 초기 가중치가 조금 잘못 설정되어 있어도, BN이 입력 분포를 적절히 정규화해줘서 학습의 안정성이 올라갑니다.
- BN 덕분에 분산이 안정되기 때문에, 기존보다 큰 학습률을 사용해도 학습이 잘 진행되고 수렴 속도가 빨라집니다.
- 미니 배치마다 평균과 분산이 조금씩 달라서, 모델 입장에서는 데이터에 잡음이 섞인 것과 유사한 효과가 납니다. 이 효과 때문에 과적합이 완화된다고 합니다.
- BN과 드롭아웃은 목적이 유사(과적합 방지)하지만, 서로 다른 방식으로 작동하기 때문에 병행 시에 시너지 효과가 일어납니다.
💡 그럼 어떤 한계가 있을까?
- 배치 크기가 너무 작으면 평균이나 분산을 안정적으로 추정하기 어렵고, 극단적으로 배치 크기가 1이라면 분산이 0이 되어 BN이 무의미해집니다. 따라서 BN은 일반적으로 적당히 큰 미니 배치에서 잘 동작합니다.
- 또 RNN 같은 경우에는 시점마다 다른 통계치를 가지기 때문에, 매 시점마다 배치 정규화를 적용하는 것이 까다롭습니다.
- 이 RNN만을 위한 변형된 기법들도 존재하긴 하지만, 일반적인 BN보다 조금 복잡합니다.
🛏️ 워드 임베딩 잠깐 알아보잡!
✨ One-hot 벡터 vs. 임베딩 벡터
- 원-핫 벡터(One-hot vector)
- 차원은 단어 집합의 크기(수만 ~ 수십만 차원까지 가능합니다.)를 가지게 되고,
- 대부분의 원소가 0이며, 특정 인덱스만 1이기 때문에 희소 벡터(Sparse Vector)라고도 불립니다.
- 그래서 단어 간 유의미한 유사도를 반영하기 어렵습니다.
- 임베딩 벡터(Embedding vector)
- 차원은 주로 256, 512, 1024 등의 비교적 저차원을 가집니다.
- 모든 원소가 실수로 구성되어 있기 때문에 밀집 벡터(Dense Vector)라고도 불리고,
- 훈련 데이터로부터 학습되므로, 단어 간 유사도를 어느 정도 반영할 수 있습니다.
항목 | One-hot 벡터 | 임베딩 벡터 |
차원 | 고차원(단어 집합의 크기) | 저차원(256, 512 등) |
표현 방법 | 1과 0으로 구성된 희소 벡터 | 실수값으로 구성된 밀집 벡터 |
학습 여부 | 미리 수동으로 인코딩(단어 위치만 1) | 신경망 학습 과정을 통해 자동 학습 |
유사도 | 계산 어려움(대부분이 0) | 코사인 유사도 등으로 유의미한 비교 가능 |
✨ 그럼 이제 임베딩 레이어도 사용할 수 있겠다
- keras에서 제공하는 Embedding() 레이어는 정수 인코딩된 단어들을 입력받아 임베딩 벡터로 매핑해줍니다.
- 주요 파라미터는 단어 집합의 크기(vocab_size), 임베딩 벡터의 차원(embedding_dim), 입력 시퀀스의 길이 등이 있습니다.
- 아래와 같이 작성할 수 있겠습니다.
# 1) 토큰화 & 정수 인코딩
tokenized_text = [['Hope', 'to', 'see', 'you', 'soon'],
['Nice', 'to', 'see', 'you', 'again']]
encoded_text = [[0, 1, 2, 3, 4],
[5, 1, 2, 3, 6]]
# 2) Embedding 레이어 정의
vocab_size = 7 # 단어 총 개수
embedding_dim = 2 # 임베딩 벡터 차원
Embedding(vocab_size, embedding_dim, input_length=5)
- 각 단어(정수)는 내부적으로 미리 준비된 임베딩 테이블을 참조해서 임베딩 벡터를 가져오게 됩니다(학습하면서 갱신).
✨ 손실 함수와 출력층 활성화 함수의 대표 콜라보
문제 유형 | 손실 함수 | 출력층 활성화 함수 | 예시 실습 |
회귀(Regression) | mean_squared_error (MSE) | (없음 or linear) | 선형 회귀 실습 |
이진 분류(Binary) | binary_crossentropy | sigmoid | IMDB 리뷰 감성 분류 |
다중 클래스 분류(One-hot) | categorical_crossentropy | softmax | 로이터 뉴스 분류 |
다중 클래스 분류(정수 레이블) | sparse_categorical_crossentropy | softmax | 양방향 LSTM을 이용한 품사 태깅 |
- sparse_categorical_crossentropy는 원-핫 인코딩을 거치지 않고 정수 인코딩된 레이블을 바로 사용 가능하다는 점이 categorical_crossentropy와 다릅니다.
🧑🏻💻 단어를 벡터로 바꿔보자!
✨ 영어 Word2Vec 만들어보기!
💡 데이터도 준비하고 XML도 파싱해보고
- 실습은 TED 강연 원문이 담긴 XML 파일로 진행했습니다. 이 파일을 다운로드한 후에 <content> 태그 사이에 있는 실제 강연 텍스트만 추출했습니다.
from lxml import etree
targetXML = open('ted_en-20160408.xml', 'r', encoding='UTF8')
target_text = etree.parse(targetXML)
# <content> 태그 내부의 텍스트 모두 가져오기
parse_text = '\\n'.join(target_text.xpath('//content/text()'))
💡 텍스트 전처리하기
- 배경음을 나타내는 (Laughter), (Applause) 등과 같이 괄호로 묶인 텍스트를 제거하고,
- 전체 텍스트를 문장 단위로 분리한 후에 각 문장을 소문자로 변환하고 구두점을 제거합니다.
- 마지막으로 각 문장을 단어 단위로 토큰화해줍니다.
import re
from nltk.tokenize import word_tokenize, sent_tokenize
# 괄호 안의 내용 제거
content_text = re.sub(r'\\([^)]*\\)', '', parse_text)
# 문장 토큰화
sent_text = sent_tokenize(content_text)
# 소문자 변환 및 구두점 제거
normalized_text = [re.sub(r"[^a-z0-9]+", " ", sentence.lower()) for sentence in sent_text]
# 단어 토큰화
result = [word_tokenize(sentence) for sentence in normalized_text]
💡 Word2Vec 모델을 학습해주기
- 전처리된 문장 리스트(result)를 바탕으로, Gensim의 Word2Vec 클래스로 단어 임베딩을 학습합니다.
- 실습에서는 100차원의 벡터를 사용했고, 주변 5단어(window)를 고려해서 등장 횟수가 5번 미만인 단어는 무시했습니다.
from gensim.models import Word2Vec
model = Word2Vec(sentences=result, vector_size=100, window=5, min_count=5, workers=4, sg=0)
# 'man'과 유사한 단어 확인
print(model.wv.most_similar("man"))
💡 모델을 저장하고 로드해보기
- 학습한 모델을 나중에 재사용할 수 있도록 파일로 저장하고, 필요할 때 다시 로드할 수 있게 해줍니다.
# 모델 저장
model.wv.save_word2vec_format('eng_w2v')
# 모델 로드
from gensim.models import KeyedVectors
loaded_model = KeyedVectors.load_word2vec_format("eng_w2v")
✨ 한국어로도 Word2Vec 만들어보기!
💡 여기서도 데이터 준비하고 로드해보고
- 실습에서는 네이버 영화 리뷰 데이터 파일(ratings.txt)을 다운로드해서 데이터프레임으로 로드했습니다.
import pandas as pd
train_data = pd.read_table('ratings.txt')
💡 데이터 전처리하기!
- 결측값을 제거하고, 정규 표현식을 통해 한글 이외의 문자들을 모두 제거합니다.
- Okt를 사용해서 각 리뷰를 토큰화하고, 불용어(의, 가, 이 등)를 제거합니다.
# 결측값 제거
train_data = train_data.dropna(how='any')
# 한글만 남기기
train_data['document'] = train_data['document'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]", "", regex=True)
# 형태소 분석 및 불용어 제거
from konlpy.tag import Okt
okt = Okt()
stopwords = ['의','가','이','은','들','는','좀','잘','걍','과','도','를','으로','자','에','와','한','하다']
tokenized_data = []
for sentence in train_data['document']:
tokenized_sentence = okt.morphs(sentence, stem=True)
stopwords_removed_sentence = [word for word in tokenized_sentence if word not in stopwords]
tokenized_data.append(stopwords_removed_sentence)
💡 모델 학습시켜주기
- 전처리된 리뷰 데이터를 이용해서 Word2Vec 모델을 학습하고 그 후에는 특정 단어(민식 형님)와 유사한 단어들을 확인할 수 있었습니다.
from gensim.models import Word2Vec
model = Word2Vec(sentences=tokenized_data, vector_size=100, window=5, min_count=5, workers=4, sg=0)
print(model.wv.most_similar("최민식"))
📷 이번엔 이미지 데이터를 분석해보잡!
✨ 관련 라이브러리 임포트하고 MNIST 준비!
from PIL.Image import NONE
from numpy.lib.function_base import append
import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
print(tf.__version__) # 2.5.0
fashion_mnist = keras.datasets.fashion_mnist # 이미지 해상도 28x28
(train_images1, train_labels1), (test_images, test_labels) = fashion_mnist.load_data()
test3 = test_images[12].reshape(1, 28, 28)
train_images = np.vstack((train_images1, test3))
train_labels = np.append(train_labels1, np.array(test_labels[12]))
# 각 인덱스에 해당하는 클래스 이름
class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
✨ 데이터 전처리하고 시각화하기!
- 데이터를 신경망에 입력하기 전에 이미지 데이터의 픽셀 값을 0~1 사이의 범위로 정규화합니다.
- 첫 번째 이미지를 단일 플롯으로 먼저 시각화해보고, 처음 25개 이미지를 5×5 그리드 형태로 출력해서 데이터 포맷과 클래스 레이블을 확인해보았습니다.
plt.figure()
plt.imshow(train_images[0])
plt.colorbar()
plt.grid(False)
plt.show()
# 정규화
train_images = train_images / 255.0 # (0 ~ 255) / 255 = (0 ~ 1)
test_images = test_images / 255.0
plt.figure(figsize=(10, 10))
for i in range(25):
plt.subplot(5, 5, i+1)
plt.xticks([])
plt.yticks([])
plt.grid(False)
if i == 24:
plt.imshow(train_images[60000], cmap=plt.cm.binary)
plt.xlabel(class_names[train_labels[60000]])
else:
plt.imshow(train_images[i], cmap=plt.cm.binary)
plt.xlabel(class_names[train_labels[i]])
plt.show()
✨ 모델 구성해보기!
- 모델 아키텍처는 다음과 같습니다.
- 먼저 첫번째 층으로 Flatten 층! 28×28 2차원 이미지를 784(=28×28)차원의 1차원 배열로 변환해주는 역할을 합니다. (학습 가능한 가중치는 없습니다.)
- 두번째는 Dense 층! 128개의 뉴런과 ReLU 활성화 함수를 사용합니다.
- 마지막으로 출력층도 Dense 층! 10개의 뉴런과 softmax 활성화 함수를 사용해서 각 클래스에 대한 확률 분포를 출력해줍니다.
- 모델 컴파일 과정에서는 옵티마이저로 Adam, 손실 함수로 sparse categorical crossentropy, 그리고 평가 지표로 정확도를 사용했습니다.
model = keras.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
✨ 학습하고 평가해보자!
- model.fit() 함수를 통해서 훈련 데이터를 사용하여 100회 동안 학습했습니다. (코드에서는 배치사이즈가 NONE으로 지정되어 있는데, 실제 사용하게 되면 적절한 배치 사이즈를 지정해야 합니다.)
- 검증 데이터로는 테스트셋을 사용해서 학습 진행 중인 모델의 성능을 모니터링했습니다.
- 그 후, 에포크에 따른 훈련, 검증 손실, 정확도를 y축 그래프로 출력해내고,
- model.evaluate()를 사용하여 테스트셋에서 모델 성능(손실과 정확도)을 평가하고 최종 테스트 정확도를 출력함으로 마무리하였습니다.
hist = model.fit(train_images, train_labels, epochs=100, batch_size=NONE, validation_data=(test_images, test_labels))
# 듀얼 y축 그래프: 손실은 왼쪽, 정확도는 오른쪽
fig, loss_ax = plt.subplots()
acc_ax = loss_ax.twinx()
loss_ax.plot(hist.history['loss'], 'y', label='train loss')
loss_ax.plot(hist.history['val_loss'], 'r', label='val loss')
acc_ax.plot(hist.history['accuracy'], 'b', label='train acc') # 'accuracy' 사용
acc_ax.plot(hist.history['val_accuracy'], 'g', label='val acc')
loss_ax.set_xlabel('epoch')
loss_ax.set_ylabel('loss')
acc_ax.set_ylabel('accuracy')
loss_ax.legend(loc='upper left')
acc_ax.legend(loc='lower left')
plt.show()
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print('\n테스트 정확도:', test_acc)
✨ 마지막으로 예측하고 시각화까지
- model.predict()를 사용해서 테스트셋 전체에 대한 예측을 수행했습니다. 예를 들어, 첫 번째 이미지의 예측 결과에서 가장 높은 확률을 가진 클래스를 np.argmax를 통해 도출했습니다.
- 시각화를 위해서 두 가지 함수를 정의했는데, 하나는 plot_image()로, 이미지와 함께 예측 결과를 시각화하며, 예측이 정답이면 파란색, 오답이면 빨간색 라벨로 표시하는 함수이고,
- 하나는 plot_value_array()로, 각 클래스별 예측 확률을 막대그래프로 나타내는 함수입니다.
predictions = model.predict(test_images)
print(np.argmax(predictions[0]))
def plot_image(i, predictions_array, true_label, img):
predictions_array, true_label, img = predictions_array[i], true_label[i], img[i]
plt.grid(False)
plt.xticks([])
plt.yticks([])
plt.imshow(img, cmap=plt.cm.binary)
predicted_label = np.argmax(predictions_array)
if predicted_label == true_label:
color = 'blue'
else:
color = 'red'
plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
100 * np.max(predictions_array),
class_names[true_label]),
color=color)
def plot_value_array(i, predictions_array, true_label):
predictions_array, true_label = predictions_array[i], true_label[i]
plt.grid(False)
plt.xticks([])
plt.yticks([])
thisplot = plt.bar(range(10), predictions_array, color="#777777")
plt.ylim([0, 1])
predicted_label = np.argmax(predictions_array)
thisplot[predicted_label].set_color('red')
thisplot[true_label].set_color('blue')
# 여러 이미지에 대해 예측 결과 시각화 (5행 3열의 그리드)
num_rows = 5
num_cols = 3
num_images = num_rows * num_cols
plt.figure(figsize=(2 * 2 * num_cols, 2 * num_rows))
for i in range(num_images):
plt.subplot(num_rows, 2 * num_cols, 2 * i + 1)
plot_image(i, predictions, test_labels, test_images)
plt.subplot(num_rows, 2 * num_cols, 2 * i + 2)
plot_value_array(i, predictions, test_labels)
plt.show()
# 단일 이미지에 대해 예측 수행
img = test_images[0]
print(img.shape)
# 단일 이미지를 배치로 변환
img = (np.expand_dims(img, 0))
print(img.shape)
predictions_single = model.predict(img)
print(predictions_single)
plot_value_array(0, predictions_single, test_labels)
_ = plt.xticks(range(10), class_names, rotation=45)
print(np.argmax(predictions_single[0]))
🤔 34일차 회고
- 오늘도 후루룩 지나가버린 딥러닝... 학부 수업 때는 정말 세심하고 깊게 배웠었는데, 수식을 제외한 이론만 보다보니 오히려 더 머리가 아픈 느낌이었습니다.
- 하지만 이제는 코드를 이해할 시기는 지났다고 생각하고, 제 머리에 이미 체화되어있는 지식들을 토대로 이 코드를 잘 읽어낼 수 있는 능력이 필요한 것 같습니다.
- 그리고 요즘 다양한 회사들에 지원도 하고, 자격증도 취득하게 되면서 제 강점을 도드라지게 볼 수 있는 시간이 많아졌습니다.
- 이렇게 블로그에 작성하는 습관을 들인지도 꽤 됐는데, 앞으로는 여기에 그치지 않고 계속 회고해보며 학습 내용뿐만 아니라 개발적인 측면에서도 자꾸 되돌아보는 시간을 가지고자 합니다.
- 이번 주 내로 딥러닝 관련 이론들은 마무리하신다고 하셔서 서둘러 진도 예습 및 빅데이터분석기사 공부를 병행해보아야겠습니다! 🔥
728x90
LIST
'부트캠프 > LG U+' 카테고리의 다른 글
🤔 요즘 어떤 강의가 가장 핫해...? (0) | 2025.03.19 |
---|---|
🤔 RNN과 LSTM 그리고 BiLSTM (6) | 2025.03.18 |
🤔 이번엔 좀 더 깊게 달려보자 (2) | 2025.03.13 |
🤔 왜 달려도 달려도 끝이 없을까... (8) | 2025.03.12 |
🤔 Words In My Bag (8) | 2025.03.11 |