[PYTHON] 100 coups de traitement linguistique (2020): 45

"""
## 45.Extraction de modèles de cas verbaux[Permalink](https://nlp100.github.io/ja/ch05.html#45-Extractiondemodèlesdecasverbaux)

Je voudrais considérer la phrase utilisée cette fois comme un corpus et enquêter sur les cas possibles de prédicats japonais. Considérez le verbe comme un prédicat et le verbe auxiliaire de la phrase liée au verbe comme une casse, et affichez le prédicat et la casse dans un format délimité par des tabulations. Cependant, assurez-vous que la sortie répond aux spécifications suivantes.

-Dans une phrase contenant un verbe, la forme de base du verbe le plus à gauche est utilisée comme prédicat.
-Utilisez les mots auxiliaires liés au prédicat comme cas
-S'il existe plusieurs mots auxiliaires (phrases) liés au prédicat, organisez tous les mots auxiliaires dans l'ordre du dictionnaire, séparés par des espaces.

Une phrase d'exemple disant "J'ai vu un être humain pour la première fois ici".txt.Prenons la huitième phrase de cabocha). Cette phrase contient deux verbes, «commencer» et «voir», et la phrase liée à «commencer» est analysée comme «ici», et la phrase liée à «voir» est analysée comme «je suis» et «chose». Devrait produire la sortie suivante.


Au début
À voir


Enregistrez la sortie de ce programme dans un fichier et vérifiez les éléments suivants à l'aide des commandes UNIX.

-Une combinaison de prédicats et de modèles de cas qui apparaissent fréquemment dans le corpus
-Modèles de casse des verbes "faire", "voir" et "donner" (organiser par ordre de fréquence d'apparition dans le corpus)
"""
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\symbole t,Vide,*,*,*,*,\u3000,\u3000,\u3000',
                             '* 1 2D 0/1 -0.764522',
                             'je\t substantif,Synonyme,Général,*,*,*,je,Wagahai,Wagahai',
                             'Est\t assistant,Assistance,*,*,*,*,Est,C,sensationnel',
                             '* 2 -1D 0/2 0.000000',
                             'Chat\t substantif,Général,*,*,*,*,Chat,chat,chat',
                             'alors\t verbe auxiliaire,*,*,*,Spécial,Type continu,Est,De,De',
                             'y a-t-il\t verbe auxiliaire,*,*,*,Cinq étapes, La ligne Al,Forme basique,y a-t-il,Al,Al',
                             '。\symbole t,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):Surface
        base (str):Base
        pos (str):Partie (base)
        pos1 (str):Sous-classification des pièces détachées 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 (Numéro de phrase).
        morphs List[Morph]: Morph (morphème) list.
        dst (str): The index of dependency target (Numéro d'index de la clause de contact).
        srcs (List[str]): The index list of dependency source. (Numéro d'index de la clause d'origine).
    """

    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(je), Morph(Est)]
        Return:
            e.g. 'je suis'
        """
        morphs = self.morphs
        res = ""
        for morph in morphs:
            if morph.pos != "symbole":
                res += morph.surface
        return res

    def validate_pos(self, pos: str) -> bool:
        """Return Ture if 'nom' or 'verbe' 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',
                                'je\t substantif,Synonyme,Général,*,*,*,je,Wagahai,Wagahai',
                                'Est\t assistant,Assistance,*,*,*,*,Est,C,sensationnel',
                                '* 1 -1D 0/2 0.000000',
                                'Chat\t substantif,Général,*,*,*,*,Chat,chat,chat',
                                'alors\t verbe auxiliaire,*,*,*,Spécial,Type continu,Est,De,De',
                                'y a-t-il\t verbe auxiliaire,*,*,*,Cinq étapes, La ligne Al,Forme basique,y a-t-il,Al,Al',
                                '。\symbole t,Phrase,*,*,*,*,。,。,。']

    Parsing format:
        e.g. "* 0 1D 0/1 0.000000"
        |colonne|sens|
        | :----: | :----------------------------------------------------------- |
        |   1    |La première colonne est`*`.. Indique qu'il s'agit d'un résultat d'analyse des dépendances.|
        |   2    |Numéro de phrase (entier à partir de 0)|
        |   3    |Numéro de contact +`D`                                              |
        |   4    |Adresse principale/Position du mot de fonction et nombre illimité de colonnes d'identité|
        |   5    |Score d'engagement. En général, plus la valeur est élevée, plus il est facile de s'engager.|

    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édicat(predicate)
        -Article(argument)
        -Cas(case)

    Args:
        chunks (List[Chunk]): A sentence contains many chunks.
            e.g. [Chunk( id: 0, dst: 5, srcs: [], morphs: [Morph(je), Morph(Est)] ),
                  Chunk( id: 1, dst: 2, srcs: [], morphs: [Morph(ici), Morph(alors)] ),
                  Chunk( id: 2, dst: 3, srcs: ['1'], morphs: [Morph(début), Morph(main)] ),
                  Chunk( id: 3, dst: 4, srcs: ['2'], morphs: [Morph(Humain), Morph(Cette)] ),
                  Chunk( id: 4, dst: 5, srcs: ['3'], morphs: [Morph(chose), Morph(À)] ),
                  Chunk( id: 5, dst: -1, srcs: ['0', '4'], morphs: [Morph(Vous voyez), Morph(Ta), Morph(。)] )]

    Returns:
        List[Dict[str, list]]: Predicate and case.
            e.g. [defaultdict(list, {'début': ['alors']}), defaultdict(list, {'à voir': ['Est', 'À']})]
    """
    patterns = []
    for chunk in chunks:
        # Skip if not valid
        if len(chunk.srcs) == 0 or all([morph.pos != "verbe" for morph in chunk.morphs]):
            continue

        # Initialize
        pred_case = defaultdict(list)

        # Get predicate
        for morph in chunk.morphs:
            if morph.pos == "verbe":
                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 == "Particule":
                    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, {'Être né': ['alors']}), defaultdict(list, {'Tsukuri': ['Ou', 'Mais']})],
                  [defaultdict(list, {'cri': ['alors']}), defaultdict(list, {'Faire': ['main', 'Seulement', 'Est']})]]
    """
    # convert_patterns_to_text
    lines = []
    for pattern_sent in pattern_sents:
        for pattern in pattern_sent:  # pattern: {'Tsukuri': ['Ou', 'Mais']}
            for predicate, case_list in pattern.items():
                case_text = " ".join(sorted(case_list))  #Trier les caractères par ordre croissant
                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":
#Être né
#Tsukuka
#En pleurant
#Juste à faire
#Au début
#À voir
#Ecoutez
#Attraper
#Ébullition
#Manger

ans45.sh


#Une combinaison de prédicats et de modèles de cas qui apparaissent fréquemment dans le corpus
# 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


#Seulement "faire"
grep "^Faire\s" patterns.txt | sort | uniq -c | sort -k1nr > "Faire.txt"

# #"Voir" uniquement
grep "^à voir\s" patterns.txt | sort | uniq -c | sort -k1nr > "à voir.txt"

# #"Donner" seulement
grep "^donner\s" patterns.txt | sort | uniq -c | sort -k1nr > "donner.txt"

Recommended Posts

100 traitements linguistiques frappent 03 ~ 05
100 coups de traitement linguistique (2020): 40
100 coups de traitement linguistique (2020): 32
100 coups de traitement linguistique (2020): 35
100 coups de traitement linguistique (2020): 47
100 coups de traitement linguistique (2020): 39
100 coups de traitement linguistique (2020): 26
100 coups de traitement linguistique (2020): 34
100 coups de traitement linguistique (2020): 42
100 coups de traitement linguistique (2020): 49
Le traitement de 100 langues frappe 06 ~ 09
100 coups de traitement linguistique (2020): 43
100 coups de traitement linguistique (2020): 24
100 coups de traitement linguistique (2020): 45
100 coups de traitement linguistique (2020): 10-19
100 coups de traitement linguistique (2020): 30
100 coups de traitement linguistique (2020): 00-09
100 coups de traitement linguistique (2020): 31
100 coups de traitement linguistique (2020): 48
100 coups de traitement linguistique (2020): 44
100 coups de traitement linguistique (2020): 41
100 coups de traitement linguistique (2020): 37
100 coups de traitement linguistique (2020): 25
100 coups de traitement linguistique (2020): 23
100 coups de traitement linguistique (2020): 33
100 coups de traitement linguistique (2020): 20
100 coups de traitement linguistique (2020): 27
100 coups de traitement linguistique (2020): 46
100 coups de traitement linguistique (2020): 21
100 coups de traitement linguistique (2020): 36
100 coups de traitement du langage amateur: 41
100 coups de traitement du langage amateur: 71
100 coups de traitement du langage amateur: 56
100 coups de traitement du langage amateur: 50
100 coups de traitement du langage amateur: 59
100 coups de traitement du langage amateur: 70
100 coups de traitement du langage amateur: 62
100 coups de traitement du langage amateur: 60
100 coups de langue amateur: 30
100 coups de langue amateur: 06
100 coups de traitement du langage amateur: 84
100 coups de traitement du langage amateur: 81
100 coups de langue amateur: 33
100 coups de traitement du langage amateur: 46
100 coups de traitement du langage amateur: 88
100 coups de traitement du langage amateur: 89
100 coups de traitement du langage amateur: 40
100 coups de traitement du langage amateur: 45
100 coups de traitement du langage amateur: 43
100 coups de traitement du langage amateur: 55
100 coups de traitement du langage amateur: 22
100 coups de traitement du langage amateur: 61
100 coups de traitement du langage amateur: 94
100 coups de traitement du langage amateur: 54
100 coups de langue amateur: 04
100 coups de traitement du langage amateur: 63
100 coups de traitement du langage amateur: 78
100 coups de traitement du langage amateur: 12
100 coups de traitement du langage amateur: 14
100 coups de langue amateur: 08
100 coups de traitement du langage amateur: 42