paint-brush
製品主導型のひねりを加えた機械学習テキスト分類のケーススタディ@bemorelavender
29,438 測定値
29,438 測定値

製品主導型のひねりを加えた機械学習テキスト分類のケーススタディ

Maria K17m2024/03/12
Read on Terminal Reader

長すぎる; 読むには

これは、製品主導型のひねりを加えた機械学習のケーススタディです。改善する必要がある実際の製品があると仮定します。データセットを探索し、ロジスティック回帰、リカレント ニューラル ネットワーク、トランスフォーマーなどのさまざまなモデルを試して、それらの精度、製品をどのように改善するか、動作速度、デバッグが簡単かどうかを確認します。そしてスケールアップします。
featured image - 製品主導型のひねりを加えた機械学習テキスト分類のケーススタディ
Maria K HackerNoon profile picture


改善が必要な実際の製品があると仮定します。データセットを探索し、ロジスティック回帰、リカレント ニューラル ネットワーク、トランスフォーマーなどのさまざまなモデルを試して、それらの精度、製品をどのように改善するか、動作速度、デバッグが簡単かどうかを確認します。そしてスケールアップします。


GitHubで完全なケーススタディ コードを読み、 Jupyter Notebook Viewerでインタラクティブなグラフを含む分析ノートブックを参照できます。


興奮した?さあ、始めましょう!

タスクの設定

私たちが電子商取引 Web サイトを所有していると想像してください。この Web サイトでは、販売者は販売したい商品の説明をアップロードできます。また、アイテムのカテゴリを手動で選択する必要があるため、速度が低下する可能性があります。


私たちの仕事は、商品説明に基づいてカテゴリの選択を自動化することです。ただし、誤って自動化を選択することは、自動化しないことよりも悪いことです。間違いが気づかれずに売上の損失につながる可能性があるためです。したがって、確信が持てない場合には、自動ラベルを設定しないことも選択できます。


このケーススタディでは、 Zenodo E コマース テキスト データセット、アイテムの説明とカテゴリが含まれています。


良いか悪いか?最適なモデルの選び方

以下では複数のモデル アーキテクチャを検討しますが、開始する前に最適なオプションを選択する方法を決定することをお勧めします。このモデルは当社の製品にどのような影響を与えるのでしょうか? …私たちのインフラ?


明らかに、さまざまなモデルをオフラインで比較するための技術的な品質の指標が得られます。この場合、マルチクラス分類タスクがあるため、不均衡なラベルを適切に処理するバランスのとれた精度スコアを使用しましょう。


もちろん、候補者をテストする一般的な最終段階は AB テストです。これは、顧客が変更によってどのような影響を受けるかをより正確に把握できるオンライン段階です。通常、AB テストはオフライン テストよりも時間がかかるため、オフライン段階の最良の候補者のみがテストを受けます。これはケーススタディであり、実際のユーザーがいないため、 AB テストについては取り上げません。


候補者を AB テストに進める前に、他に何を考慮する必要がありますか?オンライン テストの時間を節約し、可能な限り最良のソリューションを実際にテストしていることを確認するには、オフライン段階で何を考慮すればよいでしょうか?


技術的な指標をインパクト重視の指標に変える

バランスの取れた精度は素晴らしいですが、このスコアは「モデルが製品にどの程度正確に影響を与えるのか?」という質問には答えていません。より製品指向のスコアを見つけるには、モデルをどのように使用するかを理解する必要があります。


私たちの設定では、売り手は間違いに気づき、カテゴリを手動で変更する必要があるため、間違いを犯すことは答えないことよりも悪いです。気付かない間違いは売上を減少させ、販売者のユーザーエクスペリエンスを悪化させ、顧客を失うリスクがあります。


それを避けるために、1% のミスのみを許容するようにモデルのスコアのしきい値を選択します。製品指向の指標は次のように設定できます。


エラー許容範囲が 1% しかない場合、アイテムの何パーセントを自動的に分類できますか?


最適なモデルを選択する場合、以下ではこれをAutomatic categorisation percentageと呼びます。完全なしきい値選択コードはここで見つけてください。


推論時間

モデルが 1 つのリクエストを処理するのにどれくらい時間がかかりますか?


これにより、あるモデルが別のモデルよりも選択された場合に、サービスがタスクの負荷を処理するために維持する必要があるリソースの量を大まかに比較できるようになります。


スケーラビリティ

製品が成長するとき、特定のアーキテクチャを使用して成長を管理するのはどれくらい簡単になるでしょうか?


成長とは次のことを意味します。

  • より多くのカテゴリ、より高い粒度のカテゴリ
  • 長い説明
  • より大きなデータセット

成長に対応するためにモデルの選択を再考する必要があるでしょうか、それとも単純な再トレーニングで十分でしょうか?


解釈可能性

トレーニング中およびデプロイ後にモデルのエラーをデバッグするのはどれほど簡単になるでしょうか?


モデルサイズ

次の場合にモデルのサイズが重要になります。

  • モデルをクライアント側で評価したい
  • 大きすぎてRAMに収まりません


上記の両方の項目は関連性がないことが後でわかりますが、それでも簡単に検討する価値はあります。

データセットの探索とクリーニング

私たちは何に取り組んでいますか?データを見て、クリーンアップが必要かどうかを確認してみましょう。


データセットには、商品説明とカテゴリの 2 つの列、合計 50.5k 行が含まれています。

 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)


各アイテムには、 HouseholdBooksElectronics 、またはClothing & Accessoriesの 4 つのカテゴリのうち 1 つが割り当てられます。カテゴリごとに 1 つの商品説明の例を次に示します。


  • 家庭用SPK ホームデコレーション クレイハンドメイド 壁掛けフェイス (マルチカラー、H35xW12cm) この手作りのテラコッタ インディアン フェイス マスクの壁掛けでご自宅をより美しくしましょう。この手作りのものは市場では決して手に入らないでしょう。リビングやエントランスロビーなどに設置していただけます。


  • 書籍BEGF101/FEG1-英語基礎コース-1 (Neeraj Publications 2018 edition ) BEGF101/FEG1-英語基礎コース-1


  • 衣類 & アクセサリーBroadstar ウィメンズ デニム ダンガリー Broadstar のダンガリーを着てオールアクセス パスを獲得しましょう。デニムで作られたこれらのダンガリーは快適さを保ちます。白や黒のトップスと合わせればカジュアルなコーディネートが完成します。


  • エレクトロニクスCaprigo 高耐久 - 2 フィート プレミアム プロジェクター 天井マウント スタンド ブラケット (調整可能 - ホワイト - 耐荷重 15 kg)


欠損値

データセットには空の値が 1 つだけあるので、それを削除します。

 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)


重複

ただし、重複した記述がかなり多くあります。幸いなことに、重複したものはすべて 1 つのカテゴリに属しているため、安全に削除できます。

 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


有効な英単語スコアは、ASCII 単語のうち有効な英単語が 70% 未満である説明は 1.5% のみであることを示しています。

 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 メッセージ)

  • Eval 15% - 最終モデルの選択用 (4.1k メッセージ)


 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))


ベースライン モデル: バッグ オブ ワード + ロジスティック回帰

適切なベースラインを取得するには、最初は単純で些細なことを行うと役立ちます。ベースラインとして、トレイン データセットに基づいてバッグ オブ ワード構造を作成しましょう。


また、辞書のサイズを 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 軸は実際のカテゴリを示します。各列を見ると、特定のカテゴリが予測されたときの実際のカテゴリの分布がわかります。


ベースライン解の混同行列。


たとえば、 ElectronicsHouseholdとよく混同されます。しかし、この単純なモデルでも、 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モデルの 1 つである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 モデル + 事前トレーニングされた埋め込み

現在、eval データセットで 93.7% のバランスの取れた精度が得られています。うわー!


バート

シーケンスを操作するための最新のモデルはトランスフォーマーです。ただし、トランスフォーマーをゼロからトレーニングするには、大量のデータと計算リソースが必要になります。ここで試せることは、目的を果たすために事前トレーニングされたモデルの 1 つを微調整することです。これを行うには、事前トレーニングされた 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 倍長くなります。これにより、バックエンドの保守コストが増加し、おそらく、微調整されたBERTLSTM+EMBにもたらすメリットを上回るでしょう。


相互運用性に関しては、ベースラインのロジスティック回帰モデルが最も解釈しやすく、この点ではどのニューラル ネットワークもそれに負けます。同時に、ベースラインはおそらく最も拡張性が低くなります。カテゴリを追加すると、もともと低いベースラインの品質が低下します。


BERT はその精度の高さでスーパースターのように見えますが、最終的には事前にトレーニングされた埋め込み層を備えた RNN を使用することになります。なぜ?かなり正確で、遅すぎず、問題が大きくなっても処理が複雑になりすぎることはありません。


このケーススタディを楽しんでいただければ幸いです。どのモデルを選択しますか?またその理由は何ですか?