Stripe · NLP

Help Center Search

The work

  1. import pandas  as pd
  2. articles = pd.read_csv("../Data/articles.csv")
    queries_test = pd.read_csv("../Data/queries_test.csv")
    queries_train = pd.read_csv("../Data/queries_train.csv")
  3. articles.head(10)
    
    queries_test.head(10)
    
    queries_train.head(10)
      query_id                                              query  \
    0    Q1001               why set trial lentgh to 14 days asap   
    1    Q1002                       how do i fight a chargeback?   
    2    Q1003      how do i upload id to keep my account active?   
    3    Q1004             hi, dunning settings for subscriptions   
    4    Q1005  need help: upgrade a subscriber mid month on m...   
    5    Q1006      need help: how do i fight a chargeback please   
    6    Q1007                  hi, cost of loisng a dispute asap   
    7    Q1008                        help verify event signature   
    8    Q1009                 question: switch from test to live   
    9    Q1010       help block suspicious payments on my account   
    
      relevant_article_id  
    0                A121  
    1                A105  
    2                A116  
    3                A122  
    4                A120  
    5                A105  
    6                A106  
    7                A124  
    8                A123  
    9                A107  
  4. articles.shape
    (24, 4)
  5. queries_train.shape
    (216, 3)
  6. queries_test.shape
    (96, 2)
  7. articles.isnull().sum()
    queries_train.isnull().sum()
    queries_test.isnull().sum()
    query_id    0
    query       0
    dtype: int64
  8. #for baseline.py

  9. import sys
  10. sys.path.append("../src")
    from baseline import tokenize
  11. query = queries_train.iloc[0]["query"]
    print(query)
    why set trial lentgh to 14 days asap
    
  12. print(tokenize(query))
    {'asap', 'why', 'to', 'set', 'days', 'trial', 'lentgh'}
    
  13. import importlib
    import baseline
    
    importlib.reload(baseline)
    <module 'baseline' from 'c:\\Users\\Disha Agarwal\\OneDrive\\Desktop\\HelpRank\\Notebooks\\../src\\baseline.py'>
  14. from baseline import word_match_score
    
    query = queries_train.iloc[0]["query"]
    article =  articles.iloc[0] 
    
    article_text = article["title"]+""+article["body"]
    score = word_match_score(query , article_text)
    
    print (query)
    print(article["title"])
    print(score)
    why set trial lentgh to 14 days asap
    When will I receive my payout?
    2
    
  15. import importlib
    import baseline
    
    importlib.reload(baseline)
    <module 'baseline' from 'c:\\Users\\Disha Agarwal\\OneDrive\\Desktop\\HelpRank\\Notebooks\\../src\\baseline.py'>
  16. from baseline import rank_articles
    
    query = queries_train.iloc[0]["query"]
    
    results = rank_articles(query, articles)
    
    print("Query:", query)
    print("\nTop 3 Articles:")
    
    for result in results[:3]:
        print(result)
    Query: why set trial lentgh to 14 days asap
    
    Top 3 Articles:
    {'article_id': 'A101', 'score': 2}
    {'article_id': 'A102', 'score': 2}
    {'article_id': 'A103', 'score': 2}
    
  17. query_row = queries_train.iloc[0]
    
    print("Query:", query_row["query"])
    print("Correct Article:", query_row["relevant_article_id"])
    
    print("\nOur Top 3:")
    for result in results[:3]:
        print(result)
    Query: why set trial lentgh to 14 days asap
    Correct Article: A121
    
    Our Top 3:
    {'article_id': 'A101', 'score': 2}
    {'article_id': 'A102', 'score': 2}
    {'article_id': 'A103', 'score': 2}
    
  18. query_row["relevant_article_id"] in [r["article_id"] for r in results[:3]]
    False
  19. def evaluate_baseline(queries, articles):
        recall_at_3 = 0
        reciprocal_ranks = []
    
        for _, row in queries.iterrows():
            query = row["query"]
            correct_article = row["relevant_article_id"]
    
            results = rank_articles(query, articles)
    
            top_3 = [result["article_id"] for result in results[:3]]
    
            # Recall@3
            if correct_article in top_3:
                recall_at_3 += 1
    
            # MRR
            if correct_article in top_3:
                rank = top_3.index(correct_article) + 1
                reciprocal_ranks.append(1 / rank)
            else:
                reciprocal_ranks.append(0)
    
        recall_at_3 = recall_at_3 / len(queries)
        mrr = sum(reciprocal_ranks) / len(reciprocal_ranks)
    
        return recall_at_3, mrr
  20. recall_at_3, mrr = evaluate_baseline(
        queries_train,
        articles
    )
    
    print(f"Baseline Recall@3: {recall_at_3:.4f}")
    print(f"Baseline MRR: {mrr:.4f}")
    Baseline Recall@3: 0.5648
    Baseline MRR: 0.4321
    
  21. Baseline Results

    MethodRecall@3MRR
    Word Matching Baseline56.48%0.4321
  22. TF-IDF

  23. from tfidf_search import build_tfidf_index
    
    vectorizer, article_vectors = build_tfidf_index(articles)
    
    print("Article vectors shape:", article_vectors.shape)
    Article vectors shape: (24, 657)
    
  24. from tfidf_search import rank_tfidf
    
    query = queries_train.iloc[0]["query"]
    
    results = rank_tfidf(
        query,
        vectorizer,
        article_vectors,
        articles
    )
    
    print("Query:", query)
    print("\nTop 3 Articles:")
    
    for result in results[:3]:
        print(result)
    Query: why set trial lentgh to 14 days asap
    
    Top 3 Articles:
    {'article_id': 'A121', 'score': np.float64(0.345439583599322)}
    {'article_id': 'A101', 'score': np.float64(0.12160594548943633)}
    {'article_id': 'A112', 'score': np.float64(0.0775246292063744)}
    
  25. def evaluate_tfidf(queries, articles, vectorizer, article_vectors):
        recall_at_3 = 0
        reciprocal_ranks = []
    
        for _, row in queries.iterrows():
            query = row["query"]
            correct_article = row["relevant_article_id"]
    
            results = rank_tfidf(
                query,
                vectorizer,
                article_vectors,
                articles
            )
    
            top_3 = [result["article_id"] for result in results[:3]]
    
            if correct_article in top_3:
                recall_at_3 += 1
    
                rank = top_3.index(correct_article) + 1
                reciprocal_ranks.append(1 / rank)
    
            else:
                reciprocal_ranks.append(0)
    
        recall_at_3 = recall_at_3 / len(queries)
        mrr = sum(reciprocal_ranks) / len(reciprocal_ranks)
    
        return recall_at_3, mrr
  26. tfidf_recall, tfidf_mrr = evaluate_tfidf(
        queries_train,
        articles,
        vectorizer,
        article_vectors
    )
    
    print(f"TF-IDF Recall@3: {tfidf_recall:.4f}")
    print(f"TF-IDF MRR: {tfidf_mrr:.4f}")
    TF-IDF Recall@3: 0.7130
    TF-IDF MRR: 0.6080
    
  27. bm_25

  28. %pip install rank-bm25
    Collecting rank-bm25
      Using cached rank_bm25-0.2.2-py3-none-any.whl.metadata (3.2 kB)
    Requirement already satisfied: numpy in c:\users\disha agarwal\anaconda3\lib\site-packages (from rank-bm25) (2.2.6)
    Using cached rank_bm25-0.2.2-py3-none-any.whl (8.6 kB)
    Installing collected packages: rank-bm25
    Successfully installed rank-bm25-0.2.2
    Note: you may need to restart the kernel to use updated packages.
    
  29. from bm25_search import build_bm25_index
    
    bm25 = build_bm25_index(articles)
    
    print("BM25 index created successfully!")
    BM25 index created successfully!
    
  30. import pandas as pd
    
    articles = pd.read_csv("../Data/articles.csv")
    queries_train = pd.read_csv("../Data/queries_train.csv")
    queries_test = pd.read_csv("../Data/queries_test.csv")
  31. import sys
    import importlib
    
    sys.path.append("../src")
    
    import bm25_search
    
    importlib.reload(bm25_search)
    
    print(dir(bm25_search))
    ['BM25Okapi', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'build_bm25_index', 'rank_bm25', 're', 'tokenize']
    
  32. bm25 = bm25_search.build_bm25_index(articles)
    
    query = queries_train.iloc[0]["query"]
    
    results = bm25_search.rank_bm25(
        query,
        bm25,
        articles
    )
    
    print("Query:", query)
    
    for result in results[:3]:
        print(result)
    Query: why set trial lentgh to 14 days asap
    {'article_id': 'A121', 'score': np.float64(5.146791688085988)}
    {'article_id': 'A108', 'score': np.float64(1.7488502914746173)}
    {'article_id': 'A102', 'score': np.float64(1.7066221697420685)}
    
  33. import bm25_search
  34. def evaluate_bm25(queries, articles, bm25):
        recall_at_3 = 0
        reciprocal_ranks = []
    
        for _, row in queries.iterrows():
            query = row["query"]
            correct_article = row["relevant_article_id"]
    
            results = bm25_search.rank_bm25(
                query,
                bm25,
                articles
            )
    
            top_3 = [
                result["article_id"]
                for result in results[:3]
            ]
    
            if correct_article in top_3:
                recall_at_3 += 1
    
                rank = top_3.index(correct_article) + 1
                reciprocal_ranks.append(1 / rank)
            else:
                reciprocal_ranks.append(0)
    
        recall_at_3 = recall_at_3 / len(queries)
        mrr = sum(reciprocal_ranks) / len(reciprocal_ranks)
    
        return recall_at_3, mrr
  35. bm25_recall, bm25_mrr = evaluate_bm25(
        queries_train,
        articles,
        bm25
    )
    
    print(f"BM25 Recall@3: {bm25_recall:.4f}")
    print(f"BM25 MRR: {bm25_mrr:.4f}")
    BM25 Recall@3: 0.7500
    BM25 MRR: 0.6505
    
  36. import bm25_search
    import importlib
    
    importlib.reload(bm25_search)
    <module 'bm25_search' from 'c:\\Users\\Disha Agarwal\\OneDrive\\Desktop\\HelpRank\\Notebooks\\../src\\bm25_search.py'>
  37. query = queries_train.iloc[0]["query"]
    
    results = bm25_search.rank_bm25_title_weighted(
        query,
        articles
    )
    
    print("Query:", query)
    print("\nTop 3 Articles:")
    
    for result in results[:3]:
        print(result)
    Query: why set trial lentgh to 14 days asap
    
    Top 3 Articles:
    {'article_id': 'A106', 'score': np.float64(0.0)}
    {'article_id': 'A107', 'score': np.float64(0.0)}
    {'article_id': 'A110', 'score': np.float64(0.0)}
    
  38. import bm25_search
    import importlib
    
    importlib.reload(bm25_search)
    <module 'bm25_search' from 'c:\\Users\\Disha Agarwal\\OneDrive\\Desktop\\HelpRank\\Notebooks\\../src\\bm25_search.py'>
  39. title_bm25, body_bm25 = bm25_search.build_title_weighted_bm25(
        articles
    )
    
    print("Title-weighted BM25 index created!")
    Title-weighted BM25 index created!
    
  40. query = queries_train.iloc[0]["query"]
    
    results = bm25_search.rank_bm25_title_weighted(
        query,
        title_bm25,
        body_bm25,
        articles
    )
    
    print("Query:", query)
    print("\nTop 3 Articles:")
    
    for result in results[:3]:
        print(result)
    Query: why set trial lentgh to 14 days asap
    
    Top 3 Articles:
    {'article_id': 'A121', 'score': np.float64(4.994452675336603)}
    {'article_id': 'A105', 'score': np.float64(4.643673852247172)}
    {'article_id': 'A104', 'score': np.float64(4.001173237280832)}
    
  41. def evaluate_title_weighted_bm25(
        queries,
        articles,
        title_bm25,
        body_bm25
    ):
        recall_at_3 = 0
        reciprocal_ranks = []
    
        for _, row in queries.iterrows():
            query = row["query"]
            correct_article = row["relevant_article_id"]
    
            results = bm25_search.rank_bm25_title_weighted(
                query,
                title_bm25,
                body_bm25,
                articles
            )
    
            top_3 = [
                result["article_id"]
                for result in results[:3]
            ]
    
            if correct_article in top_3:
                recall_at_3 += 1
    
                rank = top_3.index(correct_article) + 1
                reciprocal_ranks.append(1 / rank)
            else:
                reciprocal_ranks.append(0)
    
        recall_at_3 = recall_at_3 / len(queries)
        mrr = sum(reciprocal_ranks) / len(reciprocal_ranks)
    
        return recall_at_3, mrr
  42. title_recall, title_mrr = evaluate_title_weighted_bm25(
        queries_train,
        articles,
        title_bm25,
        body_bm25
    )
    
    print(f"Title-weighted BM25 Recall@3: {title_recall:.4f}")
    print(f"Title-weighted BM25 MRR: {title_mrr:.4f}")
    Title-weighted BM25 Recall@3: 0.6250
    Title-weighted BM25 MRR: 0.4992
    
  43. import bm25_search
    import importlib
    
    importlib.reload(bm25_search)
    <module 'bm25_search' from 'c:\\Users\\Disha Agarwal\\OneDrive\\Desktop\\HelpRank\\Notebooks\\../src\\bm25_search.py'>
  44. expanded_articles = bm25_search.build_query_expanded_articles(
        articles,
        queries_train
    )
    
    print(expanded_articles.shape)
    (24, 4)
    
  45. print(
        expanded_articles[
            expanded_articles["article_id"] == "A121"
        ]["body"].iloc[0]
    )
    Subscriptions can start with a free trial of any length. The customer is charged at the end of the trial unless they cancel, and a reminder email can be sent a few days before. why set trial lentgh to 14 days asap question: set trial length to 14 days please customer charged when trial ended please remind users before trial expires for my shop help customer charegd when trial ended question: remind users before trial expires why customer charged when trial ended? can you tell me remind users before trial expires on my account set trial length to 14 days
    
  46. from sklearn.model_selection import KFold
  47. kf = KFold(
        n_splits=5,
        shuffle=True,
        random_state=42
    )
    
    print("5-fold cross-validation ready!")
    5-fold cross-validation ready!
    
  48. train_indices, val_indices = next(
        kf.split(queries_train)
    )
    
    train_queries_fold = queries_train.iloc[train_indices]
    val_queries_fold = queries_train.iloc[val_indices]
    
    print("Expansion queries:", len(train_queries_fold))
    print("Validation queries:", len(val_queries_fold))
    Expansion queries: 172
    Validation queries: 44
    
  49. expanded_articles_fold = bm25_search.build_query_expanded_articles(
        articles,
        train_queries_fold
    )
    
    print("Expanded articles:", expanded_articles_fold.shape)
    Expanded articles: (24, 4)
    
  50. fold_bm25 = bm25_search.build_bm25_index(
        expanded_articles_fold
    )
    
    print("Fold BM25 index created!")
    Fold BM25 index created!
    
  51. fold_results = []
    
    for fold, (train_indices, val_indices) in enumerate(
        kf.split(queries_train),
        start=1
    ):
        train_queries_fold = queries_train.iloc[train_indices]
        val_queries_fold = queries_train.iloc[val_indices]
    
        expanded_articles_fold = bm25_search.build_query_expanded_articles(
            articles,
            train_queries_fold
        )
    
        fold_bm25 = bm25_search.build_bm25_index(
            expanded_articles_fold
        )
    
        recall, mrr = evaluate_fold(
            val_queries_fold,
            expanded_articles_fold,
            fold_bm25
        )
    
        fold_results.append({
            "fold": fold,
            "recall_at_3": recall,
            "mrr": mrr
        })
    
        print(
            f"Fold {fold}: "
            f"Recall@3={recall:.4f}, "
            f"MRR={mrr:.4f}"
        )
    Fold 1: Recall@3=1.0000, MRR=1.0000
    Fold 2: Recall@3=1.0000, MRR=1.0000
    Fold 3: Recall@3=0.9535, MRR=0.9535
    Fold 4: Recall@3=0.9767, MRR=0.9535
    Fold 5: Recall@3=1.0000, MRR=1.0000
    
  52. import pandas as pd
    
    fold_results_df = pd.DataFrame(fold_results)
    
    print("\nCross-Validation Results:")
    print(fold_results_df)
    
    print("\nAverage Recall@3:",
          fold_results_df["recall_at_3"].mean())
    
    print("Average MRR:",
          fold_results_df["mrr"].mean())
    
    Cross-Validation Results:
       fold  recall_at_3       mrr
    0     1     1.000000  1.000000
    1     2     1.000000  1.000000
    2     3     0.953488  0.953488
    3     4     0.976744  0.953488
    4     5     1.000000  1.000000
    
    Average Recall@3: 0.9860465116279069
    Average MRR: 0.9813953488372092
    
  53. final_expanded_articles = bm25_search.build_query_expanded_articles(
        articles,
        queries_train
    )
    
    print("Final expanded articles:", final_expanded_articles.shape)
    Final expanded articles: (24, 4)
    
  54. final_bm25 = bm25_search.build_bm25_index(
        final_expanded_articles
    )
    
    print("Final BM25 index created!")
    Final BM25 index created!
    
  55. predictions = []
    
    for _, row in queries_test.iterrows():
        query_id = row["query_id"]
        query = row["query"]
    
        results = bm25_search.rank_bm25(
            query,
            final_bm25,
            final_expanded_articles
        )
    
        top_3 = [
            result["article_id"]
            for result in results[:3]
        ]
    
        predictions.append({
            "query_id": query_id,
            "rank_1": top_3[0],
            "rank_2": top_3[1],
            "rank_3": top_3[2]
        })
    
    print("Predictions generated:", len(predictions))
    Predictions generated: 96
    
  56. predictions_df = pd.DataFrame(predictions)
    
    predictions_df.to_csv(
        "../predictions.csv",
        index=False
    )
    
    print("predictions.csv created successfully!")
    predictions.csv created successfully!
    
  57. print(predictions_df.head())
    print("\nShape:", predictions_df.shape)
    print("\nColumns:", predictions_df.columns.tolist())
      query_id rank_1 rank_2 rank_3
    0    Q5001   A118   A108   A109
    1    Q5002   A104   A102   A106
    2    Q5003   A114   A101   A122
    3    Q5004   A113   A112   A108
    4    Q5005   A114   A103   A120
    
    Shape: (96, 4)
    
    Columns: ['query_id', 'rank_1', 'rank_2', 'rank_3']
    
  58. cv_predictions = []
    
    for fold, (train_indices, val_indices) in enumerate(
        kf.split(queries_train),
        start=1
    ):
        train_queries_fold = queries_train.iloc[train_indices]
        val_queries_fold = queries_train.iloc[val_indices]
    
        expanded_articles_fold = bm25_search.build_query_expanded_articles(
            articles,
            train_queries_fold
        )
    
        fold_bm25 = bm25_search.build_bm25_index(
            expanded_articles_fold
        )
    
        for _, row in val_queries_fold.iterrows():
            query_id = row["query_id"]
            query = row["query"]
            correct_article = row["relevant_article_id"]
    
            results = bm25_search.rank_bm25(
                query,
                fold_bm25,
                expanded_articles_fold
            )
    
            top_3 = [
                result["article_id"]
                for result in results[:3]
            ]
    
            cv_predictions.append({
                "fold": fold,
                "query_id": query_id,
                "query": query,
                "correct_article": correct_article,
                "rank_1": top_3[0],
                "rank_2": top_3[1],
                "rank_3": top_3[2]
            })
    
    print("CV predictions:", len(cv_predictions))
    CV predictions: 216
    
  59. cv_predictions_df = pd.DataFrame(cv_predictions)
    
    errors = cv_predictions_df[
        ~(
            (cv_predictions_df["correct_article"] == cv_predictions_df["rank_1"]) |
            (cv_predictions_df["correct_article"] == cv_predictions_df["rank_2"]) |
            (cv_predictions_df["correct_article"] == cv_predictions_df["rank_3"])
        )
    ]
    
    print("Total errors:", len(errors))
    Total errors: 3
    
  60. errors[
        [
            "query_id",
            "query",
            "correct_article",
            "rank_1",
            "rank_2",
            "rank_3"
        ]
    ].to_string(index=False)
    'query_id                                            query correct_article rank_1 rank_2 rank_3\n   Q1091             delete my account permanently please            A119   A101   A118   A107\n   Q1116 help delete my account permanently on my account            A119   A107   A101   A118\n   Q1044                       why clsoe business acocunt            A119   A116   A101   A112'

The brief this answers

Build a search engine that finds the right help article for questions written in customers' own words, and measure it properly.

Try it yourself

Other answers to this brief

The same problem, solved by somebody else.

Discussion

Nothing here yet. Ask how they approached it, or tell them what you would have done differently.