데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      케라스를 활용한 한국어 감성 리뷰 자동 분류 : 기술적 접근과 분석

      xipert 25.09.24
      2,330 6 1
      DEVOTEE 요약
      주어진 코드는 한국어 텍스트 감성 분류를 위한 딥러닝 모델 구현에 대한 설명과 샘플 코드를 제공합니다. 주요 과정은 텍스트 정제(특수문자 제거), 형태소 분석(Okt) 및 불용어 제거, 단어를 고유 숫자로 인코딩(토크나이저), 패딩으로 길이 조정 후, 임베딩 → BiLSTM → Dense 구조의 Keras 모델을 사용하여 긍정/부정 감정을 분류하는 것입니다. 학습 안정화를 위해 체크포인트, 얼리스탑, 텐서보드 등의 콜백도 활용하며, 테스트 문장들에 대한 모델 예측 결과(긍정/부정)도 출력하는 과정을 포함합니다.

      "케라스를 활용한 간단한 한국어 감성 리뷰 자동 분류" 샘플 코드

      '감성 분류' 라고 하면 사용자가 작성한 글을 "좋음","싫음"으로 나누는 것을 의미합니다.

      즉, 긍정/부정 2가지로 분류할 수 있습니다.

      작은 샘플 데이터로 한국어 감성 분류 파이프라인을 구축하는 샘플 코드를 작성해 보았습니다.

      아래 코드는 형태소 분석(Okt), 토큰화/패딩, Keras Embedding → BiLSTM → Dense 모델, 그리고 학습 안정화를 위한 콜백까지 한 번에 볼 수 있도록 구성하였습니다.


      문장 텍스트를 Deep Learning 모델에 Input 으로 전달하기 전에 전처리 과정을 거쳐야 되며, 전처리 과정은 아래 단계로 구성을 하였습니다.

      1. 클렌징 → 불필요한 문자 제거.

      2. 형태소 분석 + 불용어 제거 → 핵심 단어만 추출.

      3. 토크나이저 → 단어 사전 작성 + 숫자 인코딩.

      4. 패딩 → 문장 길이를 동일하게 맞춤.

      5. 어휘집 크기 계산 → Embedding 레이어 입력 차원 결정.

      이 과정을 거치면 텍스트가 신경망에 투입 가능한 숫자 배열로 변환됩니다.

      텍스트 클렌징

      `df['document'] = df['document'].str.replace(r'[^A-Za-z가-힣 ]', '', regex=True)`
      • 텍스트에서 한글, 영문, 공백만 남기고 특수문자나 불필요한 기호는 모두 제거합니다.

      • 이렇게 해야 토큰화할 때 의미 없는 기호 때문에 단어 사전이 불필요하게 커지는 것을 방지할 수 있습니다.

      • 예: “이 영화!!! 최고@@@” → “이 영화 최고”

      형태소 분석 + 불용어 제거

      okt = Okt()
      stop_words = ['은','는','이','가','을','를','에','의','와','과','도','으로','하다']
      def word_tokenization(text: str):
          return [w for w in okt.morphs(text) if w not in stop_words]
      • Okt 형태소 분석기를 이용해 문장을 단어 단위(형태소)로 나눕니다.

      • 한국어는 조사, 어미 등이 많아 그대로 쓰면 학습이 어려움 → 불용어(stop words)로 제거.

      • 예: “이 영화는 재미있어요” → [영화, 재미있다]

      토크나이저 설정/학습 및 단어사전(vocabulary) 생성

      num_words = 10000
      tokenizer = Tokenizer(oov_token='<OOV>', num_words=num_words)
      tokenizer.fit_on_texts(data_tokenized)
      seqs = tokenizer.texts_to_sequences(data_tokenized)
      • Tokenizer는 단어에 고유 번호(인덱스)를 부여해 사전을 만듭니다.

      • num_words=10000 → 상위 1만 개 단어만 사용 (희귀 단어 제외).

      • oov_token='' → 사전에 없는 단어는 로 처리.

      • tokenizer.fit_on_texts(data_tokenized)를 호출해, 토큰화된 전체 텍스트(data_tokenized)를 바탕으로 단어사전(vocabulary)을 생성하고, 각 단어에 고유한 정수 인덱스 부여

      • texts_to_sequences() 메서드를 통해 텍스트를 정수 인덱스 시퀀스로 변환

      • 예: [영화, 재미있다] → [15, 203]

      시퀀스 길이 결정 및 패딩

      max_length = max(len(s) for s in seqs)
      x_data = pad_sequences(seqs, maxlen=max_length, padding='pre', truncating='post')
      y_data = df['label'].to_numpy()
      • 문장마다 길이가 제각각 → 모델 입력 크기를 맞추기 위해 패딩 필요.

      • pad_sequences는 부족한 부분을 0으로 채워 길이를 통일.

      • padding='pre' → 앞쪽에 0을 채움.

      • truncating='post' → 너무 긴 문장은 뒤를 잘라냄.

      • 결과: 모든 문장이 동일한 길이의 정수 배열로 변환.

      어휘집 크기 계산

      vocab_size = min(num_words, len(tokenizer.word_index) + 1)
      • 실제 단어 사전 크기를 계산.

      • +1 이유 → Keras에서 인덱스 0은 패딩 전용으로 예약되어 있기 때문.

      • 최종 vocab_size는 Embedding 레이어 입력 차원으로 사용.

      콜백(Callback)

      • 체크포인트(ModelCheckpoint): 모델이 학습되는 동안 매 epoch마다 검증 성능을 모니터링합니다. 성능이 최고일 때의 가중치를 저장해 두면, 학습이 끝난 뒤 가장 좋은 성능의 모델을 바로 복원할 수 있습니다.

      • 얼리스톱(EarlyStopping): 검증 손실(val_loss)이 일정 횟수(patience) 동안 개선되지 않으면 학습을 조기 종료합니다. 불필요한 epoch를 줄이고 과적합을 예방하는 효과가 있습니다.

      • 텐서보드(TensorBoard): 학습 과정을 웹 대시보드 형태로 시각화할 수 있게 해줍니다. 손실, 정확도 곡선, 학습 시간, 레이어별 가중치 분포 등을 한눈에 확인할 수 있어 디버깅과 모델 개선에 유용합니다.

      모델 구성 요소

      모델은 크게 3가지 층(layer)으로 이루어져 있습니다.


      1. Embedding 레이어2. BiLSTM 레이어 (Bidirectional LSTM)3. Dense 레이어 (완전연결층, 분류기)

      정리하면, Embedding은 단어를 의미 벡터로 바꾸는 단계, BiLSTM은 문맥을 이해하는 단계, Dense는 최종 감정을 분류하는 단계입니다.

      이 세 가지가 합쳐져 하나의 모델을 이루며, 각 레이어가 역할을 분담해 전체 파이프라인을 완성합니다.

      • Embedding = "단어 사전을 숫자 대신 의미있는 좌표로 바꿔주는 번역기"

      • BiLSTM = "앞에서 읽는 사람 + 뒤에서 읽는 사람, 두 명의 독해 전문가가 주고받아 핵심을 뽑아내는 과정"

      • Dense = "최종 판정관(심판) — 독해 결과를 보고 ‘긍정’ 또는 ‘부정’ 결정을 내림"

      이 세 부분을 조합하여 모델(모듈의 집합)입니다. 각 부분은 레이어(부품)이고, 이 부품들을 연결한 전체가 모델입니다.

      1. Embedding

        - 무엇을 하는가?: 단어의 정수 인덱스(예: 23, 124, 3)를 받아서 각 단어를 고정 길이의 실수 벡터(예: 128차원)로 바꿉니다.

        - 왜 필요한가?: 원-핫 벡터처럼 희소하고 비의미적인 표현 대신, 비슷한 의미의 단어는 가까운 벡터로 놓이는 '의미 공간'을 학습할 수 있어서 모델 성능이 좋아집니다.

        - 입출력 형태: (batch_size, seq_len) → (batch_size, seq_len, embedding_dim)

      2. BiLSTM

        - 무엇을 하는가?: 문장을 앞→뒤(정방향)와 뒤→앞(역방향) 두 방향으로 읽어서 각각의 정보를 합칩니다. 결과적으로 각 시점의 정보가 앞뒤 문맥을 모두 반영하게 됩니다.

        - 왜 중요한가?: 한국어는 어미와 조사 등 문장 끝부분에 중요한 정보가 오기 때문에, 역방향 문맥도 같이 보는 것이 감성 파악에 도움이 됩니다.

        - units 파라미터 해석: units=16이라면 "방향당" 16개의 hidden unit을 갖습니다. 양방향(Bidirectional)일 경우 기본적으로 출력 차원은 16×2 = 32가 됩니다(연결 방식에 따라 다름).

        - return_sequences 옵션:

        return_sequences=False(기본): 시퀀스 전체를 하나의 벡터(예: 마지막 타임스텝의 요약)로 압축하여 반환 → 분류 문제에 흔히 사용

        return_sequences=True: 각 타임스텝별로 출력을 유지 → 시퀀스 라벨링(품사 태깅, 개체명 인식 등)에 사용

        . 입출력 형태(예): Embedding 출력이 (B, L, 128)이고 units=16, return_sequences=False라면 출력은 (B, 32)

        .추가 옵션: dropout, recurrent_dropout으로 과적합을 줄이거나 return_sequences=True로 중간에 Attention 층을 붙이는 등 확장이 가능합니다.

      3. Dense(완전연결층)

        - 무엇을 하는가?: BiLSTM이 만든 특징 벡터를 받아서 최종 출력값(로그잇 또는 확률)을 계산합니다.

        - 왜 필요한가?: BiLSTM은 문맥 특징을 추출하지만, 그 자체로 이유(긍정/부정)를 '결정'하지는 않습니다. Dense는 이 특징들을 선형 결합하고(그리고 비선형 활성화를 통해) 최종 클래스를 예측합니다.

        - 설정 예시:

        . 이진분류: Dense(1, activation='sigmoid') + binary_crossentropy

        . 다중클래스: Dense(num_classes, activation='softmax') + categorical_crossentropy

        . 다중레이블: Dense(num_labels, activation='sigmoid') + binary_crossentropy

        중간에 Dense(64, activation='relu') 같은 은닉층을 추가하면 표현력이 늘어나지만, 샘플 수가 적으면 과적합 위험도 커집니다.

      단계별 Shape

      입력(문장: 토큰 ID)    : (B, L)             e.g. (4, 7)
      Embedding 출력         : (B, L, 128)        e.g. (4, 7, 128)
      BiLSTM(units=16, bi)   : (B, 32)            e.g. (4, 32)  # 양방향이라 16*2
      Dense(1, sigmoid)      : (B, 1)             e.g. (4, 1)   # 확률 출력

      B (Batch size, 배치 크기)

      • 한 번에 신경망 모델에 입력으로 주는 샘플 데이터의 개수입니다.

      • 예를 들어 B=4라면, 동시에 4개의 문장이나 데이터 샘플이 모델에 입력됩니다.

      • 배치 처리는 연산 효율과 학습 안정성 측면에서 매우 중요합니다.

      • 일반적으로 학습 시 매개변수 업데이트는 배치 단위로 이루어집니다.

      L (Sequence length, 시퀀스 길이)

      • 각 입력 문장이 가지는 토큰(단어, 형태소 등)의 길이입니다.

      • 예를 들어 L=7이라면, 한 문장은 7개의 토큰으로 구성되어 있다는 뜻입니다.

      • 만약 문장 길이가 다르면, 일반적으로 패딩(padding)을 통해 일정한 길이(L)로 맞춥니다.

      아래 작성된 코드는 Keras의 Sequential 모델(순차형 모델)을 사용하였습니다.

      즉, 레이어들을 한 줄로 차례대로 쌓아 올린 구조이며, Embedding → BiLSTM → Dense 순서로 구성된 단순하고 직관적인 신경망 모델입니다.


      모델 타입: Sequential

      구성 레이어:

      1. Embedding (단어를 벡터로 표현),

      2. Bidirectional LSTM (BiLSTM) (문맥을 앞뒤 양방향으로 학습)

      3. Dense (완전연결층) (출력층, 이진 분류 수행)

        - Keras로 한국어 감성 분류 (Okt + BiLSTM)

        - 개요: 텍스트 정제 → 형태소 단위 토큰화(+불용어) → 정수 시퀀스 → 패딩 →Embedding → BiLSTM → Dense → 콜백(ModelCheckpoint/EarlyStopping/TensorBoard)

        10개 샘플만을 사용하였습니다.

      # 0) 라이브러리 임포트 -----------------------------------------------------------------
      import os, re, datetime, warnings          # 운영체제, 정규식, 시간, 경고 제어
      import numpy as np                         # 수치 연산 (배열/난수 등)
      import pandas as pd                        # 테이블 데이터 구조(DataFrame)
      import tensorflow as tf                    # 딥러닝 프레임워크 (Keras 포함)
      from tensorflow.keras.preprocessing.text import Tokenizer       # 텍스트→정수 인덱스
      from tensorflow.keras.preprocessing.sequence import pad_sequences  # 시퀀스 패딩
      from konlpy.tag import Okt                 # 한국어 형태소 분석기 (Okt)
      from tqdm import tqdm                      # 진행률 바
      
      # 품질/재현성 설정 ----------------------------------------------------------------------
      # warnings.filterwarnings('ignore')          # 깔끔한 로그를 위해 경고 숨김
      tqdm.pandas()                              # pandas의 apply와 함께 진행률 표시
      # np.random.seed(42); tf.random.set_seed(42) # (선택) 실험 재현성 확보를 위한 시드 고정
      
      # 1) 샘플 데이터 ------------------------------------------------------------------------
      docs = [
          "이 영화 정말 재미있어요!",
          "완전 별로였어요. 시간 아까워요.",
          "배우들의 연기가 뛰어났어요.",
          "스토리가 너무 지루했어요.",
          "감동적인 영화였습니다. 추천해요!",
          "진짜 재미없고 실망했어요.",
          "음악과 내용이 좋았어요.",
          "내용이 복잡하고 이해가 안 갔어요.",
          "다시 보고 싶은 영화에요.",
          "별로 재미 없었어요. 그만 볼래요."
      ]
      labels = [1,0,1,0,1,0,1,0,1,0]             # 1=긍정, 0=부정
      
      # DataFrame으로 정리 (학습/전처리에 편리)
      df = pd.DataFrame({'document': docs, 'label': labels})
      
      # 2) 텍스트 정제(클렌징) ---------------------------------------------------------------
      #  - 한글/영문/공백만 남기고 제거.
      df['document'] = df['document'].str.replace(r'[^A-Za-z가-힣 ]', '', regex=True)
      
      # 3) 형태소 분석 + 불용어 제거 ---------------------------------------------------------
      okt = Okt()
      stop_words = ['은','는','이','가','을','를','에','의','와','과','도','으로','하다']
      
      def word_tokenization(text: str):
          """Okt 형태소 분석 후 불용어를 제거해 토큰 리스트를 반환"""
          return [w for w in okt.morphs(text) if w not in stop_words]
      
      # 진행률 바가 표시되는 apply로 전체 코퍼스 토큰화
      data_tokenized = df['document'].progress_apply(word_tokenization)
      
      # 4) 토크나이저 설정/학습 및 단어사전(vocabulary) 생성 ---------------------------------------------------------------
      num_words = 10000
      tokenizer = Tokenizer(oov_token='<OOV>', num_words=num_words)
      tokenizer.fit_on_texts(data_tokenized)
      seqs = tokenizer.texts_to_sequences(data_tokenized)
      
      # 5) 시퀀스 길이 결정 및 패딩 -----------------------------------------------------------
      max_length = max(len(s) for s in seqs)
      x_data = pad_sequences(seqs, maxlen=max_length, padding='pre', truncating='post')
      y_data = df['label'].to_numpy()
      
      # 6) 어휘집 크기 계산 ------------------------------------------------------------------
      vocab_size = min(num_words, len(tokenizer.word_index) + 1)
      
      # 7) 모델 정의 -------------------------------------------------------------------------
      from tensorflow.keras.models import Sequential
      from tensorflow.keras.layers import Embedding, Bidirectional, LSTM, Dense
      from tensorflow.keras.optimizers import Adam
      from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, TensorBoard
      
      model = Sequential([
          Embedding(input_dim=vocab_size, output_dim=128, input_length=max_length),
          Bidirectional(LSTM(16, activation='tanh')),
          Dense(1, activation='sigmoid')
      ])
      
      # 모델 컴파일
      tf.keras.backend.clear_session()
      model.compile(optimizer=Adam(1e-3), loss='binary_crossentropy', metrics=['accuracy'])
      
      # 8) 콜백: 체크포인트/얼리스톱/텐서보드 -----------------------------------------------
      checkpoint_cb = ModelCheckpoint(
          './best_model.weights.h5', save_weights_only=True,
          save_best_only=True, monitor='val_loss', verbose=1
      )
      
      earlystopping_cb = EarlyStopping(
          monitor='val_loss', patience=4, restore_best_weights=True, verbose=1
      )
      
      log_dir = './logs/' + datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
      tensorboard_cb = TensorBoard(log_dir=log_dir, histogram_freq=1)
      
      # 9) 학습 실행 -------------------------------------------------------------------------
      history = model.fit(
          x_data, y_data,
          epochs=100,
          batch_size=4,
          validation_split=0.2,
          callbacks=[checkpoint_cb, earlystopping_cb, tensorboard_cb],
          verbose=1
      )

      모델 테스트

      # 테스트용 리뷰 문장 10개 직접 작성
      test_sentences = [
          "이 영화는 정말 최고였어요. 완전 추천합니다!",
          "스토리가 너무 별로였고 지루했어요.",
          "배우들의 연기가 인상적이었어요.",
          "영화가 너무 재미없어서 졸았어요.",
          "감동적이고 마음에 남는 영화였습니다.",
          "별로 내용이 없고 실망했어요.",
          "음악과 화면이 멋졌어요.",
          "구성이 산만하고 이해가 안 됐어요.",
          "다시 보고 싶어지는 영화입니다.",
          "별로 재미가 없어서 돈이 아까웠어요."
      ]
      
      # 전처리 과정 (위 학습 때와 동일한 방식 적용)
      test_df = pd.DataFrame({'document': test_sentences})
      
      # 특수문자 제거 (한글, 영어, 공백만 남김)
      test_df['document'] = test_df['document'].str.replace(r'[^A-Za-z가-힣 ]', '', regex=True)
      
      # 토큰화 및 불용어 제거 함수는 기존과 동일
      def word_tokenization(text):
          return [word for word in okt.morphs(text) if word not in stop_words]
      
      # 형태소 분석 및 불용어 제거 적용
      test_tokenized = test_df['document'].progress_apply(word_tokenization)
      
      # 학습된 토크나이저로 시퀀스 변환
      test_seq = tokenizer.texts_to_sequences(test_tokenized)
      
      # 최대 길이 맞추기 (학습 때와 같은 max_length 사용)
      x_test = pad_sequences(test_seq, maxlen=max_length, padding='pre', truncating='post')
      
      # 모델 테스트 (평가)
      predictions = model.predict(x_test)
      
      # 예측 결과 출력 (확률과 긍정/부정 분류)
      for i, pred in enumerate(predictions):
          sentiment = '긍정' if pred[0] >= 0.5 else '부정'
          print(f"문장: {test_sentences[i]}")
          print(f"예측 확률: {pred[0]:.4f} -> 감정: {sentiment}")
          print()

      모델 테스트 결과

      image.png

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      xipert 님의 최신 블로그

      더보기
      동영상 기고하기