Patrick's 데이터 세상

카피라이터 광고 문구 생성모델 학습 후기 본문

Deep Learning/NLP 개발

카피라이터 광고 문구 생성모델 학습 후기

patrick610 2023. 8. 11. 11:17
반응형
SMALL

 
 
 
 
2023년 6월부터 사내에서 비즈니스 모델로 개발한 모델입니다.
MBTI 타입, 마케팅 정보 11개, 소구점을 입력으로 하여 각 정보가 반영된 광고 문구를 생성하는 T5 모델을 이용하여 광고 문구를 생성모델을 구현하는 프로젝트입니다.
 
예시

 


👉🏻 작업 환경

Google Colab Pro Plus
GPU typeA100-SXM4-40GB
GPU count4
CPU typeIntel Xeon 2.2Ghz
RAM13GB

 
 

👉🏻 Dataset

NT, NF, ST, SF 4개 유형 각 1,584건 총 6,336건 데이터 대상으로 마케팅 관련 13개 정보를 input feature로 활용하고 구축된 광고 문구를 output feature로 활용합니다.

 
T5는 Text-to-Text 구조로 디자인하기 위해 input에 finetuning task를 데이터 앞에 prefix로 붙여서 학습했습니다.
따라서 추가한 special token와 마케팅 관련 13개 값으로 구성된 input 구조 앞에 task prefix를 붙여 구성하였습니다.

적용 예시

f"Text Generation: <type>{t1}<classified>{t2}..."

 

 

👉🏻 Model

 

⚙️ Structure

finetuning에 활용한 ‘paust/pko-t5-base’ 모델은 C4(Colossal Clean Crawled Corpus) 영어 코퍼스 750GB 데이터셋을 이용하여 학습한 t5 v1.1 모델을 한국어 데이터(나무위키, 위키피디아, 모두의말뭉치 등..)를 활용하여 학습한 모델입니다.
 

 
T5 모델은 기본적으로 BART와 마찬가지로 Transformer Encoder-Decoder 구조 기반입니다.
인코더에 스페셜 토큰으로 MBTI type, 마케팅 정보 11개, 소구점을 토큰화해서 입력하고 디코더에는 인코더의 정보가 반영된 마케팅 문구를 입력합니다.
 

 
 
 
0: i번째 token의 왼쪽 2번째 token
1: i번째 token의 왼쪽 1번째 token
2: i번째 token
3: i번째 token의 오른쪽 1번째 token
4: i번째 token의 오른쪽 2번째 token
 
 
 
기존 모델과 다른 점은 input token에서 relative position encoding을 사용하였습니다.
input의 각 token 위치 별로 동일한 encoding을 주고 attention score를 계산하지 않고 self-attention 계산 시 offset boundary 내의 token에 relative position encoding 값을 줍니다.
offset이 2일 때, 왼쪽 설명은 relative position encoding의 index입니다.
offset이 설정되면 범위를 넘어가는 단어는 가장 바깥쪽 indexdml encoding 값을 부여하는데 offset 범위를 넘기는 첫 번째 토큰 I에 index 0의 encoding이 부여됩니다.
 

 

⚙️ Code

dataloader.py

from os.path import abspath, splitext
from typing import Optional

from datasets import load_dataset, logging

logging.set_verbosity(logging.ERROR)


def load(
    tokenizer,
    seq_len,
    train_data_path: str,
    eval_data_path: Optional[str] = None,
    train_test_split: Optional[float] = None,
    worker: int = 1,
    batch_size: int = 1000,
    shuffle_seed: Optional[int] = None,
):
    def _tokenize_function(e):
        result = dict()
        input = dict()
        label = dict()

        keys = [
            "type",
            "classified",
            "advertiser",
            "product",
            "product_detail",
            "purpose",
            "benefit",
            "period",
            "target",
            "season",
            "weather",
            "anniv",
            "selling_point",
        ]
        input = tokenizer(
            [
                 "Text Generation: <type>{}<classified>{}<advertiser>{}<product>{}<product_detail>{}<purpose>{}<benefit>{}<period>{}<target>{}<season>{}<weather>{}<anniv>{}<selling_point>{}".format(
                    *args
                )
                for args in zip(*[e[k] for k in keys])
            ],
            max_length=seq_len,
            padding="max_length",
            truncation=True,
            return_tensors="np",
        )

        label = tokenizer(
            [t + tokenizer.eos_token for t in e["label"]],
            max_length=seq_len,
            padding="max_length",
            truncation=True,
            return_tensors="np",
        )
        result["input_ids"] = input["input_ids"]
        result["labels"] = label["input_ids"]

        return result

    data = load_dataset(
        extention.replace(".", ""),
        data_files=datafiles,
        split=train_test_split,
    )
    data = data.map(
        _tokenize_function,
        batched=True,
        batch_size=batch_size,
        num_proc=worker,
        remove_columns=data["train"].column_names,
    )
    return data["train"], (data["test"] if is_eval else None)

 
 
dataloader에서 중요 code 부분만 발췌.
huggingface dataloader를 통해 인코더의 스페셜 토큰, 마케팅 정보의 토큰화와 디코더의 광고 문구의 토큰을 담습니다.
디코더의 끝부분에 <eos>토큰을 더하여 문장의 끝이라는 것을 알립니다.
dataset map으로 배치만큼 토큰화 작업을 시행합니다.
 
 

train.py

import hydra
import torch
from transformers import (
    T5TokenizerFast,
    T5ForConditionalGeneration,
)
from dataloader import load

device = "cuda" if torch.cuda.is_available() else "cpu"


@hydra.main(config_path="./", config_name="config")
def main(cfg):
    # tokenizer
    tokenizer = T5TokenizerFast.from_pretrained(cfg.MODEL.name)

    # 스페셜 토큰 추가하기
    special_tokens = {
        "additional_special_tokens": [
            "<type>",
            "<classified>",
            "<advertiser>",
            "<product>",
            "<product_detail>",
            "<purpose>",
            "<benefit>",
            "<period>",
            "<target>",
            "<season>",
            "<weather>",
            "<anniv>",
            "<selling_point>",
        ]
    }
    tokenizer.add_special_tokens(special_tokens)

    # model
    model = T5ForConditionalGeneration.from_pretrained(
        cfg.MODEL.name,
        dropout_rate=cfg.MODEL.dropout,
    )
    model.resize_token_embeddings(len(tokenizer))
    
    # data loder
    train_dataset, eval_dataset = load(tokenizer=tokenizer, **cfg.DATASETS)
	
    	...
if __name__ == "__main__":
    main()

train에서 중요 code 부분만 발췌.
먼저 토크나이저에 입력 값 13개에 대한 스페셜 토큰을 추가합니다.
tokenizer.add_special_tokens로 추가한 후 model load 후 추가한 토큰에 대해 token embedding을 다시 구조화합니다.

Pretrained Model KoT5를 활용하여 구축하였습니다.
Text-to-Text denoise 사전 훈련 인코더-디코더 모델을 사용하는 조건부 생성 T5ForConditionalGeneration task를 사용하여 load 합니다.
 
이후에는 log wandb, trainingArguments, trainer 활용하여 finetuning 학습하였습니다.

 
 

👉🏻 Result

train loss : 0.1702, eval loss : 0.21993
loss가 높은 편이나 train, eval loss 모두 하향으로 선을 그려 학습은 안정적으로 되었다고 판단하였습니다.
 

최종 카피라이터 문구 결과 정성 평가 기준 0.8 정도 달성하였습니다.
 
 

👉🏻 후기

기존에 보유한 데이터를 활용하여 비즈니스 모델을 진행했던 프로젝이다보니 레이블, 학습 데이터로 핏하게 검수하는 공수가 다소 발생하였습니다.
반복적으로 문장을 생성하는 문제가 있었는데, generate에서 repetition_penalty, no_repeat_ngram_size를 시도하였고 repetition_penalty를 1.1로 주었을 때가 가장 안정적으로 문장이 출력되었습니다.
다만, 마케팅 정보를 넣었음에도 최종 출력물에서 다른 많이 학습했던 단어로 변경하는 문제가 있었는데 인코더에 입력된 정보만을 살려서 문구를 생성하는 리서치가 추가로 필요할 것으로 보입니다.
 
추가로 해당 모델을 GCP Instance에 배포 정보도 곧 포스팅할 예정입니다.
 
 
 

반응형
LIST
Comments