テキスト分類タスク

導入

テキスト分類タスクは自然言語処理 (NLP) における一般的な問題であり、その目的は、事前定義されたカテゴリに従って入力テキストを自動的に分類することです。このようなタスクは、スパム フィルタリング、感情分析、ハッシュタグ生成などのシナリオで広く使用されています。一般的に使用される手法には、単純ベイジアン分類、サポート ベクター マシン (SVM)、ニューラル ネットワークなどが含まれます。

  • テキストのカテゴリをあらかじめ設定しておき、テキストがどのカテゴリに属する​​かを予測します。
  • 例えば:
  • 感情分析:
  • このレストランはひどいです -> まともです
  • この料理はおいしいです -> ネガティブクラス
  • フィールド分類:
  • 今日のA株市場は非常に良い -> 景気
  • レイカーズは今日ウォリアーズに勝利 -> スポーツ

1. テキストの分類 - 使用シナリオ

ここに画像の説明を挿入

ここに画像の説明を挿入

  • 違反の検出
  • ポルノ、暴力、テロ、侮辱などに関連するもの。
  • 主に顧客サービス、営業対話の品質検査、ウェブサイトのコンテンツレビューなどで使用されます。

2. カスタム カテゴリ タスク

  • カテゴリの定義方法は任意です
  • 文章から判断できる範囲であれば、分類カテゴリーとして使用可能
  • のように:
  • スパム分類
  • 車の取引に関する会話、記事かどうか
  • 記事のスタイルが著者のスタイルと一致しているかどうか
  • 記事が機械生成されたものであるかどうか
  • 契約文が仕様書に準拠しているかどうか
  • 記事は読書グループ(未成年、中年、高齢者、妊婦など)に適しています。

3. ベイジアンアルゴリズム

3.1 予備知識

合計確率の計算式:
ここに画像の説明を挿入
例: 通常のサイコロを投げる
P(B1) = 結果は奇数
P(B2) = 結果は偶数
P(A) = 結果は 5
P(A) = P(B1) * P(A |B1 ) + P(B2) * P(A|B2)

3.2 ベイズの公式

公式:
ここに画像の説明を挿入

3.3 ベイズ公式の適用

解決策: 核酸検査が陽性の場合、新しいクラウンに感染する確率はどのくらいですか?

人口における新しいクラウンの感染率は 0.1% (1,000 人に 1 人) であると仮定します。
核酸検出には一定の偽陽性率があり、次のように仮定します。
ここに画像の説明を挿入

  • P(A) = 新型コロナウイルス感染症に感染する確率
  • P(B) = 核酸検査陽性の確率
  • P(A|B) = 核酸検査陽性、実際に新しいクラウンに感染している確率
  • P(B|A) = COVID-19 に感染し、検査で陽性となった
  • P(B|A) = 新型コロナウイルス感染症に感染しておらず、検査で陽性であった

ここに画像の説明を挿入

3.4 NLP におけるベイズ公式の適用

  • ベイズ数式を使用したテキスト分類タスクの処理
  • 合理的な仮定:
  • テキストがどのカテゴリに属する​​かは、テキストに含まれる単語に関係します
  • タスク:
  • テキスト内にどの単語が含まれているかを把握し、テキストが特定のカテゴリに属する​​確率を予測します

3.5 ベイジアン公式 - テキスト分類

  • 3 つのカテゴリ A1、A2、A3 があるとします。
  • テキスト S は n 個の単語 W1、W2、W3...Wn で構成されます。
  • テキスト S がカテゴリ A1 に属する確率を計算したい P(A1|S) = P(A1|W1, W2, W3...Wn)
  • ベイズの公式
  • P(A1|S) = P(W1, W2…Wn|A1) * P(A1) / P(W1,W2…Wn)
  • P(A2|S) = P(W1, W2…Wn|A2) * P(A2) / P(W1,W2…Wn)
  • P(A3|S) = P(W1, W2…Wn|A3) * P(A3) / P(W1,W2…Wn)
  • 単語の独立性の仮定
  • P(W1、W2…Wn|A3) = P(W1|A3) * P(W2|A3)…P(Wn|A3)

3.6 コードの実装

import math
import jieba
import re
import os
import json
from collections import defaultdict

jieba.initialize()

"""
贝叶斯分类实践

P(A|B) = (P(A) * P(B|A)) / P(B)
事件A:文本属于类别x1。文本属于类别x的概率,记做P(x1)
事件B:文本为s (s=w1w2w3..wn)
P(x1|s) = 文本为s,属于x1类的概率.   #求解目标#
P(x1|s) = P(x1|w1, w2, w3...wn) = P(w1, w2..wn|x1) * P(x1) / P(w1, w2, w3...wn)

P(x1) 任意样本属于x1的概率。x1样本数/总样本数
P(w1, w2..wn|x1) = P(w1|x1) * P(w2|x1)...P(wn|x1)  词的独立性假设
P(w1|x1) x1类样本中,w1出现的频率

公共分母的计算,使用全概率公式:
P(w1, w2, w3...wn) = P(w1,w2..Wn|x1)*P(x1) + P(w1,w2..Wn|x2)*P(x2) ... P(w1,w2..Wn|xn)*P(xn)
"""

class BayesApproach:
    def __init__(self, data_path):
        self.p_class = defaultdict(int)
        self.word_class_prob = defaultdict(dict)
        self.load(data_path)

    def load(self, path):
        self.class_name_to_word_freq = defaultdict(dict)
        self.all_words = set()  #汇总一个词表
        with open(path, encoding="utf8") as f:
            for line in f:
                line = json.loads(line)
                class_name = line["tag"]
                title = line["title"]
                words = jieba.lcut(title)
                self.all_words.union(set(words))
                self.p_class[class_name] += 1  #记录每个类别样本数量
                word_freq = self.class_name_to_word_freq[class_name]
                #记录每个类别下的词频
                for word in words:
                    if word not in word_freq:
                        word_freq[word] = 1
                    else:
                        word_freq[word] += 1
        self.freq_to_prob()
        return

    #将记录的词频和样本频率都转化为概率
    def freq_to_prob(self):
        #样本概率计算
        total_sample_count = sum(self.p_class.values())
        self.p_class = dict([c, self.p_class[c] / total_sample_count] for c in self.p_class)
        #词概率计算
        self.word_class_prob = defaultdict(dict)
        for class_name, word_freq in self.class_name_to_word_freq.items():
            total_word_count = sum(count for count in word_freq.values()) #每个类别总词数
            for word in word_freq:
                #加1平滑,避免出现概率为0,计算P(wn|x1)
                prob = (word_freq[word] + 1) / (total_word_count + len(self.all_words))
                self.word_class_prob[class_name][word] = prob
            self.word_class_prob[class_name]["<unk>"] = 1/(total_word_count + len(self.all_words))
        return

    #P(w1|x1) * P(w2|x1)...P(wn|x1)
    def get_words_class_prob(self, words, class_name):
        result = 1
        for word in words:
            unk_prob = self.word_class_prob[class_name]["<unk>"]
            result *= self.word_class_prob[class_name].get(word, unk_prob)
        return result


    #计算P(w1, w2..wn|x1) * P(x1)
    def get_class_prob(self, words, class_name):
        #P(x1)
        p_x = self.p_class[class_name]
        # P(w1, w2..wn|x1) = P(w1|x1) * P(w2|x1)...P(wn|x1)
        p_w_x = self.get_words_class_prob(words, class_name)
        return p_x * p_w_x

    #做文本分类
    def classify(self, sentence):
        words = jieba.lcut(sentence) #切词
        results = []
        for class_name in self.p_class:
            prob = self.get_class_prob(words, class_name)  #计算class_name类概率
            results.append([class_name, prob])
        results = sorted(results, key=lambda x:x[1], reverse=True) #排序

        #计算公共分母:P(w1, w2, w3...wn) = P(w1,w2..Wn|x1)*P(x1) + P(w1,w2..Wn|x2)*P(x2) ... P(w1,w2..Wn|xn)*P(xn)
        #不做这一步也可以,对顺序没影响,只不过得到的不是0-1之间的概率值
        pw = sum([x[1] for x in results]) #P(w1, w2, w3...wn)
        results = [[c, prob/pw] for c, prob in results]

        #打印结果
        for class_name, prob in results:
            print("属于类别[%s]的概率为%f" % (class_name, prob))
        return results


if __name__ == "__main__":
    path = "../data/train_tag_news.json"
    ba = BayesApproach(path)
    query = "目瞪口呆 世界上还有这些奇葩建筑"
    ba.classify(query)


3.7 ベイジアンアルゴリズムの長所と短所

欠点:

  1. サンプルのバランスが取れていない場合、事前確率に大きな影響を与えます。
  2. 目に見えない特徴やサンプルの場合、条件付き確率はゼロとなり、予測の意味が失われます(平滑化を導入できます)
  3. 機能独立性の仮定は単なる仮定です
  4. 語順は考慮されず、意味もありません

利点:
5. シンプルで効率的
6. 確実な解釈可能性
7. サンプルカバレッジが良好であれば、効果も良好です
8. トレーニングデータはバッチで非常にうまく処理できます

4. サポートベクターマシン

サポート ベクター マシン (SVM) は、分類および回帰問題のための教師あり学習アルゴリズムです。分類タスクでは、SVM は超平面を見つけることによって、さまざまなクラスのデータ ポイントを区別します。この超平面は、最も近いデータ ポイント (サポート ベクトル) 間の距離を最大化するように選択され、より適切な一般化が可能になります。SVM は、テキスト分類、画像認識、バイオインフォマティクスなどの多くの分野で広く使用されています。カーネル トリックを通じて非線形問題を処理することもできます。

  • SVM:サポートベクターマシン
  • 教師あり学習に属します 教師あり学習
  • データサンプルを通じて、最大マージン超平面を学習します
  • 1964年に提案

ここに画像の説明を挿入

  • 青いボールと赤い三角形を区別してみてください

ここに画像の説明を挿入

サポートベクターマシン
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入

  • 線形不可分性問題
  • 空間を高次元にマッピングして非線形データを分類する

ここに画像の説明を挿入

4.1 サポートベクターマシン - カーネル機能

  • 線形不可分性問題を解決するには、入力を高次元にマッピングする必要があります。つまり、出力次元が x よりも高い関数 f(x) を見つける必要があります。
  • 例: x = [X1, X2, X3]
  • f(x) = [X1 X1, X1 X2, X1 X3, X2 X1, X2 X2, X2 X3 , X3 X1, X3 X2, X3*X3] (それ自体のデカルト積) とします。
  • このようにして、x は 3 次元から 9 次元に上昇します
  • 高次元へのマッピングは線形分離不可能性の問題をどのように解決しますか?
  • 1 次元データのセットを考えてみましょう
  • [-1、0、1] は正のサンプル、[-3、-2、2、3] は負のサンプルです。

ここに画像の説明を挿入
ここに画像の説明を挿入

  • x を [x, x2] にマッピングした後
  • 直線で割ることができる
  • しかし、これには問題があり、高次元すぎるベクトル計算は内積演算に非常に時間がかかり、svmの解法では内積演算が非常に頻繁に行われます。
  • したがって、f(x1)f(x2) をすばやく計算する方法があることを願っています。
  • いわゆるカーネル関数は、次の条件を満たすことです。
  • K(x1, x2) = f(x1)f(x2) 関数の集合
  • 線形カーネル関数
    ここに画像の説明を挿入
  • 多項式カーネル関数
    ここに画像の説明を挿入
  • ガウスカーネル関数
    ここに画像の説明を挿入
  • 双曲線正接カーネル関数
    ここに画像の説明を挿入

4.2 サポート ベクター マシン - 複数の分類の解決

  • K 個の分類問題を解決したいとします。つまり、K 個のターゲット カテゴリがあるとします。
    1. 1 対 1 のアプローチ
  • K(K - 1)/2 svm 分類器を確立します。各分類器は K カテゴリのうち 2 つを担当し、入力サンプルがどのカテゴリに属する​​かを決定します。
  • サンプルを予測するには、分類にすべての分類子を使用し、最終的に予測単語数が最も多いカテゴリを保持します。
  • カテゴリに [A,B,C] X->SVM(A,B)->A があるとします。
  • X->SVM(A,C)->A
  • X->SVM(B,C)->B
  • 最終判定 X→A
    1. 1対残り
  • K 個の svm 分類器を確立します。各分類器は、入力サンプルが K 個のカテゴリの 1 つに属するか、他のカテゴリに属する​​かを分類する責任があります。
  • 最後に、予測スコアが最も高いカテゴリを保持します
  • カテゴリに [A,B,C] X->SVM(A,rest)->0.1 があるとします。
  • X->SVM(B,残り)->0.2
  • X->SVM(C,残り)->0.5
  • 最終判定 X→C

4.3 コード

#!/usr/bin/env python3  
#coding: utf-8

#使用基于词向量的分类器
#对比几种模型的效果

import json
import jieba
import numpy as np
from gensim.models import Word2Vec
from sklearn.metrics import classification_report
from sklearn.svm import SVC
from collections import defaultdict


LABELS = {
    
    '健康': 0, '军事': 1, '房产': 2, '社会': 3, '国际': 4, '旅游': 5, '彩票': 6, '时尚': 7, '文化': 8, '汽车': 9, '体育': 10, '家居': 11, '教育': 12, '娱乐': 13, '科技': 14, '股票': 15, '游戏': 16, '财经': 17}

#输入模型文件路径
#加载训练好的模型
def load_word2vec_model(path):
    model = Word2Vec.load(path)
    return model

#加载数据集
def load_sentence(path, model):
    sentences = []
    labels = []
    with open(path, encoding="utf8") as f:
        for line in f:
            line = json.loads(line)
            title, content = line["title"], line["content"]
            sentences.append(" ".join(jieba.lcut(title)))
            labels.append(line["tag"])
    train_x = sentences_to_vectors(sentences, model)
    train_y = label_to_label_index(labels)
    return train_x, train_y

#tag标签转化为类别标号
def label_to_label_index(labels):
    return [LABELS[y] for y in labels]

#文本向量化,使用了基于这些文本训练的词向量
def sentences_to_vectors(sentences, model):
    vectors = []
    for sentence in sentences:
        words = sentence.split()
        vector = np.zeros(model.vector_size)
        for word in words:
            try:
                vector += model.wv[word]
                # vector = np.max([vector, model.wv[word]], axis=0)
            except KeyError:
                vector += np.zeros(model.vector_size)
        vectors.append(vector / len(words))
    return np.array(vectors)


def main():
    model = load_word2vec_model("model.w2v")
    train_x, train_y = load_sentence("../data/train_tag_news.json", model)
    test_x, test_y = load_sentence("../data/valid_tag_news.json", model)
    classifier = SVC()
    classifier.fit(train_x, train_y)
    y_pred = classifier.predict(test_x)
    print(classification_report(test_y, y_pred))



if __name__ == "__main__":
    main()

4.4 サポートベクターマシンの長所と短所

アドバンテージ:

  1. 最終結果を決定するサポート ベクトルがほとんどなく、外れ値の影響を受けにくい
  2. サンプルサイズの必要性が少なくなる
  3. 高次元データも扱える

欠点:

  1. サンプル数が多すぎると計算負荷が大きくなる
  2. 複数のカテゴリのタスクは処理が面倒になる
  3. カーネル関数の選択とパラメータの選択がより困難になる

5. ディープラーニング

5.1 深層学習パイプライン

深層学習では、「パイプライン」とは一般に、データの前処理、モデルのトレーニング、モデルの評価、モデルのデプロイメントの一連のステップを指します。特定のタスクをより効率的に完了するために、これらのステップはプロセスに編成されます。

  1. データ収集: トレーニングとテスト用のデータを取得します。
  2. データ前処理: 正規化、データ拡張、単語分割などの操作を実行します。
  3. モデル設計: タスクに適したニューラル ネットワーク アーキテクチャを選択または設計します。
  4. モデルのトレーニング: モデルのトレーニングにはトレーニング データセットを使用します。
  5. 検証と調整: モデルのパフォーマンス評価とハイパーパラメーターの調整には検証データセットを使用します。
  6. モデルの評価: 最終的なモデルの評価にはテスト データセットを使用します。
  7. モデルのデプロイメント: トレーニングされたモデルを実際の環境にデプロイします。

5.2 テキスト分類 - fastText

FastText は、テキスト分類と単語ベクトル学習のためのオープンソース ライブラリです。従来の深層学習モデルと比較して、FastText はトレーニング速度と分類精度を大幅に向上させます。

  1. 原理: FastText は、階層型ソフトマックスおよび N-gram 機能を使用して、テキスト データを分類用の高次元空間に迅速にマッピングします。
  2. トレーニング: FastText はテキスト分類子を迅速にトレーニングできます。マルチクラス分類だけでなく、マルチラベル分類もサポートしています。
  3. 導入: モデルのサイズが比較的小さいため、FastText はモバイル デバイスやリソースに制約のある環境での使用に適しています。
  4. 目的: FastText は通常、感情分析、トピック分類、スパム フィルタリングなどのテキスト分類タスクに使用されます。
  5. 長所: FastText はトレーニングが速いだけでなく、特に小さなデータセットの場合に非常に正確です。

ここに画像の説明を挿入

  • ngramの特徴
  • -> [<ap、app、ppl、ple、le>]
  • x -> 埋め込み -> 平均プーリング -> ラベル

5.3 テキスト分類 - TextRNN

TextRNN は、リカレント ニューラル ネットワーク (RNN) を使用したテキスト分類のモデルです。いくつかの重要なポイントを次に示します。

  1. 構造: TextRNN は通常、単語埋め込み層、1 つ以上の RNN 層 (LSTM や GRU など)、および完全接続層で構成されます。
  2. 連続情報: RNN はテキスト内の連続情報をキャプチャできます。これはコンテキストと文の構造を理解するために重要です。
  3. トレーニング: TextRNN は、従来のテキスト分類方法よりも長いトレーニング時間とより多くのコンピューティング リソースを必要とします。
  4. 機能: 感情分析、トピック認識、固有表現認識など、さまざまなテキスト分類タスクに適用できます。
  5. 柔軟性: RNN レイヤーを追加したり、双方向 RNN を使用したりすることで、モデルの複雑さとパフォーマンスを向上させることができます。

TextRNN は、テキスト分類に対する強力ではありますが、特に長距離の依存関係や複雑な構造をキャプチャする必要があるタスクに適した、計算集約型のアプローチです。

ここに画像の説明を挿入

  • RNN (LSTM、GRU) を使用してテキストをエンコードし、分類には最後の位置の出力ベクトルを使用します。
  • バツ
  • –>埋め込み
  • –>BiLSTM
  • –>ドロップアウト
  • ->LSTM
  • ->リニア
  • –>ソフトマックス
  • –>はい

5.4 テキスト分類 - RNN

テキスト分類用の RNN (リカレント ニューラル ネットワーク) には、主に次の特徴があります。

  1. シーケンス モデリング: RNN はテキスト シーケンス内の依存関係をキャプチャできるため、テキストの分類に非常に役立ちます。
  2. 構造: 基本的な RNN モデルには通常、単語埋め込み層、RNN 層、および 1 つ以上の全結合層が含まれます。
  3. 可変長: RNN はさまざまな長さの入力シーケンスを処理できます。これはテキスト データにとって利点です。
  4. 勾配の消失/爆発: 基礎となる RNN 構造には、勾配の消失または爆発の問題が発生する傾向がありますが、これは LSTM や GRU などのバリアントを使用することで解決できます。
  5. アプリケーション シナリオ: RNN は、感情分析やトピック分類など、シーケンスの依存関係を伴うテキスト分類問題の処理において非常に優れたパフォーマンスを発揮します。
  6. 計算の複雑さ: RNN は、従来の機械学習アルゴリズムよりも多くのコンピューティング リソースとトレーニング時間を必要とします。

全体として、RNN は、特にテキスト内の連続した情報が重要な場合に、テキスト分類のための強力なモデルです。ただし、潜在的な計算コストやその他の技術的課題にも注意を払う必要があります。

ここに画像の説明を挿入

  • リカレント ニューラル ネットワーク
  • リカレントニューラルネットワーク
  • 隠れたベクトルはタイムステップごとに逆方向に渡され、メモリの役割を果たします。

5.5 テキスト分類 - LSTM

5.5.1 分析

LSTM (Long Short Term Memory Network) には、テキスト分類において次の特徴があります。

  1. シーケンス モデル: RNN と同様に、LSTM はシーケンス データを処理でき、コンテキスト情報を考慮する必要があるテキスト分類タスクに適しています。
  2. 勾配問題を解決する: LSTM は、基本的な RNN モデルにおける勾配消失および勾配爆発の問題を解決するように設計されています。
  3. ストレージ ユニット: LSTM は、長距離の依存関係をより適切に捕捉するために、特別なストレージ ユニットを通じて履歴情報を保存します。
  4. 複雑さ: LSTM モデルは、長距離の依存関係を捕捉する際にはより優れたパフォーマンスを発揮しますが、より複雑であり、基礎となる RNN と比較してより多くの計算リソースを必要とします。
  5. 複数のバリアント: LSTM には、双方向 LSTM やスタック型 LSTM などの複数のバリアントがあり、モデルのパフォーマンスをさらに向上させることができます。
  6. 一般的なアプリケーション: LSTM は、感情分析やトピック分類など、コンテキストや長距離の依存関係を考慮する必要があるテキスト分類タスクに非常に適しています。

全体として、LSTM は、特に複雑な構造と長距離の依存関係を持つテキストを扱う場合に、テキスト分類を行うための非常に柔軟で強力な方法を提供します。

ここに画像の説明を挿入

  • RNNの隠れユニットを複雑にする
  • グラデーションの消失や情報の忘れの問題をある程度回避します。
    ここに画像の説明を挿入

5.5.2 コード


import torch
import torch.nn as nn
import numpy as np

'''
用矩阵运算的方式复现一些基础的模型结构
清楚模型的计算细节,有助于加深对于模型的理解,以及模型转换等工作
'''

#构造一个输入
length = 6
input_dim = 12
hidden_size = 7
x = np.random.random((length, input_dim))
# print(x)

#使用pytorch的lstm层
torch_lstm = nn.LSTM(input_dim, hidden_size, batch_first=True)
for key, weight in torch_lstm.state_dict().items():
    print(key, weight.shape)

def sigmoid(x):
    return 1/(1 + np.exp(-x))

#将pytorch的lstm网络权重拿出来,用numpy通过矩阵运算实现lstm的计算
def numpy_lstm(x, state_dict):
    weight_ih = state_dict["weight_ih_l0"].numpy()
    weight_hh = state_dict["weight_hh_l0"].numpy()
    bias_ih = state_dict["bias_ih_l0"].numpy()
    bias_hh = state_dict["bias_hh_l0"].numpy()
    #pytorch将四个门的权重拼接存储,我们将它拆开
    w_i_x, w_f_x, w_c_x, w_o_x = weight_ih[0:hidden_size, :], \
                                 weight_ih[hidden_size:hidden_size*2, :],\
                                 weight_ih[hidden_size*2:hidden_size*3, :],\
                                 weight_ih[hidden_size*3:hidden_size*4, :]
    w_i_h, w_f_h, w_c_h, w_o_h = weight_hh[0:hidden_size, :], \
                                 weight_hh[hidden_size:hidden_size * 2, :], \
                                 weight_hh[hidden_size * 2:hidden_size * 3, :], \
                                 weight_hh[hidden_size * 3:hidden_size * 4, :]
    b_i_x, b_f_x, b_c_x, b_o_x = bias_ih[0:hidden_size], \
                                 bias_ih[hidden_size:hidden_size * 2], \
                                 bias_ih[hidden_size * 2:hidden_size * 3], \
                                 bias_ih[hidden_size * 3:hidden_size * 4]
    b_i_h, b_f_h, b_c_h, b_o_h = bias_hh[0:hidden_size], \
                                 bias_hh[hidden_size:hidden_size * 2], \
                                 bias_hh[hidden_size * 2:hidden_size * 3], \
                                 bias_hh[hidden_size * 3:hidden_size * 4]
    w_i = np.concatenate([w_i_h, w_i_x], axis=1)
    w_f = np.concatenate([w_f_h, w_f_x], axis=1)
    w_c = np.concatenate([w_c_h, w_c_x], axis=1)
    w_o = np.concatenate([w_o_h, w_o_x], axis=1)
    b_f = b_f_h + b_f_x
    b_i = b_i_h + b_i_x
    b_c = b_c_h + b_c_x
    b_o = b_o_h + b_o_x
    c_t = np.zeros((1, hidden_size))
    h_t = np.zeros((1, hidden_size))
    sequence_output = []
    for x_t in x:
        x_t = x_t[np.newaxis, :]
        hx = np.concatenate([h_t, x_t], axis=1)
        # f_t = sigmoid(np.dot(x_t, w_f_x.T) + b_f_x + np.dot(h_t, w_f_h.T) + b_f_h)
        f_t = sigmoid(np.dot(hx, w_f.T) + b_f)
        # i_t = sigmoid(np.dot(x_t, w_i_x.T) + b_i_x + np.dot(h_t, w_i_h.T) + b_i_h)
        i_t = sigmoid(np.dot(hx, w_i.T) + b_i)
        # g = np.tanh(np.dot(x_t, w_c_x.T) + b_c_x + np.dot(h_t, w_c_h.T) + b_c_h)
        g = np.tanh(np.dot(hx, w_c.T) + b_c)
        c_t = f_t * c_t + i_t * g
        # o_t = sigmoid(np.dot(x_t, w_o_x.T) + b_o_x + np.dot(h_t, w_o_h.T) + b_o_h)
        o_t = sigmoid(np.dot(hx, w_o.T) + b_o)
        h_t = o_t * np.tanh(c_t)
        sequence_output.append(h_t)
    return np.array(sequence_output), (h_t, c_t)


torch_sequence_output, (torch_h, torch_c) = torch_lstm(torch.Tensor([x]))
numpy_sequence_output, (numpy_h, numpy_c) = numpy_lstm(x, torch_lstm.state_dict())

print(torch_sequence_output)
print(numpy_sequence_output)
print("--------")
print(torch_h)
print(numpy_h)
print("--------")
print(torch_c)
print(numpy_c)

#############################################################

#使用pytorch的GRU层
torch_gru = nn.GRU(input_dim, hidden_size, batch_first=True)
# for key, weight in torch_gru.state_dict().items():
#     print(key, weight.shape)


#将pytorch的GRU网络权重拿出来,用numpy通过矩阵运算实现GRU的计算
def numpy_gru(x, state_dict):
    weight_ih = state_dict["weight_ih_l0"].numpy()
    weight_hh = state_dict["weight_hh_l0"].numpy()
    bias_ih = state_dict["bias_ih_l0"].numpy()
    bias_hh = state_dict["bias_hh_l0"].numpy()
    #pytorch将3个门的权重拼接存储,我们将它拆开
    w_r_x, w_z_x, w_x = weight_ih[0:hidden_size, :], \
                        weight_ih[hidden_size:hidden_size * 2, :],\
                        weight_ih[hidden_size * 2:hidden_size * 3, :]
    w_r_h, w_z_h, w_h = weight_hh[0:hidden_size, :], \
                        weight_hh[hidden_size:hidden_size * 2, :], \
                        weight_hh[hidden_size * 2:hidden_size * 3, :]
    b_r_x, b_z_x, b_x = bias_ih[0:hidden_size], \
                        bias_ih[hidden_size:hidden_size * 2], \
                        bias_ih[hidden_size * 2:hidden_size * 3]
    b_r_h, b_z_h, b_h = bias_hh[0:hidden_size], \
                        bias_hh[hidden_size:hidden_size * 2], \
                        bias_hh[hidden_size * 2:hidden_size * 3]
    w_z = np.concatenate([w_z_h, w_z_x], axis=1)
    w_r = np.concatenate([w_r_h, w_r_x], axis=1)
    b_z = b_z_h + b_z_x
    b_r = b_r_h + b_r_x
    h_t = np.zeros((1, hidden_size))
    sequence_output = []
    for x_t in x:
        x_t = x_t[np.newaxis, :]
        hx = np.concatenate([h_t, x_t], axis=1)
        z_t = sigmoid(np.dot(hx, w_z.T) + b_z)
        r_t = sigmoid(np.dot(hx, w_r.T) + b_r)
        h = np.tanh(r_t * (np.dot(h_t, w_h.T) + b_h) + np.dot(x_t, w_x.T) + b_x)
        h_t = (1 - z_t) * h + z_t * h_t
        sequence_output.append(h_t)
    return np.array(sequence_output), h_t

# torch_sequence_output, torch_h = torch_gru(torch.Tensor([x]))
# numpy_sequence_output, numpy_h = numpy_gru(x, torch_gru.state_dict())
#
# print(torch_sequence_output)
# print(numpy_sequence_output)
# print("--------")
# print(torch_h)
# print(numpy_h)


5.6 テキストの分類 - CNN

5.6.1 テキストの分類 - TextCNN

  • 1 次元の畳み込みを使用してテキストをエンコードします。エンコードされたテキスト行列は、分類用にプールすることによってベクトルに変換されます
    ここに画像の説明を挿入

5.6.2 テキスト分類 - ゲートされた CNN

CNN の改善
ここに画像の説明を挿入

ここに画像の説明を挿入

5.6.3 コード


import torch
import torch.nn as nn
import numpy as np



#使用pytorch的1维卷积层

input_dim = 7
hidden_size = 8
kernel_size = 2
torch_cnn1d = nn.Conv1d(input_dim, hidden_size, kernel_size)
for key, weight in torch_cnn1d.state_dict().items():
    print(key, weight.shape)

x = torch.rand((7, 4))  #embedding_size * max_length

def numpy_cnn1d(x, state_dict):
    weight = state_dict["weight"].numpy()
    bias = state_dict["bias"].numpy()
    sequence_output = []
    for i in range(0, x.shape[1] - kernel_size + 1):
        window = x[:, i:i+kernel_size]
        kernel_outputs = []
        for kernel in weight:
            kernel_outputs.append(np.sum(kernel * window))
        sequence_output.append(np.array(kernel_outputs) + bias)
    return np.array(sequence_output).T


print(x.shape)
print(torch_cnn1d(x.unsqueeze(0)))
print(torch_cnn1d(x.unsqueeze(0)).shape)
print(numpy_cnn1d(x.numpy(), torch_cnn1d.state_dict()))

5.7 テキスト分類 - TextRCNN

ここに画像の説明を挿入

5.8 テキスト分類 - バート

  • エンコーダーとしての Bert はテキストをベクトルまたは行列に変換します
    ここに画像の説明を挿入

BERT (Bidirectional Encoder Representations from Transformers) には、テキスト分類において次の特徴があります。

  1. 双方向エンコーディング: BERT は単語の前後のコンテキストを同時に考慮し、豊富な意味情報を提供します。
  2. 事前トレーニング: BERT モデルはまず大規模なテキスト データで事前トレーニングされ、次に特定のテキスト分類タスクに合わせて微調整されます。
  3. 高いパフォーマンス: BERT は強力なエンコード機能により、テキスト分類を含む多くの NLP タスクで優れたパフォーマンスを発揮します。
  4. 微調整が簡単: 事前トレーニングされた BERT モデルは、特定の分類タスクに合わせて少量のラベル付きデータを使用して簡単に微調整できます。
  5. 計算が複雑: BERT モデルは効果的ではありますが、通常はサイズが大きくなり、より多くのコンピューティング リソースを必要とします。
  6. 多目的: BERT は、テキスト分類に加えて、質問応答やエンティティ認識などのさまざまな NLP タスクでも広く使用されています。
  7. 柔軟性: 特定のシナリオでのテキスト分類のために、CNN、RNN などの他のモデルと組み合わせることができます。

全体として、BERT は、その強力なエンコード能力と高い柔軟性により、テキスト分類やその他の NLP タスクにおける主流の手法となっています。

5.9 コードのデモ

5.9.1 config.py

# -*- coding: utf-8 -*-

"""
配置参数信息
"""

Config = {
    
    
    "model_path": "output",
    "train_data_path": "../data/train_tag_news.json",
    "valid_data_path": "../data/valid_tag_news.json",
    "vocab_path":"chars.txt",
    "model_type":"lstm",
    "max_length": 20,
    "hidden_size": 128,
    "kernel_size": 3,
    "num_layers": 2,
    "epoch": 15,
    "batch_size": 64,
    "pooling_style":"max",
    "optimizer": "adam",
    "learning_rate": 1e-3,
    "pretrain_model_path":r"F:\Desktop\work_space\pretrain_models\bert-base-chinese",
    "seed": 987
}

5.9.2 モデル.py

# -*- coding: utf-8 -*-

import torch
import torch.nn as nn
from torch.optim import Adam, SGD
from transformers import BertModel
"""
建立网络模型结构
"""

class TorchModel(nn.Module):
    def __init__(self, config):
        super(TorchModel, self).__init__()
        hidden_size = config["hidden_size"]
        vocab_size = config["vocab_size"] + 1
        class_num = config["class_num"]
        model_type = config["model_type"]
        num_layers = config["num_layers"]
        self.use_bert = False
        self.embedding = nn.Embedding(vocab_size, hidden_size, padding_idx=0)
        if model_type == "fast_text":
            self.encoder = lambda x: x
        elif model_type == "lstm":
            self.encoder = nn.LSTM(hidden_size, hidden_size, num_layers=num_layers)
        elif model_type == "gru":
            self.encoder = nn.GRU(hidden_size, hidden_size, num_layers=num_layers)
        elif model_type == "rnn":
            self.encoder = nn.RNN(hidden_size, hidden_size, num_layers=num_layers)
        elif model_type == "cnn":
            self.encoder = CNN(config)
        elif model_type == "gated_cnn":
            self.encoder = GatedCNN(config)
        elif model_type == "stack_gated_cnn":
            self.encoder = StackGatedCNN(config)
        elif model_type == "rcnn":
            self.encoder = RCNN(config)
        elif model_type == "bert":
            self.use_bert = True
            self.encoder = BertModel.from_pretrained(config["pretrain_model_path"])
            hidden_size = self.encoder.config.hidden_size
        elif model_type == "bert_lstm":
            self.use_bert = True
            self.encoder = BertLSTM(config)
            hidden_size = self.encoder.bert.config.hidden_size
        elif model_type == "bert_cnn":
            self.use_bert = True
            self.encoder = BertCNN(config)
            hidden_size = self.encoder.bert.config.hidden_size
        elif model_type == "bert_mid_layer":
            self.use_bert = True
            self.encoder = BertMidLayer(config)
            hidden_size = self.encoder.bert.config.hidden_size

        self.classify = nn.Linear(hidden_size, class_num)
        self.pooling_style = config["pooling_style"]
        self.loss = nn.functional.cross_entropy  #loss采用交叉熵损失

    #当输入真实标签,返回loss值;无真实标签,返回预测值
    def forward(self, x, target=None):
        if self.use_bert:  # bert返回的结果是 (sequence_output, pooler_output)
            x = self.encoder(x)
        else:
            x = self.embedding(x)  # input shape:(batch_size, sen_len)
            x = self.encoder(x)  # input shape:(batch_size, sen_len, input_dim)

        if isinstance(x, tuple):  #RNN类的模型会同时返回隐单元向量,我们只取序列结果
            x = x[0]
        #可以采用pooling的方式得到句向量
        if self.pooling_style == "max":
            self.pooling_layer = nn.MaxPool1d(x.shape[1])
        else:
            self.pooling_layer = nn.AvgPool1d(x.shape[1])
        x = self.pooling_layer(x.transpose(1, 2)).squeeze() #input shape:(batch_size, sen_len, input_dim)

        #也可以直接使用序列最后一个位置的向量
        # x = x[:, -1, :]
        predict = self.classify(x)   #input shape:(batch_size, input_dim)
        if target is not None:
            return self.loss(predict, target.squeeze())
        else:
            return predict


class CNN(nn.Module):
    def __init__(self, config):
        super(CNN, self).__init__()
        hidden_size = config["hidden_size"]
        kernel_size = config["kernel_size"]
        pad = int((kernel_size - 1)/2)
        self.cnn = nn.Conv1d(hidden_size, hidden_size, kernel_size, bias=False, padding=pad)

    def forward(self, x): #x : (batch_size, max_len, embeding_size)
        return self.cnn(x.transpose(1, 2)).transpose(1, 2)

class GatedCNN(nn.Module):
    def __init__(self, config):
        super(GatedCNN, self).__init__()
        self.cnn = CNN(config)
        self.gate = CNN(config)

    def forward(self, x):
        a = self.cnn(x)
        b = self.gate(x)
        b = torch.sigmoid(b)
        return torch.mul(a, b)


class StackGatedCNN(nn.Module):
    def __init__(self, config):
        super(StackGatedCNN, self).__init__()
        self.num_layers = config["num_layers"]
        self.hidden_size = config["hidden_size"]
        #ModuleList类内可以放置多个模型,取用时类似于一个列表
        self.gcnn_layers = nn.ModuleList(
            GatedCNN(config) for i in range(self.num_layers)
        )
        self.ff_liner_layers1 = nn.ModuleList(
            nn.Linear(self.hidden_size, self.hidden_size) for i in range(self.num_layers)
        )
        self.ff_liner_layers2 = nn.ModuleList(
            nn.Linear(self.hidden_size, self.hidden_size) for i in range(self.num_layers)
        )
        self.bn_after_gcnn = nn.ModuleList(
            nn.LayerNorm(self.hidden_size) for i in range(self.num_layers)
        )
        self.bn_after_ff = nn.ModuleList(
            nn.LayerNorm(self.hidden_size) for i in range(self.num_layers)
        )

    def forward(self, x):
        #仿照bert的transformer模型结构,将self-attention替换为gcnn
        for i in range(self.num_layers):
            gcnn_x = self.gcnn_layers[i](x)
            x = gcnn_x + x  #通过gcnn+残差
            x = self.bn_after_gcnn[i](x)  #之后bn
            # # 仿照feed-forward层,使用两个线性层
            l1 = self.ff_liner_layers1[i](x)  #一层线性
            l1 = torch.relu(l1)               #在bert中这里是gelu
            l2 = self.ff_liner_layers2[i](l1) #二层线性
            x = self.bn_after_ff[i](x + l2)        #残差后过bn
        return x


class RCNN(nn.Module):
    def __init__(self, config):
        super(RCNN, self).__init__()
        hidden_size = config["hidden_size"]
        self.rnn = nn.RNN(hidden_size, hidden_size)
        self.cnn = GatedCNN(config)

    def forward(self, x):
        x, _ = self.rnn(x)
        x = self.cnn(x)
        return x

class BertLSTM(nn.Module):
    def __init__(self, config):
        super(BertLSTM, self).__init__()
        self.bert = BertModel.from_pretrained(config["pretrain_model_path"])
        self.rnn = nn.LSTM(self.bert.config.hidden_size, self.bert.config.hidden_size, batch_first=True)

    def forward(self, x):
        x = self.bert(x)[0]
        x, _ = self.rnn(x)
        return x

class BertCNN(nn.Module):
    def __init__(self, config):
        super(BertCNN, self).__init__()
        self.bert = BertModel.from_pretrained(config["pretrain_model_path"])
        config["hidden_size"] = self.bert.config.hidden_size
        self.cnn = CNN(config)

    def forward(self, x):
        x = self.bert(x)[0]
        x = self.cnn(x)
        return x

class BertMidLayer(nn.Module):
    def __init__(self, config):
        super(BertMidLayer, self).__init__()
        self.bert = BertModel.from_pretrained(config["pretrain_model_path"])
        self.bert.config.output_hidden_states = True

    def forward(self, x):
        layer_states = self.bert(x)[2]
        layer_states = torch.add(layer_states[-2], layer_states[-1])
        return layer_states


#优化器的选择
def choose_optimizer(config, model):
    optimizer = config["optimizer"]
    learning_rate = config["learning_rate"]
    if optimizer == "adam":
        return Adam(model.parameters(), lr=learning_rate)
    elif optimizer == "sgd":
        return SGD(model.parameters(), lr=learning_rate)


if __name__ == "__main__":
    from config import Config
    # Config["class_num"] = 3
    # Config["vocab_size"] = 20
    # Config["max_length"] = 5
    Config["model_type"] = "bert"
    model = BertModel.from_pretrained(Config["pretrain_model_path"])
    x = torch.LongTensor([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])
    sequence_output, pooler_output = model(x)
    print(x[2], type(x[2]), len(x[2]))


    # model = TorchModel(Config)
    # label = torch.LongTensor([1,2])
    # print(model(x, label))

5.9.3 main.py

# -*- coding: utf-8 -*-

import torch
import os
import random
import os
import numpy as np
import logging
from config import Config
from model import TorchModel, choose_optimizer
from evaluate import Evaluator
from loader import load_data
#[DEBUG, INFO, WARNING, ERROR, CRITICAL]
logging.basicConfig(level=logging.INFO, format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

"""
模型训练主程序
"""


seed = Config["seed"]
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)

def main(config):
    #创建保存模型的目录
    if not os.path.isdir(config["model_path"]):
        os.mkdir(config["model_path"])
    #加载训练数据
    train_data = load_data(config["train_data_path"], config)
    #加载模型
    model = TorchModel(config)
    # 标识是否使用gpu
    cuda_flag = torch.cuda.is_available()
    if cuda_flag:
        logger.info("gpu可以使用,迁移模型至gpu")
        model = model.cuda()
    #加载优化器
    optimizer = choose_optimizer(config, model)
    #加载效果测试类
    evaluator = Evaluator(config, model, logger)
    #训练
    for epoch in range(config["epoch"]):
        epoch += 1
        model.train()
        logger.info("epoch %d begin" % epoch)
        train_loss = []
        for index, batch_data in enumerate(train_data):
            if cuda_flag:
                batch_data = [d.cuda() for d in batch_data]

            optimizer.zero_grad()
            input_ids, labels = batch_data   #输入变化时这里需要修改,比如多输入,多输出的情况
            loss = model(input_ids, labels)
            loss.backward()
            optimizer.step()

            train_loss.append(loss.item())
            if index % int(len(train_data) / 2) == 0:
                logger.info("batch loss %f" % loss)
        logger.info("epoch average loss: %f" % np.mean(train_loss))
        acc = evaluator.eval(epoch)
    # model_path = os.path.join(config["model_path"], "epoch_%d.pth" % epoch)
    # torch.save(model.state_dict(), model_path)  #保存模型权重
    return acc

if __name__ == "__main__":
    main(Config)

    # for model in ["cnn"]:
    #     Config["model_type"] = model
    #     print("最后一轮准确率:", main(Config), "当前配置:", Config["model_type"])

    #对比所有模型
    #中间日志可以关掉,避免输出过多信息
    # 超参数的网格搜索
    # for model in ["gated_cnn"]:
    #     Config["model_type"] = model
    #     for lr in [1e-3]:
    #         Config["learning_rate"] = lr
    #         for hidden_size in [128]:
    #             Config["hidden_size"] = hidden_size
    #             for batch_size in [64, 128]:
    #                 Config["batch_size"] = batch_size
    #                 for pooling_style in ["avg"]:
    #                     Config["pooling_style"] = pooling_style
    #                     print("最后一轮准确率:", main(Config), "当前配置:", Config)

5.9.4 ローダー.py

# -*- coding: utf-8 -*-

import json
import re
import os
import torch
import numpy as np
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer
"""
数据加载
"""


class DataGenerator:
    def __init__(self, data_path, config):
        self.config = config
        self.path = data_path
        self.index_to_label = {
    
    0: '家居', 1: '房产', 2: '股票', 3: '社会', 4: '文化',
                               5: '国际', 6: '教育', 7: '军事', 8: '彩票', 9: '旅游',
                               10: '体育', 11: '科技', 12: '汽车', 13: '健康',
                               14: '娱乐', 15: '财经', 16: '时尚', 17: '游戏'}
        self.label_to_index = dict((y, x) for x, y in self.index_to_label.items())
        self.config["class_num"] = len(self.index_to_label)
        if self.config["model_type"] == "bert":
            self.tokenizer = BertTokenizer.from_pretrained(config["pretrain_model_path"])
        self.vocab = load_vocab(config["vocab_path"])
        self.config["vocab_size"] = len(self.vocab)
        self.load()


    def load(self):
        self.data = []
        with open(self.path, encoding="utf8") as f:
            for line in f:
                line = json.loads(line)
                tag = line["tag"]
                label = self.label_to_index[tag]
                title = line["title"]
                if self.config["model_type"] == "bert":
                    input_id = self.tokenizer.encode(title, max_length=self.config["max_length"], pad_to_max_length=True)
                else:
                    input_id = self.encode_sentence(title)
                input_id = torch.LongTensor(input_id)
                label_index = torch.LongTensor([label])
                self.data.append([input_id, label_index])
        return

    def encode_sentence(self, text):
        input_id = []
        for char in text:
            input_id.append(self.vocab.get(char, self.vocab["[UNK]"]))
        input_id = self.padding(input_id)
        return input_id

    #补齐或截断输入的序列,使其可以在一个batch内运算
    def padding(self, input_id):
        input_id = input_id[:self.config["max_length"]]
        input_id += [0] * (self.config["max_length"] - len(input_id))
        return input_id

    def __len__(self):
        return len(self.data)

    def __getitem__(self, index):
        return self.data[index]

def load_vocab(vocab_path):
    token_dict = {
    
    }
    with open(vocab_path, encoding="utf8") as f:
        for index, line in enumerate(f):
            token = line.strip()
            token_dict[token] = index + 1  #0留给padding位置,所以从1开始
    return token_dict


#用torch自带的DataLoader类封装数据
def load_data(data_path, config, shuffle=True):
    dg = DataGenerator(data_path, config)
    dl = DataLoader(dg, batch_size=config["batch_size"], shuffle=shuffle)
    return dl

if __name__ == "__main__":
    from config import Config
    dg = DataGenerator("valid_tag_news.json", Config)
    print(dg[1])

5.9.5 評価.py

# -*- coding: utf-8 -*-
import torch
from loader import load_data

"""
模型效果测试
"""

class Evaluator:
    def __init__(self, config, model, logger):
        self.config = config
        self.model = model
        self.logger = logger
        self.valid_data = load_data(config["valid_data_path"], config, shuffle=False)
        self.stats_dict = {
    
    "correct":0, "wrong":0}  #用于存储测试结果

    def eval(self, epoch):
        self.logger.info("开始测试第%d轮模型效果:" % epoch)
        self.model.eval()
        self.stats_dict = {
    
    "correct": 0, "wrong": 0}  # 清空上一轮结果
        for index, batch_data in enumerate(self.valid_data):
            if torch.cuda.is_available():
                batch_data = [d.cuda() for d in batch_data]
            input_ids, labels = batch_data   #输入变化时这里需要修改,比如多输入,多输出的情况
            with torch.no_grad():
                pred_results = self.model(input_ids) #不输入labels,使用模型当前参数进行预测
            self.write_stats(labels, pred_results)
        acc = self.show_stats()
        return acc

    def write_stats(self, labels, pred_results):
        assert len(labels) == len(pred_results)
        for true_label, pred_label in zip(labels, pred_results):
            pred_label = torch.argmax(pred_label)
            if int(true_label) == int(pred_label):
                self.stats_dict["correct"] += 1
            else:
                self.stats_dict["wrong"] += 1
        return

    def show_stats(self):
        correct = self.stats_dict["correct"]
        wrong = self.stats_dict["wrong"]
        self.logger.info("预测集合条目总量:%d" % (correct +wrong))
        self.logger.info("预测正确条目:%d,预测错误条目:%d" % (correct, wrong))
        self.logger.info("预测准确率:%f" % (correct / (correct + wrong)))
        self.logger.info("--------------------")
        return correct / (correct + wrong)

6. データの疎性の問題

  • トレーニング データの量が少ないため、モデルはトレーニング サンプルに収束できますが、予測精度は非常に低くなります。
  • 解決:
    1. さらに多くのデータにラベルを付ける
    1. トレーニング サンプルを構築してみる (データ拡張)
    1. データ要件を軽減するためにモデルを置き換えます (事前トレーニング済みモデルの使用など)。
    1. ルールを追加して構成する
    1. 閾値を調整し、正解率として再現率を使用します。
    1. カテゴリの再定義(カテゴリの削減)

7. アンバランスなラベル

  • サンプルが豊富なカテゴリもあれば、サンプルが非常に少ないカテゴリもあります
  • 解決:
  • データの疎性に対するすべてのソリューションは引き続き適用されます
    1. オーバーサンプリングは、指定されたクラスのサンプルをコピーし、サンプリングで繰り返します
    1. ダウンサンプリングは、部分をランダムに使用して、マルチサンプル クラスのサンプリングを削減します。
    1. サンプル重量の調整は、損失関数の重量調整に反映されます。

8. 複数ラベル分類問題

  • マルチラベルはマルチ分類とは異なります
  • 映画概要: 戦闘で負傷し下半身麻痺となった元海軍兵士ジャック・サリー(サム・ワーシントン演じるサム・ワーシントン)は、グレース博士(シガニー・ウィーバー・シガニー)を操るため、亡き弟のためにパンドラ星に来ることを決意する。 Weaver) は、人間の遺伝子と地元のナメイ族の遺伝子を使用して、「アバター」ハイブリッド生物を作成します...
  • タグ: アクション、SF
  • 複数ラベルの問題の変換
    1. 複数の独立した二項分類問題に分解
    1. 複数ラベルの分類問題を複数クラスの分類問題に変換する
    1. マルチラベル分類のモデルで損失を直接置き換えます。

9.BCELロス

  • 損失関数を直接置き換える
    ここに画像の説明を挿入

コード

import torch
import torch.nn as nn

m = nn.Sigmoid()
bceloss = nn.BCELoss()
input = torch.randn(5)
target = torch.FloatTensor([1,0,1,0,0])
output = bceloss(m(input), target)
print(output)

celoss = nn.CrossEntropyLoss()
input = torch.FloatTensor([[0.1,0.2,0.3,0.1,0.3],[0.1,0.2,0.3,0.1,0.3]])
target = torch.LongTensor([2,3])
output = celoss(input, target)
print(output)

おすすめ

転載: blog.csdn.net/m0_63260018/article/details/132510104