У овој лекцији истражићемо како нам алгоритми машинског учења могу помоћи у организацији колекција правних докумената. Прецизније, одговорићемо на следећа питања:
- Шта је то вишелабеларна класификација?
- Чему служи платформа Eur-Lex и тезаурус EurоVoc?
- Како је организован скуп података ЕuroLex57k?
- Шта су трансформери и како се може користити унапређени модел legalBERT?
- Како се уз помоћ библиотеке PyPDF2 могу читати документи у PDF формату?
Вишелабеларна класификација докумената¶
У послу се неретко сусрећемо са изазовом да уредимо и организујемо колекцију докумената којом располажемо. Некада је документе потребно уредити хронолошки, некада по имену аутора, а некада по теми. За разлику од прва два критеријума уређивања у којима се датуми и аутори докумената могу релативно једноставно очитати, одређивање тема присутних у документу захтева читање целог документа и његову пажљиву анализу. Што је колекција докумената већа и разноврснија, то је овај задатак временски захтевнији и напорнији. Зато су од користи програми који у документима могу аутоматски идентификовати присутне теме и придружити им их. Такве програме зовемо класификаторима (енгл. classifier). Скуп могућих тема обично дефинишемо унапред тако да садржи неки коначан, за посао релевантан, скуп вредности. Ове вредности ћемо надаље називати обележјима или лабелама (енгл. label). Задатак вишелабеларне класификације (engl. multilabel classification) докумената је да сваком од докумената придружи одговарајуће лабеле, једну или више њих.
У наставку ћемо проћи кроз пример развоја и коришћења једног оваквог класификатора.
Да бисмо развили овакав класификатор биће нам потребна група докумената која већ има мануелно придружене лабеле. Такву групу докумената зовемо скупом за обучавање (енгл. training set). Поред скупа за обучавање биће нам потребна и једна група мануелно обележених докумената над којом ћемо моћи да тестирамо колико успешно ради класификатор који смо обучили. Ту групу докумената зовемо скупом за тестирање (енгл. test set). У пракси издвајамо и једну групу докумената коју називамо валидационим скупом (енгл. validation set) и коју користимо за праћење развоја самог класификатора.
Због захтева за постојањем ових скупова, у заједници која се бави машинским учењем задатак класификације се убраја у задатке надгледаног машинског учења (енгл. supervised machine learning). Алгоритми који се користе за решавање ове групе задатака имају за циљ да науче везу између улаза (у нашем случају докумената) и придружених лабела тако да је грешка коју праве на скупу за тестирање што је могуће мања, а успешност примене на новим, будућим, улазима, што већа. Ово својство класификатора се назива генерализација. Више о задатку класификације и скуповима које смо увели можете прочитати у лекцији Класификација докумената.
Платформа Eur-Lex, скуп података EurLex57k и тезаурус EuroVoc¶
Како се могу организовати правни документи приказаћемо на примеру скупа података који се зове EurLex57k. Овај скуп је настао обједињавањем докумената преузетих са платформе EUR-lex, централне платформе преко које се са јавношћу деле званични документи Европске уније попут закона, међународних споразума, судских пракси, националних мера транспозиције и многих других типова правних докумената. Сваки од ових докумената је доступан на 24 званична језика Европске уније. Скуп података EurLex57k садржи верзије ових докумената на енглеском језику, а као што се може наслутити из имена скупа, укупно броји 57 хиљада докумената. Скуп за тренирање садржи 45 хиљада докумената, а скупови за валидацију и тестирање по 6 хиљада докумената. У лекцији Skup podataka EurLex57k можете пронаћи више информација о скупу и његовим карактеризацијама.
Свим документима скупа EurLex57 су мануелно придружене лабеле које су побројане у тезаурусу који се зове EuroVoc. Тезаурус EuroVoc представља један хијерархијски организован вокабулар са двадесет и једним доменом на највишем нивоу. Сваки од ових домена покрива неки специфични сегмент деловања и садржи лабеле које су за њега релевантне. Тако, на пример, домен environment обухвата поддомене environmental policy, natural environment и deterioration of the environment од којих први садржи лабеле climate change, water policy, waste managment и друге. Сваком од домена, поддомена и лабела је придружен и јединствен нумерички идентификатор. Тако домену environment одговара идентификатор 52, поддомену environmental policy идентификатор 5206, а лабели waste management идентификатор 1158. Попис свих домена, њихових имена и јединствених нумеричких идентификатора, издвојен је у датотеци domains_with_id_and_name.txt, док је попис свих идентификатора лабела придружених доменима издвојен у датотеци concepts_per_domain.txt (организација која ради на развоју тезауруса EuroLex фаворизује коришћење термина концепт уместо лабела). Нешто касније ћемо видети и садржај ових датотека, а све специфичности у вези са њиховим креирањем и додатна појашњења у вези са организацијом тезауруса EuroVoc се могу пронаћи у свесци 03-Tezaurus EuroVoc.
![]() |
![]() |
Библиотека transformers и модел LegalBert¶
Библиотека чије ћемо функционалности користити за учитавање скупа података, обучавање модела, његову анализу и покретање зове се transformers. Трансформери су специјалан тип неуронских мрежа који се са великим успехом користе у многим задацима машинског учења. Библиотека transformers поштује концепте отвореног кода и обједињава готово све познате трансформере уз могућност удобног и интуитивног коришћења.
Неки општи протокол у раду са трансформерима је да се пође од неког подесног трансформера, а затим да се ради на његовом прилагођавању специфичном задатку који треба решити (овај процес се на енглеском језику назива fine-tuning). Mи смо у раду пошли од трансформера који се зове LegalBert зато што може лепо да разуме семантику правних докумената, а потом смо га прилагодили тако да може да класификује документа на горе описани начин. Званична страница библиотеке transformers посвећена моделу LegalBert се може прегледати на овој адреси. Финални прилагођени модел се налази у директоријуму legalbert_model_finetuned и користићемо га у даљем раду.
У лекцији Библиотека transformers и модел LegalBert можете прочитати више о мотивацији за овакав приступ у раду са трансформерима. Овде ја важно напоменути да су трансформери који се користе у пракси по правилу велике мреже које захтевају напредне рачунске ресурсе у процесу обучавања. Примера ради, један познати трансформер са именом Берт је обучаван на специјализованом хардверу, а сами трошкови обучавања се процењују на око 7 хиљада долара. Зато је смисао дељења обучених модела врло оправдан и из еколошких и из материјалних разлога.
Експеримент: класификација правних докумената¶
Експеримент ћемо започети анализом скупa података EurLex57k. У учитавању скупа ће нам помоћи функција load_dataset библиотеке transformers тј. њеног пакета datasets.
from datasets import load_dataset
dataset = load_dataset('eurlex')
Информације о величини скупа, тј. његових делова за обучавање, тестирање и валидацију, можемо прочитати коришћењем својства shape.
print(dataset.shape)
Појединачне делове учитаног скупа можемо даље издвојити навођењем квалификатора train, validation и test.
train_data = dataset['train']
validation_data = dataset['validation']
test_data = dataset['test']
Информације о учитаним скуповима попут описа, лиценци за коришћење, званичне странице скупа и слично се могу добити коришћењем својства info.
print('Opis skupa EurLex57k: ', train_data.info.description)
print('Zvanicna stranica skupa: ', train_data.info.homepage)
print('Licenca za koriscenje: ', train_data.info.license)
print('Skup podataka je objavljen u radu: ', train_data.info.citation)
Да бисмо стекли представу како су организовани подаци унутар ових скупова, прочитаћемо један насумичан унос. Нека то буде унос који се налази на позицији 10 у скупу за обучавање.
example = train_data[10]
example
Из исписа можемо закључити да један унос садржи јединствени идентификатор документа (поље celex_id), наслов (поље title), садржај документа (поље text) и листу придружених EuroVoc лабела (поље eurovoc_concepts).
Даље ћемо документима придружити нумеричке репрезентације, низове бројева који осликавају њихов садржај. Овај корак је неопходан како би алгоритми могли да раде са садржајима који нису нужно нумеричке природе. Овај корак се званично назива токенизација (енгл. tokenization), а нама ће у токенизацији помоћи функционалност AutoTokenizer библиотеке transformers. Сваки модел који постоји у овој библиотеци упарен је са одговарајућим токенизатором тако да га, уколико знамо име модела, можемо учитати позивом функције from_pretrained.
Име модела LegalBert је nlpaueb/legal-bert-base-uncased и сачуваћемо га у посебној променљивој.
MODEL_PATH = 'nlpaueb/legal-bert-base-uncased'
Даље ћемо учитати токенизатор придружен овом моделу.
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
Учитани токенизатор покрива рад са 30.522 токена. Скуп свих токена по правилу називамо вокабуларом (енгл. vocabulary) па у очитавању његове димензије нам може помоћи својство vocab_size токенизатора.
tokenizer.vocab_size
Токени су целе речи или парчићи речи одабрани тако да једнозначно и нумерички оптимално могу представити документе. У наставку можемо видети све токене вокабулара са којима може да ради LegalBert модел. Парчићи речи почињу карактерима ##. Поред сваког токена наведен је и јединствени идентификатор токена. Ovo je prvih 10:
list(tokenizer.vocab.items())[:10]
Идентификатори токена се користе у креирању репрезентација докумената. За креирање репрезентације користи се функција tokenize. У наставку можемо видети листу идентификатора токена која је придружена примеру који смо издвојили.
tokenized_example = tokenizer(example['text'])
print(tokenized_example['input_ids'])
Одређени број токена који се користи има специјално значење. Информације о тим токенима се могу добити својствима all_special_ids и all_special_tokens токенизатора.
for token, id in zip(tokenizer.all_special_tokens, tokenizer.all_special_ids):
print(token, id)
Овде ћемо прокоментарисати само значење токена CLS и SEP - први ће се увек користити да назначи почетак репрезентације, а други да назначи крај репрезентације. Токен UNK се користи да обележи непознату реч текста (енгл. unknown token).
Сада када знамо на који начин ће се представити документи у процесу класификације, можемо се позабавити вредностима које ће им класификатор придруживати. То ће бити, као што смо нагласили, идентификатори домена тезауруса EuroVoc. Идентификаторе домена и њихова имена можемо прочитати из датотеке domains_with_id_and_name.txt. Укупно их има 21.
import ast
number_of_domains = 21
with open('data/domains_with_id_and_name.txt', 'r') as file:
domains_with_id_and_name = ast.literal_eval(file.read())
domains_with_id_and_name
Када нам затреба да из овог речника на основу редног броја домена тј. листе ових вредности очитамо имена домена, можемо искористи функцију from_domain_indexes_to_names.
def from_domain_indexes_to_names(domain_indexes, domains_with_id_and_name=domains_with_id_and_name):
domain_names = []
for domain_index, (domain_id, domain_name) in enumerate(domains_with_id_and_name.items()):
if domain_index in domain_indexes:
domain_names.append(domain_name)
return domain_names
На пример, имена домена чији су индекси 2, 3 и 4 можемо добити следећим позивом. Не заборавите да нумерација почиње од нуле.
from_domain_indexes_to_names([2, 3, 4])
У скупу података EurLex су документима придруживане лабеле са нижих слојева хијерархије тезауруса EuroVoc. Зато је било неопходно да мапирамо ове идентификаторе лабела у одговарајуће кровне идентификаторе домена. Попис свих лабела по доменима којима припадају се налази у датотеци concepts_per_domain.txt, а функција from_labels_to_domains врши сама мапирања. Следећим блоком кода учитаћемо датотеку са лабелама и демонстрирати како ради from_labels_to_domains функција - ако одлучимо да редукујемо или променимо домене са којима класификатор може да ради, довољно је променити њен код.
with open('data/concepts_per_domain.txt', 'r') as file:
eurovoc_labels_per_domain = ast.literal_eval(file.read())
def from_labels_to_domains(labels, eurovoc_labels_per_domain, number_of_domains=21):
domains = []
for label in labels:
for domain, domain_labels in eurovoc_labels_per_domain.items():
if label in domain_labels:
domains.append(domain)
return list(set(domains))
Тако ћемо документу из примера чије су лабеле ['1118', '1605', '2635', '693'] придружити домене Finance и Agri-foodstuffs чији су идентификатори, редом, 24 и 60.
from_labels_to_domains(example['eurovoc_concepts'], eurovoc_labels_per_domain)
Пошто ће нам очитавање имена домена за задате идентификаторе значити и у даљем раду, користићемо функцију from_domain_ids_to_names. Ова функција ће за задату листу идентификатора домена генерисати листу њихових имена очитавајући парове из речника domains_with_id_and_name.
def from_domain_ids_to_names(domain_ids, domains_with_id_and_name=domains_with_id_and_name):
return [domains_with_id_and_name[domain_id] for domain_id in domain_ids]
from_domain_ids_to_names(['24', '60'])
Сада можемо учитати обучени модел и тестирати како ради над неким произвољним правним документом.
За учитавање модела искористићемо функцију AutoModelForSequenceClassification библиотеке transformers. Ова функција очекује путању до прилагођеног модела.
from transformers import AutoModelForSequenceClassification
FINETUNED_MODEL_PATH = 'mldata/legalbert_model_finetuned'
model = AutoModelForSequenceClassification.from_pretrained(FINETUNED_MODEL_PATH)
Коришћењем својства num_labels можемо се уверити и да учитани модел подржава рад са двадесет и једном класом - имамо баш толико домена у тезаурусу EuroVoc.
model.num_labels
Функција document_classification обједињује кораке генерисања репрезентације задатог документа, позивa модела, очитавањa његових нумеричких предикција и њихово мапирање у имена домена.
import torch
def document_classification(document, tokenizer=tokenizer, model=model, domains_with_id_and_name=domains_with_id_and_name, return_probabilities=False):
document_representation = tokenizer(document, return_tensors="pt")
model_predictions = model(**document_representation)
model_probabilities = model_predictions.logits.sigmoid().detach().cpu().flatten()
domain_indexes = torch.where(model_probabilities >= 0.5)[0].squeeze().tolist()
domain_names = from_domain_indexes_to_names(domain_indexes, domains_with_id_and_name)
if return_probabilities:
return domain_names, model_probabilities.reshape(1, -1).tolist()
return domain_names
Искористимо за тестирање део превода Закона o правној заштити индустријског дизајна Републике Србије.
document = '''
Content of the Design Right Article 39 The holder of right to industrial design has the exclusive right to
use the protected industrial design and to deny that right to every third party. The use from paragraph 1
of this article implies in particular the manufacture, offering, marketing, import, export or use of that
product, implying that design is embedded there or applied for that or stored for the mentioned purposes.
Industrial Design Author Rights Article 40 Author of the industrial design shall have moral and economic rights.
The moral right shall be understood to mean the right of the industrial design author to have his name indicated
in the registration application, documents and certificate of the industrial design.
'''
Класификатор овом документу придружује лабеле 'TRADE', 'PRODUCTION, TECHNOLOGY AND RESEARCH', 'INDUSTRY', што је заправо врло релевантно!
predicted_domains = document_classification(document)
predicted_domains
Предикције класификатора неће увек бити тачне. Око неких домена ће класификатор бити врло сигуран, предвиђаће их са великом вероватноћом, док ће у неким случајевима бити мање сигуран и предвиђаће их са мањим вредностима вероватноћа. Оцена вероватноће креће се на скали од 0 до 1 и веће вредности указују на вероватније догађаје. Ми смо у функцији која придружује лабеле документима користили праг 0.5 - уколико је вероватноћа одређене лабеле већа од 0.5 придруживали смо је документу. Уколико се у функцији document_classification постави аргумент return_probabilities са вредношћу True, можемо да пратимо и вредности вероватноћа класификатора приликом придруживања.
predicted_domains, probabilities = document_classification(document, return_probabilities=True)
for domain, probability_score in zip(domains_with_id_and_name.values(), probabilities[0]):
print ('{domain:40}: {probability_score}'.format(domain=domain, probability_score=probability_score))
Функција visualize_probabilities ће нам помоћи да овако добијене вероватноће прикажемо у форми топлотне мапе и нешто лакше испратимо понашање класификатора.
from matplotlib import pyplot as plt
def visualize_probabilities(probabilities, domains_with_id_and_name=domains_with_id_and_name, number_of_domains=21):
domains = list(domains_with_id_and_name.values())
figure, ax = plt.subplots(1, 1, figsize=(10, 8))
image = ax.imshow(probabilities)
ax.set_title("Mapa vrednosti verovatnoća")
ax.set_xticks(torch.arange(number_of_domains))
ax.set_xticklabels(domains)
plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor")
ax.set_yticks([])
for i in range(number_of_domains):
text = ax.text(i, 0, round(probabilities[0][i], 2), ha="center", va="center", color="w")
cbar = ax.figure.colorbar(image, ax=ax, orientation='horizontal', location='top')
cbar.ax.set_xlabel("Skala boja", rotation=0, va="bottom")
figure.tight_layout()
plt.show()
visualize_probabilities(probabilities)
Као што можемо видети, класификатор је најсигурнији око предикције лабеле 'PRODUCTION, TECHNOLOGY AND RESEARCH'. Променом прагова који се користе можемо утицати на строгост класификације.
Важна напомена у раду нашег модела је да ради само са првих 512 токена репрезентације документа. Уколико је потребно класификовати дужи садржај, предлог је поделити га на неке разумно дуге делове, на пример, на чланове, и покренути класификатор над сваким од делова.
Закони се много чешће могу пронаћи као документи у PDF формату. Због тога може бити згодно познавање библиотеке PyPDF2 која омогућава рад са овом врстом докумената. Демонстрираћемо како ова библиотека ради над PDF верзијом Закона o правној заштити индустријског дизајна Републике Србије. Закон се може пронаћи на овој адреси, а ми смо га преузели и сачували у директоријуму data/the_law_on_legal_protection_of_design.pdf.
document_path = "data/laws/the_law_on_legal_protection_of_design.pdf"
Следећим блоком кода можемо учитати библиотеку PyPDF2 и припремити читач document_reader документа.
import PyPDF2
file = open(document_path, "rb")
document_reader = PyPDF2.PdfFileReader(file)
Неке основне информације о документу попут наслова, имена аутора, датума креирања и слично можемо добити коришћењем функције getDocumentInfo.
document_info = document_reader.getDocumentInfo()
document_info
Информације о броју страна документа можемо добити коришћењем функције getNumPages.
print('Broj strana dokumenta: ', document_reader.getNumPages())
Неку конкретну страну документа можемо добити коришћењем функције getPage, а њен текстуални садржај коришћењем функције extractText. Следећим блоком кода можемо прочитати садржај са странице број 12 (подсетник: нумерација почиње од нуле!).
test_page = document_reader.getPage(11)
test_page_content = test_page.extractText()
test_page_content[:500]
Као што можемо видети, прочитани текст садржи знаке за нови ред (\n) које је потребно обрисати. Модел са којим радимо очекује текст записан и малим и великим словима (у његовом имену стоји uncased) па није потребно вршити нормализацију и усаглашавање овог типа. По потреби можемо обрисати бројеве страница, уклонити електронске адресе или слично. Функција clean_document може да обједини све неопходне кораке припреме. Тренутно, она брише знак за нови ред, замењује вишеструке белине једноструким и брише број странице са почетка документа.
def clean_document(document, page_number=None):
document = document.replace('\n', '')
document = document.replace('\s+', '')
if page_number:
document = document.strip()
page_number = str(page_number)
page_chars = len(page_number)
if document[0:page_chars] == page_number:
document = document[page_chars+1:]
return document
Можемо проверити и како изгледа резултат ове функције.
clean_document(test_page_content, page_number=12)[:500]
Коначно, можемо видети које лабеле су придружене овој страници и колико је класификатор сигуран приликом њиховог придруживања.
domains, probabilities = document_classification(clean_document(test_page_content), return_probabilities=True)
print(domains)
visualize_probabilities(probabilities)
Лабела 'GEOGRAPHY' је можда неочекивана, претпоставка је да су имена земаља које се налазе у овом документу утицала на овакав закључак.
Задатак за вежбу¶
- Извршите ћелију у наставку, а потом кликом на дугме Upload учитајте документ по избору. У даљем раду га можете користити под именом demo.pdf. Можете даље да издвојите неку страницу пратећи функционалности библиотеке PyPDF, а затим проверити које лабеле јој класификатор придружује.
Напомена: Не заборавите да модел оптимално ради са документима који су дужине око 512 токена тј. дужине два до три параграфа.
import ipywidgets as widgets
from IPython.display import display
uploader = widgets.FileUpload(accept='.pdf', multiple=False, maxsize='10M')
def on_document_upload(change):
for f in uploader.value:
content = uploader.value[f]['content']
with open('data/laws/demo.pdf', 'wb') as demo_file:
demo_file.write(content)
uploader.observe(on_document_upload)
display(uploader)

