You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ML-For-Beginners/translations/ja/6-NLP/5-Hotel-Reviews-2
leestott eaff2e5814
🌐 Update translations via Co-op Translator
2 weeks ago
..
solution 🌐 Update translations via Co-op Translator 3 weeks ago
README.md 🌐 Update translations via Co-op Translator 2 weeks ago
assignment.md 🌐 Update translations via Co-op Translator 3 weeks ago
notebook.ipynb 🌐 Update translations via Co-op Translator 3 weeks ago

README.md

ホテルレビューを用いた感情分析

データセットを詳細に調査した後は、列をフィルタリングし、NLP技術を使用してホテルに関する新たな洞察を得る時です。

事前講義クイズ

フィルタリングと感情分析の操作

おそらく気づいたと思いますが、このデータセットにはいくつかの問題があります。一部の列には無意味な情報が含まれており、他の列は正確性に疑問があります。仮に正確だとしても、それがどのように計算されたのか不明であり、自分自身の計算で独立して検証することができません。

演習: もう少しデータを処理する

データをもう少しクリーンにしましょう。後で役立つ列を追加し、他の列の値を変更し、特定の列を完全に削除します。

  1. 初期の列処理

    1. latlng を削除する

    2. Hotel_Address の値を以下の値に置き換える(住所に都市名と国名が含まれている場合、それを都市名と国名だけに変更する)。

      データセットに含まれる都市と国は以下の通りです:

      アムステルダム, オランダ
      バルセロナ, スペイン
      ロンドン, イギリス
      ミラノ, イタリア
      パリ, フランス
      ウィーン, オーストリア

      def replace_address(row):
          if "Netherlands" in row["Hotel_Address"]:
              return "Amsterdam, Netherlands"
          elif "Barcelona" in row["Hotel_Address"]:
              return "Barcelona, Spain"
          elif "United Kingdom" in row["Hotel_Address"]:
              return "London, United Kingdom"
          elif "Milan" in row["Hotel_Address"]:        
              return "Milan, Italy"
          elif "France" in row["Hotel_Address"]:
              return "Paris, France"
          elif "Vienna" in row["Hotel_Address"]:
              return "Vienna, Austria" 
      
      # Replace all the addresses with a shortened, more useful form
      df["Hotel_Address"] = df.apply(replace_address, axis = 1)
      # The sum of the value_counts() should add up to the total number of reviews
      print(df["Hotel_Address"].value_counts())
      

      これで国レベルのデータをクエリできます:

      display(df.groupby("Hotel_Address").agg({"Hotel_Name": "nunique"}))
      
      Hotel_Address Hotel_Name
      Amsterdam, Netherlands 105
      Barcelona, Spain 211
      London, United Kingdom 400
      Milan, Italy 162
      Paris, France 458
      Vienna, Austria 158
  2. ホテルのメタレビュー列を処理する

    1. Additional_Number_of_Scoring を削除する

    2. Total_Number_of_Reviews を、そのホテルに実際に含まれるレビューの総数に置き換える

    3. Average_Score を独自に計算したスコアに置き換える

Drop Additional_Number_of_Scoring

df.drop(["Additional_Number_of_Scoring"], axis = 1, inplace=True)

Replace Total_Number_of_Reviews and Average_Score with our own calculated values

df.Total_Number_of_Reviews = df.groupby('Hotel_Name').transform('count') df.Average_Score = round(df.groupby('Hotel_Name').Reviewer_Score.transform('mean'), 1)


3. レビュー列を処理する

 1. `Review_Total_Negative_Word_Counts`、`Review_Total_Positive_Word_Counts`、`Review_Date`、`days_since_review` を削除する

 2. `Reviewer_Score`、`Negative_Review`、`Positive_Review` はそのまま保持する

 3. `Tags` は一時的に保持する

    - 次のセクションでタグに対して追加のフィルタリング操作を行い、その後タグを削除します

4. レビュアー列を処理する

 1. `Total_Number_of_Reviews_Reviewer_Has_Given` を削除する

 2. `Reviewer_Nationality` を保持する

### タグ列

`Tag` 列は問題があります。この列にはリストテキスト形式が格納されており、順序やサブセクションの数が常に同じではありません。このため、515,000行、1,427のホテルがあり、それぞれがレビュアーに異なる選択肢を提供しているため、人間が興味のある正しいフレーズを特定するのは困難です。ここでNLPが役立ちます。テキストをスキャンして最も一般的なフレーズを見つけ、それらをカウントすることができます。

残念ながら、単語単位ではなく、複数単語のフレーズ(例:*Business trip*に興味があります。このようなデータ6,762,646語に対して複数単語の頻度分布アルゴリズムを実行すると、非常に多くの時間がかかる可能性があります。しかし、データを見ずにそれが必要なコストだと判断するのは早計です。ここで探索的データ分析が役立ちます。例えば、`[' Business trip  ', ' Solo traveler ', ' Single Room ', ' Stayed 5 nights ', ' Submitted from  a mobile device ']` のようなタグのサンプルを見た場合、処理を大幅に削減できる可能性があるかどうかを考え始めることができます。幸いにも、それは可能です。ただし、まずは興味のあるタグを確認するためにいくつかのステップを踏む必要があります。

### タグのフィルタリング

このデータセットの目的は、感情や列を追加して、最適なホテルを選ぶのに役立てることです(自分自身のため、またはホテル推薦ボットを作るよう依頼されたクライアントのため)。タグが最終的なデータセットで有用かどうかを自問する必要があります。以下は一つの解釈です(他の目的でデータセットが必要な場合、異なるタグが選択に含まれる可能性があります):

1. 旅行の種類は関連性があり、保持すべき
2. ゲストグループの種類は重要であり、保持すべき
3. ゲストが宿泊した部屋、スイート、スタジオの種類は無関係(どのホテルも基本的に同じ部屋を持っている)
4. レビューが送信されたデバイスは無関係
5. 宿泊日数は、長期滞在がホテルを気に入ったことを示す場合に関連性があるかもしれないが、可能性は低く、おそらく無関係

要約すると、**2種類のタグを保持し、他を削除する**ということです。

まず、タグをカウントする前に、より良い形式にする必要があります。つまり、角括弧や引用符を削除する必要があります。これを行う方法はいくつかありますが、データ量が多いため、最速の方法を選びたいところです。幸いにも、pandasにはこれらのステップを簡単に実行する方法があります。

```Python
# Remove opening and closing brackets
df.Tags = df.Tags.str.strip("[']")
# remove all quotes too
df.Tags = df.Tags.str.replace(" ', '", ",", regex = False)

各タグは次のようになります:Business trip, Solo traveler, Single Room, Stayed 5 nights, Submitted from a mobile device

次に問題が発生します。一部のレビューには5列、他には3列、6列のものもあります。これはデータセットの作成方法によるもので、修正が困難です。各フレーズの頻度をカウントしたいのですが、レビューごとに順序が異なるため、カウントが正確でない可能性があり、ホテルが本来得るべきタグを割り当てられない場合があります。

しかし、この異なる順序を逆に利用することができます。各タグは複数単語で構成されていますが、カンマで区切られています最も簡単な方法は、6つの一時的な列を作成し、各タグをその順序に対応する列に挿入することです。その後、6つの列を1つの大きな列にマージし、value_counts() メソッドを実行します。これを出力すると、2,428個のユニークなタグがあることがわかります。以下はその一部です

Tag Count
Leisure trip 417778
Submitted from a mobile device 307640
Couple 252294
Stayed 1 night 193645
Stayed 2 nights 133937
Solo traveler 108545
Stayed 3 nights 95821
Business trip 82939
Group 65392
Family with young children 61015
Stayed 4 nights 47817
Double Room 35207
Standard Double Room 32248
Superior Double Room 31393
Family with older children 26349
Deluxe Double Room 24823
Double or Twin Room 22393
Stayed 5 nights 20845
Standard Double or Twin Room 17483
Classic Double Room 16989
Superior Double or Twin Room 13570
2 rooms 12393

Submitted from a mobile device のような一般的なタグの一部は役に立たないため、フレーズの出現回数をカウントする前に削除するのが賢明かもしれませんが、操作が非常に高速であるため、それらを残して無視することもできます。

宿泊日数タグの削除

これらのタグを削除するのが最初のステップです。これにより、考慮すべきタグの総数が若干減少します。データセットから削除するのではなく、レビューのデータセットでカウント/保持する値として考慮しないだけです。

Length of stay Count
Stayed 1 night 193645
Stayed 2 nights 133937
Stayed 3 nights 95821
Stayed 4 nights 47817
Stayed 5 nights 20845
Stayed 6 nights 9776
Stayed 7 nights 7399
Stayed 8 nights 2502
Stayed 9 nights 1293
... ...

部屋、スイート、スタジオ、アパートメントなどの種類は非常に多様です。これらはほぼ同じ意味を持ち、関連性がないため、考慮から除外します。

Type of room Count
Double Room 35207
Standard Double Room 32248
Superior Double Room 31393
Deluxe Double Room 24823
Double or Twin Room 22393
Standard Double or Twin Room 17483
Classic Double Room 16989
Superior Double or Twin Room 13570

最後に、これは非常に簡単な処理で(ほとんど処理を必要としませんでした)、以下の有用なタグが残ります:

Tag Count
Leisure trip 417778
Couple 252294
Solo traveler 108545
Business trip 82939
Group (combined with Travellers with friends) 67535
Family with young children 61015
Family with older children 26349
With a pet 1405

Travellers with friendsGroup とほぼ同じ意味であると考えられるため、上記のように2つを統合するのが妥当です。正しいタグを特定するコードはTags notebookにあります。

最終ステップは、これらのタグごとに新しい列を作成することです。そして、各レビュー行について、Tag 列が新しい列のいずれかに一致する場合は1を追加し、一致しない場合は0を追加します。その結果、例えばビジネス目的で選ばれたホテルの数や、レジャー目的で選ばれたホテルの数などを集計することができ、ホテルを推薦する際に有用な情報となります。

# Process the Tags into new columns
# The file Hotel_Reviews_Tags.py, identifies the most important tags
# Leisure trip, Couple, Solo traveler, Business trip, Group combined with Travelers with friends, 
# Family with young children, Family with older children, With a pet
df["Leisure_trip"] = df.Tags.apply(lambda tag: 1 if "Leisure trip" in tag else 0)
df["Couple"] = df.Tags.apply(lambda tag: 1 if "Couple" in tag else 0)
df["Solo_traveler"] = df.Tags.apply(lambda tag: 1 if "Solo traveler" in tag else 0)
df["Business_trip"] = df.Tags.apply(lambda tag: 1 if "Business trip" in tag else 0)
df["Group"] = df.Tags.apply(lambda tag: 1 if "Group" in tag or "Travelers with friends" in tag else 0)
df["Family_with_young_children"] = df.Tags.apply(lambda tag: 1 if "Family with young children" in tag else 0)
df["Family_with_older_children"] = df.Tags.apply(lambda tag: 1 if "Family with older children" in tag else 0)
df["With_a_pet"] = df.Tags.apply(lambda tag: 1 if "With a pet" in tag else 0)

ファイルの保存

最後に、現在の状態のデータセットを新しい名前で保存します。

df.drop(["Review_Total_Negative_Word_Counts", "Review_Total_Positive_Word_Counts", "days_since_review", "Total_Number_of_Reviews_Reviewer_Has_Given"], axis = 1, inplace=True)

# Saving new data file with calculated columns
print("Saving results to Hotel_Reviews_Filtered.csv")
df.to_csv(r'../data/Hotel_Reviews_Filtered.csv', index = False)

感情分析の操作

この最終セクションでは、レビュー列に感情分析を適用し、その結果をデータセットに保存します。

演習: フィルタリングされたデータの読み込みと保存

ここでは、元のデータセットではなく、前のセクションで保存したフィルタリング済みのデータセットを読み込みます。

import time
import pandas as pd
import nltk as nltk
from nltk.corpus import stopwords
from nltk.sentiment.vader import SentimentIntensityAnalyzer
nltk.download('vader_lexicon')

# Load the filtered hotel reviews from CSV
df = pd.read_csv('../../data/Hotel_Reviews_Filtered.csv')

# You code will be added here


# Finally remember to save the hotel reviews with new NLP data added
print("Saving results to Hotel_Reviews_NLP.csv")
df.to_csv(r'../data/Hotel_Reviews_NLP.csv', index = False)

ストップワードの削除

ネガティブおよびポジティブレビュー列で感情分析を実行すると、時間がかかる可能性があります。高速なCPUを搭載したテスト用ートPCでテストしたところ、使用する感情分析ライブラリによって1214分かかりました。これは比較的長い時間なので、速度を向上させる方法を検討する価値があります。

最初のステップとして、ストップワード(英語の一般的な単語で、文の感情に影響を与えないもの)を削除します。これにより、感情分析の速度が向上するはずですが、精度が低下することはありません(ストップワードは感情に影響を与えませんが、分析を遅くします)。

最も長いネガティブレビューは395語でしたが、ストップワードを削除すると195語になりました。

ストップワードの削除も高速な操作であり、515,000行の2つのレビュー列からストップワードを削除するのに3.3秒かかりました。デバイスのCPU速度、RAM、SSDの有無、その他の要因によって若干の違いがあるかもしれませんが、操作が短時間で済むため、感情分析の時間が改善されるのであれば実行する価値があります。

from nltk.corpus import stopwords

# Load the hotel reviews from CSV
df = pd.read_csv("../../data/Hotel_Reviews_Filtered.csv")

# Remove stop words - can be slow for a lot of text!
# Ryan Han (ryanxjhan on Kaggle) has a great post measuring performance of different stop words removal approaches
# https://www.kaggle.com/ryanxjhan/fast-stop-words-removal # using the approach that Ryan recommends
start = time.time()
cache = set(stopwords.words("english"))
def remove_stopwords(review):
    text = " ".join([word for word in review.split() if word not in cache])
    return text

# Remove the stop words from both columns
df.Negative_Review = df.Negative_Review.apply(remove_stopwords)   
df.Positive_Review = df.Positive_Review.apply(remove_stopwords)

感情分析の実行

次に、ネガティブおよびポジティブレビュー列の感情分析を計算し、その結果を2つの新しい列に保存します。感情のテストは、同じレビューに対するレビュアースコアと比較することです。例えば、感情分析がネガティブレビューの感情を1非常にポジティブな感情と判断し、ポジティブレビューの感情も1と判断したが、レビュアーがホテルに最低スコアを付けた場合、レビューのテキストがスコアと一致していないか、感情分析ツールが感情を正しく認識できなかった可能性があります。一部の感情スコアが完全に間違っていることを予想すべきであり、それはしばしば説明可能です。例えば、レビューが非常に皮肉的である場合「もちろん、暖房のない部屋で寝るのが大好きでした」は、感情分析ツールがそれをポジティブな感情と判断するかもしれませんが、人間が読めば皮肉であることがわかります。 NLTKは、さまざまな感情分析ツールを提供しており、それらを置き換えて感情分析の精度が向上するかどうかを確認することができます。ここではVADER感情分析を使用しています。

Hutto, C.J. & Gilbert, E.E. (2014). VADER: A Parsimonious Rule-based Model for Sentiment Analysis of Social Media Text. Eighth International Conference on Weblogs and Social Media (ICWSM-14). Ann Arbor, MI, June 2014.

from nltk.sentiment.vader import SentimentIntensityAnalyzer

# Create the vader sentiment analyser (there are others in NLTK you can try too)
vader_sentiment = SentimentIntensityAnalyzer()
# Hutto, C.J. & Gilbert, E.E. (2014). VADER: A Parsimonious Rule-based Model for Sentiment Analysis of Social Media Text. Eighth International Conference on Weblogs and Social Media (ICWSM-14). Ann Arbor, MI, June 2014.

# There are 3 possibilities of input for a review:
# It could be "No Negative", in which case, return 0
# It could be "No Positive", in which case, return 0
# It could be a review, in which case calculate the sentiment
def calc_sentiment(review):    
    if review == "No Negative" or review == "No Positive":
        return 0
    return vader_sentiment.polarity_scores(review)["compound"]    

プログラム内で感情を計算する準備が整ったら、以下のように各レビューに適用することができます。

# Add a negative sentiment and positive sentiment column
print("Calculating sentiment columns for both positive and negative reviews")
start = time.time()
df["Negative_Sentiment"] = df.Negative_Review.apply(calc_sentiment)
df["Positive_Sentiment"] = df.Positive_Review.apply(calc_sentiment)
end = time.time()
print("Calculating sentiment took " + str(round(end - start, 2)) + " seconds")

これには私のコンピュータで約120秒かかりますが、コンピュータによって異なります。結果を印刷して、感情がレビューと一致しているか確認したい場合は以下を実行してください。

df = df.sort_values(by=["Negative_Sentiment"], ascending=True)
print(df[["Negative_Review", "Negative_Sentiment"]])
df = df.sort_values(by=["Positive_Sentiment"], ascending=True)
print(df[["Positive_Review", "Positive_Sentiment"]])

チャレンジでファイルを使用する前に最後に行うべきことは、ファイルを保存することです!また、新しい列をすべて並べ替えて、人間が扱いやすいようにすることを検討してください(見た目の変更です)。

# Reorder the columns (This is cosmetic, but to make it easier to explore the data later)
df = df.reindex(["Hotel_Name", "Hotel_Address", "Total_Number_of_Reviews", "Average_Score", "Reviewer_Score", "Negative_Sentiment", "Positive_Sentiment", "Reviewer_Nationality", "Leisure_trip", "Couple", "Solo_traveler", "Business_trip", "Group", "Family_with_young_children", "Family_with_older_children", "With_a_pet", "Negative_Review", "Positive_Review"], axis=1)

print("Saving results to Hotel_Reviews_NLP.csv")
df.to_csv(r"../data/Hotel_Reviews_NLP.csv", index = False)

分析ノートブックのコード全体を実行する必要があります(フィルタリングノートブックを実行してHotel_Reviews_Filtered.csvファイルを生成した後

手順を振り返ると以下の通りです:

  1. 元のデータセットファイル Hotel_Reviews.csv は、前のレッスンでエクスプローラーノートブックを使用して調査されました。
  2. Hotel_Reviews.csv はフィルタリングノートブックによってフィルタリングされ、Hotel_Reviews_Filtered.csv が生成されます。
  3. Hotel_Reviews_Filtered.csv は感情分析ノートブックによって処理され、Hotel_Reviews_NLP.csv が生成されます。
  4. 以下のNLPチャレンジでHotel_Reviews_NLP.csvを使用します。

結論

最初は列とデータが含まれたデータセットを持っていましたが、そのすべてが検証または使用できるわけではありませんでした。データを調査し、不要な部分をフィルタリングし、タグを有用なものに変換し、独自の平均値を計算し、感情に関する列を追加しました。そして、自然言語テキストを処理することについて興味深いことを学べたのではないでしょうか。

講義後のクイズ

チャレンジ

感情分析が完了したデータセットを使用して、このカリキュラムで学んだ戦略(例えばクラスタリング)を活用し、感情に関するパターンを特定してみてください。

復習と自己学習

このLearnモジュールを受講して、さらに学び、異なるツールを使用してテキストの感情を探求してください。

課題

別のデータセットを試してみる


免責事項:
この文書は、AI翻訳サービス Co-op Translator を使用して翻訳されています。正確性を期すよう努めておりますが、自動翻訳には誤りや不正確な表現が含まれる可能性があります。元の言語で記載された原文を公式な情報源としてご参照ください。重要な情報については、専門の人間による翻訳を推奨します。本翻訳の利用に起因する誤解や誤認について、当方は一切の責任を負いません。