# Praktyczna implementacja NER do przetwarzania CV: od anotacji danych po serwis produkcyjny
Named Entity Recognition (NER) — kluczowy komponent nowoczesnych systemów NLP, szczególnie w automatyzacji HR. W tym artykule omówimy aspekty techniczne wdrożenia rozwiązania NER do wyodrębniania strukturyzowanych danych z CV, z naciskiem na teksty rosyjskojęzyczne i specyfikę wdrożenia produkcyjnego.
Określenie granic zadania
Przed rozpoczęciem development należy dokładnie sformalizować wymagania. Dla systemu HR kluczowymi encjami są:
- Dane osobowe (imię i nazwisko, email, telefon)
- Umiejętności zawodowe (technologie, narzędzia)
- Parametry finansowe (oczekiwane wynagrodzenie z walutą)
Ważne jest uwzględnienie, że w CV umiejętności często są wymieniane bez separatorów (np. "Python SQL Docker"). To wymaga użycia schematu anotacji BIO zamiast prostego IO, aby model poprawnie rozdzielał sąsiadujące encje. Dla języka rosyjskiego kluczowa jest obsługa form deklinacyjnych i skrótów ("JS" zamiast "JavaScript").
Określ precyzyjne reguły dla każdej encji:
- SKILL: tylko terminy zawodowe (wykluczając soft skills)
- SALARY: kwota + waluta ("150 tys. rubles.", "$2500")
- PHONE: wszystkie formaty numerów z kodem kraju
Przygotowanie danych: strategie i narzędzia
Wyszukiwanie gotowych zbiorów danych
Pierwszy etap — sprawdzenie istniejących zasobów:
- Hugging Face Datasets
Filtrowanie po: Task=Token Classification, Language=Russian. Na moment pisania artykułu nie znaleziono specjalistycznych zbiorów danych dla CV. Angielskojęzyczne odpowiedniki (np. Resume Entities Dataset) wymagają adaptacji poprzez tłumaczenie i dofine-tuning.
- Kaggle i Zenodo
Wyszukiwanie po słowach kluczowych "resume NER", "CV entities". Znalezione zbiory danych często zawierają:
* Niekompletną anotację (tylko umiejętności)
* Brak przykładów rosyjskojęzycznych
* Różne schematy tagowania (BIOES vs BIO)
- Syntetyczna generacja
Przy braku danych użyj bibliotek typu nlpaug do:
* Zamiany rzeczywistych danych na odpowiedniki ("Ivanov" → "Petrov")
* Wprowadzania literówek w dane kontaktowe
* Generowania wariantów zapisu umiejętności ("PyTorch" → "pytorch")
Ręczna anotacja w Label Studio
Gdy gotowych danych jest za mało, tworzymy niestandardowy zbiór danych. Kluczowe kroki:
- Przygotowanie dokumentów PDF
Używamy pdfplumber do wyodrębniania tekstu z zachowaniem struktury:
```python
with pdfplumber.open('resume.pdf') as pdf:
text = '\n\n'.join([page.extract_text() for page in pdf.pages])
```
- Konwersja do formatu JSON dla Label Studio
Skrypt konwertujący PDF do formatu akceptowanego przez platformę:
```python
import json
import os
def convert_to_ls_format(pdf_dir):
tasks = [{"text": open(f, 'r').read()}
for f in os.listdir(pdf_dir) if f.endswith('.txt')]
with open('import.json', 'w') as f:
json.dump(tasks, f, ensure_ascii=False, indent=2)
```
- Konfiguracja interfejsu anotacji
W Label Studio tworzymy szablon z tagami:
```xml
<View>
<Text name="text" value="$text"/>
<Labels name="ner" toName="text">
<Label value="SKILL" background="#ffcc00"/>
<Label value="SALARY" background="#66ccff"/>
</Labels>
</View>
```
Kluczowa jest jednolita anotacja: ustal reguły dla przypadków spornych (np. "C++" — jedna umiejętność czy dwie). Aby poprawić jakość, zaangażuj dwóch anotatorów i oblicz metrykę Cohen's Kappa.
Wybór i trenowanie modelu
Podstawowe architektury
Dla tekstów rosyjskojęzycznych polecamy następujące podejścia:
- Fine-tuning modeli pretrenowanych
* RuBERT (DeepPavlov) — optymalny dla języka rosyjskiego
* mBERT — w razie potrzeby wsparcia multilingualnego
* Kluczowy parametr: max_seq_length=512 (CV często zawierają długie opisy)
- Dostosowanie tokenizacji
Dodaj do słownika specyficzne terminy:
```python
tokenizer.add_tokens(["CI/CD", "ETL", "MLflow"])
model.resize_token_embeddings(len(tokenizer))
```
- Obsługa niezrównoważonych klas
Użyj ważonej funkcji straty:
```python
class_weights = compute_class_weight('balanced',
classes=np.unique(labels),
y=labels)
criterion = nn.CrossEntropyLoss(weight=torch.tensor(class_weights))
```
Metryki ewaluacji
Standardowa accuracy nie nadaje się do NER. Śledź:
- Token-level F1 — główna metryka
- Entity-level precision/recall — za pomocą biblioteki
seqeval - Prędkość przetwarzania — tokenów/sekundę na CPU
Zwróć szczególną uwagę na recall dla encji SALARY — jego spadek doprowadzi do utraty krytycznie ważnych danych.
Wdrożenie produkcyjne
Architektura serwisu
Zalecany schemat integracji:
- Przedprzetwarzanie
* Konwersja PDF/DOCX na tekst (przez antiword i pdf2text)
* Normalizacja tekstu (usuwanie zbędnych spacji, kodowanie walut)
- Rdzeń NER
```python
class NERServer:
def __init__(self):
self.model = AutoModelForTokenClassification.from_pretrained('ner_model')
def extract_entities(self, text):
inputs = tokenizer(text, return_tensors='pt', truncation=True)
outputs = self.model(**inputs)
# Przetwarzanie logitów na encje
```
- Postprzetwarzanie
* Grupowanie tokenów w encje według tagów BIO
* Walidacja formatu (wyrażenia regularne dla email/telefonu)
* Normalizacja umiejętności ("JS" → "JavaScript") przez mapowanie
Optymalizacja wydajności
- Cache'owanie — dla powtarzających się zapytań
- Przetwarzanie wsadowe — łączenie kilku CV w batch
- Kwantyzacja — konwersja modelu do FP16 przez
torch.quantization
Dla systemów o wysokiej obciążeniu zaimplementuj kolejkę zadań przez RabbitMQ, aby uniknąć timeoutów przy przetwarzaniu dużych plików.
Co jest ważne
- Adaptacja domenowa jest kluczowa: model trenowany na wiadomościach zawiedzie przy przetwarzaniu CV
- Jakość anotacji decyduje o 80% ostatecznego wyniku — zainwestuj w anotatorów
- Schemat BIO jest obowiązkowy dla list umiejętności bez separatorów
- Walidacja edge cases — sprawdzaj obsługę niestandardowych formatów ("100k $", "~2000 euro")
- Monitorowanie dryfu danych — regularnie przeliczaj metryki na nowych CV
— Editorial Team
Brak komentarzy.