Euromind
  • Javascript
    • Javascript

      Dansk evighedskalender

      7. december, 2020

      Javascript

      API til Statistikbanken

      21. september, 2019

      Javascript

      IntersectionObserver

      9. august, 2019

      Javascript

      Navngivne RegEx-grupper i ECMAScript 2018

      29. juli, 2019

      Javascript

      RegEx: Unicode og Look Backward i ECMAScript 2018

      24. juli, 2019

  • CSS/SCSS
    • CSS/SCSS

      Dansk evighedskalender

      7. december, 2020

      CSS/SCSS

      Variable fonte med dansk tegnsæt i open source

      11. august, 2019

      CSS/SCSS

      Progressbar for dokumentposition

      31. juli, 2019

      CSS/SCSS

      Media Query i 2019

      18. juli, 2019

      CSS/SCSS

      Danske Adressers Web API

      17. juli, 2019

  • C#
    • C#

      Authentication for IOS og Android med Firebase i…

      4. oktober, 2019

      C#

      Andersen, Grundvig, Kierkegaard og ML.NET – del 3

      5. september, 2019

      C#

      Hurtig eksport til Excel

      4. september, 2019

      C#

      Andersen, Grundtvig, Kierkegaard og ML.NET – del 2

      2. september, 2019

      C#

      Andersen, Grundvig, Kierkegaard og ML.NET – del 1

      11. august, 2019

  • Javascript
    • Javascript

      Dansk evighedskalender

      7. december, 2020

      Javascript

      API til Statistikbanken

      21. september, 2019

      Javascript

      IntersectionObserver

      9. august, 2019

      Javascript

      Navngivne RegEx-grupper i ECMAScript 2018

      29. juli, 2019

      Javascript

      RegEx: Unicode og Look Backward i ECMAScript 2018

      24. juli, 2019

  • CSS/SCSS
    • CSS/SCSS

      Dansk evighedskalender

      7. december, 2020

      CSS/SCSS

      Variable fonte med dansk tegnsæt i open source

      11. august, 2019

      CSS/SCSS

      Progressbar for dokumentposition

      31. juli, 2019

      CSS/SCSS

      Media Query i 2019

      18. juli, 2019

      CSS/SCSS

      Danske Adressers Web API

      17. juli, 2019

  • C#
    • C#

      Authentication for IOS og Android med Firebase i…

      4. oktober, 2019

      C#

      Andersen, Grundvig, Kierkegaard og ML.NET – del 3

      5. september, 2019

      C#

      Hurtig eksport til Excel

      4. september, 2019

      C#

      Andersen, Grundtvig, Kierkegaard og ML.NET – del 2

      2. september, 2019

      C#

      Andersen, Grundvig, Kierkegaard og ML.NET – del 1

      11. august, 2019

Euromind
C#

Stylometri i C# – del 1

af Per Lindsø Larsen 5. juni, 2019
skrevet af Per Lindsø Larsen 5. juni, 2019
Stylometri i C# – del 1

Nogle vil kalde stylometri en nørdet måde at misbruge god litteratur på, og det kan der være noget om. Til gengæld er det på mange måder et fascinerende misbrug. Min undskyldning for at kaste mig over det i denne blog-serie er, at jeg skal bruge nogle rutiner til klargøring af online-tekster til analyse i Microsoft’s Machine Learning modul ML.NET.  

Ordet Stylometri skriver sig tilbage til  Wincenty Lutosławski’s bog ”Principes de stylométrie” fra 1890, hvor han med baggrund i kvantitative metoder søger at fastslå en kronologi i Platons klassiske dialoger. Stylometrien arbejder med at afdække statistisk målbare forfatter-karakteristika gennem analyser af tekster. En klassisk opgave for stylometrien er, at kunne påvise eller sandsynliggøre, hvem forfatteren er til en given tekst af ukendt oprindelse. Stylometrien har også undersøgt forskelle mellem mandlige og kvindelige forfatteres stilmæssige karakteristika, udvikling i løbet af en forfatters karriere, herunder f.eks. hvorvidt det stylometrisk kan spores i forfatterskaber, at de pågældende forfattere sent i produktionen udviklede Alzheimers sygdom eller led af bipolar affektiv lidelse i perioder under forfatterskabet.

I en mere åndløs digital tidsalder gør stylometrien sig nyttig ved at bidrage med metoder til f.eks. at identificere spam-mails, falske blog-indlæg og såmænd også identificere hvilke opslag på Donald Trumps Twitterprofil, han selv har skrevet.  Det viser sig at være indlæg med overvægt af ord som “dumb”, “dead”, “badly”, “weak”, “crazy”, “guns”. Hvem havde gættet det? 

Som bekendt er der på nettet et væld af fuld-tekster at tage fat på, bl.a. på Gutenberg.org og Arkiv for Dansk Litteratur.  Digitale tekster kan være af meget svingende kvalitet med OCR-læsninger, der lader en del tilbage at ønske. Ideelt set burde man vel gå dem manuelt igennem før videre bearbejdning, men det er kun fristende, hvis man har et meget kedeligt liv. Så det kommer ikke på tale.

Hent de ønskede tekster ned og gem dem i en UTF8-fil. Fjern tekstdele som ikke hører til det egentlige værk, f.eks. kolofon, copyrightnoter, udgivers forord etc. Herefter kan følgende kode bruges til at indlæse teksten og foretage en “grov-vaskning”:

public static string PreProcessText(string fname, Encoding enc, bool fixDoubleA = true)
 {
     StringBuilder builder = new StringBuilder();
     using (var file = new StreamReader(fname, enc))
     {
         string line;
         while ((line = file.ReadLine()) != null)
         {
             //Ændre dobbelt-a til å    
             line = (fixDoubleA) ? line.Replace("aa", "å").Replace("Aa", "Å").Trim() : line;
             // sammenføj evt. orddeling ved libnjeafslutning
             if ((line.Length > 0) && (line.Last() == '-'))
                 builder.Append(line.Remove(line.Length - 1, 1).Trim());
             else
                 builder.AppendLine(line.Trim());
         }
     }
     // Fjern uønskede karakterer fra teksten
     Regex rgx = new Regex("[^a-zøæåöüA-ZÆØÅÖÜ0-9.,;\'-:!?()=\"”'»«(\r\n)-]");
     string Text = rgx.Replace(builder.ToString(), " ");
     //Ensart linjeskift
     Text = Regex.Replace(Text, @"(\r\n)+", "\r\n");
     return Text;
 }

Et teksteksempel viser den indlæste tekst før grov-vaskningen:

||Jeg var et Øieblik raadvild, derpaa henvendte jeg mig til Guderne saaledes: Høistærede Samtidige, jeg vælger een Ting, at jeg altid maa have Latteren paa min Side. Der var ikke en Gud, der svarede et Ord, derimod gave de sig alle til at lee.

Deraf sluttede jeg, at min Bøn var opfyldt, og fandt, at Guderne vidste at udtrykke sig med Smag; thi det havde jo dog været upassende, alvorligt at svare: det er Dig indrømmet.
-
De umiddelbare erotiske Stadier eller det Musikalsk-Erotiske <

Og efter :

Jeg var et Øieblik rådvild, derpå henvendte jeg mig til Guderne således: Høistærede Samtidige, jeg vælger een Ting, at jeg altid må have Latteren på min Side. Der var ikke en Gud, der svarede et Ord, derimod gave de sig alle til at lee.
Deraf sluttede jeg, at min Bøn var opfyldt, og fandt, at Guderne vidste at udtrykke sig med Smag; thi det havde jo dog været upassende, alvorligt at svare: det er Dig indrømmet.
De umiddelbare erotiske Stadier eller det Musikalsk-Erotiske

I denne første manipulation laves de gamle teksters dobbelt-a om til å. Det er for at gøre dem mere kompatible med  stopords-lister,  sentiments-lister og lignende. Desuden fjernes lidt andet overflødigt skrammel fra teksterne. Der er givetvis plads til forbedringer i rutinen, men funktionen indlæser tekster på et pænt tilfredsstillende niveau.

Næste skridt er muligheden for at få splittet den indlæste tekst op i en liste af ord. Følgende giver i bogstaveligste forstand rene ord for pengene:

private static readonly char[] wordDelimiters = new[] { ' ', '.', ',', ';', '\'', '-', ':', '!', '?', '(', ')', '=', '"', '”', '\t', '\r', '\n' };

public static List<string> RemoveStopWords(List<string> words)
{
    List<string> reduced = new List<string>();
    foreach (string word in words)
    {
        if ((!Resources.stopwords.Contains(word.ToLower())) && (Regex.Replace(word, @"[\d-]", string.Empty) != "")) reduced.Add(word);
    }
    return reduced;
}

public static List<string> GetWords(string text, bool removeStopwords)
{
    var normalizedString = text.Split(wordDelimiters, StringSplitOptions.RemoveEmptyEntries);
    var words = Array.ConvertAll(normalizedString, a => a.ToLower()).ToList();
    return removeStopwords ? RemoveStopWords(words) : words;
}

Genereringen af ord-listen kan vælges med eller uden stopord. Hvad er så lige stopord??? Ifølge den danske ordbog er stopord: ”meget almindeligt eller udbredt ord eller tegn som er uegnet som søgeord ved søgning i elektroniske tekster og derfor ofte udelukkes ved søgningen fx og,eller”. I mange tilfælde kan de med fordel fjernes af teksten. Der findes på nettet en dansk stopord-liste, f.eks. her og her.

Ovenstående teksteksempel ser herefter således ud – uden fjernelse af stopord:

jeg var et øieblik rådvild derpå henvendte jeg mig til guderne således høistærede samtidige jeg vælger een ting at jeg altid må have latteren på min side der var ikke en gud der svarede et ord derimod gave de sig alle til at lee deraf sluttede jeg at min bøn var opfyldt og fandt at guderne vidste at udtrykke sig med smag thi det havde jo dog været upassende alvorligt at svare det er dig indrømmet de umiddelbare erotiske stadier eller det musikalsk erotiske

Og efter fjernelse af stopord:

øieblik rådvild henvendte guderne høistærede samtidige vælger een ting latteren gud svarede derimod gave lee deraf sluttede bøn opfyldt fandt guderne vidste udtrykke smag upassende alvorligt svare indrømmet umiddelbare erotiske stadier musikalsk erotiske

En yderligere brugbar funktion er muligheden for at kunne opdele en længere tekst i mindre sammenlignelige bidder. De kan efter behag kaldes tekst-blokke, klynger, clustrer. Funktionen getWordClusters  er et bud på at løse den problemstilling:

public static List<string> getWordClusters(string text, int ClusterSize, bool removeStopwords = false)
{
    var wordlist = GetWords(text, removeStopwords);
    decimal numberOfClusters = wordlist.Count / ClusterSize;
    numberOfClusters = Math.Floor(numberOfClusters);
    var resultlist = new List<string>();
    for (var counter = 0; counter < numberOfClusters; counter++)
    {
        resultlist.Add(wordlist.GetRange(counter * ClusterSize, ClusterSize).Aggregate((ii, jj) => ii + " " + jj));
    }
    return resultlist;
}

Med en valgt clusterSize på 10, vil ovenstående teksteksempel ende med tre tekstblokke af hver 10 ord:

øieblik rådvild henvendte guderne høistærede samtidige vælger een ting latteren
gud svarede derimod gave lee deraf sluttede bøn opfyldt fandt
guderne vidste udtrykke smag upassende alvorligt svare indrømmet umiddelbare erotiske

Den samme øvelse, som her er gjort med ord, kan gøres på sætninger. Efter en flaske rødvin vil man  være tilbøjelig til at argumentere for, at en sætning er et tilfældigt antal på hinanden følgende ord, der enten afsluttes med punktum, udråbstegn eller spørgsmålstegn. Efter at være blevet ædru igen, vil de fleste sædvanligvis erkende, at det ikke er helt så enkelt. F.eks. vil teksten ”Hr. P.G. Philipsen betalte 12 rd. og 10 sk. for varerne.” ende op som 6 sætninger, hvilket i betydelig grad udvander begrebet en sætning. Jeg er ikke bekendt med, at der findes en endegyldig formel til at hive sætninger ud af tekster på en helt fejlfri måde.  I arbejde med ældre tekster har jeg brugt en liste, sentenceIgnoreList, med hyppigst forekommende forkortelser, efter hvilke et punktum ikke udgør en sætnings-afslutning. Følgende funktion giver endvidere mulighed for at træffe en diktatorisk beslutning om, at en gyldig sætning har i det mindste minWords ord (default=3) og  minChars antal bogstaver (default=50). Det optimerer i en vis grad og efterfølgende manuel gennemgang viser, at det giver resultater tæt på det ønskede.

private const string sentenceIgnoreList = "sk,hr,frk,rbd,f,ex,etc";


  public static string StringWordsRemove(string stringToClean, List<string> stopwords)
  {
      return string.Join(" ", stringToClean
           .Split(new[] { ' ', ',', '.', '?', '!' }, StringSplitOptions.RemoveEmptyEntries)
           .Except(stopwords));
  }


  public static List<string> GetSentences(string text, bool removeStopwords = false, int wordMin = 3, int charMin = 50)
  {
      var stopwords = Properties.Resources.stopwords.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList();
      var sentences = new List<string>();
      string sentence = "";
      var temporarySentences = Regex.Split(text, @"(?<=[\.!\?])\s+").ToList();

      foreach (var temporarySentence in temporarySentences)
      {
          sentence += (removeStopwords)
              ? removeLineBreaksAndMultipleSpaces(StringWordsRemove(temporarySentence, stopwords))
              : removeLineBreaksAndMultipleSpaces(temporarySentence);

          //Split sætningen op i ord for kontrol af længde m.v.
          var words = GetWords(sentence, false);
          if ((words.Count >= wordMin) && (sentence.Length >= charMin) &&
              (!sentenceIgnoreList.Contains(words.Last().ToLower())))
          {
              //Sætningen valideret - tilføj
              sentences.Add(sentence);
              sentence = "";
          }
          else
          {
              //Sætning ikke valid - fortsæt med tilføjelse af næste
              sentence += " ";
          }
      }
      return sentences;
  }


  public static List<string> getSentenceClusters(string text, int ClusterSize, bool removeStopWords = false)
  {
      var sentencelist = GetSentences(text, removeStopWords);
      decimal numberOfClusters = sentencelist.Count / ClusterSize;
      numberOfClusters = Math.Floor(numberOfClusters);
      var resultlist = new List<string>();
      for (var counter = 0; counter < numberOfClusters; counter++)
      {
          resultlist.Add(
              sentencelist.GetRange(counter * ClusterSize, ClusterSize).Aggregate((ii, jj) => ii + " " + jj));
      }
      return resultlist;
  }

Hermed er meget af det grove arbejde gjort for at komme i gang med stylometriens sjovere og mere meningsfulde sider. Herom mere i del 2, del 3 og del 4.

Stylometri
0 Kommentarer
7
FacebookTwitterPinterestEmail
forrige post
Helt ude i AMP’en
næste post
GULP møder AMP – del 1

Relaterede indlæg

Authentication for IOS og Android med Firebase i...

4. oktober, 2019

Andersen, Grundvig, Kierkegaard og ML.NET – del 3

5. september, 2019

Hurtig eksport til Excel

4. september, 2019

Andersen, Grundtvig, Kierkegaard og ML.NET – del 2

2. september, 2019

Andersen, Grundvig, Kierkegaard og ML.NET – del 1

11. august, 2019

Forbind Visual Studio til IOS devices på 10...

12. juli, 2019

Stylometri i C# – del 4

9. juli, 2019

Serialisering og deserialisering i C#

5. juli, 2019

Stylometri i C# – del 3

2. juli, 2019

Stylometri i C# – del 2

7. juni, 2019

Efterlad en kommentar Afbryd svar

Gem mit navn, email, og website i denne browser til senere kommentarer.

Seneste indlæg

  • Dansk evighedskalender

    7. december, 2020
  • Automatisk køreafstand i Excel

    17. august, 2020
  • Headless browser på 10 minutter

    25. juli, 2020

Kategorier

  • C#
  • CSS/SCSS
  • Excel
  • HTML
  • Javascript
  • Mobile
  • Webdesign
  • Xamarin

Om mig

Om mig

Per Lindsø Larsen

Freelance fullstack developer bo9sat i Aarhus.

Du kan hyre mig til korterevarende projekter eller konkrete opgaveløsninger.

Pæn rabat til non-profit organisationer og foreninger.

Når jeg ikke koder, deltager jeg løbende i diverse spændende forskningsprojekter om alt andet end kodning.

Keep in touch

Facebook Twitter Email Github

Tags

Adresser AMP AMP Story Android API Billedformater Billedoptimering Brand C# Codepen Cordova CPR Crome DevTools CSS Debug Ecmascript Excel Fonte Gmail Gulp HTML Ikoner IOS Javascript JsFiddle Machine Learning Mail Mediaquery ML.NET Mobile RegEx SCSS SMTP Stylometri Visual Studio Webdesign Xamarin

Nyhedsbrev

Timeld nyhedsbrev for info om nye blog-indlæg, tips m.v.

  • Facebook
  • Twitter
  • Email
  • Github

@2019 - Euromind.com - Code-To-Go. All Right Reserved.
lindsoe@gmail.com - mobil: 42797273


Tilbage til top
Euromind
  • Javascript
    • Javascript

      Dansk evighedskalender

      7. december, 2020

      Javascript

      API til Statistikbanken

      21. september, 2019

      Javascript

      IntersectionObserver

      9. august, 2019

      Javascript

      Navngivne RegEx-grupper i ECMAScript 2018

      29. juli, 2019

      Javascript

      RegEx: Unicode og Look Backward i ECMAScript 2018

      24. juli, 2019

  • CSS/SCSS
    • CSS/SCSS

      Dansk evighedskalender

      7. december, 2020

      CSS/SCSS

      Variable fonte med dansk tegnsæt i open source

      11. august, 2019

      CSS/SCSS

      Progressbar for dokumentposition

      31. juli, 2019

      CSS/SCSS

      Media Query i 2019

      18. juli, 2019

      CSS/SCSS

      Danske Adressers Web API

      17. juli, 2019

  • C#
    • C#

      Authentication for IOS og Android med Firebase i…

      4. oktober, 2019

      C#

      Andersen, Grundvig, Kierkegaard og ML.NET – del 3

      5. september, 2019

      C#

      Hurtig eksport til Excel

      4. september, 2019

      C#

      Andersen, Grundtvig, Kierkegaard og ML.NET – del 2

      2. september, 2019

      C#

      Andersen, Grundvig, Kierkegaard og ML.NET – del 1

      11. august, 2019

Populære indlæg

  • 1

    Stylometri i C# – del 2

    7. juni, 2019
  • 2

    Send email fra Javascript med Gmail API

    21. juni, 2019
  • 3

    Andersen, Grundvig, Kierkegaard og ML.NET – del 1

    11. august, 2019
  • 4

    Gmail, Yahoo og Outlook som SMTP-server

    18. april, 2019
  • 5

    Registrer Gmail API til brug i javascript

    27. juni, 2019
@2019 - Euromind.com - Code-To-Go. All Right Reserved.
lindsoe@gmail.com - mobil: 42797273

Læs ogsåx

Andersen, Grundvig, Kierkegaard og ML.NET – del 3

5. september, 2019

Andersen, Grundvig, Kierkegaard og ML.NET – del 1

11. august, 2019

Gmail, Yahoo og Outlook som SMTP-server

18. april, 2019