[Tuto] Watermarks IA : nettoyer les filigranes et réécrire localement un texte avec Ollama

l’Almanet doLys Gnu/Linux – Open Source – Entreprises Forums L’almanet doLys Open Source [Tuto] Watermarks IA : nettoyer les filigranes et réécrire localement un texte avec Ollama

  • This topic is empty.
Affichage de 1 message (sur 1 au total)
  • Auteur
    Articles
  • #13338
    nam1962nam1962
    Keymaster

      Le terme watermark signifie littéralement filigrane.

      Dans un document classique, un filigrane peut être visible, comme un texte ou un logo placé en arrière-plan d’une page. Dans le contexte des contenus générés par IA, le mot watermark peut désigner des mécanismes beaucoup moins visibles.

      On peut notamment rencontrer :

      • des caractères Unicode invisibles ou inhabituels ;
      • des métadonnées ajoutées au fichier ;
      • des informations de provenance ;
      • des structures particulières dans le document ;
      • des signaux statistiques intégrés à la génération même du texte, par exemple dans la succession et le choix des tokens.

      Ces différents mécanismes sont souvent regroupés sous le terme de watermarks IA, ou filigranes IA en français, même s’ils ne ressemblent pas nécessairement à un filigrane graphique traditionnel.

      Le but de ce tutoriel est donc de disposer de deux outils distincts :

      wr-clean fichier.md

      pour nettoyer les filigranes ou marques déterministes que l’on peut directement retirer du fichier, et :

      wr-rewrite-gui

      pour effectuer une véritable réécriture locale avec Ollama lorsqu’on souhaite également perturber un éventuel watermark statistique lié au choix des tokens.

      Il est important de distinguer ces deux familles.

      Un caractère Unicode invisible ou une métadonnée peut être détecté puis supprimé sans toucher au texte lui-même.

      Un watermark statistique fonctionne différemment. Il peut être lié à la manière dont le modèle choisit successivement ses mots ou ses tokens. Dans ce cas, il n’existe pas nécessairement une chaîne cachée qu’il suffirait d’effacer. Il faut modifier suffisamment la formulation du texte pour produire une nouvelle succession de tokens.

      C’est précisément la différence entre le nettoyage effectué par wr-clean et la réécriture effectuée avec Ollama.

      Tout le traitement présenté ici reste local sur la machine.

      1. Principe de l’installation

      Nous allons utiliser :

      • Docker ;
      • Ollama ;
      • le projet watermarks-remover ;
      • curl et jq ;
      • YAD pour l’interface graphique ;
      • deux petits wrappers placés dans ~/.local/bin.

      L’installation finale sera organisée ainsi :

      ~/.local/bin/
      ├── wr-clean
      └── wr-rewrite-gui
      
      ~/Bureau/
      └── wr-rewrite.desktop

      Le nettoyage déterministe sera réalisé par watermarks-remover dans Docker.

      La réécriture sera réalisée directement par Ollama installé sur la machine.

      2. Installer les dépendances sous EndeavourOS

      Sous EndeavourOS ou Arch Linux :

      sudo pacman -S --needed \
          git \
          docker \
          docker-compose \
          python \
          curl \
          jq \
          yad \
          ollama

      On active Docker :

      sudo systemctl enable --now docker

      Et Ollama :

      sudo systemctl enable --now ollama

      Pour pouvoir utiliser Docker sans sudo :

      sudo usermod -aG docker "$USER"

      Il faut ensuite fermer puis rouvrir la session utilisateur pour que le nouveau groupe soit pris en compte.

      On peut vérifier :

      docker --version
      ollama --version

      3. Récupérer le projet watermarks-remover

      Créons un emplacement propre pour les sources :

      mkdir -p ~/.local/src
      cd ~/.local/src

      Puis clonons le dépôt :

      git clone https://github.com/guillaumemeyer/watermarks-remover.git

      On obtient :

      ~/.local/src/watermarks-remover/

      Pour mettre le dépôt à jour plus tard :

      cd ~/.local/src/watermarks-remover
      git pull

      Le projet contient notamment des scripts destinés à inspecter, nettoyer et réécrire les fichiers.

      Dans l’image Docker utilisée ici, on retrouve par exemple :

      /app/scripts/inspect_file.py
      /app/scripts/clean_file.py
      /app/scripts/rewrite_text.py

      4. Installer watermarks-remover avec Docker

      On récupère l’image :

      docker pull ghcr.io/guillaumemeyer/watermarks-remover:latest

      Puis on crée le conteneur en exposant son API uniquement en local :

      docker run -d \
          --name watermarks-remover \
          --restart unless-stopped \
          -p 127.0.0.1:8765:8765 \
          ghcr.io/guillaumemeyer/watermarks-remover:latest

      Vérification :

      docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'

      On doit obtenir une ligne proche de :

      watermarks-remover    ghcr.io/guillaumemeyer/watermarks-remover:latest    127.0.0.1:8765->8765/tcp

      Quelques commandes utiles :

      docker stop watermarks-remover
      docker start watermarks-remover
      docker logs watermarks-remover

      Pour mettre l’image à jour :

      docker pull ghcr.io/guillaumemeyer/watermarks-remover:latest

      5. Créer la commande wr-clean

      Nous allons placer le script dans :

      ~/.local/bin/wr-clean

      Création :

      mkdir -p ~/.local/bin
      
      cat > ~/.local/bin/wr-clean <<'EOF'
      #!/usr/bin/env bash
      # wr-clean : nettoie un fichier via watermarks-remover
      
      set -euo pipefail
      
      API="http://127.0.0.1:8765/clean"
      
      if [[ $# -lt 1 ]]; then
          echo "Usage: wr-clean <fichier> [fichier_sortie]"
          echo
          echo "Exemples :"
          echo "  wr-clean draft.md"
          echo "  wr-clean photo.png photo.cleaned.png"
          echo "  wr-clean notes.docx notes.propre.docx"
          exit 1
      fi
      
      INPUT="$1"
      
      if [[ ! -f "$INPUT" ]]; then
          echo "Erreur : $INPUT n'existe pas"
          exit 1
      fi
      
      if [[ $# -ge 2 ]]; then
          OUTPUT="$2"
      elif [[ "$INPUT" == *.* ]]; then
          OUTPUT="${INPUT%.*}.cleaned.${INPUT##*.}"
      else
          OUTPUT="${INPUT}.cleaned"
      fi
      
      echo "→ Nettoyage de $INPUT ..."
      
      PAYLOAD=$(
          jq -n \
              --arg file "$(base64 -w0 "$INPUT")" \
              --arg name "$(basename "$INPUT")" \
              '{file:$file, name:$name}'
      )
      
      RESULT=$(
          curl -sS \
              -X POST "$API" \
              -H 'Content-Type: application/json' \
              -d "$PAYLOAD"
      )
      
      if printf '%s' "$RESULT" | jq -e '.ok == true' >/dev/null; then
          printf '%s' "$RESULT" |
              jq -r '.cleaned' |
              base64 -d > "$OUTPUT"
      
          echo "✓ Fichier nettoyé → $OUTPUT"
      else
          echo "Erreur :"
          printf '%s' "$RESULT" | jq .
          exit 1
      fi
      EOF
      
      chmod +x ~/.local/bin/wr-clean

      Vérifions que le script est bien trouvé :

      command -v wr-clean

      On doit normalement obtenir quelque chose comme :

      /home/utilisateur/.local/bin/wr-clean

      6. Utiliser wr-clean

      Pour un fichier Markdown :

      wr-clean document.md

      Le script crée automatiquement :

      document.cleaned.md

      On peut également préciser le nom de sortie :

      wr-clean document.md document.propre.md

      Avec une image :

      wr-clean photo.png

      Avec un document Word :

      wr-clean notes.docx notes.propre.docx

      Le fichier original reste intact.

      Un point important : le message :

      ✓ Fichier nettoyé

      signifie que le traitement s’est correctement déroulé.

      Cela ne signifie pas nécessairement que quelque chose a effectivement été supprimé.

      Pour vérifier si le fichier a été modifié :

      cmp -s document.md document.cleaned.md \
          && echo "Fichiers identiques" \
          || echo "Le fichier a été modifié"

      Si les deux fichiers sont identiques, cela signifie simplement que le nettoyage déterministe n’a trouvé aucune marque à retirer.

      7. Pourquoi wr-clean ne suffit pas toujours

      Un watermark statistique n’est pas nécessairement stocké dans un caractère caché ou dans une métadonnée.

      Il peut résulter du choix probabiliste des tokens effectué pendant la génération.

      Imaginons qu’un modèle dispose de plusieurs mots possibles pour continuer une phrase.

      Un mécanisme de watermark peut favoriser certaines familles de tokens plutôt que d’autres selon une règle déterminée pendant la génération.

      Le texte reste parfaitement lisible.

      Il n’y a alors aucun caractère particulier à supprimer.

      Pour perturber ce type de signal, l’une des approches possibles consiste à produire une nouvelle formulation du texte avec un autre modèle.

      C’est le rôle de notre seconde commande.

      8. Installer les modèles Ollama

      Sur une machine équipée de 16 Go de RAM, trois modèles raisonnables peuvent être utilisés :

      ollama pull qwen3.5:9b
      ollama pull ministral-3:8b
      ollama pull gemma4:12b

      On vérifie :

      ollama list

      Dans mes essais sur un Ryzen 5 5600H avec 16 Go de RAM :

      qwen3.5:9b       environ 59 secondes
      ministral-3:8b   environ 66 secondes
      gemma4:12b       environ 4 min 24 s

      pour une petite réécriture réalisée avec un modèle chargé à froid.

      qwen3.5:9b constitue donc ici le meilleur compromis entre qualité et vitesse.

      9. Tester Ollama directement

      On peut créer un petit texte de test :

      cat >/tmp/rewrite-test.txt <<'EOF'
      Réécris ce texte en français naturel en modifiant nettement la formulation,
      l'ordre des propositions et les connecteurs, sans modifier les informations.
      Réponds uniquement avec le texte réécrit.
      
      Je tiens d'abord à saluer la qualité de votre synthèse. Elle est claire,
      documentée, et les questions que vous posez sont bien posées.
      EOF

      Puis tester Qwen :

      time curl -s http://127.0.0.1:11434/api/chat \
        -H 'Content-Type: application/json' \
        -d "$(jq -n \
          --arg model 'qwen3.5:9b' \
          --arg prompt "$(cat /tmp/rewrite-test.txt)" \
          '{
            model:$model,
            messages:[{role:"user",content:$prompt}],
            think:false,
            stream:false,
            keep_alive:0
          }')" \
        | jq -r '.message.content'

      Deux options importantes apparaissent ici :

      "think": false

      et :

      "keep_alive": 0

      think:false désactive le raisonnement lorsque le modèle le prend en charge.

      Pour une simple réécriture, cela évite de perdre du temps dans une phase de raisonnement inutile.

      keep_alive:0 demande à Ollama de décharger le modèle une fois le traitement terminé.

      C’est pratique sur une machine disposant de 16 Go de RAM.

      10. Créer l’interface graphique wr-rewrite-gui

      On place là encore le script dans :

      ~/.local/bin/

      Création :

      cat > ~/.local/bin/wr-rewrite-gui <<'EOF'
      #!/usr/bin/env bash
      
      set -u
      
      TITLE="Watermark Rewriter"
      OLLAMA_URL="http://127.0.0.1:11434"
      LOGFILE="$HOME/.local/state/wr-rewrite.log"
      
      mkdir -p "$(dirname "$LOGFILE")"
      
      log() {
          echo "[wr-rewrite] $(date '+%Y-%m-%d %H:%M:%S') $*" \
              >> "$LOGFILE" 2>/dev/null || true
      }
      
      error_box() {
          yad \
              --error \
              --center \
              --width=600 \
              --title="$TITLE" \
              --text="$1"
      }
      
      for cmd in yad curl jq; do
          command -v "$cmd" >/dev/null 2>&1 || {
              echo "$cmd est requis"
              exit 1
          }
      done
      
      if ! curl -sf "$OLLAMA_URL/api/tags" >/dev/null 2>&1; then
          error_box "Ollama ne répond pas sur $OLLAMA_URL"
          exit 1
      fi
      
      CHOICE=$(
          yad \
              --title="$TITLE" \
              --center \
              --width=720 \
              --height=430 \
              --form \
              --separator="|" \
              --text="<big><b>Réécriture locale</b></big>
      
      Réécriture via Ollama avec raisonnement désactivé." \
              --field="Fichier:FL" "$HOME/Bureau/" \
              --field="Modèle:CB" \
                  "qwen3.5:9b!ministral-3:8b!gemma4:12b" \
              --field="Méthode:CB" \
                  "paraphrase!humanize" \
              --field="Température:NUM" \
                  "0.7!0.1..1.2!0.1" \
              --button="Annuler":1 \
              --button="Réécrire":0
      )
      
      [ $? -ne 0 ] && exit 0
      
      IFS='|' read -r INPUT MODEL STRENGTH TEMPERATURE _ <<< "$CHOICE"
      
      if [ ! -f "$INPUT" ]; then
          error_box "Fichier introuvable :
      
      $INPUT"
          exit 1
      fi
      
      case "$STRENGTH" in
          paraphrase)
              INSTRUCTION="Réécris le texte suivant en français naturel en utilisant une formulation nettement différente au niveau lexical et syntaxique. Modifie l'ordre des propositions lorsque cela reste naturel, varie les connecteurs, la longueur des phrases et les tournures. Préserve strictement tous les faits, nombres, noms, dates, références et affirmations. N'ajoute ni ne retire aucune information. Réponds uniquement avec le texte réécrit."
              ;;
      
          humanize)
              INSTRUCTION="Réécris le texte suivant comme s'il avait été rédigé naturellement à partir de zéro. Varie le rythme, la longueur des phrases, les formulations et les transitions. Préserve strictement tous les faits, nombres, noms, dates, références et affirmations. N'ajoute ni ne retire aucune information. Réponds uniquement avec le texte réécrit."
              ;;
      esac
      
      if [[ "$INPUT" == *.* ]]; then
          OUTPUT="${INPUT%.*}.rewritten.${INPUT##*.}"
      else
          OUTPUT="${INPUT}.rewritten"
      fi
      
      PROMPT="$INSTRUCTION
      
      ---
      $(cat "$INPUT")"
      
      log "input=$INPUT model=$MODEL strength=$STRENGTH output=$OUTPUT"
      
      TMP_RESPONSE=$(mktemp)
      TMP_ERROR=$(mktemp)
      
      START=$(date +%s)
      
      (
          echo "10"
          echo "# Chargement de $MODEL..."
      
          curl -sS \
              --max-time 1800 \
              "$OLLAMA_URL/api/chat" \
              -H 'Content-Type: application/json' \
              -d "$(
                  jq -n \
                      --arg model "$MODEL" \
                      --arg prompt "$PROMPT" \
                      --argjson temperature "$TEMPERATURE" \
                      '{
                          model: $model,
                          messages: [
                              {
                                  role: "user",
                                  content: $prompt
                              }
                          ],
                          think: false,
                          stream: false,
                          keep_alive: 0,
                          options: {
                              temperature: $temperature
                          }
                      }'
              )" \
              > "$TMP_RESPONSE" \
              2> "$TMP_ERROR"
      
          if [ $? -ne 0 ]; then
              echo "FAIL" > "${TMP_RESPONSE}.status"
              exit
          fi
      
          echo "85"
          echo "# Écriture du résultat..."
      
          jq -r '.message.content // empty' "$TMP_RESPONSE" > "$OUTPUT"
      
          if [ ! -s "$OUTPUT" ]; then
              echo "FAIL" > "${TMP_RESPONSE}.status"
              exit
          fi
      
          echo "OK" > "${TMP_RESPONSE}.status"
      
          echo "100"
          echo "# Terminé"
      
      ) | yad \
              --progress \
              --title="$TITLE" \
              --center \
              --width=520 \
              --auto-close \
              --no-buttons \
              --text="Réécriture en cours..."
      
      END=$(date +%s)
      DURATION=$((END - START))
      
      STATUS=$(cat "${TMP_RESPONSE}.status" 2>/dev/null || echo FAIL)
      
      if [ "$STATUS" != "OK" ]; then
          ERR=$(cat "$TMP_ERROR" 2>/dev/null)
      
          error_box "La réécriture a échoué.
      
      ${ERR:-Réponse Ollama invalide.}"
      
          rm -f \
              "$TMP_RESPONSE" \
              "$TMP_ERROR" \
              "${TMP_RESPONSE}.status"
      
          exit 1
      fi
      
      rm -f \
          "$TMP_RESPONSE" \
          "$TMP_ERROR" \
          "${TMP_RESPONSE}.status"
      
      MIN=$((DURATION / 60))
      SEC=$((DURATION % 60))
      
      yad \
          --title="$TITLE" \
          --center \
          --width=620 \
          --text="<big><b>Réécriture terminée</b></big>
      
      <b>Modèle :</b> $MODEL
      <b>Méthode :</b> $STRENGTH
      <b>Durée :</b> ${MIN} min ${SEC} s
      
      <b>Sortie :</b>
      $OUTPUT" \
          --button="Ouvrir le dossier":2 \
          --button="Ouvrir le fichier":3 \
          --button="Fermer":0
      
      case $? in
          2)
              xdg-open "$(dirname "$OUTPUT")" >/dev/null 2>&1 &
              ;;
          3)
              xdg-open "$OUTPUT" >/dev/null 2>&1 &
              ;;
      esac
      
      exit 0
      EOF
      
      chmod +x ~/.local/bin/wr-rewrite-gui

      On peut lancer directement l’interface :

      wr-rewrite-gui

      11. Ce que propose l’interface

      La fenêtre permet de sélectionner :

      Fichier
      Modèle
      Méthode
      Température

      Les modèles disponibles dans notre configuration sont :

      qwen3.5:9b
      ministral-3:8b
      gemma4:12b

      Deux modes sont proposés :

      paraphrase
      humanize

      Le mode paraphrase cherche surtout à modifier fortement le vocabulaire et la structure syntaxique.

      Le mode humanize privilégie une rédaction plus naturelle et moins mécanique.

      Le fichier original n’est jamais écrasé.

      Par exemple :

      document.md

      produira :

      document.rewritten.md

      12. Ajouter un lanceur XFCE sur le Bureau

      Pour éviter de lancer la commande depuis un terminal, on peut créer un raccourci sur le Bureau.

      Sur une installation française d’XFCE :

      cat > ~/Bureau/wr-rewrite.desktop <<EOF
      [Desktop Entry]
      Version=1.0
      Type=Application
      Name=Watermark Rewriter
      Comment=Réécriture locale via Ollama
      Exec=$HOME/.local/bin/wr-rewrite-gui
      Icon=accessories-text-editor
      Terminal=false
      Categories=Utility;
      StartupNotify=true
      EOF
      
      chmod +x ~/Bureau/wr-rewrite.desktop

      On obtient alors une icône :

      Watermark Rewriter

      directement sur le Bureau.

      XFCE peut demander d’autoriser le lanceur lors de sa première utilisation.

      13. Organisation finale

      L’installation ressemble finalement à ceci :

      ~/.local/src/
      └── watermarks-remover/
      
      ~/.local/bin/
      ├── wr-clean
      └── wr-rewrite-gui
      
      ~/Bureau/
      └── wr-rewrite.desktop

      Avec côté services :

      Docker
      └── watermarks-remover
          └── 127.0.0.1:8765

      et :

      Ollama
      ├── qwen3.5:9b
      ├── ministral-3:8b
      └── gemma4:12b

      14. Les deux opérations en pratique

      Pour nettoyer les marques directement présentes dans le fichier :

      wr-clean document.md

      Résultat :

      document.cleaned.md

      Pour effectuer une véritable réécriture :

      wr-rewrite-gui

      Résultat :

      document.rewritten.md

      Les deux opérations sont donc différentes :

      document.md
          |
          +--> wr-clean
          |       |
          |       +--> document.cleaned.md
          |
          +--> wr-rewrite-gui
                  |
                  +--> Ollama
                  |
                  +--> document.rewritten.md

      15. Nettoyer également le texte après réécriture

      On peut aller un peu plus loin.

      Après une réécriture par Ollama :

      document.rewritten.md

      on peut repasser le résultat dans wr-clean :

      wr-clean document.rewritten.md

      Cela produit :

      document.rewritten.cleaned.md

      La chaîne complète devient alors :

      texte original
           |
           v
      réécriture Ollama
           |
           v
      nouvelle succession de tokens
           |
           v
      nettoyage déterministe
           |
           v
      fichier final

      Cela permet de combiner les deux approches sans les confondre.

      16. Vérifier la mémoire utilisée par Ollama

      Pour savoir si un modèle est actuellement chargé :

      ollama ps

      Grâce à :

      "keep_alive": 0

      le modèle doit normalement être déchargé à la fin de la réécriture.

      On peut également arrêter manuellement un modèle :

      ollama stop qwen3.5:9b

      17. Nettoyer sa bibliothèque Ollama

      Avec 16 Go de RAM, il est inutile d’accumuler de nombreux gros modèles ayant le même usage.

      Lister les modèles :

      ollama list

      Supprimer un modèle :

      ollama rm nom-du-modele

      Par exemple :

      ollama rm ancien-modele:7b

      Les modèles peuvent être retéléchargés plus tard avec :

      ollama pull nom-du-modele

      18. Limites à garder en tête

      Il faut distinguer une suppression déterministe d’une tentative de perturbation statistique.

      Avec un caractère Unicode invisible, on peut généralement dire précisément :

      ce caractère était présent

      puis :

      il a été supprimé

      Avec un watermark statistique propriétaire, la situation est différente.

      Si l’algorithme de détection, les paramètres ou les clés ne sont pas publics, on ne dispose pas forcément d’un moyen de mesurer directement le signal avant et après la réécriture.

      La réécriture doit donc être comprise comme une méthode de modification profonde de la succession des tokens, pas comme une certification universelle.

      19. Pourquoi faire tout cela en local ?

      L’intérêt principal de cette installation est aussi ailleurs.

      Un document potentiellement confidentiel reste sur l’ordinateur :

      document
         |
         +--> Docker local
         |
         +--> Ollama local

      Il n’est pas nécessaire de l’envoyer vers un site de paraphrase ou un service de nettoyage en ligne.

      On garde également :

      • le fichier original ;
      • le contrôle sur le modèle utilisé ;
      • le contrôle sur la température ;
      • les logs locaux ;
      • la possibilité d’inspecter les scripts ;
      • une séparation claire entre nettoyage et réécriture.

      Conclusion

      Les watermarks IA, ou filigranes IA, ne désignent pas une seule technologie.

      Certains sont directement présents dans les fichiers et peuvent être nettoyés de manière déterministe.

      D’autres peuvent être liés à la façon dont un modèle génère successivement ses tokens.

      Dans notre installation :

      wr-clean

      s’occupe du premier cas, tandis que :

      wr-rewrite-gui

      utilise un autre modèle local pour produire une nouvelle rédaction.

      Avec Docker, Ollama et deux petits scripts dans ~/.local/bin, on obtient ainsi une chaîne de traitement locale, relativement simple à utiliser et entièrement sous notre contrôle.

      Un jeune site que j'aime bien, la ferrari du T-shirt ...bio en plus : GoudronBlanc

    Affichage de 1 message (sur 1 au total)
    • Vous devez être connecté pour répondre à ce sujet.