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