[PYTHON] Erkennen Sie Anomalien in Sätzen mit ELMo, BERT, USE

Überblick

Zuvor veröffentlichte Artikel Erkennen Sie Abnormalitäten in Sätzen mit dem Universal Satzcodierer Mit Universal Sentence Encoder (USE) wird nun die Aufgabe, den Text des Wertpapierberichts gemischt mit dem Text von Soseki Natsume zu finden, als Anomalie in den Richtungsdaten erkannt. Ich habe es als Problem behandelt. Dieses Mal werden nicht nur USE, sondern auch ELMo und BERT verwendet, um die gleiche Art von Aufgaben zu lösen. 3 Vergleichen wir zwei Encoder-Modelle.

Sowohl ELMo als auch BERT, die auf Japanisch vorgelernt wurden, verwenden das von Stockmark veröffentlichte Modell.

Umgebung

Alle Berechnungen wurden in Google Colaboratory durchgeführt. BERT verwendet die TensorFlow 1.x-Serie und USE die TensorFlow 2.x-Serie. Arbeiten Sie daher wie unten gezeigt mit mehreren Notebooks, um die Umgebung zu unterteilen. data_preparation.ipynb ――Datenvorbereitung ELMo_BERT_embedding.ipynb ――Berechnung des eingebetteten Vektors durch ELMo und BERT USE_embedding.ipynb ――Berechnung des eingebetteten Vektors durch USE anomaly_detection.ipynb ――Abnormalitätserkennungsberechnung

Datenaufbereitung

Im vorherigen Artikel habe ich hauptsächlich den Text des Romans von Soseki Natsume verwendet und den Text des Wertpapierberichts als abnormale Daten gemischt. Diesmal ist er jedoch wertvoll, da das Aktienmarkenmodell im Korpus des Geschäftsbereichs vorab erlernt wurde. Der Haupttext ist der Wertpapierbericht. Abnormale Daten werden aus dem Livedoor News Corpus gesammelt. Das Livedoor News Corpus ist keine gewöhnliche Nachricht, sondern ein Datensatz, der aus Artikeln wie Haushaltsgeräten, Sport- und Unterhaltungsklatsch besteht.

Importieren Sie zunächst die erforderlichen Bibliotheken und hängen Sie das Google-Laufwerk ein.

data_preparation.ipynb


import re
import json
import glob
import numpy as np
from sklearn.model_selection import train_test_split

from google.colab import drive
drive.mount('/content/drive')

Im Folgenden arbeiten wir in einem Verzeichnis namens anomaly_detection unter My Drive. Bitte ersetzen Sie den Verzeichnisnamen entsprechend.

data_preparation.ipynb


%cd /content/drive/'My Drive'/anomaly_detection

Laden Sie zunächst chABSA Dataset herunter und extrahieren Sie nur den Text des Wertpapierberichts. Das Verfahren ist das gleiche wie im vorherigen Artikel.

data_preparation.ipynb


#Daten herunterladen und extrahieren
!wget https://s3-ap-northeast-1.amazonaws.com/dev.tech-sketch.jp/chakki/public/chABSA-dataset.zip
!unzip chABSA-dataset.zip
!rm chABSA-dataset.zip

#Erstellen Sie eine Liste mit Pfaden zu Dateien
chabsa_path_list = glob.glob("chABSA-dataset/*.json")

#Speichern Sie nur den Textteil des Wertpapierberichts in der Liste
chabsa_texts = []
for p in chabsa_path_list:
    with open(p, "br") as f:
        j =  json.load(f)
    for line in j["sentences"]:
        chabsa_texts += [line["sentence"].replace('\n', '')]

print(len(chabsa_texts))
# 6119

Löschen Sie zu kurze und zu lange Sätze.

data_preparation.ipynb


def filter_by_length(texts_input, min_len=20, max_len=300):
    texts_output = []
    for t in texts_input:
        length = len(t)
        if length >= min_len and length <= max_len:
            texts_output.append(t)
    return texts_output

chabsa_texts = filter_by_length(chabsa_texts)
print(len(chabsa_texts))
# 5148

Laden Sie dann das Livesoor-News-Korpus herunter. Ich werde Sportuhren aus mehreren Artikeln verwenden.

data_preparation.ipynb


#Daten herunterladen und extrahieren
!wget https://www.rondhuit.com/download/ldcc-20140209.tar.gz
!tar -xf ldcc-20140209.tar.gz
!rm ldcc-20140209.tar.gz

#Erstellen Sie eine Liste mit Pfaden zu Dateien
livedoor_path_list = glob.glob('./text/sports-watch/sports-watch-*.txt')
len(livedoor_path_list)
# 900

Da der Text des Livedoor-Nachrichtenkorpus viele Symbole wie ■ enthält, wird die Vorverarbeitung mit Ausnahme der aufgelisteten Symbole durchgeführt. Außerdem werde ich den gesamten Satz einschließlich der URL ausschließen. Schließen Sie außerdem Zeilen aus, die nur aus alphanumerischen Zeichen bestehen, z. B. Zeilen, die sich auf das Urheberrecht beziehen.

data_preparation.ipynb


def cleaning_text(texts):
    #Schließt Anweisungen mit URLs aus
    p = re.compile('https?://')
    if p.search(texts):
        return ''
    #Schließt Zeilen mit nur alphanumerischen Zeichen aus
    p = re.compile('[\w /:%#\$&\?\(\)~\.,=\+\-…()<>]+')
    if p.fullmatch(texts):
        return ''
    #Ausgeschlossen sind Zeilenumbrüche, Leerzeichen in voller Breite und häufige Symbole
    remove_list = ['\n', '\u3000', ' ', '<', '>', '・', 
                   '■', '□', '●', '○', '◆', '◇', 
                   '★', '☆', '▲', '△', '※', '*', '*', '——']
    for s in remove_list:
        texts = texts.replace(s, '')
    return texts

livedoor_texts = []
for path in livedoor_path_list:
    with open(path) as f:
        texts = f.readlines()
    livedoor_texts += [cleaning_text(t) for t in texts[4:]] #Die ersten 3 Zeilen sind nicht erforderlich
print(len(livedoor_texts))

Teilen Sie den Satz durch Interpunktion, um ihn an den Satz und das Format der chABSA-Daten anzupassen, und filtern Sie nach der Länge des Satzes.

data_preparation.ipynb


def split_texts(texts_input, split_by='。'):
    texts_output = []
    for t in texts_input:
        texts_output += t.split(split_by)
    return texts_output

livedoor_texts = split_texts(livedoor_texts)
print(len(livedoor_texts))
# 17522

livedoor_texts = filter_by_length(livedoor_texts)
print(len(livedoor_texts))
# 8149

Nachdem wir nun eine Liste von zwei Arten von Sätzen haben, werden wir Modellentwicklungsdaten und Testdaten erstellen. Das Etikett 0 enthält den Text des Wertpapierberichts und das Etikett 1 den Text der Nachrichten von Livedoor. 80% des Textes des Wertpapierberichts werden für Entwicklungsdaten und die restlichen 20% für Testdaten verwendet. Nur etwa 1% des Livedoor-Nachrichtentextes ist in den Entwicklungsdaten gemischt. Die Anzahl der beiden Arten von Sätzen in den Testdaten beträgt jeweils 50%, damit die Genauigkeit leicht bewertet werden kann.

data_preparation.ipynb


def make_dataset(main_texts, anom_texts, main_dev_rate=0.8, anom_dev_rate=0.01):
    len1 = len(main_texts)
    len2 = len(anom_texts)
    num_dev1 = int(len1 * main_dev_rate)
    num_dev2 = int(num_dev1 * anom_dev_rate)
    num_test1 = len1 - num_dev1
    num_test2 = num_test1

    print("Entwicklungsdaten main: {}, anom: {}".format(num_dev1, num_dev2))
    print("Testdaten main: {}, anom: {}".format(num_test1, num_test2))

    main_arr = np.hstack([np.reshape(np.zeros(len1), (-1, 1)), 
                          np.reshape(main_texts, (-1, 1))])
    anom_arr = np.hstack([np.reshape(np.ones(len2), (-1, 1)), 
                          np.reshape(anom_texts, (-1, 1))])
    
    dev1, test1 = train_test_split(main_arr, train_size=num_dev1)
    dev2, test2 = train_test_split(anom_arr, train_size=num_dev2)
    test2, _ = train_test_split(test2, train_size=num_test2)

    dev_arr = np.vstack([dev1, dev2])
    np.random.shuffle(dev_arr)
    test_arr = np.vstack([test1, test2])
    np.random.shuffle(test_arr)
    return dev_arr, test_arr

dev_arr, test_arr = make_dataset(chabsa_texts, livedoor_texts)
#Entwicklungsdaten main: 4118, anom: 41
#Testdaten main: 1030, anom: 1030

print(dev_arr.shape, test_arr.shape)
# (4159, 2) (2060, 2)

Speichern Sie den von Ihnen erstellten Datensatz.

data_preparation.ipynb


np.save('dev.npy', dev_arr)
np.save('test.npy', test_arr)

Das Folgende ist ein Beispiel einiger Testdaten.

1: Rakutens nachlässiges Spiel wäre definitiv eine große Flamme gewesen, wenn es von Seibu so wie es war besiegt worden wäre 0: Der Fondsverwaltungssaldo betrug 17,9 Milliarden Yen (plus 8,4% gegenüber dem Vorjahr), da die Zinsen für Kredite gestiegen sind, obwohl die Zinsen und Dividenden für Wertpapiere gesunken sind. 1: Ich kenne ihn seit er 18 Jahre alt ist, aber jetzt ist er wirklich erwachsen 0: Mit der Einrichtung des Business Succession Navigator werden wir Vertriebsaktivitäten durchführen, um den Anforderungen von Managern mit mittlerer Marktkapitalisierung (mittelständische Unternehmen) gerecht zu werden, die über einen relativ langen Zeitraum aus mehreren Optionen den besten Business Succession Plan auswählen möchten. ich würde, ich möchte 0: Bezüglich des Segmentgewinns 3.977 aufgrund eines Anstiegs der qualitätsbezogenen Kosten durch Airbag-Inflatoren und verschiedener Ausgaben, die sich auf die Verkaufskosten aufgrund steigender Zinssätze in den USA, der Auswirkungen von Wechselkursschwankungen und eines Anstiegs der Test- und Forschungskosten konzentrieren. Der Gewinn verringerte sich gegenüber dem vorherigen konsolidierten Geschäftsjahr um 146 Milliarden Yen (26,8%) auf 100 Millionen Yen. 1: Seit wir gekommen sind, wollte ich, dass der Regisseur es einmal genehmigt. 1: Und der 1. Platz war "Ich frage mich, ob dies der einzige war, der laut Bild perfekt gekickt hat 0: Obwohl der Umschlag von importierter Luftfracht unverändert blieb, betrug der Umsatz 101,7 Milliarden Yen, ein Rückgang von 13,3 Milliarden Yen oder 11,6% gegenüber dem vorherigen konsolidierten Geschäftsjahr, und das Betriebsergebnis betrug aufgrund von Wechselkurseffekten und anderen Faktoren 1,1 Milliarden Yen. Gegenüber dem vorangegangenen konsolidierten Geschäftsjahr verringerte sich der Gewinn um 500 Millionen Yen, 33,5%. 0: In der japanischen Wirtschaft erholte sich das Beschäftigungs- und Einkommensumfeld zwar weiter, die Wirtschaft erholte sich jedoch weiterhin moderat, obwohl einige Verzögerungen bei der Verbesserung zu verzeichnen waren. 0: In Bezug auf das Geschäft mit der Verbreitung von PC-Inhalten werden wir kostenpflichtige Fanclub-Websites für PCs wie Künstler und Talente betreiben und Auftragsproduktionen für offizielle Websites durchführen, was in Zukunft zu neuen Gewinnen führen wird, einschließlich anderer Geschäftsbereiche. Vor diesem Hintergrund haben wir unser Geschäft weiterentwickelt.

Installation des MeCab + NEologd-Wörterbuchs

Von hier aus arbeiten Sie am Notebook ELMo_BERT_embedding.ipynb. Mounten Sie Google Drive zunächst wie zuvor.

ELMo_BERT_embedding.ipynb


from google.colab import drive
drive.mount('/content/drive')

Das Stockmark-Vorlernmodell verwendet das MeCab + NEologd-Wörterbuch als Tokenizer. Folgen Sie der Official Distribution Page, um das MeCab + NEologd-Wörterbuch zu installieren. Führen Sie zunächst den folgenden Befehl auf dem Notebook aus, um MeCab selbst zu installieren.

ELMo_BERT_embedding.ipynb


!apt install aptitude swig
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y

Installieren Sie dann das NEologd-Wörterbuch. Wenn Sie unter dem Verzeichnis "Mein Laufwerk" arbeiten, wird eine Fehlermeldung angezeigt, da der Verzeichnisname Leerzeichen enthält. Arbeiten Sie also im Stammverzeichnis.

ELMo_BERT_embedding.ipynb


%cd /content
!git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
%cd mecab-ipadic-neologd
!echo yes | ./bin/install-mecab-ipadic-neologd -n
%cd /content/drive/'My Drive'/anomaly_detection2

Installieren Sie abschließend die Bibliothek zum Aufrufen von MeCab mit Python.

ELMo_BERT_embedding.ipynb


!pip install mecab-python3
import MeCab

In der japanischen Version von ELMo, die dieses Mal verwendet wird, müssen die durch MeCab getrennten Token eingegeben werden. Lesen Sie daher den zuvor erstellten Datensatz und konvertieren Sie ihn in Token.

ELMo_BERT_embedding.ipynb


%cd /content/drive/'My Drive'/anomaly_detection
dev_arr = np.load('dev.npy')
test_arr = np.load('test.npy')

#Schneiden Sie nur Sätze aus
dev_texts = dev_arr[:, 1]
test_texts = test_arr[:, 1]

def MeCab_tokenizer(texts):
    mecab = MeCab.Tagger(
        "-Owakati -d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd")
    token_list = []
    for s in texts:
        parsed = mecab.parse(s).replace('\n', '')
        token_list += [parsed.split()]
    return token_list

dev_tokens = MeCab_tokenizer(dev_texts)
test_tokens = MeCab_tokenizer(test_texts)

Da das diesmal verwendete BERT-Programm aus einer Textdatei eingegeben werden muss, schreiben Sie es in die Datei. Dies muss nicht im Voraus getoken werden.

ELMo_BERT_embedding.ipynb


with open('dev_text.txt', mode='w') as f:
    f.write('\n'.join(dev_texts))
with open('test_text.txt', mode='w') as f:
    f.write('\n'.join(test_texts))

ELMo Das ELMo-Modell verwendet diese Implementierung (https://github.com/HIT-SCIR/ELMoForManyLangs) mit vorgefertigten Standardparametern. Die spezifische Verwendung ist

Ich bezog mich auf. Installieren Sie zunächst die erforderlichen Bibliotheken und klonen Sie das Repository.

ELMo_BERT_embedding.ipynb


!pip install overrides
!git clone https://github.com/HIT-SCIR/ELMoForManyLangs.git

Durch Ausführen von setup.py wird die Installation abgeschlossen.

ELMo_BERT_embedding.ipynb


%cd ./ELMoForManyLangs
!python setup.py install
%cd ..

Laden Sie dann das vorgefertigte Stockmark-Modell von diesem [Link] herunter (https://drive.google.com/drive/folders/1sau1I10rFeAn8BDk8eZDL5qaEjTlNghp). Es gibt zwei Arten von Modellen: "wortbasiertes eingebettetes Modell" und "zeichenbasiertes / wortbasiertes eingebettetes Modell". Dieses Mal werden wir das "wortbasierte eingebettete Modell" verwenden. Ich habe die heruntergeladene Datei im Ordner ". / ELMo_ja_word_level" des bereitgestellten Google Drive abgelegt. Der Inhalt des Ordners sollte folgendermaßen aussehen:

ELMo_BERT_embedding.ipynb


!ls ./ELMo_ja_word_level/
# char.dic  config.json  configs  encoder.pkl  token_embedder.pkl  word.dic

Das Modell ist jetzt fertig. Erstellen Sie eine "Embedder" -Instanz und definieren Sie eine Funktion zum Berechnen des eingebetteten Vektors des Textes.

ELMo_BERT_embedding.ipynb


from ELMoForManyLangs.elmoformanylangs import Embedder
from overrides import overrides

elmo_model_path = "./ELMo_ja_word_level"
elmo_embedder = Embedder(elmo_model_path, batch_size=64)

def get_elmo_embeddings(token_list, batch_size=64):
    length = len(token_list)
    n_loop = int(length / batch_size) + 1
    sent_emb = []
    for i in range(n_loop):
        token_emb = elmo_embedder.sents2elmo(
            token_list[batch_size*i: min(batch_size*(i+1), length)])
        for emb in token_emb:
          # sum over tokens to obtain the embedding for a sentence
          sent_emb.append(sum(emb))
    return np.array(sent_emb)

Die Ausgabe des Modells ist ein eingebetteter Vektor für jedes Eingabetoken. Die Summe aller Tokenvektoren im Text wird als Texteinbettungsvektor verwendet. Die Dimension des ELMo-Vektors beträgt 1024.

ELMo_BERT_embedding.ipynb


dev_elmo_embeddings = get_elmo_embeddings(dev_tokens)
test_elmo_embeddings = get_elmo_embeddings(test_tokens)
print(dev_elmo_embeddings.shape, test_elmo_embeddings.shape)
# (4159, 1024) (2060, 1024)

np.save('dev_elmo_embeddings.npy', dev_elmo_embeddings)
np.save('test_elmo_embeddings.npy', test_elmo_embeddings)

BERT Für BERT verwenden wir das von Stockmark veröffentlichte vorgefertigte Modell. Ich habe die TensorFlow-Version von diesem Download-Link heruntergeladen und in den Ordner ". / BERT_base_stockmark" gestellt. Der Inhalt des Ordners ist wie folgt.

ELMo_BERT_embedding.ipynb


!ls ./BERT_base_stockmark
# bert_config.json  vocab.txt  output_model.ckpt.index  output_model.ckpt.meta output_model.ckpt.data-00000-of-00001 

Importieren Sie den Tensorflow, indem Sie die Serie 1.x angeben.

ELMo_BERT_embedding.ipynb


%tensorflow_version 1.x
import tensorflow as tf

Klonen Sie den Modellkörper aus dem offiziellen Repository (https://github.com/google-research/bert).

ELMo_BERT_embedding.ipynb


!git clone https://github.com/google-research/bert.git

Das offizielle Repository verfügt über den Skriptcode "extract_features.py" zum Extrahieren des eingebetteten Vektors. Wenn es sich also um die englische Originalversion handelt, können Sie ihn einfach ausführen, aber das japanische Vorlernmodell verwenden Muss einige Dateien bearbeiten.

Ändern Sie tokenization.py, um MeCab als Tokenizer zu verwenden, indem Sie den Anweisungen zur Verwendung von [dieser Seite] folgen (https://qiita.com/mkt3/items/3c1278339ff1bcc0187f). Ändern Sie zunächst die Funktion "convert_tokens_to_ids (vocab, tokens)" so, dass 1 zurückgegeben wird, was die ID von [UNK] für unbekannte Wörter wie folgt ist.

bert/tokenization.py


def convert_tokens_to_ids(vocab, tokens):
  # modify so that it returns id=1 which means [UNK] when a token is not in vocab
  output = []
  for t in tokens:
    if t in vocab.keys():
      i = vocab[t]
    else:   # if t is [UNK]
      i = 1
    output.append(i)
  return output

Schreiben Sie außerdem die Klasse "FullTokenizer (Objekt)" wie folgt um, um MecabTokenizer anstelle von WordpieceTokenizer zu verwenden. Andere japanische vorgelernte BERTs haben MeCab oder Human ++, die sie in morphologische Elemente unterteilen und dann Wordpiece Tokenizer anwenden. Das Standardmodell verwendet jedoch nur Mecab.

bert/tokenization.py


class FullTokenizer(object):
  """Runs end-to-end tokenziation."""

  def __init__(self, vocab_file, do_lower_case=True):
    self.vocab = load_vocab(vocab_file)
    self.inv_vocab = {v: k for k, v in self.vocab.items()}
    #self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case)
    #self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab)
    # use Mecab
    self.mecab_tokenizer = MecabTokenizer()

  def tokenize(self, text):
    split_tokens = []
    # for token in self.basic_tokenizer.tokenize(text):
    # use Mecab
    for token in self.mecab_tokenizer.tokenize(text):
    	split_tokens.append(token)

    return split_tokens

  def convert_tokens_to_ids(self, tokens):
    #return convert_by_vocab(self.vocab, tokens)
    return convert_tokens_to_ids(self.vocab, tokens)

  def convert_ids_to_tokens(self, ids):
    return convert_by_vocab(self.inv_vocab, ids)

Fügen Sie abschließend die Klasse MecabTokenizer hinzu, die BasicTokenizer erbt.

bert/tokenization.py


class MecabTokenizer(BasicTokenizer):
  def __init__(self):
    import MeCab
    path = "-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd"
    self._mecab = MeCab.Tagger(path)

  def tokenize(self, text):
    """Tokenizes a piece of text."""
    text = convert_to_unicode(text.replace(' ', ''))
    text = self._clean_text(text)

    mecab_result = self._mecab.parseToNode(text)
    split_tokens = []
    while mecab_result:
    	if mecab_result.feature.split(",")[0] != 'BOS/EOS':
	        split_tokens.append(mecab_result.surface)
    	mecab_result = mecab_result.next

    output_tokens = whitespace_tokenize(" ".join(split_tokens))
    print(split_tokens)
    return output_tokens

Jetzt können Sie BERT verwenden, das bereits auf Japanisch gelernt wurde. Extract_features.py gibt jedoch eine Datei aus, in der die eingebetteten Vektoren für alle Token des eingegebenen Textes gespeichert sind. Sie wird also ausgegeben, wenn viele Sätze vorhanden sind. Die Dateigröße wird groß sein. In dieser Aufgabe verwenden wir nur den eingebetteten Vektor des Textes, nicht den eingebetteten Vektor jedes Tokens. Daher schreiben wir "extract_features.py" neu, um nur diesen auszugeben. Als Einbettungsvektor des Satzes wird der Durchschnitt der Vektoren aller Token wie in ELMo verwendet. Für die Ebene, aus der der eingebettete Vektor extrahiert wird, gibt der ursprüngliche Code den Vektor der angegebenen Ebene aus, ändert ihn jedoch so, dass die Vektoren aller angegebenen Ebenen gemittelt werden. Die Ausgabe wird im npy-Format von numpy gespeichert, also im Header-Teil

bert/extract_features.py


import numpy as np

Hinzugefügt. Ändern Sie außerdem den letzten Teil der Hauptfunktion, input_fn = input_fn_builder (nach der Zeile, wie folgt.

bert/extract_features.py


  input_fn = input_fn_builder(
      features=features, seq_length=FLAGS.max_seq_length)

  arr = []
  for result in estimator.predict(input_fn, yield_single_examples=True):
    cnt = 0
    for (i, token) in enumerate(feature.tokens):
      for (j, layer_index) in enumerate(layer_indexes):
        layer_output = result["layer_output_%d" % j]
        if token != '[CLS]' and token != '[SEP]':
          if cnt == 0:
            averaged_emb = np.array(layer_output[i:(i + 1)].flat)
          else:
            averaged_emb += np.array(layer_output[i:(i + 1)].flat)
          cnt += 1
    averaged_emb /= cnt
    arr += [averaged_emb]
  np.save(FLAGS.output_file, arr)

Nachdem Sie fertig sind, führen Sie den folgenden Befehl in Ihrem Notizbuch ELMo_BERT.ipynb aus. Als Schicht zum Extrahieren des eingebetteten Vektors werden die letzten 5 Schichten mit Ausnahme der letzten Schicht verwendet.

ELMo_BERT_embedding.ipynb


#BERT Ausführung dev
!python ./bert/extract_features.py \
  --input_file=dev_text.txt \
  --output_file=dev_bert_embeddings.npy \
  --vocab_file=./BERT_base_stockmark/vocab.txt \
  --bert_config_file=./BERT_base_stockmark/bert_config.json \
  --init_checkpoint=./BERT_base_stockmark/output_model.ckpt \
  --layers -2,-3,-4,-5,-6

ELMo_BERT_embedding.ipynb


#BERT-Ausführungstest
!python ./bert/extract_features.py \
  --input_file=test_text.txt \
  --output_file=test_bert_embeddings.npy \
  --vocab_file=./BERT_base_stockmark/vocab.txt \
  --bert_config_file=./BERT_base_stockmark/bert_config.json \
  --init_checkpoint=./BERT_base_stockmark/output_model.ckpt \
  --layers -2,-3,-4,-5,-6

USE USE verwendet eine leichtgewichtige Version (Multilingual, CNN version, v3) eines Modells, das in mehreren Sprachen einschließlich Japanisch gelernt wurde. .. Die Verwendung erfolgt wie in Vorheriger Artikel erläutert. TensorFlow verwendet die Version 2.x-Serie.

USE_embedding.ipynb


import tensorflow_hub as hub
import tensorflow_text
import tensorflow as tf

from google.colab import drive
drive.mount('/content/drive')

USE_embedding.ipynb


%cd /content/drive/'My Drive'/anomaly_detection

use_url = 'https://tfhub.dev/google/universal-sentence-encoder-multilingual/3'
embed = hub.load(use_url)

def get_use_embeddings(texts, batch_size=100):
    length = len(texts)
    n_loop = int(length / batch_size) + 1
    embeddings = use_embedder(texts[: batch_size])
    for i in range(1, n_loop):
        arr = use_embedder(texts[batch_size*i: min(batch_size*(i+1), length)])
        embeddings = tf.concat([embeddings, arr], axis=0)
    return np.array(embeddings)

USE_embedding.ipynb


dev_use_embeddings = get_use_embeddings(dev_arr[:, 1])
test_use_embeddings = get_use_embeddings(test_arr[:, 1])
print(dev_use_embeddings.shape, test_use_embeddings.shape)
# (4159, 512) (2060, 512)

np.save('dev_use_embeddings.npy', dev_use_embeddings)
np.save('test_use_embeddings.npy', test_use_embeddings)

Entwicklung eines Anomalieerkennungsmodells

Nachdem wir die eingebetteten Vektoren für die drei Modelle haben, werden wir ein Anomalieerkennungsmodell erstellen und die Genauigkeit vergleichen. Laden Sie Entwicklungs- / Testdaten und eingebettete Vektordaten in das Notizbuch anomaly_detection.ipynb.

anomaly_detection.ipynb


import numpy as np
from scipy.stats import chi2

from google.colab import drive
drive.mount('/content/drive')

anomaly_detection.ipynb


%cd /content/drive/'My Drive'/anomaly_detection
dev_arr = np.load('dev.npy')
test_arr = np.load('test.npy')
dev_elmo_embeddings = np.load('dev_elmo_embeddings.npy')
test_elmo_embeddings = np.load('test_elmo_embeddings.npy')
dev_bert_embeddings = np.load('dev_bert_embeddings.npy')
test_bert_embeddings = np.load('test_bert_embeddings.npy')
dev_use_embeddings = np.load('dev_use_embeddings.npy')
test_use_embeddings = np.load('test_use_embeddings.npy')

print(dev_elmo_embeddings.shape, test_elmo_embeddings.shape)
# (4159, 1024) (2060, 1024)
print(dev_bert_embeddings.shape, test_bert_embeddings.shape)
# (4159, 768) (2060, 768)
print(dev_use_embeddings.shape, test_use_embeddings.shape)
# (4159, 512) (2060, 512)

Weitere Informationen zum Anomalieerkennungsmodell finden Sie im vorherigen Artikel (https://qiita.com/jovyan/items/e5d2dc7ffabc2353db38). Der Code des Modells ist in der folgenden Klasse "DirectionalAnomalyDetection" zusammengefasst.

anomaly_detection.ipynb


class DirectionalAnomalyDetection:
  def __init__(self, dev_embeddings, test_embeddings, dev_arr):
    self.dev_embeddings = self.normalize_arr(dev_embeddings)
    self.test_embeddings = self.normalize_arr(test_embeddings)
    self.dev_arr = dev_arr
    self.mu, self.anom  = self.calc_anomality(self.dev_embeddings)
    self.mhat, self.shat = self.calc_chi2params(self.anom)
    print("mhat: {:.3f}".format(self.mhat))
    print("shat: {:.3e}".format(self.shat))
    self.anom_test = None

  def normalize_arr(self, arr):  
    norm = np.apply_along_axis(lambda x: np.linalg.norm(x), 1, arr) # norm of each vector
    normed_arr = arr / np.reshape(norm, (-1,1))
    return normed_arr

  def calc_anomality(self, embeddings):
    mu = np.mean(embeddings, axis=0)
    mu /= np.linalg.norm(mu)
    anom = 1 - np.inner(mu, embeddings)
    return mu, anom

  def calc_chi2params(self, anom):
    anom_mean = np.mean(anom)
    anom_mse = np.mean(anom**2) - anom_mean**2
    mhat = 2 * anom_mean**2 / anom_mse
    shat = 0.5 * anom_mse / anom_mean
    return mhat, shat

  def decide_ath_by_alpha(self, alpha, x_ini, max_ite=100, eps=1.e-12):
    # Newton's method
    x = x_ini
    for i in range(max_ite):
      xnew = x - (chi2.cdf(x, self.mhat, loc=0, scale=self.shat) 
            - (1 - alpha)) / chi2.pdf(x, self.mhat, loc=0, scale=self.shat)
      if abs(xnew - x) < eps:
        print("iteration: ", i+1)
        break
      x = xnew
    print("ath: {:.4f}".format(x))
    return x

  def decide_ath_by_labels(self, x_ini, max_ite=100, eps=1.e-12):
    anom0 = self.anom[self.dev_arr[:, 0] == '0.0']
    anom1 = self.anom[self.dev_arr[:, 0] == '1.0']
    mhat0, shat0 = self.calc_chi2params(anom0)
    mhat1, shat1 = self.calc_chi2params(anom1)
    ath = self._find_crossing_point(mhat0, shat0, mhat1, shat1, x_ini, max_ite, eps)
    print("ath: {:.4f}".format(ath))
    return ath

  def _find_crossing_point(self, mhat0, shat0, mhat1, shat1, x_ini, max_ite, eps):
    # Newton's method
    x = x_ini
    for i in range(max_ite):
      xnew = x - self._crossing_func(x, mhat0, shat0, mhat1, shat1)
      if abs(xnew - x) < eps:
        print("iteration: ", i+1)
        break
      x = xnew
    return x

  def _crossing_func(self, x, mhat0, shat0, mhat1, shat1):
    chi2_0 = chi2.pdf(x, mhat0, loc=0, scale=shat0)
    chi2_1 = chi2.pdf(x, mhat1, loc=0, scale=shat1)
    nume = x * (chi2_0 - chi2_1)
    deno = (mhat0 * 0.5 - 1 - x / shat0 * 0.5) * chi2_0 \
          -(mhat1 * 0.5 - 1 - x / shat1 * 0.5) * chi2_1
    return nume / deno

  def predict(self, ath):
    self.anom_test = 1 - np.inner(self.mu, self.test_embeddings)
    predict_arr = (self.anom_test > ath).astype(np.int)
    return predict_arr

Erstellen Sie eine Modellinstanz für jeden der drei Datentypen. Die Parameter $ \ hat {m} $ und $ \ hat {s} $, wenn die Anomalieverteilung mit der Chi-Quadrat-Verteilung angepasst wird, werden ausgegeben.

anomaly_detection.ipynb


#-- ELMo
dad_elmo = DirectionalAnomalyDetection(dev_elmo_embeddings, test_elmo_embeddings, dev_arr)
# mhat: 6.813
# shat: 3.011e-03

#-- BERT
dad_bert = DirectionalAnomalyDetection(dev_bert_embeddings, test_bert_embeddings, dev_arr)
# mhat: 20.358
# shat: 6.912e-03

#-- USE
dad_use = DirectionalAnomalyDetection(dev_use_embeddings, test_use_embeddings, dev_arr)
# mhat: 36.410
# shat: 1.616e-02

Die effektive Dimension $ \ hat {m} $ für jedes Modell ist viel kleiner als die tatsächliche eingebettete Vektordimension. Es ist interessant, dass die effektive Dimension umso kleiner ist, je größer die Dimension des tatsächlichen eingebetteten Vektors ist (ELMo: 1024, BERT: 768, USE: 512). Die Chi-Quadrat-Verteilung mit den auf diese Weise bestimmten Parametern wird zusammen mit der tatsächlichen Verteilung des Anomaliegrades wie folgt aufgetragen. Aufgrund der Kleinheit der Anomaliedimension ist der Spitzenwert der Anomalieverteilung auch bei BERT und insbesondere bei ELMo gering. Es ist auch ersichtlich, dass die Anpassung der Chi-Quadrat-Verteilung für ELMo und BERT relativ schlecht ist.

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/fadbad99-0e34-1c35-55c2-699617b0f370.png ", height=200><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/e4c3dee9-2af5-d68a-966c-59ebebf7c52a.png ", height=200><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/909631e1-a7f4-8eb0-14f1-f3d2414b7b1f.png ", height=200>

Suchen Sie als Nächstes den Schwellenwert $ a_ \ text {th} $ für den Grad der Anomalie. Im vorherigen Artikel wurde die Formel für die vorab festgelegte Falsch-Positiv-Rate $ \ alpha $ angegeben $ 1- \ alpha = \ int_0 ^ {a_ \ text {th}} \\! Dx \, \ chi ^ 2 (x | \ hat {m}, \ hat {s}) Durch Lösen von $ wird der Schwellenwert $ Bestimmt a_ \ text {th} $. Diese Methode ist in der Methode entscheidend_ath_by_alpha der oben definierten Klasse implementiert. Es werden nur die Ergebnisse angezeigt, aber diese Methode führt zu einer signifikant geringeren Klassifizierungsgenauigkeit für Testdaten in einem der drei Modelle. Der Wert der Fehlalarmrate beträgt 0,01, was dem vorherigen Zeitpunkt entspricht.

Accuracy Precision Recall F1
ELMo 0.568 0.973 0.141 0.246
BERT 0.580 0.946 0.170 0.288
USE 0.577 0.994 0.155 0.269

Um zu sehen, warum es so ungenau ist, zeichnen Sie das Ausreißerhistogramm für die Testdaten getrennt für normale und anomale Daten. Der Berg links sind normale Daten, und der Berg rechts sind abnormale Daten. Die rote vertikale Linie stellt den Schwellenwert $ a_ \ text {th} $ dar, der durch Entscheidung_ath_by_alpha erhalten wird. Hier werden nur die Ergebnisse für USE angezeigt, die Ergebnisse für andere Modelle sind jedoch ähnlich. Obwohl normale Daten und abnormale Daten bis zu einem gewissen Grad getrennt werden können, ist ersichtlich, dass die meisten Daten als normale Daten klassifiziert werden, da der Schwellenwert zu groß ist. <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/800c812d-897d-8472-bac2-0f6bf5431e97.png ", height=240> In den im vorherigen Artikel gezeigten Ergebnissen passt die Chi-Quadrat-Verteilung gut zur Verteilung des Grads der Anomalie, und die Verteilung der Normaldaten und die Verteilung der anomalen Daten sind ziemlich klar voneinander getrennt, so dass mit dem durch das obige Verfahren bestimmten Schwellenwert eine hohe Genauigkeit erreicht wird. Wurde erhalten. Bei diesen Daten funktionierte dies jedoch nicht, da die Überlappung zwischen den normalen Daten und der abnormalen Datenverteilung relativ groß ist.

Jetzt möchte ich den Schwellenwert $ a_ \ text {th} $ mit einer anderen Methode bestimmen, aber es scheint keinen weiteren Weg zu geben, unbeaufsichtigt zu lernen. Daher setzen wir in der Einstellung für überwachtes Lernen den Schwellenwert, dass das normale / abnormale Flag der Entwicklungsdaten bekannt ist. Führen Sie insbesondere die folgenden Schritte aus.

    1. Der Grad der Abnormalität der Entwicklungsdaten wird in diejenigen für normale Daten und diejenigen für abnormale Daten unterteilt.
  1. Passen Sie jede der beiden Anomalieverteilungen mit einer Chi-Quadrat-Verteilung an.
  2. Der Schnittpunkt der beiden Chi-Quadrat-Verteilungen sei die Schwelle für den Grad der Anomalie. Die folgende Abbildung zeigt das Ergebnis der Befolgung dieses Verfahrens für den von USE berechneten Grad der Abnormalität. Die durchgezogenen schwarzen und blauen Linien repräsentieren die beiden Chi-Quadrat-Verteilungen, und die gepunktete rote Linie repräsentiert den Schwellenwert. <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/d05ab151-a0a6-b681-8769-9704e6c9c536.png ", height=240> Diese Prozedur zum Ermitteln des Schwellenwerts ist in der Methode entscheidend_ath_by_labels der Klasse DirectionalAnomalyDetection implementiert. Führen Sie sie daher wie folgt aus.

anomaly_detection.ipynb


#-- ELMo
ath_elmo = dad_elmo.decide_ath_by_labels(x_ini=0.02)
# iteration:  5
# ath: 0.0312

#-- BERT
ath_bert = dad_bert.decide_ath_by_labels(x_ini=0.2)
# iteration:  6
# ath: 0.1740

#-- USE
ath_use = dad_use.decide_ath_by_labels(x_ini=0.8)
# iteration:  5
# ath: 0.7924

Der berechnete Schwellenwert wird verwendet, um Vorhersagen für die Testdaten zu treffen und die Genauigkeit zu bewerten.

anomaly_detection.ipynb


#Prognose
predict_elmo = dad_elmo.predict(ath_elmo)
predict_bert = dad_bert.predict(ath_bert)
predict_use = dad_use.predict(ath_use)

#Richtige Antwortdaten
answer = test_arr[:, 0].astype(np.float)

anomaly_detection.ipynb


#Funktion zur Genauigkeitsbewertung
def calc_precision(answer, predict, title, export_fig=False):
  acc = accuracy_score(answer, predict)
  precision = precision_score(answer, predict)
  recall = recall_score(answer, predict)
  f1 = f1_score(answer, predict)
  cm = confusion_matrix(answer, predict)
  print("Accuracy: {:.3f}, Precision: {:.3f}, Recall: {:.3f}, \
        F1: {:.3f}".format(acc, precision, recall, f1))

  plt.rcParams["font.size"] = 18
  cmd = ConfusionMatrixDisplay(cm, display_labels=[0,1])
  cmd.plot(cmap=plt.cm.Blues, values_format='d')
  plt.title(title)
  if export_fig:
      plt.savefig("./cm_" + title + ".png ", bbox_inches='tight')
  plt.show()
  return [acc, precision, recall, f1]

anomaly_detection.ipynb


#Genauigkeitsbewertung
_ = calc_precision(answer, predict_elmo, title='ELMo', export_fig=True)
_ = calc_precision(answer, predict_bert2, title='BERT', export_fig=True)
_ = calc_precision(answer, predict_use, title='USE', export_fig=True)

Die Ergebnisse sind wie folgt.

Accuracy Precision Recall F1
ELMo 0.916 0.910 0.923 0.917
BERT 0.851 0.847 0.858 0.852
USE 0.946 0.931 0.963 0.947

Verwirrte Matrix <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/ec194af5-3dc1-836f-0739-18e3dceb6529.png ", height=200><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/a539e4d5-161d-2c87-5fc0-5da16b9780ee.png ", height=200><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/70faf102-db47-c39f-4908-d8dc529cad3f.png ", height=200>

Durch die Verwendung des in den Einstellungen für überwachtes Lernen festgelegten Schwellenwerts wird die Genauigkeit jedes Modells erheblich verbessert. Beim Vergleich der drei Modelle ist die Genauigkeit von USE bei allen Indikatoren am höchsten, und umgekehrt ist die Genauigkeit von BERT am niedrigsten.

Zeichnen Sie schließlich die Anomalie der Testdaten für jedes Modell separat für normale und anomale Daten. <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/98cee787-b773-e0fe-b10c-09262fc9440c.png ", height=200><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/c57dbbdc-4b5e-06cf-d542-b6b35c5344e5.png ", height=200><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613488/fe357b32-518e-58b8-07eb-fd1e2c74bb70.png ", height=200> Die rote vertikale gepunktete Linie stellt den Schwellenwert dar, der mit dem richtigen Antwortetikett erhalten wurde. Es ist ein Schwellenwert, der aus den Entwicklungsdaten bestimmt wird, die nur 1% abnormaler Daten enthalten, aber es ist ersichtlich, dass er sich sicherlich am Schnittpunkt der Verteilung von normalen Daten und abnormalen Daten befindet. In Bezug auf BERT ist ersichtlich, dass sich die Verteilung normaler und abnormaler Daten stark überlappt und daher die Genauigkeit am geringsten ist.

abschließend

Da es nicht möglich war, den Schwellenwert für den Grad der Anomalie mit der Einstellung des unbeaufsichtigten Lernens wie im vorherigen Artikel richtig einzustellen, haben wir mit einem Lehrer ein Anomalieerkennungsmodell erstellt. Selbst wenn es einen Lehrer gibt, wird das richtige Antwortetikett nur verwendet, um den Schwellenwert für den Grad der Abnormalität zu bestimmen. Daher bestimmt die Qualität des Encoder-Modells die Genauigkeit, dh wie gut die Verteilung des Abnormalitätsgrads von normalen Daten und abnormalen Daten getrennt ist. Es hing davon ab, was ich tun konnte. Das genaueste Encoder-Modell war daher USE. Ist die Aufgabe im Zusammenhang mit der Kosinusähnlichkeit immer noch das, was USE gut kann?

Recommended Posts

Erkennen Sie Anomalien in Sätzen mit ELMo, BERT, USE
Verwenden Sie MeCab, um schlampige Sätze "langsam" zu übersetzen.
Verwendung von Klassen in Theano
Mock in Python-Wie man Mox benutzt
Verwendung von SQLite in Python
Verwendung von ChemSpider in Python
Verwendung von PubChem mit Python
Verwenden Sie ELMo und BERT, um die Wortähnlichkeit für Polynomwörter zu bestimmen
Verwendung berechneter Spalten in CASTable
[Einführung in Python] Wie verwende ich eine Klasse in Python?
Verwendung von Google Test in C-Sprache
Einfache Möglichkeit, Wikipedia mit Python zu verwenden
Mindestkenntnisse zur Verwendung von Form with Flask
Verwendung von Anacondas Interpreter mit PyCharm
Verwendung von __slots__ in der Python-Klasse
Verwendung regulärer Ausdrücke in Python
So verwenden Sie Map in ViewPager von Android
Verwendung ist und == in Python
Verwendung der C-Bibliothek in Python
Verwendung der Python-Bildbibliothek in der Python3-Serie
EP 11 Verwenden Sie "zip", um Iteratoren parallel zu verarbeiten
Verwenden Sie ein Kryptografiemodul, das OpenSSL in Python verarbeitet
Wie man tkinter mit Python in Pyenv benutzt
Loggen Sie sich mit json mit pygogo ein.
Verwenden Sie os.getenv, um Umgebungsvariablen in Python abzurufen
[Erklärung zur Implementierung] Verwendung der japanischen Version von BERT in Google Colaboratory (PyTorch)
[Für Anfänger] Wie man den Befehl say mit Python benutzt!
Ich möchte mich mit Backpropagation (tf.custom_gradient) (Tensorflow) selbst verwenden.
Verwendung von Bootstrap in der generischen Klassenansicht von Django
Verwendung der Template-Engine in einer Dateianwendung von Pyramid
Verwendung der Exist-Klausel in Django Queryset
Verwendung von Variablen in systemd Unit-Definitionsdateien
Praktisch, um Matplotlib-Unterzeichnungen in for-Anweisungen zu verwenden
Ich habe versucht zusammenzufassen, wie man Pandas von Python benutzt
Verwendung des in Lobe in Python erlernten Modells
Wie man Decorator in Django benutzt und wie man es macht
Verwenden Sie das Datum auf der x-Achse des in seaborn dargestellten tsplot
Verwendung des japanischen Spacy-Modells mit Google Colaboratory
Ich möchte R-Datensatz mit Python verwenden