BERT MLM model fine-tune on small data bad results
I want to fine tune a BERT MLM model from Hugging Face. I have only a small amount of data (train.csv) like this:text
בראשית ברא אלהים את השמים ואת הארץ
והארץ היתה תהו ובהו וחשך על פני תהום ורוח אלהים מרחפת על פני המים
ויאמר אלהים יהי אור ויהי אור
וירא אלהים את האור כי טוב ויבדל אלהים בין האור ובין החשך
ויקרא אלהים לאור יום ולחשך קרא לילה ויהי ערב ויהי בקר יום אחד
ויאמר אלהים יהי רקיע בתוך המים ויהי מבדיל בין מים למים
ויעש אלהים את הרקיע ויבדל בין המים אשר מתחת לרקיע ובין המים אשר מעל לרקיע ויהי כן
ויקרא אלהים לרקיע שמים ויהי ערב ויהי בקר יום שני
ויאמר אלהים יקוו המים מתחת השמים אל מקום אחד ותראה היבשה ויהי כן
ויקרא אלהים ליבשה ארץ ולמקוה המים קרא ימים וירא אלהים כי טובBelow is my script in doing the fine-tuning:from huggingface_hub import login
from transformers import AutoModelForMaskedLM
from transformers import AutoTokenizer
import torch
from datasets import load_dataset
from transformers import DataCollatorForLanguageModeling
import collections
import numpy as np
from transformers import default_data_collator
from transformers import TrainingArguments
from transformers import Trainer
import math
from transformers import EarlyStoppingCallback, IntervalStrategy

access_token_write = "REDACTED_TOKEN"
login(token = access_token_write)

model_checkpoint = "dicta-il/BEREL_2.0"
model = AutoModelForMaskedLM.from_pretrained(model_checkpoint)

tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)

sam_dataset = load_dataset("johnlockejrr/samv2")

def tokenize_function(examples):
    result = tokenizer(examples["text"])
    if tokenizer.is_fast:
        result["word_ids"] = [result.word_ids(i) for i in range(len(result["input_ids"]))]
    return result

tokenized_datasets = sam_dataset.map(
    tokenize_function, batched=True, remove_columns=["text"]
)

chunk_size = 128
tokenized_samples = tokenized_datasets["train"][:3]

concatenated_examples = {
    k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys()
}

def group_texts(examples):
    # Concatenate all texts
    concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
    # Compute length of concatenated texts
    total_length = len(concatenated_examples[list(examples.keys())[0]])
    # We drop the last chunk if it's smaller than chunk_size
    total_length = (total_length // chunk_size) * chunk_size
    # Split by chunks of max_len
    result = {
        k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)]
        for k, t in concatenated_examples.items()
    }
    # Create a new labels column
    result["text"] = result["input_ids"].copy()
    return result

lm_datasets = tokenized_datasets.map(group_texts, batched=True)

data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15)

samples = [lm_datasets["train"][i] for i in range(2)]

wwm_probability = 0.2

def whole_word_masking_data_collator(features):
    for feature in features:
        word_ids = feature.pop("word_ids")

        # Create a map between words and corresponding token indices
        mapping = collections.defaultdict(list)
        current_word_index = -1
        current_word = None
        for idx, word_id in enumerate(word_ids):
            if word_id is not None:
                if word_id != current_word:
                    current_word = word_id
                    current_word_index += 1
                mapping[current_word_index].append(idx)

        # Randomly mask words
        mask = np.random.binomial(1, wwm_probability, (len(mapping),))
        input_ids = feature["input_ids"]
        labels = feature["text"]
        new_labels = [-100] * len(labels)
        for word_id in np.where(mask)[0]:
            word_id = word_id.item()
            for idx in mapping[word_id]:
                new_labels[idx] = labels[idx]
                input_ids[idx] = tokenizer.mask_token_id
        feature["text"] = new_labels

    return default_data_collator(features)

samples = [lm_datasets["train"][i] for i in range(2)]
batch = whole_word_masking_data_collator(samples)


train_size = 1_000
test_size = int(0.1 * train_size)

downsampled_dataset = lm_datasets["train"].train_test_split(
    train_size=train_size, test_size=test_size, seed=42
)

batch_size = 64
logging_steps = len(downsampled_dataset["train"]) // batch_size
model_name = model_checkpoint.split("/")[-1]

training_args = TrainingArguments(
    output_dir=f"{model_name}-finetuned-sam-v1",
    overwrite_output_dir=True,
    evaluation_strategy=IntervalStrategy.STEPS, # "steps"
    eval_steps = 50, # Evaluation and Save happens every 50 steps
    save_total_limit = 5, # Only last 5 models are saved. Older ones are deleted.
    learning_rate=2e-5,
    weight_decay=0.01,
    per_device_train_batch_size=batch_size,
    per_device_eval_batch_size=batch_size,
    num_train_epochs=5,
    push_to_hub=True,
    fp16=True,
    logging_steps=logging_steps,
    metric_for_best_model = 'f1',
    load_best_model_at_end=True
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=downsampled_dataset["train"],
    eval_dataset=downsampled_dataset["test"],
    data_collator=data_collator,
    tokenizer=tokenizer,
)

trainer.train()

eval_results = trainer.evaluate()
print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}")

trainer.push_to_hub()All went well, model trained, pushed to Hugging Face. Problems:Loss: 6.2156Model work forFill-Maskbut with my new data seems something happened in the process and some words are masked as tokens and not full words: Here is a script as an example ofFill-Maskand the output:from transformers import pipeline

mask_filler = pipeline(
    "fill-mask", model="johnlockejrr/BEREL_2.0-finetuned-sam-v1"
)

text = "ואלין שמהת בני [MASK] דעלו למצרים עם יעקב גבר וביתה עלו"
preds = mask_filler(text)

for pred in preds:
    print(f">>> {pred['sequence']}")Output:>>> ואלין שמה ##ת בני יעקב דעלו למצרים עם יעקב גבר ובית ##ה עלו
>>> ואלין שמה ##ת בני ישראל דעלו למצרים עם יעקב גבר ובית ##ה עלו
>>> ואלין שמה ##ת בני לאה דעלו למצרים עם יעקב גבר ובית ##ה עלו
>>> ואלין שמה ##ת בני ראובן דעלו למצרים עם יעקב גבר ובית ##ה עלו
>>> ואלין שמה ##ת בני דן דעלו למצרים עם יעקב גבר ובית ##ה עלוI was expecting this output:>>> ואלין שמהת בני יעקב דעלו למצרים עם יעקב גבר וביתה עלו
>>> ואלין שמהת בני ישראל דעלו למצרים עם יעקב גבר וביתה עלו
>>> ואלין שמהת בני לאה דעלו למצרים עם יעקב גבר וביתה עלו
>>> ואלין שמהת בני ראובן דעלו למצרים עם יעקב גבר וביתה עלו
>>> ואלין שמהת בני דן דעלו למצרים עם יעקב גבר וביתה עלו