Partage
  • Partager sur Facebook
  • Partager sur Twitter

Erreur de memoire avec un gros dictionnaire

Sujet résolu
    6 juin 2020 à 19:18:59

    Bonjour à tous,

    j'essayait de programmer un générateur de mots à sonorité françaises tel que l'as fait ScienceEtonnante dans sa vidéo. Après l'avoir terminé je l'ai améliorer en rajoutant la longueur des chaines de Markov en paramètre variable. Mais à partir des chaines de longueur 4 et plus le dictionnaire devient trop grand. J'ai chercher sur internet mais je n'ai trouvé aucune information aboutissante. J'aimerais donc savoir si il existe une bibliothèque ou une classe qui permet de contourner cette limitation.

    Voici mon code:

    Analyse.py:

    # -*- coding: utf-8 -*-
    from string import whitespace
    
    
    def find_char(file):
        # Liste de tout les caractères présent dans le fichier
        chars = set()
        with open(file, "r", encoding="utf-8") as f:
            for char in f.read():
                if not (char in whitespace or char in chars):
                    chars.add(char)
        chars.add(" ")
        c = list(chars)
        c.sort()
        return c
    
    
    def init_dic(car, degree):
        # Retourne la iste de tout les clés du dictionnaire
        loop_end = False
        first_run = True
        foot_counter = []
        liste_keys = []
        for x in range(degree):
            foot_counter.append(0)
        while True:
            for x in range(degree - 1, -1, -1):
                foot_counter[x] += 1
                if foot_counter[x] > len(car) - 1:
                    if x == 0:
                        loop_end = True
                        break
                    foot_counter[x] = 0
                else:
                    break
            if loop_end:
                break
            if foot_counter[-1] == 1 and first_run:
                foot_counter[-1] = 0
                first_run = False
            liste_keys.append("".join([car[x] for x in foot_counter]))
        return liste_keys
    
    
    def correct_dic(liste_keys, file):
        # Remplis le dictionnaire à partir des mots
        degree = len(liste_keys[0])
        dic = {x: dict() for x in liste_keys}
        with open(file, "r", encoding="utf-8") as f:
            f2 = " " * degree + (" " * degree).join(f.read().split("\n"))
            for num in range(len(f2)):
                f3 = f2[num : num + degree]
                try:
                    dic[f3][f2[num + degree]] += 1
                except IndexError:
                    pass
                except KeyError:
                    try:
                        dic[f3][f2[num + degree]] = 1
                    except KeyError:
                        pass
                    except IndexError:
                        pass
        adic = {x: y for x, y in dic.items()}
        for x in adic:
            if adic[x] == {}:
                del dic[x]
        return dic
    
    
    def compile_dic(dic):
        # Transforme les entrés du dictionnaire tel que "ab" : {"c": 6, "d": 3, "f": 8} en "ab" : {"c": 6, "d": 9, "f": 17}
        # Pour faciliter la génération aléatoire
        dico = {}
        for x in dic:
            dico[x] = {}
            sum = 0
            for y in dic[x]:
                sum += dic[x][y]
                dico[x][y] = sum
        return dico
    

    Generateur.py:

    from random import randint
    from Analyse import *
    
    # Trouve le plus grand nombre inférieure dans à x dans une séquence
    hightsmall = lambda seq, x: min([(i - x, i) for i in seq if x <= i] or [(0, None)])[1]
    
    # Trouve la première clé dans un dictionnaire à partir de sa valeur
    key = lambda v, liste: str([k for (k, val) in liste.items() if val == v][0])
    
    fr = "francais.txt"
    
    degree = int(
        input(
            """Choisissez la précision:
    2 Prend 7 seconde
    3 Prend 9 seconde
    4 et plus pas operationnel
    >>> """
        )
    )
    
    print("Analyse ...")
    proba = compile_dic(correct_dic(init_dic(find_char(fr), degree), fr))
    
    
    def gen_word(proba):
        # Parti compliqué qu même moi ais du mal à comprendre 
        i = 0
        word = [""]
        while word[-1] != " ":
            if i == 0:
                rand = randint(0, max(proba[" " * degree].values()))
                word[0] = key(
                    hightsmall(proba[" " * degree].values(), rand), proba[" " * degree]
                )
            elif i < degree:
                rand = randint(
                    0,
                    max(
                        proba[
                            (" " * (degree - i) + "{}").format("".join(word[:i]))
                        ].values()
                    ),
                )
                lettre = key(
                    hightsmall(
                        proba[
                            (" " * (degree - i) + "{}").format("".join(word[:i]))
                        ].values(),
                        rand,
                    ),
                    proba[(" " * (degree - i) + "{}").format("".join(word[:i]))],
                )
                word.append(lettre)
            else:
                rand = randint(
                    0, max(proba[("{}").format("".join(word[-degree:]))].values())
                )
                lettre = key(
                    hightsmall(
                        proba[("{}").format("".join(word[-degree:]))].values(), rand
                    ),
                    proba[("{}").format("".join(word[-degree:]))],
                )
                word.append(lettre)
            i += 1
            del rand
    
        return "".join(word)
    
    
    nb_mot = int(input("Combien de mots voulez vous generer: "))
    for x in range(nb_mot):
        print(gen_word(proba))
    

    Si vous avez des remarques ou des commentaires sur mon code n'hésitez pas je suis preneur

    PS: J'ai utilisé black sur les codes


    -
    Edité par rokonio 6 juin 2020 à 19:28:34

    • Partager sur Facebook
    • Partager sur Twitter
      6 juin 2020 à 21:17:50

      Tu souhaites faire un génerateur de mot :inventer des mot à partir de mot existant ?

      Par ex : policier ,table,carte >> pobarte .

      Si c'est vraiment ca , ton code est complexe alors qu'il y a beaucoup plus simple :)

      • Partager sur Facebook
      • Partager sur Twitter
        6 juin 2020 à 22:47:33

        Non il créer d'abord un tableau à partir de la liste des mots français en comptant par exemple qu'il y a tel pourcent de chance que tel lettre débute un mot puis tel pourcent pour cela qui suit et ainsi de suite. La précision vient du nombre de caractères pris en compte pour générer aléatoirement la lettre suivante. Par exemple avec deux degrés de précision, pour générer une lettre il va regarder les deux précédente puis chercher dans le tableau les probabilités des lettre suivantes pour générer la lettre. J'espère avoir été clair 😀

        -
        Edité par rokonio 7 juin 2020 à 11:34:56

        • Partager sur Facebook
        • Partager sur Twitter
          7 juin 2020 à 18:53:24

          Salut,
          Je ne connais pas la grosseur du fichier que tu importes (en passant tu donnes le lien html, il y a un problème avec l'éditeur du forum?)
          J'ai essayé d'imaginer toutes sortes de trucs. Comme en Python, on ne peut pas faire d'accès direct sur disque comme en C, tu n'a pas beaucoup de choix.
          J'ai pensé à des listes de listes ou des dictionnaires avec sous-dictionnaires, mais l'information de contrôle augmente encore plus la mémoire demandée.
          Je te suggère de télécharger le fichier une fois pour toutes et le garder sur ton PC,
          Ensuite, tu peux créer un dictionnaire avec des séquences de 1, 2, 3 (ou plus) caractères, tels que 'a', 'et', 'car', 'cons', etc.
          Pour chaque séquence, tu accumules la fréquence, Je suppose que tu sais comment faire:
          dico = dict()
          Si  sequence  est une séquence de caractères, tu peux faire:
          if sequence not in dico.keys():
              dico[sequence] = 0
          dico[sequence] += 1
          Si on a une fonction pseudo-aléatoire à discribution uniforme et si on veut choisir une lettre en fonction de sa fréquence probable,
          On peut faire comme suit:
          Tu te crée une liste des fréquences pour une lettre. Tu auras donc une liste de 26 entrées (je suppose que tu fais abstraction des majuscules).
          Donc  freq[0]  est la fréquence pour le 'a'.
          On calcule l'indice comme suit:
          i = ord(lettre) - ord('a')
          freq[i] = dico[lettre]  # ça se fait en une ligne si tu remplaces i par sa valeur
          Une fois cela fait, tu fais le calcul suivant: tu remplaces la valeur par la somme courante plus cette valeur:
          sum = 0
          for i in range(26):
              freq[i] += sum
              sum = freq[i]
          La variable sum donnera la fréquence totale (toutes les lettres).
          Tu calcules une valeur avec random.randrange(sum)
          Si la valeur est dans l'intervalle d'une lettre, tu choisis la lettre, ça devrait donner quelque chose du genre:
          lettre = random.randrange(1,sum+1)
          prec = 0
          for i in range(26):
              if prec < lettre and lettre <= freq[i]:
                  lettre = chr(ord('a') + i)
                  break
              prec = freq[i]
          Tu obtiendra une lettre en proportion de la fréquence de cette lettre.
          Il y a des façon plus rapides, mais qui demanderont beaucoup plus de mémoire, et tu sembles en manquer.
          • Partager sur Facebook
          • Partager sur Twitter

          Le Tout est souvent plus grand que la somme de ses parties.

            8 juin 2020 à 0:05:23

            Bonjour j'ai modifié le lien de mon dernier message, le lien devrait fonctionner.

            Je n'ai pas bien compris dans quel but tu me propose toutes ces modification, serait-ce dans le but d'alléger le dictionnaire? Si c'est le cas je ne pense pas que ce soit très optimal ou alors il faudrait en faire une class. Mon code fonctionne très bien (tu peux l'essayer si tu veux) avec des degrées inférieure à 4.

            Aussi le fichier est déjà enregistré.

            Pourrais tu juste édité ton message pour mettre ton code entre balise pour que ça soit plus lisible ?

            En tout cas merci pour ta réponse.

            -
            Edité par rokonio 8 juin 2020 à 0:06:08

            • Partager sur Facebook
            • Partager sur Twitter
              8 juin 2020 à 5:38:51

              Salut,
              Pour les balises, je ne peux pas, ma synthèse vocale ne me le permet pas (voir mon profil).
              Il semble que tu n'as pas besoin de conseil pour écrire ou optimiser ton code.
              >     # Parti compliqué qu même moi ais du mal à comprendre
              La question telle que je la conçois est "peut-on faire entrer 300 Go dans 250 Go ?"
              Pour information, il y a 321272407 combinaisons de 1 à 6 lettres.
              Mais il n'y en a pas autant dans la langue française.
              Et quand on pense que les chaînes ne représentent qu'une fraction de l'information pour une liste ou un dictionnaire, ça peut monter assez haut.
              Il n'y a pas de bibliothèque magique.
              Pour ce qui est des classes, je viens de penser que les array prennent un peu moins de place que les listes ou les dictionnaires, mais sont plus difficiles à manipuler.
              Ils sont moins efficaces en temps d'exécution.
              Et il faut ajouter les informations de fréquence et probabilité.
              Si tu peux éliminer toute structure dont tu n'as plus besoin (liste, dictionnaire, fichier ouvert, etc.) cela te donnera peut-être un peu de place.
              Autrement, je ne vois rien qui peut réduire la mémoire demandée de façon significative.
              edit:
              J'ai un dictionnaire d'environ 325000 mots pris sur OC (jeu du pendu)
              Si j'ai un peu de temps, je vais essayer ton code.

              -
              Edité par PierrotLeFou 8 juin 2020 à 7:53:34

              • Partager sur Facebook
              • Partager sur Twitter

              Le Tout est souvent plus grand que la somme de ses parties.

                8 juin 2020 à 9:35:17

                Je vais essayer d'aller étape par étape .Ton code est loin d’être simple 

                1) La premiere fonction find_char issu de Analyse.py

                def find_char(file):
                    # Liste de tout les caractères présent dans le fichier
                    
                    chars = set()
                    with open(file, "r", encoding="utf-8") as f:
                        for char in f.read():
                           
                            if not (char in whitespace or char in chars):
                                chars.add(char)
                                print (chars) #fait pour faire des tests
                    chars.add(" ")
                    
                    c = list(chars)
                   
                    c.sort()
                    return c


                Cette fonction prend en paramètre file (le fichier qu'on parcourt) .

                Considérons que le fichier a 3 ligne :

                a

                alpha

                bd

                Pour chaque ligne parcouru dans ton fichier, tu enregistre dans le set nommé chars .

                Vu que c'est un set , tu n'as pas besoin de vérifier que ton caractère est déjà dans le set . (Un set ne peut pas avoir élement dupliqué (caractère)

                donc tu peux supprimer

                or char in chars

                Quand j'ai voulu voir comment il enregistrait dans le set en affichant print (chars) , les caractères était ajouter bizarement dans le set ^^ .

                On pourrait penser que ca donne chars = {a,l,p,h,b,d) mais ca peut donner p,h,a,b,d,l . Ca ne change rien à la suite de ton script qui ajoute dans une list et la trie .

                2)

                def init_dic(car, degree):
                    # Retourne la iste de tout les clés du dictionnaire
                    loop_end = False
                    first_run = True
                    foot_counter = []
                    liste_keys = []
                    for x in range(degree):
                        foot_counter.append(0)
                    while True:
                        for x in range(degree - 1, -1, -1):
                            foot_counter[x] += 1
                            if foot_counter[x] > len(car) - 1:
                                if x == 0:
                                    loop_end = True
                                    break
                                foot_counter[x] = 0
                            else:
                                break
                        if loop_end:
                            break
                        if foot_counter[-1] == 1 and first_run:
                            foot_counter[-1] = 0
                            first_run = False
                        liste_keys.append("".join([car[x] for x in foot_counter]))
                    return liste_keys

                Je comprends mieux pourquoi tu as un problème de mémoire ^^

                Tu est entrain de faire toute les combinaisons possible avec les caractères trouvés (liste c ) et en limitant à une longueur maximal (=Degré) .

                Tu te doutes bien que cette partie pose énormément de calcul et de memoire  quand tu demandes 4 dégrés .

                En regardant les caractère dans la liste C , je me suis aperçu que tu fais la disctinction entre minuscule et majuscule . Ca augmente considérablement les combinaisons .

                J'ai donc remplacer les majuscules en minuscules et ca fonctionne maintenant avec 4 degré :)

                # -*- coding: utf-8 -*-
                from string import whitespace
                 
                 
                def find_char(file):
                    # Liste de tout les caractères présent dans le fichier
                    chars = set()
                    with open(file, "r", encoding="utf-8") as f:
                        for char in f.read():
                            if not (char in whitespace ):
                                chars.add(char.lower())
                    chars.add(" ")
                    c = list(chars)
                    c.sort()
                    
                    return c
                 
                 
                def init_dic(car, degree):
                    # Retourne la iste de tout les clés du dictionnaire
                    loop_end = False
                    first_run = True
                    foot_counter = []
                    liste_keys = []
                    for x in range(degree):
                        foot_counter.append(0)
                    while True:
                        for x in range(degree - 1, -1, -1):
                            foot_counter[x] += 1
                            if foot_counter[x] > len(car) - 1:
                                if x == 0:
                                    loop_end = True
                                    break
                                foot_counter[x] = 0
                            else:
                                break
                        if loop_end:
                            break
                        if foot_counter[-1] == 1 and first_run:
                            foot_counter[-1] = 0
                            first_run = False
                        
                        liste_keys.append("".join([car[x] for x in foot_counter]))
                
                    return liste_keys
                 
                 
                def correct_dic(liste_keys, file):
                    # Remplis le dictionnaire à partir des mots
                    degree = len(liste_keys[0])
                    dic = {x: dict() for x in liste_keys}
                    with open(file, "r", encoding="utf-8") as f:
                        f2 = " " * degree + (" " * degree).join(f.read().split("\n"))
                        for num in range(len(f2)):
                            f3 = f2[num : num + degree]
                            try:
                                dic[f3][f2[num + degree]] += 1
                            except IndexError:
                                pass
                            except KeyError:
                                try:
                                    dic[f3][f2[num + degree]] = 1
                                except KeyError:
                                    pass
                                except IndexError:
                                    pass
                    adic = {x: y for x, y in dic.items()}
                    for x in adic:
                        if adic[x] == {}:
                            del dic[x]
                    return dic
                 
                 
                def compile_dic(dic):
                    # Transforme les entrés du dictionnaire tel que "ab" : {"c": 6, "d": 3, "f": 8} en "ab" : {"c": 6, "d": 9, "f": 17}
                    # Pour faciliter la génération aléatoire
                    dico = {}
                    for x in dic:
                        dico[x] = {}
                        sum = 0
                        for y in dic[x]:
                            sum += dic[x][y]
                            dico[x][y] = sum
                    return dico
                
                






                 . Si tu veux gagner en perf, tu peux plutot que creer toute les combinaison ,enregistrer que celle qui existe vraiment


                -
                Edité par ChFzaf 8 juin 2020 à 15:21:17

                • Partager sur Facebook
                • Partager sur Twitter
                  8 juin 2020 à 19:35:09

                  À moins de lire un dictionnaire très restreint, la fonction find_char() est inutile.
                  Et si je comprend bien, on lit le fichier deux fois.
                  On peut facilement la remplacer par une liste des lettres de 'a' à 'z'.
                  @rokonio:
                  J'ai pris ton module Generateur.py et le module Analyseur.py de ChFzaf.
                  J'ai fait la fonction find_char comme j'ai dit. Je dois rajouter [' ', '-']
                  Je peux faire la précision 4 en peu de temps et la précision 5 en environ 30 à 45 secondes.
                  J'ai essayé la précision 6 mais c'est trop long (je vais voir plus tard).
                  > Si tu veux gagner en perf, tu peux plutot que creer toute les combinaison ,enregistrer que celle qui existe vraiment
                  Comme je l'ai dit, il y a plus de 300 millions de combinaisons. Combien sont inutiles telles que 'wx' ...¸
                  J'ai écrit une version plus simple de la fonction init_dic(), je crois qu'ell est plus rapide.
                  Je crois encore qu'elle contient trop de combinaisons.
                  Tu cherchais un moyen de réduire la mémoire. Prends les séquences de ton dictionnaire.
                  -
                  def init_dic(chars, degres):
                      list_keys =[]
                      def sequence(chars, seq, degree):
                          if degree <= 0:
                             list_keys.append(seq)
                          else:
                              for c in chars:
                                  sequence(chars,seq+c, degree-1)
                      for c in chars:
                          sequence(chars, c, degres-1)
                      return list_keys
                  -
                  Pour une précision de 6, j'ai testé avec mon dictionnaire pour les mots de 6 caractères ou plus.
                  J'ai la fréquence pour chaque longueur de mots (de 1 à 25).
                  J'ai calculé 26**6, et ça donne:
                  Fausses séquences de 6 caractères: 308915776
                  Vraies séquences de 6 caractères:  1677989
                  Tu part avec un dictionnaire environ 200 fois trop gros.

                  -
                  Edité par PierrotLeFou 9 juin 2020 à 7:38:52

                  • Partager sur Facebook
                  • Partager sur Twitter

                  Le Tout est souvent plus grand que la somme de ses parties.

                    9 juin 2020 à 10:45:36

                    Ok merci pour vos réponse :)

                    Je vais essayer de recoder ces deux fonction comme vous me l'avez conseiller.

                    Et désolé je n'avais pas penser à lire ta description. Est-que tu peux accéder au lien de la liste de mots ?

                    Edit: La fonction find_char servait car je voulais le faire sur plusieurs langue

                    Ré-edit car je peux pas poster de nouveaux message avant 24h :

                    C'est bon j'ai terminé:

                    J'ai remplacé les fonction correct_dic, init_dic et find_char par le fonction create_dic:

                    def create_dic(file, degree):
                    	dic = dict()
                    	with open(file, "r", encoding="utf-8") as f:
                    		seq1 = f.read().split("\n")
                    		seq1 = (" "*degree).join(seq1)
                    		for char in range(len(seq1)):
                    			try:
                    				seq2 = seq1[char:char+degree]
                    				seq3 = seq1[char+degree]
                    				if seq2 in dic.keys():
                    					if seq3 in dic[seq2].keys():
                    						dic[seq2][seq3] += 1
                    					else:
                    						dic[seq2][seq3] = 1
                    				else:
                    					dic[seq2] = dict()
                    					dic[seq2][seq3] = 1
                    			except:
                    				pass
                    	return dic

                    Du coup il faut changer la ligne de la création du dictionnaire proba en 

                    proba = compile_dic(create_dic(fr, degree))

                    Ca m'as étonner lorsque j'ai vu que ça prenait q'une dizaine de secondes pour un degrée 6 et sans problème de mémoire.

                    Merci beaucoup vous m'avez bien aidé:)

                    -
                    Edité par rokonio 9 juin 2020 à 11:16:41

                    • Partager sur Facebook
                    • Partager sur Twitter
                      9 juin 2020 à 18:43:00

                      Tu peux répondre en moins de 24 heures si quelqu'un a répondu à ton message.
                      Tu peux également modifier ton dernier message comme j'ai fait pour le précédent et celui-ci.
                      J'ai modifié ta nouvelle fonction.
                      Je me suis arrangé pour ne pas avoir de if ... else, seulement des if
                      J'ai modifié le range pour que tu n'aies plus besoin du try ... except
                      j'ai fait des tests jusqu'à une précision de 25 qqui est la longueur de mots la plus longue.
                      J'ai continué jusqu'à 80 par tranche de 10.
                      Ça prend environ 10 fois plus de temps pour 17 que pour 3.
                      Et ça ne plante pas.
                      -
                      def create_dic(file, degree):
                          dic = dict()
                          with open(file, "r", encoding="utf-8") as f:
                              seq1 = f.read().split("\n")
                              seq1 = (" "*degree).join(seq1)  # je me demande si on ne devrait pas avoir degree+1 espaces
                              for char in range(len(seq1)-degree-1):
                                  seq2 = seq1[char:char+degree]
                                  seq3 = seq1[char+degree]
                                  if seq2 not in dic.keys():
                                      dic[seq2] = dict()
                                  if seq3 not in dic[seq2].keys():
                                      dic[seq2][seq3] = 0
                                  dic[seq2][seq3] += 1
                          return dic

                      -
                      Edité par PierrotLeFou 9 juin 2020 à 19:00:58

                      • Partager sur Facebook
                      • Partager sur Twitter

                      Le Tout est souvent plus grand que la somme de ses parties.

                        10 juin 2020 à 10:30:33

                        Merci beaucoup.

                        J'ai juste rajouté un bout de code à la génération à la génération de mots pour ne pas tombé sur des mots qui existe déjà. Le problème c'est que au délà de 10 ça prend trop de temps mais je ne pense pas qu'une solution existe:

                        Le voici:

                        nb_mot = int(input("Combien de mots voulez vous generer: "))
                        with open(fr, "r", encoding="utf-8") as f:
                            liste_mot = f.read()
                        for x in range(nb_mot):
                            mot = gen_word(proba)
                            while mot in liste_mot:
                                mot = gen_word(proba)
                            print(mot)
                        


                        Du coup je passe le sujet en résolu

                        -
                        Edité par rokonio 10 juin 2020 à 10:31:00

                        • Partager sur Facebook
                        • Partager sur Twitter

                        Erreur de memoire avec un gros dictionnaire

                        × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                        × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                        • Editeur
                        • Markdown