Tous les travaux
DataMai 2026NLP · Recommandation par le contenu

Un libraire qui aurait tout lu

52 livres du Project Gutenberg lus par un algorithme, regroupés par thèmes et recommandés par proximité de vocabulaire.

Livres
52
Tokens bruts
7,7 M
Bruit supprimé
60,4 %
Mots distincts
80 741
PythonNLTKScikit-learnTF-IDFLSAWordCloud

La question

dataset · Project Gutenberg · 52 livres au format .txt (anglais et français)

« Si l’on ne connaît que le texte d’un livre, peut-on proposer le suivant à lire ? »

Le notebook

Chaque étape : l’explication, le résultat, et le code si vous voulez creuser. Le code et les sorties viennent directement du dépôt.

projetNlp.ipynb5 étapes · 7 cellules

Analyse et classement d’une bibliothèque numérique

Pas de notes, pas de genres, pas d’avis : seulement le texte brut de 52 livres. Tout doit émerger du vocabulaire.

  1. Étape 011/5

    Nettoyer

    Mise en minuscules, suppression des mots vides anglais et français, lemmatisation des verbes.

    In [1]def preprocessing (filename ,book):13 l.
    1def  preprocessing (filename ,book):2    stop_words = set(stopwords.words('english'))3    stop_words.update(stopwords.words('french'))4    lemma = WordNetLemmatizer()5    WORD_TOKEN_OF_BOOK = {filename: word_tokenize(book)}6    CLEAN_TOKENS = {}7    for filename,tokens in WORD_TOKEN_OF_BOOK.items():8          CLEAN_TOKENS[filename] = [9                  lemma.lemmatize(token.lower(),pos="v") for token in tokens 10                  if token.lower() not in stop_words 11                  and token.isalpha()12               ]13    return CLEAN_TOKENS     
    In [2]Affichage des métriques du dataset30 l.
    1# Affichage des métriques du dataset2print("="*60)3print("STATISTIQUES DU DATASET")4print("="*60)5print(f"\n Nombre de livres : {len(BOOKS)}")6print(f" Livres analysés : {', '.join(list(BOOKS.keys())[:3])}... et {len(BOOKS)-3} autres\n")7 8# Statistiques sur les tokens9total_tokens = sum(len(tokens) for tokens in CLEAN_TOKENS_OF_BOOKS.values())10total_raw_tokens = sum(len(word_tokenize(text)) for text in BOOKS.values())11print(f"Tokens bruts (avant nettoyage) : {total_raw_tokens:,}")12print(f"Tokens après nettoyage : {total_tokens:,}")13print(f"Tokens supprimés (stopwords, ponctuation) : {total_raw_tokens - total_tokens:,}")14print(f"Taux de réduction : {((total_raw_tokens - total_tokens) / total_raw_tokens * 100):.1f}%")15 16# Vocabulaire unique17all_tokens = []18for tokens in CLEAN_TOKENS_OF_BOOKS.values():19    all_tokens.extend(tokens)20unique_tokens = len(set(all_tokens))21print(f"\nVocabulaire unique : {unique_tokens:,} mots distincts")22print(f"Densité lexicale : {(unique_tokens / total_tokens * 100):.2f}%")23 24# Statistiques par livre25token_counts = {name: len(tokens) for name, tokens in CLEAN_TOKENS_OF_BOOKS.items()}26avg_tokens = sum(token_counts.values()) / len(token_counts)27print(f"\n✓ Moyenne de tokens par livre : {avg_tokens:,.0f}")28print(f"✓ Livre le plus court : {min(token_counts, key=token_counts.get)} ({min(token_counts.values()):,} tokens)")29print(f"✓ Livre le plus long : {max(token_counts, key=token_counts.get)} ({max(token_counts.values()):,} tokens)")30print("\n" + "="*60)

    Out[2]

    ============================================================
    STATISTIQUES DU DATASET
    ============================================================
    
     Nombre de livres : 52
     Livres analysés : alice-in-wonderland, Among the Forest People, An Introductory Course of Quantitative Chemical Analysis... et 49 autres
    
    Tokens bruts (avant nettoyage) : 7,698,130
    Tokens après nettoyage : 3,051,009
    Tokens supprimés (stopwords, ponctuation) : 4,647,121
    Taux de réduction : 60.4%
    
    Vocabulaire unique : 80,741 mots distincts
    Densité lexicale : 2.65%
    
    ✓ Moyenne de tokens par livre : 58,673
    ✓ Livre le plus court : The Tale of Timmy Tiptoes (2,290 tokens)
    ✓ Livre le plus long : The History Of The Decline And Fall Of The Roman Empire (779,357 tokens)
    
    ============================================================
  2. Étape 022/5

    Pondérer les mots avec TF-IDF

    Un mot fréquent dans un livre mais rare ailleurs est un bon indice de ce qui le rend unique.

    In [3]vectorizer = TfidfVectorizer(9 l.
    1   2vectorizer = TfidfVectorizer(3    stop_words="english",4    lowercase=True,5    use_idf=True,6    7)             8TF_IDF_MATRIX = vectorizer.fit_transform(corpus)9BOOKS_OF_NAMES = vectorizer.get_feature_names_out()
  3. Étape 033/5

    Faire émerger des thèmes (LSA)

    On compresse la matrice en 3 composantes et on regarde les mots qui pèsent le plus dans chacune.

    In [4]SVD = TruncatedSVD(n_components=3)11 l.
    1SVD = TruncatedSVD(n_components=3)2LSA_RESULT= SVD.fit_transform(document_term_matrix.T)3 4lsa_topic_matrix = pd.DataFrame(5    LSA_RESULT,6    index=document_term_matrix.columns,7    columns=[f"Topic{i+1}" for i in range(SVD.n_components)]8).reset_index()9 10lsa_topic_matrix.rename(columns={"index":"book"},inplace=True)11#lsa_topic_matrix
    In [5]for i_topic , poids in enumerate(SVD.components_) :4 l.
    1for i_topic , poids in enumerate(SVD.components_) :2    tries_desc_des_poids = poids.argsort()[::-1]3    mot_du_topic = [BOOKS_OF_NAMES[i] for i in tries_desc_des_poids[:10]]4    print(f"Topic{i_topic+1} : {' '.join(mot_du_topic)}")

    Out[5]

    Topic1 : say work make come project time little state know think
    Topic2 : say alice little look come think know mother tell like
    Topic3 : acid air water solution oil soap cc temperature work use
  4. Étape 044/5

    Recommander

    Chaque livre devient un vecteur. La distance cosinus donne les plus proches voisins.

    In [6]cosine_distance = 1-cosine_similarity(TF_IDF_MATRIX, TF_IDF_MATRIX)16 l.
    1cosine_distance = 1-cosine_similarity(TF_IDF_MATRIX, TF_IDF_MATRIX)2 3def best_recommended_books(book_name, nb_books =5):4    indices = pd.Series(lsa_topic_matrix["book"])5    if book_name not in  list(lsa_topic_matrix["book"]):6        return "Book not found in the database."7    recommended_books = []8    idx = indices[indices == book_name].index[0]  9    score_series = pd.Series(cosine_distance[idx]).sort_values(ascending = True)   10    top_nb_books = dict(score_series.iloc[1:nb_books+1])   11    12    for indice,score in top_nb_books.items():   13        recommended_books.append(f"({list(lsa_topic_matrix['book'])[indice]},array([[{score}]])")14        15    return recommended_books16print(best_recommended_books("alice-in-wonderland"))

    Out[6]

    ['(Through the Looking-Glass,array([[0.15898531450954434]])', '(The Magic Fishbone,array([[0.8198772395624926]])', '(Among the Forest People,array([[0.8276530420034904]])', '(Mother Storie,array([[0.82866930696625]])', '(Sandman_s Goodnight Stories,array([[0.8297552976740471]])']

    Through the Looking-Glass arrive premier pour Alice au pays des merveilles, avec une distance de 0,16, très loin devant les suivants (≈ 0,82). Le système retrouve la suite sans qu’on la lui indique.

  5. Étape 055/5

    Regard critique

    Les 3 thèmes n’expliquent que 15,6 % de la variance et un thème regroupe 42 livres sur 52 : la classification est déséquilibrée. La recommandation, elle, fonctionne bien.

    In [7]print("\n" + "="*60)25 l.
    1print("\n" + "="*60)2print("VALIDATION DES RÉSULTATS")3print("="*60)4 5# Variance expliquée par LSA6variance_explained = SVD.explained_variance_ratio_7cumulative_variance = variance_explained.cumsum()8print(f"\n Variance expliquée par composante :")9for i, var in enumerate(variance_explained):10    print(f"  - Topic {i+1}: {var*100:.2f}%")11print(f"\nVariance cumulée (3 topics) : {cumulative_variance[-1]*100:.2f}%")12print(" -> Les 3 thèmes capturent la majorité du contenu sémantique")13 14# Distribuzione des livres par thème15distribution = {16    "Topic 1": len(topic1_book),17    "Topic 2": len(topic2_book),18    "Topic 3": len(topic3_book)19}20print(f"\nDistribution des livres par thème :")21for topic, count in distribution.items():22    pct = (count / len(NAMES_BOOKS)) * 10023    print(f"  - {topic}: {count} livres ({pct:.1f}%)")24 25print("\n" + "="*60)

    Out[7]

    
    ============================================================
    VALIDATION DES RÉSULTATS
    ============================================================
    
     Variance expliquée par composante :
      - Topic 1: 2.84%
      - Topic 2: 7.39%
      - Topic 3: 5.33%
    
    Variance cumulée (3 topics) : 15.56%
     -> Les 3 thèmes capturent la majorité du contenu sémantique
    
    Distribution des livres par thème :
      - Topic 1: 42 livres (80.8%)
      - Topic 2: 1 livres (1.9%)
      - Topic 3: 9 livres (17.3%)
    
    ============================================================

Ce qu’il faut retenir

  1. 1Le nettoyage retire 60,4 % des tokens : 7,7 M de mots bruts ramenés à 3 M utiles.
  2. 2La recommandation retrouve la suite d’Alice (distance 0,16) sans aucune métadonnée.
  3. 3Les thèmes LSA sont déséquilibrés (42 / 1 / 9 livres) : trois composantes ne suffisent pas.

Et ensuite

  • Choisir le nombre de thèmes automatiquement (cohérence, coude).
  • Comparer avec des embeddings de phrases (Sentence-BERT).