# Dependencies
Tested with:
* [Python](https://www.python.org/) 3.9
* [Polars](https://github.com/pola-rs/polars) >= 0.19.2
* [Transformers](https://huggingface.co/docs/transformers/index) >= 4.33.1
* [Torch](https://github.com/pytorch/pytorch) >= 1.13.1
* [NLTK](https://www.nltk.org/) >= 3.8.1
* [importlib-resources](https://pypi.org/project/importlib-resources/) >= 6.1.0
# Installation
## From Github
```
pip3 install git+https://github.com/er1kb/secretaries
```
or clone and install locally:
```
git clone https://github.com/er1kb/secretaries.git && cd secretaries && pip3 install .
```
## From PyPI
```
python3 -m pip install secretaries
```
# English
## Purpose and motivations
This package is a three-pronged approach to finding and substituting personal data in text. It uses BERT models to search for named entities and regex to pin-point potential personal information. It summarizes counts of the tokens and entities found in the text. Although large language models might have the ability to do something like this, if you have sensitive and/or proprietary data, you probably do not want to shove it into some online cloud. The default model is the popular [David S. Lim's NER model](https://huggingface.co/dslim/bert-base-NER) at the Huggingface Hub. You can choose to run another one depending on your GPU resources and accuracy needs.
In the English version, the base corpus is a mix of two online resources: a corpus of [7579 unique first names](https://www.cs.cmu.edu/Groups/AI/util/areas/nlp/corpora/names/) by Mark Kantrowitz and Github user craigh411's list of the [1000 most common American surnames](https://gist.github.com/craigh411/19a4479b289ae6c3f6edb95152214efc). It will be downloaded automatically the first time you run the code, assuming an Internet connection. For production use, you probably want to try and compile your own corpus from slightly more comprehensive sources. You can add to the corpus using the [input\_en/names](#names) folders and/or when calling the main function.
Even though the model obfuscates names and such, it does not handle other information which may be unique to an individual. If someone writes their place of residence and their occupation, you might still be able to identify that person through text, even though their name has been removed. No computer model will ever completely absolve you of manual work, if required. The secrecy of people mentioned in your text is **your** responsibility. The model helps you obfuscate and summarize personal data but it makes no assumptions on the gravity of the context surrounding the task.
## Instructions
### Data
You can feed the model a single string, a list of strings or a csv file. If supplying a csv, your data needs to have an id column of unique row ids and a text column.
BERT models have an upper limit of 512 tokens including punctuation. Longer texts will be split up for you into parts of at most 512 tokens, guided by punctuation so that sentences are preserved. The final (obfuscated) data is supplied as two versions: one split up by the max token length, and one concatenated into the original format.
###
### Input
To begin with, you can get good enough results by just running the NER part of the model, setting *corpus=False*. With domain specific data in a production setting, this might not be enough. The precision of the model will continue to improve if you help it by sorting words into the following three categories: words that are always names, words that are sometimes names and sometimes not (ambiguous), and finally words or phrases that you want to leave untouched (masks).
The first time you run the main function, a language specific input folder will be created in your working directory (ie where you are running the code), along with three subfolders. Each subfolder can contain any number of csv files. Each csv file contains a column named "token" and then has one token or phrase per row. If a subfolder contains multiple files, remember that all of these need to contain the "token" column in order to be combined when you run the model.
#### Names
This is where you put words that are definitely names, irrespective of context. They will be added to the corpus search and replace (ie substitution by spelling), unless already present in the pre-loaded corpus.
#### Ambiguous
Some words may or may not be names. Names that are ambiguous need to be inferred from the surrounding context, which is something that Named Entity Recognition does well. Consider the sentences "He is my best friend" and "His name is George Best". If "best" is hidden from the corpus search and replace, the first instance will be untouched, while the actual name in the second sentence will still be detected using NER.
If the corpus search returns a lot more names than the NER and you know you are using the right NER model, this indicates false positives. Go through output\_en/names\_corpus.csv, sort out the problematic words and put them in one or more csv files under input\_en/ambiguous/, then re-run the code.
You will have to decide for yourself which words are ambiguous. A small starter kit will be loaded into the folder when it is created, for reference.
#### Masks
You have the ability to mask tokens from the model. Masking tokens can consist of one or several words. These tokens will be hidden from the algorithm and then re-substituted at the end of the run.
For example, adding "Big Ben" to the input\_en/maskings subfolder (of your working directory) ensures that this string stays untouched, while "Ben" not preceded by "Big " can still be treated as a name. Create a csv file in the maskings folder, where the first and only column is named "token" and each subsequent row is a masked token.
If, like me, you work for a city, you might want to put all the names of streets and places into this folder. Streets are good candidates for masking because they often contain proper names that you might not want to remove, for example "Harris St." and "Coleman Rd.".
## Examples
### A single, short text
```
from secretaries import secretary as s
t = s.run("Bear Grylls once met a bear at Bear lake.",
lang = "English",
ambiguous = ["bear"],
masks = ["Bear lake"],
single_text_mode = True)
print(t)
```
```
[name] once met a bear at Bear lake.
```
### A longer text
This example runs the model on Jane Austen's *Pride and prejudice*. Time elapsed is about 12 minutes on a 24 core Threadripper CPU and an A4000 GPU. We set *corpus=False* to avoid getting a lot of false positives due to ambiguous words.
```
import re
import urllib.request
url = r"https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
req = urllib.request.Request(url)
resp = urllib.request.urlopen(req)
text = resp.read().decode('utf-8')
print('n characters: ' + str(len(text)))
print('n tokens: ' + str(len(re.split(r"\b", text))))
from secretaries import secretary as s
text = s.run(text = text, id_column = "id", text_column = "text",
model_name = "dslim/bert-large-NER",
lang = "English", corpus = False,
single_text_mode = True)
```
```
n characters: 763250
n tokens: 263829
```
```
> cat output_en/names_ner.csv
1 │ token,count
2 │ Elizabeth,338
3 │ Darcy,287
4 │ Jane,199
5 │ Bennet,154
6 │ Bingley,138
7 │ Collins,131
8 │ Lydia,123
9 │ Wickham,104
10 │ Catherine,74
11 │ Gardiner,73
12 │ Lizzy,63
... │ ...
```
### A comma-separated text file (csv)
This example uses the default NER model. The data has an integer id column named "text\_number" and a text column aptly named "text". The unique ids are integers and specifying this helps with the final sorting of the data. The text has some unwanted html tags.
```
from secretaries import secretary as s
d = s.run(csv = "my_data.csv", lang = "English",
id_column = 'text_number', text_column = 'text',
remove_html = True, id_column_as_int = True)
```
# Other languages
Support for other languages could possibly be added. At minimum there needs to be a suitable model for Named Entity Recognition (NER) at the [Huggingface Model Hub](https://huggingface.co/models?pipeline_tag=text-classification&sort=trending&search=ner).
# Swedish
## Syfte och användningsområde
Sekreteraren är ett Python-paket för att flagga, ersätta och sammanfatta personuppgifter i löpande text. Den är en optimerad version av en tidigare kodbas som används av Malmö stad för att gallra personuppgifter i kundtjänstärenden. Det finns sannolikt andra modeller som tacklar samma problem, men detta är resultatet av våra överväganden och erfarenheter. Modellen vilar på [Kungliga Bibliotekets språkmodeller](https://github.com/Kungbib/swedish-bert-models) för NER (detektering av namngivna enheter i texten) och namnkorpuset från [Svensktext](https://github.com/peterdalle/svensktext).
Modellen rensar personuppgifter med hög precision, men har ingen rutin för att identifiera utpekande information. Det är **ditt** ansvar att kontrollera texten med avseende på röjanderisk, det vill säga att kombinationen av olika uppgifter (inte) ger möjlighet att identifiera en eller flera nu levande individer. Modellen hjälper dig att rensa och sammanfatta personuppgifter, men den hjälper dig inte att bedöma situationens allvar.
Namnen från [Svensktext](https://github.com/peterdalle/svensktext) laddas ner automatiskt till din arbetsmapp första gången du kör huvudfunktionen. Du kan göra tillägg till detta via undermappen [input\_se/namn](#namn).
## Instruktioner
### Data
Du kan mata in en enskild textsträng eller en lista med textsträngar: ["text1", "text2"]. För större datamängder med många texter vill du förmodligen mata in en csv-fil. Du behöver då ange namnen på en id-kolumn med unika id:n och en kolumn som innehåller texterna.
BERT-modellen som används för att peka ut namngivna enheter i texten har en maxlängd på 512 ord inkl. skiljetecken. Längre texter kommer att delas upp i delar om max denna ordlängd, med hjälp av skiljetecken så att meningar inte delas upp. När skriptet har körts finns rensad data i två versioner: en uppdelad i max ordlängd och en sammanslagen till samma antal rader som den ursprungliga datamängden.
### Övrig input (register)
Du kan öka modellens precision genom att sortera ord i tre kategorier: ord som alltid är namn, ord som kan vara namn eller inte (tveksamma), samt ord/fraser som ska bevaras i sin helhet (masker). Första gången du kör huvudfunktionen kommer en språkspecifik input-mapp med tre undermappar skapas i din arbetsmapp, det vill säga den mapp där du kör koden. Varje undermapp kan innehålla en eller flera csv-filer (kommaseparerad text). Varje csv-fil innehåller en kolumn med namnet "token" (utan citationstecken) och har sedan ett ord (alt. en fras) per rad. Om du har flera csv-filer i en undermapp, kom ihåg att samtliga måste ha kolumnen "token" namngiven i första raden, för att filerna ska kunna kombineras när du kör modellen.
#### Namn
Här förvarar du ord som definitivt är namn, oavsett sammanhang. Dessa ord kommer att kombineras med namn-korpuset, såvida de inte redan finns där. Den svenska modellen bygger på ett mycket stort antal namn från [Svensktext](https://github.com/peterdalle/svensktext), men ovanliga namn sorteras bort för att undvika fel (false positives). Om din text innehåller ovanliga namn kan du behöva lägga till dessa, antingen via namn-mappen eller i kod när du anropar funktionen.
#### Tveksamma
Vissa ord är tvetydiga och kan vara namn eller inte, beroende på sammanhanget. Exempel är Stig, Björn, Lotta och Finn. Eftersom dessa namn ibland är meningsbärande ord kan vi inte rutinmässigt ta bort dem baserat på stavning. Om sammanhanget däremot indikerar att de utgör namn bör vi ta bort dem. Detta är poängen med att använda Named Entity Recognition (NER).
Om sökningen med Svensktext uppenbart har flaggat fler namn än NER, lider din data av false positives, det vill säga ord som felaktigt flaggas som namn (givet att du har använt rätt NER-modell). Gå igenom csv-filen output\_se/namn\_korpus.csv, sortera uppenbara felaktigheter i en eller flera csv-filer under mappen input\_se/tveksamma och kör därefter skriptet på nytt. Det finns också en nedre brytpunkt i form av parametern min\_n\_persons = 100. Exempel: Enligt Svensktext finns 129 förekomster av namnet Snabb, som för-, efter- eller tilltalsnamn. Ordet finns därmed i korpuset, men du vill förmodligen inte slentrianmässigt flagga det som ett namn. När du på detta vis osynliggör ett namn för korpus-ersättningen, så kommer det fortfarande plockas upp av övriga delar av skriptet (NER och Regex) om sammanhanget indikerar att det är ett namn.
Första gången du kör huvudfunktionen sparas ett startkit med tveksamma ord under input\_se/tveksamma samtidigt som mapparna skapas. Vill du mot förmodan inte använda detta, ta bort filen men låt mappen vara kvar.
#### Masker
Du kan maskera ord och fraser som inte ska ersättas. Exempelvis, i meningen "Gustav bor vid Gustav Adolfs torg" är bara den första förekomsten av "Gustav" en personuppgift. För att hålla resten av meningen intakt lägger du masken "Gustav Adolfs torg" i undermappen input\_se/maskeringar (under din arbetsmapp, där din kod körs). Om du jobbar inom en kommun så vill du förmodligen lägga in en lista på alla era gator och platser/besöksmål, för att hålla dessa intakta i texten.
## Exempel
### En kort text
```
from secretaries import secretary as s
t = s.run(text = "Stig mötte Björn på en stig i skogen",
ambiguous = ["stig","björn"],
single_text_mode = True)
print(t)
```
```
[namn] mötte [namn] på en stig i skogen
```
### En längre text
I det här exemplet laddar vi ner och analyserar Hjalmar Söderbergs *Förvillelser*. Det tar cirka 1,5 minut på en dator med 24-kärnors Threadripper CPU och en A4000 GPU. Till skillnad från motsvarande engelska exempel anger vi inte språk eller modell, eftersom svenska är standard och den mest omfattande BERT-modellen redan används.
```
import re
import urllib.request
url = r"https://www.gutenberg.org/cache/epub/30078/pg30078.txt"
req = urllib.request.Request(url)
resp = urllib.request.urlopen(req)
text = resp.read().decode('utf-8')
text = "\n".join(text.split("\n")[63:])
print('antal bokstäver: ' + str(len(text)))
print('antal ord/skiljetecken: ' + str(len(re.split(r"\b", text))))
from secretaries import secretary as s
text = s.run(text = text, id_column = "id", text_column = "text",
single_text_mode = True, corpus = False)
# sök inte med stavning (korpus), eftersom det blir många false positives
```
```
antal bokstäver: 256914
antal ord/skiljetecken: 85753
```
```
> cat output_se/namn_ner.csv
1 │ token,count
2 │ Tomas,105
3 │ Märta,53
4 │ Hall,29
5 │ Ellen,26
6 │ Greta,17
7 │ Mortimer,14
8 │ Arvidsons,7
9 │ Weber,7
10 │ Arvidson,7
11 │ Gabel,6
12 │ fru Wenschen,6
13 │ Märta Brehm,5
... │ ...
```
### En kommaseparerad textfil (csv)
Här läser vi in en csv-fil där kolumnen "text\_nummer" innehåller ett unikt id för varje text och kolumnen text innehåller själva texterna. Vi förtydligar också att id-kolumnen består av heltal, för sorteringens skull. Vi passar också på att radera html-taggar som smugit sin in i texten.
```
from secretaries import secretary as s
d = s.run(csv = "min_data.csv",
id_column = 'text_nummer', text_column = 'text',
remove_html = True, id_column_as_int = True)
```
Raw data
{
"_id": null,
"home_page": null,
"name": "secretaries",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "anonymization, gdpr, personal data, personuppgifter, python",
"author": null,
"author_email": "Erik Broman <mikroberna@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/72/f6/231d397329588d129d359f9cecfc3571c994854882407a5ce1dd76c00f41/secretaries-0.0.1.12.tar.gz",
"platform": null,
"description": "# Dependencies\nTested with: \n* [Python](https://www.python.org/) 3.9\n* [Polars](https://github.com/pola-rs/polars) >= 0.19.2\n* [Transformers](https://huggingface.co/docs/transformers/index) >= 4.33.1\n* [Torch](https://github.com/pytorch/pytorch) >= 1.13.1\n* [NLTK](https://www.nltk.org/) >= 3.8.1\n* [importlib-resources](https://pypi.org/project/importlib-resources/) >= 6.1.0\n\n# Installation\n\n\n## From Github\n```\npip3 install git+https://github.com/er1kb/secretaries\n```\nor clone and install locally:\n```\ngit clone https://github.com/er1kb/secretaries.git && cd secretaries && pip3 install .\n```\n\n## From PyPI\n```\npython3 -m pip install secretaries\n```\n\n\n# English\n\n## Purpose and motivations\nThis package is a three-pronged approach to finding and substituting personal data in text. It uses BERT models to search for named entities and regex to pin-point potential personal information. It summarizes counts of the tokens and entities found in the text. Although large language models might have the ability to do something like this, if you have sensitive and/or proprietary data, you probably do not want to shove it into some online cloud. The default model is the popular [David S. Lim's NER model](https://huggingface.co/dslim/bert-base-NER) at the Huggingface Hub. You can choose to run another one depending on your GPU resources and accuracy needs. \n\nIn the English version, the base corpus is a mix of two online resources: a corpus of [7579 unique first names](https://www.cs.cmu.edu/Groups/AI/util/areas/nlp/corpora/names/) by Mark Kantrowitz and Github user craigh411's list of the [1000 most common American surnames](https://gist.github.com/craigh411/19a4479b289ae6c3f6edb95152214efc). It will be downloaded automatically the first time you run the code, assuming an Internet connection. For production use, you probably want to try and compile your own corpus from slightly more comprehensive sources. You can add to the corpus using the [input\\_en/names](#names) folders and/or when calling the main function. \n\nEven though the model obfuscates names and such, it does not handle other information which may be unique to an individual. If someone writes their place of residence and their occupation, you might still be able to identify that person through text, even though their name has been removed. No computer model will ever completely absolve you of manual work, if required. The secrecy of people mentioned in your text is **your** responsibility. The model helps you obfuscate and summarize personal data but it makes no assumptions on the gravity of the context surrounding the task. \n\n## Instructions\n### Data\nYou can feed the model a single string, a list of strings or a csv file. If supplying a csv, your data needs to have an id column of unique row ids and a text column.\n\nBERT models have an upper limit of 512 tokens including punctuation. Longer texts will be split up for you into parts of at most 512 tokens, guided by punctuation so that sentences are preserved. The final (obfuscated) data is supplied as two versions: one split up by the max token length, and one concatenated into the original format. \n\n### \n\n### Input\n\nTo begin with, you can get good enough results by just running the NER part of the model, setting *corpus=False*. With domain specific data in a production setting, this might not be enough. The precision of the model will continue to improve if you help it by sorting words into the following three categories: words that are always names, words that are sometimes names and sometimes not (ambiguous), and finally words or phrases that you want to leave untouched (masks).\n\nThe first time you run the main function, a language specific input folder will be created in your working directory (ie where you are running the code), along with three subfolders. Each subfolder can contain any number of csv files. Each csv file contains a column named \"token\" and then has one token or phrase per row. If a subfolder contains multiple files, remember that all of these need to contain the \"token\" column in order to be combined when you run the model. \n\n#### Names\nThis is where you put words that are definitely names, irrespective of context. They will be added to the corpus search and replace (ie substitution by spelling), unless already present in the pre-loaded corpus. \n\n#### Ambiguous\nSome words may or may not be names. Names that are ambiguous need to be inferred from the surrounding context, which is something that Named Entity Recognition does well. Consider the sentences \"He is my best friend\" and \"His name is George Best\". If \"best\" is hidden from the corpus search and replace, the first instance will be untouched, while the actual name in the second sentence will still be detected using NER.\n\nIf the corpus search returns a lot more names than the NER and you know you are using the right NER model, this indicates false positives. Go through output\\_en/names\\_corpus.csv, sort out the problematic words and put them in one or more csv files under input\\_en/ambiguous/, then re-run the code. \n\nYou will have to decide for yourself which words are ambiguous. A small starter kit will be loaded into the folder when it is created, for reference. \n\n#### Masks\nYou have the ability to mask tokens from the model. Masking tokens can consist of one or several words. These tokens will be hidden from the algorithm and then re-substituted at the end of the run. \n\nFor example, adding \"Big Ben\" to the input\\_en/maskings subfolder (of your working directory) ensures that this string stays untouched, while \"Ben\" not preceded by \"Big \" can still be treated as a name. Create a csv file in the maskings folder, where the first and only column is named \"token\" and each subsequent row is a masked token. \n\nIf, like me, you work for a city, you might want to put all the names of streets and places into this folder. Streets are good candidates for masking because they often contain proper names that you might not want to remove, for example \"Harris St.\" and \"Coleman Rd.\".\n\n## Examples\n\n### A single, short text\n```\nfrom secretaries import secretary as s\nt = s.run(\"Bear Grylls once met a bear at Bear lake.\", \n lang = \"English\", \n ambiguous = [\"bear\"],\n masks = [\"Bear lake\"],\n single_text_mode = True)\nprint(t)\n```\n```\n[name] once met a bear at Bear lake.\n```\n\n### A longer text\n\nThis example runs the model on Jane Austen's *Pride and prejudice*. Time elapsed is about 12 minutes on a 24 core Threadripper CPU and an A4000 GPU. We set *corpus=False* to avoid getting a lot of false positives due to ambiguous words. \n\n```\nimport re\nimport urllib.request\nurl = r\"https://www.gutenberg.org/cache/epub/1342/pg1342.txt\" \nreq = urllib.request.Request(url)\nresp = urllib.request.urlopen(req)\ntext = resp.read().decode('utf-8')\n\nprint('n characters: ' + str(len(text)))\nprint('n tokens: ' + str(len(re.split(r\"\\b\", text))))\n\nfrom secretaries import secretary as s\ntext = s.run(text = text, id_column = \"id\", text_column = \"text\", \n model_name = \"dslim/bert-large-NER\",\n lang = \"English\", corpus = False,\n single_text_mode = True)\n```\n\n```\nn characters: 763250\nn tokens: 263829\n```\n\n```\n> cat output_en/names_ner.csv\n 1 \u2502 token,count\n 2 \u2502 Elizabeth,338\n 3 \u2502 Darcy,287\n 4 \u2502 Jane,199\n 5 \u2502 Bennet,154\n 6 \u2502 Bingley,138\n 7 \u2502 Collins,131\n 8 \u2502 Lydia,123\n 9 \u2502 Wickham,104\n 10 \u2502 Catherine,74\n 11 \u2502 Gardiner,73\n 12 \u2502 Lizzy,63\n ... \u2502 ...\n```\n\n### A comma-separated text file (csv)\n\nThis example uses the default NER model. The data has an integer id column named \"text\\_number\" and a text column aptly named \"text\". The unique ids are integers and specifying this helps with the final sorting of the data. The text has some unwanted html tags. \n\n```\nfrom secretaries import secretary as s\nd = s.run(csv = \"my_data.csv\", lang = \"English\",\n id_column = 'text_number', text_column = 'text', \n remove_html = True, id_column_as_int = True)\n```\n\n\n\n\n# Other languages\nSupport for other languages could possibly be added. At minimum there needs to be a suitable model for Named Entity Recognition (NER) at the [Huggingface Model Hub](https://huggingface.co/models?pipeline_tag=text-classification&sort=trending&search=ner).\n\n\n# Swedish\n\n## Syfte och anv\u00e4ndningsomr\u00e5de\nSekreteraren \u00e4r ett Python-paket f\u00f6r att flagga, ers\u00e4tta och sammanfatta personuppgifter i l\u00f6pande text. Den \u00e4r en optimerad version av en tidigare kodbas som anv\u00e4nds av Malm\u00f6 stad f\u00f6r att gallra personuppgifter i kundtj\u00e4nst\u00e4renden. Det finns sannolikt andra modeller som tacklar samma problem, men detta \u00e4r resultatet av v\u00e5ra \u00f6verv\u00e4ganden och erfarenheter. Modellen vilar p\u00e5 [Kungliga Bibliotekets spr\u00e5kmodeller](https://github.com/Kungbib/swedish-bert-models) f\u00f6r NER (detektering av namngivna enheter i texten) och namnkorpuset fr\u00e5n [Svensktext](https://github.com/peterdalle/svensktext). \n\nModellen rensar personuppgifter med h\u00f6g precision, men har ingen rutin f\u00f6r att identifiera utpekande information. Det \u00e4r **ditt** ansvar att kontrollera texten med avseende p\u00e5 r\u00f6janderisk, det vill s\u00e4ga att kombinationen av olika uppgifter (inte) ger m\u00f6jlighet att identifiera en eller flera nu levande individer. Modellen hj\u00e4lper dig att rensa och sammanfatta personuppgifter, men den hj\u00e4lper dig inte att bed\u00f6ma situationens allvar. \n\nNamnen fr\u00e5n [Svensktext](https://github.com/peterdalle/svensktext) laddas ner automatiskt till din arbetsmapp f\u00f6rsta g\u00e5ngen du k\u00f6r huvudfunktionen. Du kan g\u00f6ra till\u00e4gg till detta via undermappen [input\\_se/namn](#namn).\n\n\n## Instruktioner\n### Data\nDu kan mata in en enskild textstr\u00e4ng eller en lista med textstr\u00e4ngar: [\"text1\", \"text2\"]. F\u00f6r st\u00f6rre datam\u00e4ngder med m\u00e5nga texter vill du f\u00f6rmodligen mata in en csv-fil. Du beh\u00f6ver d\u00e5 ange namnen p\u00e5 en id-kolumn med unika id:n och en kolumn som inneh\u00e5ller texterna. \n\nBERT-modellen som anv\u00e4nds f\u00f6r att peka ut namngivna enheter i texten har en maxl\u00e4ngd p\u00e5 512 ord inkl. skiljetecken. L\u00e4ngre texter kommer att delas upp i delar om max denna ordl\u00e4ngd, med hj\u00e4lp av skiljetecken s\u00e5 att meningar inte delas upp. N\u00e4r skriptet har k\u00f6rts finns rensad data i tv\u00e5 versioner: en uppdelad i max ordl\u00e4ngd och en sammanslagen till samma antal rader som den ursprungliga datam\u00e4ngden. \n\n\n### \u00d6vrig input (register)\nDu kan \u00f6ka modellens precision genom att sortera ord i tre kategorier: ord som alltid \u00e4r namn, ord som kan vara namn eller inte (tveksamma), samt ord/fraser som ska bevaras i sin helhet (masker). F\u00f6rsta g\u00e5ngen du k\u00f6r huvudfunktionen kommer en spr\u00e5kspecifik input-mapp med tre undermappar skapas i din arbetsmapp, det vill s\u00e4ga den mapp d\u00e4r du k\u00f6r koden. Varje undermapp kan inneh\u00e5lla en eller flera csv-filer (kommaseparerad text). Varje csv-fil inneh\u00e5ller en kolumn med namnet \"token\" (utan citationstecken) och har sedan ett ord (alt. en fras) per rad. Om du har flera csv-filer i en undermapp, kom ih\u00e5g att samtliga m\u00e5ste ha kolumnen \"token\" namngiven i f\u00f6rsta raden, f\u00f6r att filerna ska kunna kombineras n\u00e4r du k\u00f6r modellen. \n\n#### Namn\nH\u00e4r f\u00f6rvarar du ord som definitivt \u00e4r namn, oavsett sammanhang. Dessa ord kommer att kombineras med namn-korpuset, s\u00e5vida de inte redan finns d\u00e4r. Den svenska modellen bygger p\u00e5 ett mycket stort antal namn fr\u00e5n [Svensktext](https://github.com/peterdalle/svensktext), men ovanliga namn sorteras bort f\u00f6r att undvika fel (false positives). Om din text inneh\u00e5ller ovanliga namn kan du beh\u00f6va l\u00e4gga till dessa, antingen via namn-mappen eller i kod n\u00e4r du anropar funktionen.\n\n#### Tveksamma\nVissa ord \u00e4r tvetydiga och kan vara namn eller inte, beroende p\u00e5 sammanhanget. Exempel \u00e4r Stig, Bj\u00f6rn, Lotta och Finn. Eftersom dessa namn ibland \u00e4r meningsb\u00e4rande ord kan vi inte rutinm\u00e4ssigt ta bort dem baserat p\u00e5 stavning. Om sammanhanget d\u00e4remot indikerar att de utg\u00f6r namn b\u00f6r vi ta bort dem. Detta \u00e4r po\u00e4ngen med att anv\u00e4nda Named Entity Recognition (NER). \n\nOm s\u00f6kningen med Svensktext uppenbart har flaggat fler namn \u00e4n NER, lider din data av false positives, det vill s\u00e4ga ord som felaktigt flaggas som namn (givet att du har anv\u00e4nt r\u00e4tt NER-modell). G\u00e5 igenom csv-filen output\\_se/namn\\_korpus.csv, sortera uppenbara felaktigheter i en eller flera csv-filer under mappen input\\_se/tveksamma och k\u00f6r d\u00e4refter skriptet p\u00e5 nytt. Det finns ocks\u00e5 en nedre brytpunkt i form av parametern min\\_n\\_persons = 100. Exempel: Enligt Svensktext finns 129 f\u00f6rekomster av namnet Snabb, som f\u00f6r-, efter- eller tilltalsnamn. Ordet finns d\u00e4rmed i korpuset, men du vill f\u00f6rmodligen inte slentrianm\u00e4ssigt flagga det som ett namn. N\u00e4r du p\u00e5 detta vis osynligg\u00f6r ett namn f\u00f6r korpus-ers\u00e4ttningen, s\u00e5 kommer det fortfarande plockas upp av \u00f6vriga delar av skriptet (NER och Regex) om sammanhanget indikerar att det \u00e4r ett namn.\n\nF\u00f6rsta g\u00e5ngen du k\u00f6r huvudfunktionen sparas ett startkit med tveksamma ord under input\\_se/tveksamma samtidigt som mapparna skapas. Vill du mot f\u00f6rmodan inte anv\u00e4nda detta, ta bort filen men l\u00e5t mappen vara kvar. \n\n#### Masker\nDu kan maskera ord och fraser som inte ska ers\u00e4ttas. Exempelvis, i meningen \"Gustav bor vid Gustav Adolfs torg\" \u00e4r bara den f\u00f6rsta f\u00f6rekomsten av \"Gustav\" en personuppgift. F\u00f6r att h\u00e5lla resten av meningen intakt l\u00e4gger du masken \"Gustav Adolfs torg\" i undermappen input\\_se/maskeringar (under din arbetsmapp, d\u00e4r din kod k\u00f6rs). Om du jobbar inom en kommun s\u00e5 vill du f\u00f6rmodligen l\u00e4gga in en lista p\u00e5 alla era gator och platser/bes\u00f6ksm\u00e5l, f\u00f6r att h\u00e5lla dessa intakta i texten. \n\n## Exempel\n\n### En kort text\n```\nfrom secretaries import secretary as s\nt = s.run(text = \"Stig m\u00f6tte Bj\u00f6rn p\u00e5 en stig i skogen\", \n ambiguous = [\"stig\",\"bj\u00f6rn\"],\n single_text_mode = True)\nprint(t)\n```\n```\n[namn] m\u00f6tte [namn] p\u00e5 en stig i skogen\n```\n\n\n### En l\u00e4ngre text\nI det h\u00e4r exemplet laddar vi ner och analyserar Hjalmar S\u00f6derbergs *F\u00f6rvillelser*. Det tar cirka 1,5 minut p\u00e5 en dator med 24-k\u00e4rnors Threadripper CPU och en A4000 GPU. Till skillnad fr\u00e5n motsvarande engelska exempel anger vi inte spr\u00e5k eller modell, eftersom svenska \u00e4r standard och den mest omfattande BERT-modellen redan anv\u00e4nds.\n\n```\nimport re\nimport urllib.request\nurl = r\"https://www.gutenberg.org/cache/epub/30078/pg30078.txt\" \nreq = urllib.request.Request(url)\nresp = urllib.request.urlopen(req)\ntext = resp.read().decode('utf-8')\ntext = \"\\n\".join(text.split(\"\\n\")[63:])\n\nprint('antal bokst\u00e4ver: ' + str(len(text)))\nprint('antal ord/skiljetecken: ' + str(len(re.split(r\"\\b\", text))))\n\nfrom secretaries import secretary as s\ntext = s.run(text = text, id_column = \"id\", text_column = \"text\", \n single_text_mode = True, corpus = False)\n # s\u00f6k inte med stavning (korpus), eftersom det blir m\u00e5nga false positives\n```\n\n```\nantal bokst\u00e4ver: 256914\nantal ord/skiljetecken: 85753\n\n```\n\n```\n> cat output_se/namn_ner.csv\n 1 \u2502 token,count\n 2 \u2502 Tomas,105\n 3 \u2502 M\u00e4rta,53\n 4 \u2502 Hall,29\n 5 \u2502 Ellen,26\n 6 \u2502 Greta,17\n 7 \u2502 Mortimer,14\n 8 \u2502 Arvidsons,7\n 9 \u2502 Weber,7\n 10 \u2502 Arvidson,7\n 11 \u2502 Gabel,6\n 12 \u2502 fru Wenschen,6\n 13 \u2502 M\u00e4rta Brehm,5\n ... \u2502 ...\n```\n\n### En kommaseparerad textfil (csv)\n\nH\u00e4r l\u00e4ser vi in en csv-fil d\u00e4r kolumnen \"text\\_nummer\" inneh\u00e5ller ett unikt id f\u00f6r varje text och kolumnen text inneh\u00e5ller sj\u00e4lva texterna. Vi f\u00f6rtydligar ocks\u00e5 att id-kolumnen best\u00e5r av heltal, f\u00f6r sorteringens skull. Vi passar ocks\u00e5 p\u00e5 att radera html-taggar som smugit sin in i texten. \n\n```\nfrom secretaries import secretary as s\nd = s.run(csv = \"min_data.csv\", \n id_column = 'text_nummer', text_column = 'text', \n remove_html = True, id_column_as_int = True)\n```\n\n\n",
"bugtrack_url": null,
"license": null,
"summary": "A utility for removing personal data in text.",
"version": "0.0.1.12",
"project_urls": {
"Bug Tracker": "https://github.com/er1kb/secretaries/issues",
"Homepage": "https://github.com/er1kb/secretaries"
},
"split_keywords": [
"anonymization",
" gdpr",
" personal data",
" personuppgifter",
" python"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "92b62341a8c5d6ba18c3fd7b949f1ba417d0d60e685b6be1add13268a014142b",
"md5": "423885db4c9fa31664327615f70b3ed6",
"sha256": "d4f87a605203c240409d5662c4350a8d76c37c31847051e2c2817aeb4cd9b610"
},
"downloads": -1,
"filename": "secretaries-0.0.1.12-py3-none-any.whl",
"has_sig": false,
"md5_digest": "423885db4c9fa31664327615f70b3ed6",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 22962,
"upload_time": "2024-06-17T11:12:20",
"upload_time_iso_8601": "2024-06-17T11:12:20.922185Z",
"url": "https://files.pythonhosted.org/packages/92/b6/2341a8c5d6ba18c3fd7b949f1ba417d0d60e685b6be1add13268a014142b/secretaries-0.0.1.12-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "72f6231d397329588d129d359f9cecfc3571c994854882407a5ce1dd76c00f41",
"md5": "49cf1647c94367e7c165ac3446c9d250",
"sha256": "2143183539c10a01e48409881150d4d183543db1de86dccd3cf388e0d82011ab"
},
"downloads": -1,
"filename": "secretaries-0.0.1.12.tar.gz",
"has_sig": false,
"md5_digest": "49cf1647c94367e7c165ac3446c9d250",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 20868,
"upload_time": "2024-06-17T11:12:22",
"upload_time_iso_8601": "2024-06-17T11:12:22.115727Z",
"url": "https://files.pythonhosted.org/packages/72/f6/231d397329588d129d359f9cecfc3571c994854882407a5ce1dd76c00f41/secretaries-0.0.1.12.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-06-17 11:12:22",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "er1kb",
"github_project": "secretaries",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "secretaries"
}