En fonction de la base de données, le prénom et le nom sont stockés ensemble, et il peut y avoir un désir de les séparer mécaniquement. Il est étonnamment difficile de faire cela même avec une liste complète de noms de famille. Le BERT est un sujet brûlant en ce moment, mais je voudrais le présenter car j'ai pu séparer le prénom et le nom avec une grande précision en apprenant le prénom de la personne.
Le code source est assez long, je vais donc le montrer à partir du résultat. Nous avons pu séparer 1 200 données de vérification avec une précision de ** 99,0% **. Le contenu des données de vérification et une partie de la prédiction (nom de famille uniquement) sont les suivants.
last_name | first_name | full_name | pred | |
---|---|---|---|---|
89 | Forme | Mayu | Mayu Katabe | Forme |
1114 | Kumazoe | Norio | Norio Kumazoe | Kumazoe |
1068 | Kimoto | Souho | Kimoto Soho | Kimoto |
55 | Maison | Hiroki | Hiroki Iegami | Maison |
44 | De base | Shodai | Kishodai | De base |
Les 12 cas ayant échoué sont les suivants. Même pour les humains, Toshikatsu Sabune est susceptible d'être divisé en Toshikatsu Sabane.
last_name | first_name | full_name | pred | |
---|---|---|---|---|
11 | Toshi Saburi | Gagner | Toshikatsu Sabane | Sabane |
341 | Brosse | Kasumi | Brosse Sumi | Brosse fleur |
345 | Shinto | Shinichi | Shinichi Shinto | Makoto Shinto |
430 | châtaigne | Kanae | Kanae Kuri | Kurika |
587 | Keisuke | Nina | Kei Ryojina | Kei |
785 | Bansho | Bien | Bansho | Tour |
786 | Yutaka | Wakana | Kana Toyowa | Toyokazu |
995 | Seri | Yu | Seriyoshi | Se |
1061 | Alors | Vraie princesse | Somihime | Somi |
1062 | Cambrure | fruit | Kogi Nomi | Koki |
1155 | Hotaka | Natsuho | Hotaka Natsuho | Hotakaho |
1190 | Extrêmement moyen | rêver | Rêve extrême | très |
À propos, en utilisant uniquement le dictionnaire prédéfini (ipadic) avec janome (un outil d'analyse morphologique qui est complété uniquement avec python et a les mêmes performances que mecab) comme suit Avec une simple séparation du prénom et du nom, la précision était de 34,5%.
def extract_last_name(sentence):
for token in tokenizer.tokenize(sentence):
if 'Nom de famille' in token.part_of_speech:
return token.surface
df['pred'] = df['full_name'].apply(lambda full_name: extract_last_name(full_name))
Si vous obtenez le dictionnaire des noms de famille de Last Name Database et le transmettez à janome comme suit, il sera exact. S'est amélioré à 79,7%.
tokenizer2 = Tokenizer('last_name_dic.csv', udic_enc="utf8")
def extract_last_name2(sentence):
token_arr = [token for token in tokenizer2.tokenize(sentence)]
if 'Nom de famille' 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))
Peut-être que l'ajout d'une liste de noms améliorera encore la précision, mais cela semblait assez difficile à obtenir et à traiter, alors j'aimerais la vérifier si j'ai le temps. (Je ne pense pas)
Importez d'abord ce dont vous avez besoin.
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
En tant que préréglage, je pense qu'il existe de nombreux exemples d'utilisation de `` bert-base-japanese-whole-word-masking '', mais comme il est difficile de le diviser étrangement au moment du tokenize, cette fois un caractère à la fois Utilisez le caractère pour diviser.
tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-char-whole-word-masking')
J'ai utilisé Amazing Name Generator comme source des données de l'enseignant. C'est un site intéressant rien qu'en regardant le caractère inhabituel du nom, qui est quantifié. Cette fois, le nombre de noms était de 48 000 et les types de noms de famille étaient d'environ 22 000. Il est assez largement diffusé, y compris les noms de famille mineurs. Le seul format de csv est full_name, last_name, first_name. Tout d'abord, jetez chaque caractère avec le code suivant.
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]
En règle générale, après avoir divisé full_name un caractère à la fois, la probabilité que chaque caractère soit 1 pour le nom et 0 pour le prénom sera donnée. Pour que BERT comprenne les données de réponse correctes, par exemple, Taro Tanaka-> ['Ta', 'Middle', 'Ta', 'Ro'] -> [1, 1, 0, 0] Je vais y arriver. attention_masks est simplement le tableau cible remplacé par 1 (peut-être inutile)
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...]Créer un tableau de balises
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 doit tous avoir la même longueur de tableau de jetons, donc le remplissage est effectué. Ensuite, divisez l'ensemble de données.
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)
Utilisez GPUorCPU pour charger l'ensemble de données. Chargez ensuite le modèle pré-entraîné.
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()
Affiche un aperçu du modèle. Il n'est pas lié au traitement, vous pouvez donc l'ignorer.
# 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()))))
Définissez la précision. Utilisé uniquement pour afficher les données de validation. Ici, c'est la valeur F1. Pour chaque personnage, si le nom ou le prénom est jugé et qu'il est élevé, il s'approche de 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))
C'est la partie principale de la formation.
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)
#entraînement
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))
Pendant l'entraînement ... Cela a pris environ 10 minutes.
Enregistrez le modèle entraîné ici.
pd.to_pickle(model_token_cls, 'Modèle de séparation du prénom et du nom.pkl')
Sur la base des données de vérification préparées séparément, nous entrerons en fait les caractères considérés comme le nom de famille dans les mots-clés. Les caractères définis par le tokenizer ci-dessus sont de 4 000 caractères et les caractères non inclus dans la liste sont [UNK]. Il semble difficile pour le tokenizer de se souvenir de tout, y compris des variantes, j'ai donc décidé d'ajouter un traitement spécial dans de tels cas.
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])
#Si le résultat de la restauration contient inconnu, obtenez le nombre de caractères du résultat depuis le début.
if '[UNK]' in s:
s = s2[j][0:len(s)]
keywords.append(''.join(s))
Pourtant, le jugement peut être étrange si le même kanji est inclus dans le nom de famille et le nom, mais il semble que ce sera encore mieux si la condition que les noms de famille soient continus est ajoutée plus tard. J'ai trouvé que même si je ne faisais pas vraiment de liste de noms et de noms, je pourrais bien apprendre en mettant le nom complet dans BERT dans une certaine mesure. Cette fois, je suis un peu confus sur ce que je fais, mais en créant correctement les données des enseignants, la même implémentation peut être appliquée à la logique d'extraction de mots-clés dans les phrases.
Recommended Posts