[PYTHON] 100 Sprachverarbeitungsklopfen (2020): 45

"""
## 45.Extraktion von Verbfallmustern[Permalink](https://nlp100.github.io/ja/ch05.html#45-ExtraktionvonVerbfallmustern)

Ich möchte den diesmal verwendeten Satz als Korpus betrachten und die möglichen Fälle japanischer Prädikate untersuchen. Stellen Sie sich das Verb als Prädikat und das Hilfsverb der Phrase, die sich auf das Verb bezieht, als Fall vor und geben Sie das Prädikat und den Fall in einem durch Tabulatoren getrennten Format aus. Stellen Sie jedoch sicher, dass die Ausgabe den folgenden Spezifikationen entspricht.

-In einer Phrase, die ein Verb enthält, wird die Grundform des Verbs ganz links als Prädikat verwendet.
-Verwenden Sie die Hilfswörter, die sich auf das Prädikat beziehen
-Wenn das Prädikat mehrere Hilfswörter (Phrasen) enthält, ordnen Sie alle Hilfswörter in Wörterbuchreihenfolge an, die durch Leerzeichen getrennt sind.

Ein Beispielsatz, dass "ich hier zum ersten Mal einen Menschen gesehen habe" (neko.txt.Betrachten Sie den 8. Satz von Cabocha). Dieser Satz enthält zwei Verben, "begin" und "see", und die Phrase, die sich auf "begin" bezieht, wird als "here" analysiert, und die Phrase, die sich auf "see" bezieht, wird als "I am" und "thing" analysiert. Sollte die folgende Ausgabe erzeugen.


Am Anfang
Sehen


Speichern Sie die Ausgabe dieses Programms in einer Datei und überprüfen Sie die folgenden Elemente mit UNIX-Befehlen.

-Eine Kombination von Prädikaten und Fallmustern, die häufig im Korpus vorkommen
-Fallmuster der Verben "do", "see" und "give" (in der Reihenfolge der Häufigkeit des Auftretens im Korpus anordnen)
"""
from collections import defaultdict
from typing import Dict, List


def read_file(fpath: str) -> List[List[str]]:
    """Get clear format of parsed sentences.

    Args:
        fpath (str): File path.

    Returns:
        List[List[str]]: List of sentences, and each sentence contains a word list.
                         e.g. result[1]:
                            ['* 0 2D 0/0 -0.764522',
                             '\u3000\t Symbol,Leer,*,*,*,*,\u3000,\u3000,\u3000',
                             '* 1 2D 0/1 -0.764522',
                             'ich\t Substantiv,Gleichbedeutend,Allgemeines,*,*,*,ich,Wagahai,Wagahai',
                             'Ist\t Assistent,Hilfe,*,*,*,*,Ist,C.,Beeindruckend',
                             '* 2 -1D 0/2 0.000000',
                             'Katze\t Substantiv,Allgemeines,*,*,*,*,Katze,Katze,Katze',
                             'damit\t Hilfsverb,*,*,*,Besondere,Kontinuierlicher Typ,Ist,De,De',
                             'Gibt es\t Hilfsverb,*,*,*,Fünf Schritte, La Linie Al,Grundform,Gibt es,Al,Al',
                             '。\t Symbol,Phrase,*,*,*,*,。,。,。']
    """
    with open(fpath, mode="rt", encoding="utf-8") as f:
        sentences = f.read().split("EOS\n")
    return [sent.strip().split("\n") for sent in sentences if sent.strip() != ""]


class Morph:
    """Morph information for each token.

    Args:
        data (dict): A dictionary contains necessary information.

    Attributes:
        surface (str):Oberfläche
        base (str):Base
        pos (str):Teil (Basis)
        pos1 (str):Teil Teil Unterklassifizierung 1 (pos1)
    """

    def __init__(self, data):
        self.surface = data["surface"]
        self.base = data["base"]
        self.pos = data["pos"]
        self.pos1 = data["pos1"]

    def __repr__(self):
        return f"Morph({self.surface})"

    def __str__(self):
        return "surface[{}]\tbase[{}]\tpos[{}]\tpos1[{}]".format(
            self.surface, self.base, self.pos, self.pos1
        )


class Chunk:
    """Containing information for Clause/phrase.

    Args:
        data (dict): A dictionary contains necessary information.

    Attributes:
        chunk_id (str): The number of clause chunk (Phrasennummer).
        morphs List[Morph]: Morph (Morphem) list.
        dst (str): The index of dependency target (Indexnummer der Kontaktklausel).
        srcs (List[str]): The index list of dependency source. (Original-Klauselindexnummer).
    """

    def __init__(self, chunk_id, dst):
        self.id = chunk_id
        self.morphs = []
        self.dst = dst
        self.srcs = []

    def __repr__(self):
        return "Chunk( id: {}, dst: {}, srcs: {}, morphs: {} )".format(
            self.id, self.dst, self.srcs, self.morphs
        )

    def get_surface(self) -> str:
        """Concatenate morph surfaces in a chink.

        Args:
            chunk (Chunk): e.g. Chunk( id: 0, dst: 5, srcs: [], morphs: [Morph(ich), Morph(Ist)]
        Return:
            e.g. 'ich bin'
        """
        morphs = self.morphs
        res = ""
        for morph in morphs:
            if morph.pos != "Symbol":
                res += morph.surface
        return res

    def validate_pos(self, pos: str) -> bool:
        """Return Ture if 'Substantiv' or 'Verb' in chunk's morphs. Otherwise, return False."""
        morphs = self.morphs
        return any([morph.pos == pos for morph in morphs])


def convert_sent_to_chunks(sent: List[str]) -> List[Morph]:
    """Extract word and convert to morph.

    Args:
        sent (List[str]): A sentence contains a word list.
                            e.g. sent:
                               ['* 0 1D 0/1 0.000000',
                                'ich\t Substantiv,Gleichbedeutend,Allgemeines,*,*,*,ich,Wagahai,Wagahai',
                                'Ist\t Assistent,Hilfe,*,*,*,*,Ist,C.,Beeindruckend',
                                '* 1 -1D 0/2 0.000000',
                                'Katze\t Substantiv,Allgemeines,*,*,*,*,Katze,Katze,Katze',
                                'damit\t Hilfsverb,*,*,*,Besondere,Kontinuierlicher Typ,Ist,De,De',
                                'Gibt es\t Hilfsverb,*,*,*,Fünf Schritte, La Linie Al,Grundform,Gibt es,Al,Al',
                                '。\t Symbol,Phrase,*,*,*,*,。,。,。']

    Parsing format:
        e.g. "* 0 1D 0/1 0.000000"
        |Säule|Bedeutung|
        | :----: | :----------------------------------------------------------- |
        |   1    |Die erste Spalte ist`*`.. Zeigt an, dass es sich um ein Ergebnis der Abhängigkeitsanalyse handelt.|
        |   2    |Phrasennummer (Ganzzahl ab 0)|
        |   3    |Kontaktnummer +`D`                                              |
        |   4    |Hauptadresse/Funktionswortposition und beliebig viele Identitätsspalten|
        |   5    |Verlobungspunktzahl. Im Allgemeinen ist es umso einfacher, sich zu engagieren, je größer der Wert ist.|

    Returns:
        List[Chunk]: List of chunks.
    """
    chunks = []
    chunk = None
    srcs = defaultdict(list)

    for i, word in enumerate(sent):
        if word[0] == "*":
            # Add chunk to chunks
            if chunk is not None:
                chunks.append(chunk)

            # eNw Chunk beggin
            chunk_id = word.split(" ")[1]
            dst = word.split(" ")[2].rstrip("D")
            chunk = Chunk(chunk_id, dst)
            srcs[dst].append(chunk_id)  # Add target->source to mapping list

        else:  # Add Morch to chunk.morphs
            features = word.split(",")
            dic = {
                "surface": features[0].split("\t")[0],
                "base": features[6],
                "pos": features[0].split("\t")[1],
                "pos1": features[1],
            }
            chunk.morphs.append(Morph(dic))

            if i == len(sent) - 1:  # Add the last chunk
                chunks.append(chunk)

    # Add srcs to each chunk
    for chunk in chunks:
        chunk.srcs = list(srcs[chunk.id])

    return chunks


def get_predicate_pattern(chunks: List[Chunk]) -> List[Dict[str, list]]:
    """Get edges from sentence chunks.

    Terms:
        -Prädikat(predicate)
        -Artikel(argument)
        -Fall(case)

    Args:
        chunks (List[Chunk]): A sentence contains many chunks.
            e.g. [Chunk( id: 0, dst: 5, srcs: [], morphs: [Morph(ich), Morph(Ist)] ),
                  Chunk( id: 1, dst: 2, srcs: [], morphs: [Morph(Hier), Morph(damit)] ),
                  Chunk( id: 2, dst: 3, srcs: ['1'], morphs: [Morph(Start), Morph(Hand)] ),
                  Chunk( id: 3, dst: 4, srcs: ['2'], morphs: [Morph(Mensch), Morph(Das)] ),
                  Chunk( id: 4, dst: 5, srcs: ['3'], morphs: [Morph(Ding), Morph(Zu)] ),
                  Chunk( id: 5, dst: -1, srcs: ['0', '4'], morphs: [Morph(Sie sehen), Morph(Ta), Morph(。)] )]

    Returns:
        List[Dict[str, list]]: Predicate and case.
            e.g. [defaultdict(list, {'Start': ['damit']}), defaultdict(list, {'sehen': ['Ist', 'Zu']})]
    """
    patterns = []
    for chunk in chunks:
        # Skip if not valid
        if len(chunk.srcs) == 0 or all([morph.pos != "Verb" for morph in chunk.morphs]):
            continue

        # Initialize
        pred_case = defaultdict(list)

        # Get predicate
        for morph in chunk.morphs:
            if morph.pos == "Verb":
                predicate = morph.base
                break

        # Get case
        for src in chunk.srcs:
            src_chunk = chunks[int(src)]
            for morph in src_chunk.morphs:
                if morph.pos == "Partikel":
                    pred_case[predicate].append(morph.base)

        # Add to patterns
        patterns.append(pred_case)
    return patterns


def write_to_file(pattern_sents, path):
    """Write patterns to file.

    Args:
        pattern_sents ([type]): predicate-case patterns.
            e.g. [[defaultdict(list, {'Geboren': ['damit']}), defaultdict(list, {'Tsukuri': ['Oder', 'Aber']})],
                  [defaultdict(list, {'Schrei': ['damit']}), defaultdict(list, {'Machen': ['Hand', 'Nur', 'Ist']})]]
    """
    # convert_patterns_to_text
    lines = []
    for pattern_sent in pattern_sents:
        for pattern in pattern_sent:  # pattern: {'Tsukuri': ['Oder', 'Aber']}
            for predicate, case_list in pattern.items():
                case_text = " ".join(sorted(case_list))  #Sortieren Sie die Zeichen in aufsteigender Reihenfolge
                lines.append((predicate, case_text))

    # write_to_file
    with open(path, "w") as f:
        for line in lines:
            f.write(f"{line[0]}\t{line[1]}\n")


fpath = "neko.txt.cabocha"
sentences = read_file(fpath)
sentences = [convert_sent_to_chunks(sent) for sent in sentences]  # ans41

# ans45
pattern_sents = [get_predicate_pattern(sent) for sent in sentences]
pattern_sents = list(filter(lambda x: len(x) != 0, pattern_sents))
write_to_file(pattern_sents, "patterns.txt")

# "patterns.txt":
#Geboren werden
#Tsukuka
#Durch Weinen
#Nur um es zu tun
#Am Anfang
#Sehen
#Hör mal zu
#Fangen
#Kochen
#Essen

ans45.sh


#Eine Kombination von Prädikaten und Fallmustern, die häufig im Korpus vorkommen
# see ans19.sh
sort patterns.txt | uniq -c | sort -k1nr > patterns_sorted.txt
# -k 3: sort as the 3rd column
# -n: numeric sort
# -r: reverse order


#Nur "tun"
grep "^Machen\s" patterns.txt | sort | uniq -c | sort -k1nr > "Machen.txt"

# #Nur "Sehen"
grep "^sehen\s" patterns.txt | sort | uniq -c | sort -k1nr > "sehen.txt"

# #Nur "Geben"
grep "^geben\s" patterns.txt | sort | uniq -c | sort -k1nr > "geben.txt"

Recommended Posts

100 Sprachverarbeitungsklopfen 03 ~ 05
100 Sprachverarbeitungsklopfen (2020): 40
100 Sprachverarbeitungsklopfen (2020): 32
100 Sprachverarbeitungsklopfen (2020): 35
100 Sprachverarbeitungsklopfen (2020): 47
100 Sprachverarbeitungsklopfen (2020): 39
100 Sprachverarbeitungsklopfen (2020): 26
100 Sprachverarbeitungsklopfen (2020): 34
100 Sprachverarbeitungsklopfen (2020): 42
100 Sprachverarbeitungsklopfen (2020): 49
100 Sprachverarbeitungsklopfen 06 ~ 09
100 Sprachverarbeitungsklopfen (2020): 43
100 Sprachverarbeitungsklopfen (2020): 24
100 Sprachverarbeitungsklopfen (2020): 45
100 Sprachverarbeitungsklopfen (2020): 10-19
100 Sprachverarbeitungsklopfen (2020): 30
100 Sprachverarbeitungsklopfen (2020): 00-09
100 Sprachverarbeitungsklopfen (2020): 31
100 Sprachverarbeitungsklopfen (2020): 48
100 Sprachverarbeitungsklopfen (2020): 44
100 Sprachverarbeitungsklopfen (2020): 41
100 Sprachverarbeitungsklopfen (2020): 37
100 Sprachverarbeitungsklopfen (2020): 25
100 Sprachverarbeitungsklopfen (2020): 23
100 Sprachverarbeitungsklopfen (2020): 33
100 Sprachverarbeitungsklopfen (2020): 20
100 Sprachverarbeitungsklopfen (2020): 27
100 Sprachverarbeitungsklopfen (2020): 46
100 Sprachverarbeitungsklopfen (2020): 21
100 Sprachverarbeitungsklopfen (2020): 36
100 Amateur-Sprachverarbeitungsklopfen: 41
100 Amateur-Sprachverarbeitungsklopfen: 71
100 Amateur-Sprachverarbeitungsklopfen: 56
100 Amateur-Sprachverarbeitungsklopfen: 50
100 Amateur-Sprachverarbeitungsklopfen: 59
100 Amateur-Sprachverarbeitungsklopfen: 70
100 Amateur-Sprachverarbeitungsklopfen: 62
100 Amateur-Sprachverarbeitungsklopfen: 60
100 Amateur-Sprachverarbeitungsklopfen: 30
100 Amateur-Sprachverarbeitungsklopfen: 06
100 Amateur-Sprachverarbeitungsklopfen: 84
100 Amateur-Sprachverarbeitungsklopfen: 81
100 Amateur-Sprachverarbeitungsklopfen: 33
100 Amateur-Sprachverarbeitungsklopfen: 46
100 Amateur-Sprachverarbeitungsklopfen: 88
100 Amateur-Sprachverarbeitungsklopfen: 89
100 Amateur-Sprachverarbeitungsklopfen: 40
100 Amateur-Sprachverarbeitungsklopfen: 45
100 Amateur-Sprachverarbeitungsklopfen: 43
100 Amateur-Sprachverarbeitungsklopfen: 55
100 Amateur-Sprachverarbeitungsklopfen: 22
100 Amateur-Sprachverarbeitungsklopfen: 61
100 Amateur-Sprachverarbeitungsklopfen: 94
100 Amateur-Sprachverarbeitungsklopfen: 54
100 Amateur-Sprachverarbeitungsklopfen: 04
100 Amateur-Sprachverarbeitungsklopfen: 63
100 Amateur-Sprachverarbeitungsklopfen: 78
100 Amateur-Sprachverarbeitungsklopfen: 12
100 Amateur-Sprachverarbeitungsklopfen: 14
100 Amateur-Sprachverarbeitungsklopfen: 08
100 Amateur-Sprachverarbeitungsklopfen: 42