Abhängig von der Datenbank werden Vor- und Nachname zusammen gespeichert, und es besteht möglicherweise der Wunsch, sie mechanisch zu trennen. Es ist überraschend schwierig, dies selbst mit einer vollständigen Liste von Nachnamen zu tun. BERT ist momentan ein heißes Thema, aber ich möchte es gerne vorstellen, da ich den Nachnamen und den Vornamen mit hoher Genauigkeit trennen konnte, indem ich den Namen der Person lernte.
Der Quellcode ist ziemlich lang, daher werde ich ihn anhand des Ergebnisses anzeigen. Wir konnten 1.200 Verifizierungsdaten mit einer Genauigkeit von ** 99,0% ** trennen. Der Inhalt der Verifizierungsdaten und ein Teil der Vorhersage (nur Nachname) sind wie folgt.
last_name | first_name | full_name | pred | |
---|---|---|---|---|
89 | Gestalten | Mayu | Mayu Katabe | Gestalten |
1114 | Kumazoe | Norio | Norio Kumazoe | Kumazoe |
1068 | Kimoto | Souho | Kimoto Soho | Kimoto |
55 | Haus | Hiroki | Hiroki Iegami | Haus |
44 | Basic | Shodai | Kishodai | Basic |
Die 12 fehlgeschlagenen Fälle sind wie folgt. Selbst für Menschen wird Toshikatsu Sabune wahrscheinlich in Toshikatsu Sabane unterteilt.
last_name | first_name | full_name | pred | |
---|---|---|---|---|
11 | Toshi Saburi | Sieg | Toshikatsu Sabane | Sabane |
341 | Bürste | Kasumi | Sumi-Pinsel | Pinsel Blume |
345 | Schintoismus | Shinichi | Shinichi Shinto | Makoto Shinto |
430 | Kastanie | Kanae | Kanae Kuri | Kurika |
587 | Keisuke | Nina | Kei Ryojina | Kei |
785 | Bansho | Gut | Bansho | Wende |
786 | Yutaka | Wakana | Kana Toyowa | Toyokazu |
995 | Seri | Yu | Seriyoshi | Se |
1061 | Damit | Echte Prinzessin | Irgendwann | Somi |
1062 | Spann | Obst | Kogi Nomi | Koki |
1155 | Hotaka | Natsuho | Hotaka Natsuho | Hotakaho |
1190 | Extrem durchschnittlich | Traum | Extremer Traum | sehr |
Übrigens: Verwenden Sie nur das voreingestellte Wörterbuch (ipadic) mit janome (ein morphologisches Analysetool, das nur mit Python ausgeführt wird und die gleiche Leistung wie Mecab aufweist) Bei einer einfachen Trennung von Vor- und Nachnamen betrug die Genauigkeit 34,5%.
def extract_last_name(sentence):
for token in tokenizer.tokenize(sentence):
if 'Nachname' in token.part_of_speech:
return token.surface
df['pred'] = df['full_name'].apply(lambda full_name: extract_last_name(full_name))
Wenn Sie das Nachnamen-Wörterbuch von Last Name Database erhalten und es wie folgt an janome weiterleiten, ist es korrekt. Hat sich auf 79,7% verbessert.
tokenizer2 = Tokenizer('last_name_dic.csv', udic_enc="utf8")
def extract_last_name2(sentence):
token_arr = [token for token in tokenizer2.tokenize(sentence)]
if 'Nachname' in token_arr[0].part_of_speech:
return token_arr[0].surface
df['pred2'] = df['full_name'].apply(lambda full_name: extract_last_nam2(full_name))
Vielleicht wird das Hinzufügen einer Namensliste die Genauigkeit weiter verbessern, aber es schien ziemlich schwierig zu sein, sie zu erhalten und zu verarbeiten, daher würde ich sie gerne überprüfen, wenn ich Zeit habe. (Ich glaube nicht)
Importieren Sie zuerst das, was Sie brauchen.
import pandas as pd
import numpy as np
from transformers import BertConfig, BertTokenizer, BertJapaneseTokenizer, BertForTokenClassification
from keras.preprocessing.sequence import pad_sequences
import torch
import MeCab
import math
Als Voreinstellung denke ich, dass es viele Beispiele für die Verwendung von "Bert-Base-Japanisch-Ganzwort-Maskierung" gibt, aber da es schwierig ist, sie zum Zeitpunkt der Tokenisierung seltsam zu teilen, diesmal jeweils ein Zeichen nach dem anderen Verwenden Sie das Zeichen, um zu teilen.
tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-char-whole-word-masking')
Ich habe Amazing Name Generator als Quelle für die Lehrerdaten verwendet. Es ist eine interessante Seite, wenn man nur die Ungewöhnlichkeit des Namens betrachtet, der quantifiziert wird. Diesmal betrug die Anzahl der Namen 48.000 und die Arten der Nachnamen etwa 22.000. Es ist ziemlich weit verbreitet, einschließlich kleinerer Nachnamen. Das einzige Format von csv ist full_name, last_name, first_name. Tokenisieren Sie zunächst jedes Zeichen mit dem folgenden Code.
df = pd.read_csv('name_list.csv')
text1s = list(df.full_name.values)
targets = list(df.last_name.values)
text1_tokenize = [tokenizer.encode(s) for s in text1s]
target_tokenize = [[tokenizer.encode(vv)[1:-1] for vv in v] for v in targets]
Als Fluss wird nach dem Teilen von full_name durch jeweils ein Zeichen die Wahrscheinlichkeit angegeben, dass jedes Zeichen 1 für den Nachnamen und 0 für den Vornamen ist. Damit BERT die richtigen Antwortdaten versteht, z. B. Taro Tanaka-> ['Ta', 'Mitte', 'Ta', 'Ro'] -> [1, 1, 0, 0] Ich werde es schaffen. Aufmerksamkeitsmasken ist einfach das Zielarray, das durch 1 ersetzt wird (möglicherweise nicht erforderlich).
def make_tags_arr(x, token):
start_indexes = arr_indexes(x, token)
max_len = len(x)
token_len = len(token)
arr = [0] * max_len
for i in start_indexes:
arr[i:i+token_len] = [1] * token_len
return arr
tags_ids = []
for i in range(len(text1_tokenize)):
text1 = text1_tokenize[i]
targets = target_tokenize[i]
tmp = [0] * len(text1)
for t in targets:
# [0,0,1,1,0,0...]Erstellen Sie ein Tag-Array
arr = make_tags_arr(text1, t)
tmp = [min(x + y, 1) for (x, y) in zip(tmp, arr)]
tags_ids.append(tmp)
attention_masks = [[float(i > 0) for i in ii] for ii in text1_tokenize]
BERT muss alle dieselbe Token-Array-Länge haben, damit das Auffüllen durchgeführt wird. Teilen Sie dann den Datensatz auf.
MAX_LEN = 32
input_ids = pad_sequences(text1_tokenize, maxlen=MAX_LEN, dtype="long", truncating="pre", padding="pre")
tags_ids = pad_sequences(tags_ids, maxlen=MAX_LEN, dtype="long", truncating="pre", padding="pre")
attention_masks = pad_sequences(attention_masks, maxlen=MAX_LEN, dtype="long", truncating="pre", padding="pre")
from sklearn.model_selection import train_test_split
RAN_SEED = 2020
train_inputs, validation_inputs, train_labels, validation_labels = train_test_split(input_ids, tags_ids, random_state=RAN_SEED, test_size=0.1)
train_masks, validation_masks = train_test_split(attention_masks, random_state=RAN_SEED, test_size=0.2)
train_inputs = torch.LongTensor(train_inputs)
validation_inputs = torch.LongTensor(validation_inputs)
train_labels = torch.LongTensor(train_labels)
validation_labels = torch.LongTensor(validation_labels)
train_masks = torch.LongTensor(train_masks)
validation_masks = torch.LongTensor(validation_masks)
Verwenden Sie GPUorCPU, um das Dataset zu laden. Laden Sie dann das vorgefertigte Modell.
if torch.cuda.is_available():
# Tell PyTorch to use the GPU.
device = torch.device("cuda")
print('There are %d GPU(s) available.' % torch.cuda.device_count())
print('We will use the GPU:', torch.cuda.get_device_name(0))
# If not...
else:
print('No GPU available, using the CPU instead.')
device = torch.device("cpu")
from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler
batch_size = 32
# Create the DataLoader for our training set.
train_data = TensorDataset(train_inputs, train_masks, train_labels)
train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=batch_size)
# Create the DataLoader for our validation set.
validation_data = TensorDataset(validation_inputs, validation_masks, validation_labels)
validation_sampler = SequentialSampler(validation_data)
validation_dataloader = DataLoader(validation_data, sampler=validation_sampler, batch_size=batch_size)
from transformers import AdamW, BertConfig
model_token_cls = BertForTokenClassification.from_pretrained('cl-tohoku/bert-base-japanese-char-whole-word-masking', num_labels=2)
model_token_cls.cuda()
Zeigt eine Übersicht über das Modell. Es hat nichts mit der Verarbeitung zu tun, daher können Sie es überspringen.
# Get all of the model's parameters as a list of tuples.
params = list(model_token_cls.named_parameters())
print('The BERT model has {:} different named parameters.\n'.format(len(params)))
print('==== Embedding Layer ====\n')
for p in params[0:5]:
print("{:<55} {:>12}".format(p[0], str(tuple(p[1].size()))))
print('\n==== First Transformer ====\n')
for p in params[5:21]:
print("{:<55} {:>12}".format(p[0], str(tuple(p[1].size()))))
print('\n==== Output Layer ====\n')
for p in params[-4:]:
print("{:<55} {:>12}".format(p[0], str(tuple(p[1].size()))))
Genauigkeit definieren. Wird nur zum Anzeigen von Validierungsdaten verwendet. Hier ist es der F1-Wert. Wenn für jedes Zeichen der Nachname oder Vorname beurteilt wird und er hoch ist, nähert er sich 1.
import datetime
def flat_accuracy(pred_masks, labels, input_masks):
tp = ((pred_masks == 1) * (labels == 1)).sum().item()
fp = ((pred_masks == 1) * (labels == 0)).sum().item()
fn = ((pred_masks == 0) * (labels == 1)).sum().item()
tn = ((pred_masks == 0) * (labels == 0)).sum().item()
precision = tp/(tp+fp)
recall = tp/(tp+fn)
f1 = 2*precision*recall/(precision+recall)
return f1
def format_time(elapsed):
# Round to the nearest second.
elapsed_rounded = int(round((elapsed)))
# Format as hh:mm:ss
return str(datetime.timedelta(seconds=elapsed_rounded))
Dies ist der Hauptteil des Trainings.
from torch.optim import Adam
from transformers import get_linear_schedule_with_warmup
param_optimizer = list(model_token_cls.named_parameters())
no_decay = ["bias", "gamma", "beta"]
optimizer_grouped_parameters = [
{'params' : [p for n, p in param_optimizer if not any (nd in n for nd in no_decay)],
'weight_decay_rate' : 0.01},
{'params' : [p for n, p in param_optimizer if any(nd in n for nd in no_decay)],
'weight_decay_rate' : 0.0}
]
optimizer = Adam(optimizer_grouped_parameters, lr=3e-5)
epochs = 3
max_grad_norm = 1.0
total_steps = len(train_dataloader) * epochs
scheduler = get_linear_schedule_with_warmup(optimizer,
num_warmup_steps = 0,
num_training_steps = total_steps)
#Ausbildung
for epoch_i in range(epochs):
# TRAIN loop
model_token_cls.train()
train_loss = 0
nb_train_examples, nb_train_steps = 0, 0
t0 = time.time()
print('======== Epoch {:} / {:} ========'.format(epoch_i + 1, epochs))
print('Training...')
for step, batch in enumerate(train_dataloader):
if step % 40 == 0 and not step == 0:
elapsed = format_time(time.time() - t0)
print(' Batch {:>5,} of {:>5,}. Elapsed: {:}.'.format(step, len(train_dataloader), elapsed))
b_input_ids = batch[0].to(device)
b_input_mask = batch[1].to(device)
b_labels = batch[2].to(device)
# forward pass
loss = model_token_cls(b_input_ids, token_type_ids = None, attention_mask = b_input_mask, labels = b_labels)
# backward pass
loss[0].backward()
# track train loss
train_loss += loss[0].item()
nb_train_examples += b_input_ids.size(0)
nb_train_steps += 1
# gradient clipping
torch.nn.utils.clip_grad_norm_(parameters = model_token_cls.parameters(), max_norm = max_grad_norm)
# update parameters
optimizer.step()
scheduler.step()
model_token_cls.zero_grad()
# Calculate the average loss over the training data.
avg_train_loss = train_loss / len(train_dataloader)
print("")
print(" Average training loss: {0:.2f}".format(avg_train_loss))
print(" Training epcoh took: {:}".format(format_time(time.time() - t0)))
# ========================================
# Validation
# ========================================
print("")
print("Running Validation...")
t0 = time.time()
model_token_cls.eval()
eval_loss, eval_accuracy = 0, 0
nb_eval_steps, nb_eval_examples = 0, 0
for batch in validation_dataloader:
batch = tuple(t.to(device) for t in batch)
b_input_ids, b_input_mask, b_labels = batch
with torch.no_grad():
outputs = model_token_cls(b_input_ids, token_type_ids = None, attention_mask = b_input_mask, labels = b_labels)
result = outputs[1].to('cpu')
labels = b_labels.to('cpu')
input_mask = b_input_mask.to('cpu')
# Mask predicted label
pred_masks = torch.min(torch.argmax(result, dim=2), input_mask)
# Calculate the accuracy for this batch of test sentences.
tmp_eval_accuracy = flat_accuracy(pred_masks, labels, input_mask)
# Accumulate the total accuracy.
eval_accuracy += tmp_eval_accuracy
# Track the number of batches
nb_eval_steps += 1
# Report the final accuracy for this validation run.
print(" Accuracy: {0:.2f}".format(eval_accuracy/nb_eval_steps))
print(" Validation took: {:}".format(format_time(time.time() - t0)))
print("Train loss: {}".format(train_loss / nb_train_steps))
Während des Trainings ... Es dauerte ungefähr 10 Minuten.
Speichern Sie das trainierte Modell hier.
pd.to_pickle(model_token_cls, 'Vor- und Nachname Trennungsmodell.pkl')
Basierend auf den separat vorbereiteten Verifizierungsdaten geben wir die als Nachname bewerteten Zeichen in Schlüsselwörter ein. Die vom obigen Tokenizer definierten Zeichen sind 4.000 Zeichen, und die nicht in der Liste enthaltenen Zeichen sind [UNK]. Es scheint für den Tokenizer schwierig zu sein, sich an alles zu erinnern, einschließlich der Varianten. Deshalb habe ich beschlossen, in solchen Fällen eine spezielle Verarbeitung hinzuzufügen.
df = pd.read_csv('name_list_valid.csv')
keywords = []
MAX_LEN = 32
alls = list(df.full_name)
batch_size = 100
for i in range(math.ceil(len(alls)/batch_size)):
print(i)
s2 = list(df.full_name[i*batch_size:(i+1)*batch_size])
d = torch.LongTensor(pad_sequences([tokenizer.encode(s) for s in s2], maxlen=MAX_LEN, dtype="long", truncating="pre", padding="pre")).cuda()
attention_mask = (d > 0) * 1
output = model_token_cls(d, token_type_ids = None, attention_mask = attention_mask)
result = output[0].to('cpu')
pred_masks = torch.min(torch.argmax(result, dim=2), attention_mask.to('cpu'))
d = d.to('cpu')
pred_mask_squeeze = pred_masks.nonzero().squeeze()
b = d[pred_mask_squeeze.T.numpy()]
pred_mask_squeeze[:,1]=b
for j in range(len(s2)):
tmp = pred_mask_squeeze[pred_mask_squeeze[:,0] == j]
s = tokenizer.convert_ids_to_tokens(tmp[:,1])
#Wenn das Wiederherstellungsergebnis unbekannt ist, ermitteln Sie von Anfang an die Anzahl der Zeichen im Ergebnis.
if '[UNK]' in s:
s = s2[j][0:len(s)]
keywords.append(''.join(s))
Trotzdem mag das Urteil seltsam sein, wenn das gleiche Kanji im Nachnamen und im Namen enthalten ist, aber es scheint noch besser zu sein, wenn die Bedingung, dass die Nachnamen fortlaufend sind, später hinzugefügt wird. Ich fand heraus, dass ich, selbst wenn ich keine Liste mit Nachnamen und Namen erstellt hätte, gut lernen könnte, indem ich den vollständigen Namen bis zu einem gewissen Grad in BERT einfügte. Dieses Mal bin ich mir nicht sicher, was ich tue, aber durch die ordnungsgemäße Erstellung von Lehrerdaten kann dieselbe Implementierung auf die Schlüsselwortextraktionslogik in Sätzen angewendet werden.
Recommended Posts