paint-brush
제품 중심 트위스트를 사용한 기계 학습 텍스트 분류 사례 연구~에 의해@bemorelavender
29,438 판독값
29,438 판독값

제품 중심 트위스트를 사용한 기계 학습 텍스트 분류 사례 연구

~에 의해 Maria K17m2024/03/12
Read on Terminal Reader

너무 오래; 읽다

이것은 제품 중심으로 변형된 기계 학습 사례 연구입니다. 개선해야 할 실제 제품이 있다고 가정하겠습니다. 데이터 세트를 탐색하고 로지스틱 회귀, 순환 신경망 및 변환기와 같은 다양한 모델을 시험해 보면서 해당 모델이 얼마나 정확한지, 제품을 어떻게 개선할 것인지, 작동 속도는 물론 디버그하기 쉬운지 여부를 살펴보겠습니다. 그리고 규모를 확대하세요.
featured image - 제품 중심 트위스트를 사용한 기계 학습 텍스트 분류 사례 연구
Maria K HackerNoon profile picture


우리는 개선이 필요한 실제 제품이 있다고 가정하겠습니다. 데이터 세트를 탐색하고 로지스틱 회귀, 순환 신경망 및 변환기와 같은 다양한 모델을 시험해 보면서 해당 모델이 얼마나 정확한지, 제품을 어떻게 개선할 것인지, 작동 속도는 물론 디버그하기 쉬운지 여부를 살펴보겠습니다. 그리고 규모를 확대하세요.


GitHub 에서 전체 사례 연구 코드를 읽고 Jupyter 노트북 뷰어 에서 대화형 차트가 포함된 분석 노트북을 볼 수 있습니다.


흥분한? 시작해 봅시다!

작업 설정

우리가 전자상거래 웹사이트를 소유하고 있다고 상상해 보세요. 이 웹사이트에서 판매자는 판매하려는 품목에 대한 설명을 업로드할 수 있습니다. 또한 항목 카테고리를 수동으로 선택해야 하므로 속도가 느려질 수 있습니다.


우리의 임무는 항목 설명을 기반으로 카테고리 선택을 자동화하는 것입니다. 그러나 잘못 자동화된 선택은 자동화하지 않는 것보다 더 나쁩니다. 실수가 눈에 띄지 않고 판매 손실로 이어질 수 있기 때문입니다. 따라서 확실하지 않은 경우 자동 라벨을 설정하지 않을 수도 있습니다.


이 사례 연구에서는 다음을 사용합니다. Zenodo 전자상거래 텍스트 데이터세트 , 항목 설명 및 카테고리가 포함되어 있습니다.


좋거나 나쁘거나? 최고의 모델을 선택하는 방법

아래에서는 여러 모델 아키텍처를 고려할 것이며 시작하기 전에 항상 최선의 옵션을 선택하는 방법을 결정하는 것이 좋습니다. 이 모델이 우리 제품에 어떤 영향을 미칠까요? …우리 인프라는요?


분명히 우리는 다양한 모델을 오프라인으로 비교할 수 있는 기술적 품질 측정 기준을 갖게 될 것입니다. 이 경우 다중 클래스 분류 작업이 있으므로 불균형 레이블을 잘 처리하는 균형 정확도 점수를 사용하겠습니다.


물론, 후보자 테스트의 일반적인 최종 단계는 AB 테스트입니다. 즉, 온라인 단계로, 고객이 변경으로 인해 어떤 영향을 받는지 더 잘 이해할 수 있습니다. 일반적으로 AB 테스트는 오프라인 테스트보다 시간이 더 많이 소요되므로 오프라인 단계에서 가장 우수한 후보자만 테스트됩니다. 이는 사례 연구이고 실제 사용자가 없으므로 AB 테스트는 다루지 않습니다.


AB 테스트를 위해 후보자를 이동하기 전에 무엇을 고려해야 합니까? 온라인 테스트 시간을 절약하고 실제로 가능한 최상의 솔루션을 테스트하고 있는지 확인하기 위해 오프라인 단계에서 무엇을 생각할 수 있습니까?


기술 지표를 영향력 중심 지표로 전환

균형 잡힌 정확성은 훌륭하지만 이 점수는 "모델이 제품에 정확히 얼마나 영향을 미칠 것인가?"라는 질문에 대한 답을 제공하지 않습니다. 더 많은 제품 중심 점수를 찾으려면 모델을 어떻게 사용할 것인지 이해해야 합니다.


우리 설정에서는 판매자가 실수를 인지하고 카테고리를 수동으로 변경해야 하기 때문에 실수하는 것은 답변을 하지 않는 것보다 더 나쁩니다. 눈에 띄지 않는 실수는 매출을 감소시키고 판매자의 사용자 경험을 악화시켜 고객을 잃을 위험이 있습니다.


이를 방지하기 위해 모델 점수에 대한 임계값을 선택하여 실수의 1%만 허용합니다. 그런 다음 제품 지향 측정항목을 다음과 같이 설정할 수 있습니다.


오류 허용 범위가 1%에 불과한 경우 항목의 몇 퍼센트를 자동으로 분류할 수 있습니까?


최고의 모델을 선택할 때 이를 아래의 Automatic categorisation percentage 이라고 부르겠습니다. 여기에서 전체 임계값 선택 코드를 찾아보세요.


추론 시간

모델이 하나의 요청을 처리하는 데 얼마나 걸리나요?


이를 통해 한 모델이 다른 모델보다 선택된 경우 서비스가 작업 로드를 처리하기 위해 얼마나 더 많은 리소스를 유지해야 하는지 비교할 수 있습니다.


확장성

우리 제품이 성장할 때 주어진 아키텍처를 사용하여 성장을 관리하는 것이 얼마나 쉬울까요?


성장이란 다음을 의미할 수 있습니다.

  • 더 많은 카테고리, 더 높은 카테고리 세분화
  • 더 긴 설명
  • 더 큰 데이터세트

성장을 처리하기 위해 모델 선택을 다시 생각해야 합니까? 아니면 간단한 재교육으로 충분합니까?


해석 가능성

훈련 중과 배포 후에 모델 오류를 디버깅하는 것이 얼마나 쉬울까요?


모델 크기

다음과 같은 경우 모델 크기가 중요합니다.

  • 우리는 모델이 클라이언트 측에서 평가되기를 원합니다.
  • 너무 커서 RAM에 들어갈 수 없습니다.


나중에 위의 두 항목이 모두 관련이 없다는 것을 알게 되겠지만, 여전히 간략하게 고려해 볼 가치가 있습니다.

데이터 세트 탐색 및 정리

우리는 무엇을 가지고 일하고 있나요? 데이터를 살펴보고 정리가 필요한지 살펴보겠습니다.


데이터 세트에는 항목 설명 및 카테고리라는 2개의 열이 포함되어 있으며 총 50.5,000개의 행이 있습니다.

 file_name = "ecommerceDataset.csv" data = pd.read_csv(file_name, header=None) data.columns = ["category", "description"] print("Rows, cols:", data.shape) # >>> Rows, cols: (50425, 2)


각 품목에는 Household , Books , 전자제품, Electronics Clothing & Accessories 4가지 카테고리 중 하나가 지정됩니다. 다음은 카테고리당 1개의 항목 설명 예시입니다.


  • 가정용 SPK 홈 데코 클레이 핸드메이드 벽걸이 페이스(다색, H35xW12cm) 이 핸드메이드 테라코타 인디언 페이스 마스크 벽걸이로 집을 더욱 아름답게 만드세요. 시장에서 이 핸드메이드 물건을 잡을 수 없는 적이 없습니다. 거실/입구 로비에 추가할 수 있습니다.


  • 도서 BEGF101/FEG1-Foundation Course in English-1 (Neeraj Publications 2018 edition ) BEGF101/FEG1-Foundation Course in English-1


  • 의류 및 액세서리 Broadstar 여성 데님 던가리 Broadstar의 바지를 입고 올 액세스 패스를 획득하세요. 데님으로 제작된 이 바지는 편안함을 유지해 줍니다. 화이트나 블랙 컬러의 탑과 함께 매치해 캐주얼한 룩을 완성해보세요.


  • Electronics Caprigo 헤비 듀티 - 2피트 프리미엄 프로젝터 천장 장착 스탠드 브라켓(조절 가능 - 흰색 - 무게 용량 15Kgs)


누락된 값

데이터세트에는 빈 값이 하나만 있는데, 이를 제거하겠습니다.

 print(data.info()) # <class 'pandas.core.frame.DataFrame'> # RangeIndex: 50425 entries, 0 to 50424 # Data columns (total 2 columns): # # Column Non-Null Count Dtype # --- ------ -------------- ----- # 0 category 50425 non-null object # 1 description 50424 non-null object # dtypes: object(2) # memory usage: 788.0+ KB data.dropna(inplace=True)


중복

그런데 중복된 설명이 꽤 많네요. 다행히 모든 중복 항목은 하나의 카테고리에 속하므로 안전하게 삭제할 수 있습니다.

 repeated_messages = data \ .groupby("description", as_index=False) \ .agg( n_repeats=("category", "count"), n_unique_categories=("category", lambda x: len(np.unique(x))) ) repeated_messages = repeated_messages[repeated_messages["n_repeats"] > 1] print(f"Count of repeated messages (unique): {repeated_messages.shape[0]}") print(f"Total number: {repeated_messages['n_repeats'].sum()} out of {data.shape[0]}") # >>> Count of repeated messages (unique): 13979 # >>> Total number: 36601 out of 50424


중복 항목을 제거한 후에는 원본 데이터 세트의 55%가 남습니다. 데이터 세트는 균형이 잘 잡혀 있습니다.

 data.drop_duplicates(inplace=True) print(f"New dataset size: {data.shape}") print(data["category"].value_counts()) # New dataset size: (27802, 2) # Household 10564 # Books 6256 # Clothing & Accessories 5674 # Electronics 5308 # Name: category, dtype: int64


설명 언어

데이터 세트 설명에 따르면

데이터세트는 인도 전자상거래 플랫폼에서 스크랩되었습니다.


설명은 반드시 영어로 작성될 필요는 없습니다. 그 중 일부는 ASCII가 아닌 기호를 사용하여 힌디어 또는 기타 언어로 작성되거나 라틴 알파벳으로 음역되거나 여러 언어를 혼합하여 사용됩니다. Books 카테고리의 예:


  • यू जी सी – नेट जूनियर रिसर्च फैलोशिप एवं सहायक प्रोफेसर योग्यता …
  • Prarambhik Bhartiy Itihas
  • History of NORTH INDIA/வட இந்திய வரலாறு/ …


설명에 영어가 아닌 단어가 있는지 평가하기 위해 2가지 점수를 계산해 보겠습니다.


  • ASCII 점수: 설명에서 ASCII가 아닌 기호의 비율
  • 유효한 영어 단어 점수: 라틴 문자만 고려하는 경우 설명에서 영어로 유효한 단어의 비율은 몇 퍼센트입니까? 유효한 영어 단어가 영어 코퍼스에서 훈련된 Word2Vec-300 에 있는 단어라고 가정해 보겠습니다.


ASCII 점수를 사용하면 설명의 2.3%만이 1% 이상의 비ASCII 기호로 구성된다는 것을 알 수 있습니다.

 def get_ascii_score(description): total_sym_cnt = 0 ascii_sym_cnt = 0 for sym in description: total_sym_cnt += 1 if sym.isascii(): ascii_sym_cnt += 1 return ascii_sym_cnt / total_sym_cnt data["ascii_score"] = data["description"].apply(get_ascii_score) data[data["ascii_score"] < 0.99].shape[0] / data.shape[0] # >>> 0.023


유효한 영어 단어 점수는 설명의 1.5%만이 ASCII 단어 중 유효한 영어 단어가 70% 미만임을 나타냅니다.

 w2v_eng = gensim.models.KeyedVectors.load_word2vec_format(w2v_path, binary=True) def get_valid_eng_score(description): description = re.sub("[^az \t]+", " ", description.lower()) total_word_cnt = 0 eng_word_cnt = 0 for word in description.split(): total_word_cnt += 1 if word.lower() in w2v_eng: eng_word_cnt += 1 return eng_word_cnt / total_word_cnt data["eng_score"] = data["description"].apply(get_valid_eng_score) data[data["eng_score"] < 0.7].shape[0] / data.shape[0] # >>> 0.015


따라서 대부분의 설명(약 96%)이 영어로 되어 있거나 대부분 영어로 되어 있습니다. 다른 모든 설명을 제거할 수 있지만 대신 그대로 두고 각 모델이 이를 어떻게 처리하는지 살펴보겠습니다.

모델링

데이터세트를 3개 그룹으로 나누어 보겠습니다.

  • 70% 훈련 - 모델 훈련용(19,000개 메시지)

  • 15% 테스트 - 매개변수 및 임계값 선택(4.1k 메시지)

  • 평가 15% - 최종 모델 선택(4,100개 메시지)


 from sklearn.model_selection import train_test_split data_train, data_test = train_test_split(data, test_size=0.3) data_test, data_eval = train_test_split(data_test, test_size=0.5) data_train.shape, data_test.shape, data_eval.shape # >>> ((19461, 3), (4170, 3), (4171, 3))


기본 모델: 단어 가방 + 로지스틱 회귀

좋은 기준선을 얻으려면 처음에는 간단하고 사소한 일을 하는 것이 도움이 됩니다. 기본적으로 기차 데이터 세트를 기반으로 BW(Bag of Words) 구조를 만들어 보겠습니다.


또한 사전 크기를 100단어로 제한해 보겠습니다.

 count_vectorizer = CountVectorizer(max_features=100, stop_words="english") x_train_baseline = count_vectorizer.fit_transform(data_train["description"]) y_train_baseline = data_train["category"] x_test_baseline = count_vectorizer.transform(data_test["description"]) y_test_baseline = data_test["category"] x_train_baseline = x_train_baseline.toarray() x_test_baseline = x_test_baseline.toarray()


로지스틱 회귀를 모델로 사용할 계획이므로 훈련 전에 카운터 기능을 정규화해야 합니다.

 ss = StandardScaler() x_train_baseline = ss.fit_transform(x_train_baseline) x_test_baseline = ss.transform(x_test_baseline) lr = LogisticRegression() lr.fit(x_train_baseline, y_train_baseline) balanced_accuracy_score(y_test_baseline, lr.predict(x_test_baseline)) # >>> 0.752


다중클래스 로지스틱 회귀분석은 75.2%의 균형정확도를 보였다. 이것은 훌륭한 기준입니다!


전반적인 분류 품질은 좋지 않지만 모델은 여전히 몇 가지 통찰력을 제공할 수 있습니다. 예측된 레이블 수로 정규화된 혼동 행렬을 살펴보겠습니다. X축은 예측 범주를 나타내고 Y축은 실제 범주를 나타냅니다. 각 열을 보면 특정 카테고리가 예측되었을 때 실제 카테고리의 분포를 확인할 수 있습니다.


기준 솔루션에 대한 혼동 행렬입니다.


예를 들어, Electronics Household 와 혼동되는 경우가 많습니다. 하지만 이 단순한 모델도 Clothing & Accessories 아주 정확하게 포착할 수 있습니다.


Clothing & Accessories 카테고리를 예측할 때 중요한 기능은 다음과 같습니다.

'의류 및 액세서리' 라벨에 대한 기본 솔루션의 기능 중요성


Clothing & Accessories 카테고리에 대해 가장 많이 기여한 상위 6개 단어:

 women 1.49 book -2.03 men 0.93 table -1.47 cotton 0.92 author -1.11 wear 0.69 books -1.10 fit 0.40 led -0.90 stainless 0.36 cable -0.85


RNN

이제 시퀀스( 반복 신경망) 와 함께 작동하도록 특별히 설계된 고급 모델을 고려해 보겠습니다. GRULSTM은 간단한 RNN에서 발생하는 폭발적인 경사에 대처하기 위한 일반적인 고급 레이어입니다.


설명을 토큰화하고 모델을 구축 및 훈련하기 위해 pytorch 라이브러리를 사용할 것입니다.


먼저 텍스트를 숫자로 변환해야 합니다.

  1. 설명을 단어로 나누기
  2. 학습 데이터 세트를 기반으로 코퍼스의 각 단어에 인덱스를 할당합니다.
  3. 알 수 없는 단어 및 패딩에 대한 특수 인덱스 예약
  4. 학습 및 테스트 데이터 세트의 각 설명을 인덱스 벡터로 변환합니다.


단순히 기차 데이터 세트를 토큰화하여 얻은 어휘는 거의 90,000개 단어로 매우 많습니다. 단어가 많을수록 모델이 학습해야 하는 임베딩 공간이 커집니다. 훈련을 단순화하기 위해 가장 희귀한 단어를 제거하고 설명의 3% 이상에 나타나는 단어만 남겨 두겠습니다. 그러면 어휘가 340 단어로 줄어듭니다.

( 여기서 전체 CorpusDictionary 구현을 찾으세요)


 corpus_dict = util.CorpusDictionary(data_train["description"]) corpus_dict.truncate_dictionary(min_frequency=0.03) data_train["vector"] = corpus_dict.transform(data_train["description"]) data_test["vector"] = corpus_dict.transform(data_test["description"]) print(data_train["vector"].head()) # 28453 [1, 1, 1, 1, 12, 1, 2, 1, 6, 1, 1, 1, 1, 1, 6,... # 48884 [1, 1, 13, 34, 3, 1, 1, 38, 12, 21, 2, 1, 37, ... # 36550 [1, 60, 61, 1, 62, 60, 61, 1, 1, 1, 1, 10, 1, ... # 34999 [1, 34, 1, 1, 75, 60, 61, 1, 1, 72, 1, 1, 67, ... # 19183 [1, 83, 1, 1, 87, 1, 1, 1, 12, 21, 42, 1, 2, 1... # Name: vector, dtype: object


다음으로 결정해야 할 것은 RNN에 입력으로 제공할 벡터의 공통 길이입니다. 가장 긴 설명에는 9.4k 토큰이 포함되어 있으므로 전체 벡터를 사용하고 싶지 않습니다.


그러나 열차 데이터 세트에 있는 설명의 95%는 352개 토큰을 넘지 않습니다. 이는 자르기에 적합한 길이입니다. 설명을 짧게 하면 어떻게 되나요?


공통 길이까지 패딩 인덱스로 패딩됩니다.

 print(max(data_train["vector"].apply(len))) # >>> 9388 print(int(np.quantile(data_train["vector"].apply(len), q=0.95))) # >>> 352


다음으로, 손실을 계산하고 각 훈련 단계에서 역전파를 수행하기 위해 대상 범주를 0-1 벡터로 변환해야 합니다.

 def get_target(label, total_labels=4): target = [0] * total_labels target[label_2_idx.get(label)] = 1 return target data_train["target"] = data_train["category"].apply(get_target) data_test["target"] = data_test["category"].apply(get_target)


이제 모델에 제공할 사용자 정의 pytorch 데이터 세트 및 데이터 로더를 만들 준비가 되었습니다. 여기에서 전체 PaddedTextVectorDataset 구현을 찾아보세요.

 ds_train = util.PaddedTextVectorDataset( data_train["description"], data_train["target"], corpus_dict, max_vector_len=352, ) ds_test = util.PaddedTextVectorDataset( data_test["description"], data_test["target"], corpus_dict, max_vector_len=352, ) train_dl = DataLoader(ds_train, batch_size=512, shuffle=True) test_dl = DataLoader(ds_test, batch_size=512, shuffle=False)


마지막으로 모델을 만들어 보겠습니다.


최소 아키텍처는 다음과 같습니다.

  • 임베딩 레이어
  • RNN 레이어
  • 선형 레이어
  • 활성화층


작은 값의 매개변수(임베딩 벡터 크기, RNN의 숨겨진 레이어 크기, RNN 레이어 수)와 정규화 없이 시작하여 과적합의 강한 징후가 나타날 때까지 모델을 점차적으로 더 복잡하게 만든 다음 균형을 맞출 수 있습니다. 정규화(RNN 레이어와 마지막 선형 레이어 이전의 드롭아웃).


 class GRU(nn.Module): def __init__(self, vocab_size, embedding_dim, n_hidden, n_out): super().__init__() self.vocab_size = vocab_size self.embedding_dim = embedding_dim self.n_hidden = n_hidden self.n_out = n_out self.emb = nn.Embedding(self.vocab_size, self.embedding_dim) self.gru = nn.GRU(self.embedding_dim, self.n_hidden) self.dropout = nn.Dropout(0.3) self.out = nn.Linear(self.n_hidden, self.n_out) def forward(self, sequence, lengths): batch_size = sequence.size(1) self.hidden = self._init_hidden(batch_size) embs = self.emb(sequence) embs = pack_padded_sequence(embs, lengths, enforce_sorted=True) gru_out, self.hidden = self.gru(embs, self.hidden) gru_out, lengths = pad_packed_sequence(gru_out) dropout = self.dropout(self.hidden[-1]) output = self.out(dropout) return F.log_softmax(output, dim=-1) def _init_hidden(self, batch_size): return Variable(torch.zeros((1, batch_size, self.n_hidden)))


손실 함수로 Adam 옵티마이저와 cross_entropy 사용하겠습니다.


 vocab_size = len(corpus_dict.word_to_idx) emb_dim = 4 n_hidden = 15 n_out = len(label_2_idx) model = GRU(vocab_size, emb_dim, n_hidden, n_out) opt = optim.Adam(model.parameters(), 1e-2) util.fit( model=model, train_dl=train_dl, test_dl=test_dl, loss_fn=F.cross_entropy, opt=opt, epochs=35 ) # >>> Train loss: 0.3783 # >>> Val loss: 0.4730 

에포크당 학습 및 테스트 손실, RNN 모델

이 모델은 평가 데이터 세트에서 84.3%의 균형 정확도를 보여주었습니다. 와, 정말 진전이네요!


사전 훈련된 임베딩 소개

RNN 모델을 처음부터 훈련할 때의 주요 단점은 단어 자체의 의미를 배워야 한다는 것입니다. 이것이 임베딩 레이어의 작업입니다. 사전 훈련된 word2vec 모델은 미리 만들어진 임베딩 레이어로 사용할 수 있으며, 이를 통해 매개변수 수를 줄이고 토큰에 훨씬 더 많은 의미를 추가합니다. pytorch 에서 사용할 수 있는 word2vec 모델 중 하나( glove, dim=300 를 사용해 보겠습니다.


데이터 세트 생성을 약간만 변경하면 됩니다. 이제 각 설명과 모델 아키텍처에 대해 사전 정의된 glove 인덱스 벡터를 생성하려고 합니다.

 ds_emb_train = util.PaddedTextVectorDataset( data_train["description"], data_train["target"], emb=glove, max_vector_len=max_len, ) ds_emb_test = util.PaddedTextVectorDataset( data_test["description"], data_test["target"], emb=glove, max_vector_len=max_len, ) dl_emb_train = DataLoader(ds_emb_train, batch_size=512, shuffle=True) dl_emb_test = DataLoader(ds_emb_test, batch_size=512, shuffle=False)
 import torchtext.vocab as vocab glove = vocab.GloVe(name='6B', dim=300) class LSTMPretrained(nn.Module): def __init__(self, n_hidden, n_out): super().__init__() self.emb = nn.Embedding.from_pretrained(glove.vectors) self.emb.requires_grad_ = False self.embedding_dim = 300 self.n_hidden = n_hidden self.n_out = n_out self.lstm = nn.LSTM(self.embedding_dim, self.n_hidden, num_layers=1) self.dropout = nn.Dropout(0.5) self.out = nn.Linear(self.n_hidden, self.n_out) def forward(self, sequence, lengths): batch_size = sequence.size(1) self.hidden = self.init_hidden(batch_size) embs = self.emb(sequence) embs = pack_padded_sequence(embs, lengths, enforce_sorted=True) lstm_out, (self.hidden, _) = self.lstm(embs) lstm_out, lengths = pad_packed_sequence(lstm_out) dropout = self.dropout(self.hidden[-1]) output = self.out(dropout) return F.log_softmax(output, dim=-1) def init_hidden(self, batch_size): return Variable(torch.zeros((1, batch_size, self.n_hidden)))


그리고 우리는 훈련할 준비가 되었습니다!

 n_hidden = 50 n_out = len(label_2_idx) emb_model = LSTMPretrained(n_hidden, n_out) opt = optim.Adam(emb_model.parameters(), 1e-2) util.fit(model=emb_model, train_dl=dl_emb_train, test_dl=dl_emb_test, loss_fn=F.cross_entropy, opt=opt, epochs=11) 

에포크당 훈련 및 테스트 손실, RNN 모델 + 사전 훈련된 임베딩

이제 평가 데이터 세트에서 93.7%의 균형 잡힌 정확도를 얻었습니다. 우!


버트

시퀀스 작업을 위한 최신 최첨단 모델은 변환기입니다. 그러나 변환기를 처음부터 훈련하려면 엄청난 양의 데이터와 계산 리소스가 필요합니다. 여기서 시도할 수 있는 것은 사전 훈련된 모델 중 하나를 우리의 목적에 맞게 미세 조정하는 것입니다. 이를 위해서는 사전 훈련된 BERT 모델을 다운로드하고 드롭아웃 및 선형 레이어를 추가하여 최종 예측을 얻어야 합니다. 4개 에포크에 대해 조정된 모델을 학습시키는 것이 좋습니다. 시간을 절약하기 위해 2개의 에포크만 추가로 훈련했습니다. 그렇게 하는 데 40분이 걸렸습니다.


 from transformers import BertModel class BERTModel(nn.Module): def __init__(self, n_out=12): super(BERTModel, self).__init__() self.l1 = BertModel.from_pretrained('bert-base-uncased') self.l2 = nn.Dropout(0.3) self.l3 = nn.Linear(768, n_out) def forward(self, ids, mask, token_type_ids): output_1 = self.l1(ids, attention_mask = mask, token_type_ids = token_type_ids) output_2 = self.l2(output_1.pooler_output) output = self.l3(output_2) return output


 ds_train_bert = bert.get_dataset( list(data_train["description"]), list(data_train["target"]), max_vector_len=64 ) ds_test_bert = bert.get_dataset( list(data_test["description"]), list(data_test["target"]), max_vector_len=64 ) dl_train_bert = DataLoader(ds_train_bert, sampler=RandomSampler(ds_train_bert), batch_size=batch_size) dl_test_bert = DataLoader(ds_test_bert, sampler=SequentialSampler(ds_test_bert), batch_size=batch_size)


 b_model = bert.BERTModel(n_out=4) b_model.to(torch.device("cpu")) def loss_fn(outputs, targets): return torch.nn.BCEWithLogitsLoss()(outputs, targets) optimizer = optim.AdamW(b_model.parameters(), lr=2e-5, eps=1e-8) epochs = 2 scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=0, num_training_steps=total_steps ) bert.fit(b_model, dl_train_bert, dl_test_bert, optimizer, scheduler, loss_fn, device, epochs=epochs) torch.save(b_model, "models/bert_fine_tuned")


훈련 기록:

 2024-02-29 19:38:13.383953 Epoch 1 / 2 Training... 2024-02-29 19:40:39.303002 step 40 / 305 done 2024-02-29 19:43:04.482043 step 80 / 305 done 2024-02-29 19:45:27.767488 step 120 / 305 done 2024-02-29 19:47:53.156420 step 160 / 305 done 2024-02-29 19:50:20.117272 step 200 / 305 done 2024-02-29 19:52:47.988203 step 240 / 305 done 2024-02-29 19:55:16.812437 step 280 / 305 done 2024-02-29 19:56:46.990367 Average training loss: 0.18 2024-02-29 19:56:46.990932 Validating... 2024-02-29 19:57:51.182859 Average validation loss: 0.10 2024-02-29 19:57:51.182948 Epoch 2 / 2 Training... 2024-02-29 20:00:25.110818 step 40 / 305 done 2024-02-29 20:02:56.240693 step 80 / 305 done 2024-02-29 20:05:25.647311 step 120 / 305 done 2024-02-29 20:07:53.668489 step 160 / 305 done 2024-02-29 20:10:33.936778 step 200 / 305 done 2024-02-29 20:13:03.217450 step 240 / 305 done 2024-02-29 20:15:28.384958 step 280 / 305 done 2024-02-29 20:16:57.004078 Average training loss: 0.08 2024-02-29 20:16:57.004657 Validating... 2024-02-29 20:18:01.546235 Average validation loss: 0.09


마지막으로 미세 조정된 BERT 모델은 평가 데이터 세트에서 무려 95.1%의 균형 정확도를 보여줍니다.


우승자 선택

우리는 충분한 정보를 바탕으로 최종 선택을 하기 위해 검토해야 할 고려 사항 목록을 이미 확립했습니다.

측정 가능한 매개변수를 보여주는 차트는 다음과 같습니다.

모델의 성능 지표


미세 조정된 BERT가 품질 면에서 선두를 달리고 있지만, 사전 훈련된 임베딩 레이어 LSTM+EMB 갖춘 RNN은 근소한 차이로 2위이며 자동 카테고리 할당에서 3%만 뒤처집니다.


반면, 미세 조정된 BERT의 추론 시간은 LSTM+EMB 보다 14배 더 깁니다. 이는 아마도 미세 조정된 BERT LSTM+EMB 에 제공하는 이점보다 더 클 수 있는 백엔드 유지 관리 비용을 추가할 것입니다.


상호 운용성에 관해서는 우리의 기본 로지스틱 회귀 모델이 가장 해석하기 쉽고 이 점에서 모든 신경망은 이를 잃습니다. 동시에 기준선은 아마도 확장성이 가장 낮을 것입니다. 범주를 추가하면 기준선의 이미 낮은 품질이 저하됩니다.


BERT가 정확도가 높은 슈퍼스타처럼 보이지만 결국 사전 훈련된 임베딩 레이어를 갖춘 RNN을 사용하게 됩니다. 왜? 꽤 정확하고, 너무 느리지도 않고, 일이 커져도 처리하기가 너무 복잡해지지 않습니다.


이 사례 연구가 즐거웠기를 바랍니다. 어떤 모델을 선택하시겠습니까? 그리고 그 이유는 무엇입니까?