見出し画像

未経験からデータサイエンティストへの道Day3. 口コミ分析に挑戦

 皆さん、こんにちは!Haedam workoutの李へダムです。
こちらでは、引き続き、未経験からデータサイエンティストになるため、私が分析を行った日記を記録しております。
 未経験者の目線から、一つ一つ学んでいくステップが、データサイエンティストにチャレンジする他の方に少しでもためになればと思います!
 それでは早速、今回の実装に踏み込んでみたいと思います。

1. 背景(テーマ選定の理由)

[実装のゴール]

 Kaggle上に公開されている、アマゾンの口コミを自然言語処理によって分析することにより、
「ある製品に対する消費者口コミを、傾向毎にまとめてみること」
が今回の実装のゴールです。

[テーマ選定の背景]

 私自身、今現在食品会社で働いていますが、自社製品に対する消費者の反応を確認する際に、時間・費用がかかりすぎるところに課題を感じました。
 
例えば、某ECでの自社商品のレビューを見ようとすると、

3084件、読む勇気が出ない、

 3084件もあると、どうも読む勇気が出ません。1件ずつ口コミを読んでいくと、何となく傾向はつかめますが、気づいたら、その傾向は頭の中で去っていき、ボーとしながら2~3時間読んでいる自分がいました。
 ここで、Aidemyで習った、自然言語処理を使い、キーワード毎にまとめ、全レビューを読まず、スマートに傾向を掴むことはできないか?と思い、今回の実装を企画しました。

2. 実装の全体の流れ

 [実装の流れ]
1. Kaggle上公開されているアマゾン口コミデータを入手
2. 英文データを日本語訳し、データクレンジング
3. 口コミを形態素で分ける(token化)&ベクトル化
4. K-meansでクラスタリング
5. ワードクラウドやトピックモデルで可視化し、傾向を掴む
 それでは、早速実装してみます!

Step1. 口コミデータの入手(1h)

 本来ならECサイト上の自社の口コミを使いたいのですが、ECサイトのスクレイピング禁止や、社内の情報保護という問題もあり、今回はKaggle上に公開されている、アマゾン口コミデータを練習相手にしました。

(元データの出典:https://www.kaggle.com/datasets/snap/amazon-fine-food-reviews)

美味しいクッキーが待っている予感ですね。

#Step1. 元データのアップロード
con = sqlite3.connect('データの経路')
df = pd.read_sql_query(""" SELECT * FROM Reviews""", con)
df

データがあんまりにも大きく、普通の方法では中々データフレームが出て来なかったのですが、こーゆー時、SQLが活躍してくれること、今回学びました(データフレームをアップロードするだけで1時間かかり、、泣)。

データフレームの様子

 な~~るほど、、沢山の製品のデータがありますね、、しかも、口コミは英語であると、、、
 これを解決するため、
 ①口コミが最も多い製品に絞ること
 ②口コミを英語に翻訳することと
が必要になりました。
 まず、口コミの多い製品を探ししぼってみます。

#Step2. 製品を絞るため、一番頻度の多い製品をカウント
product_ID=df['ProductId']
c=collections.Counter(product_ID)
c.most_common(6)
913件の口コミを有するB007が今回のターゲットですね


B007製品はどうやらcookieのようです!

なお、この製品に関する口コミをざっと見た感じCookieが出たため、ちょうど私と密接な食品であることも確認できました。
 これで、ターゲット製品と口コミの大まかなデータが取得できましたので、分析に向けてデータクレンジングをしてみます。

Step2. データクレンジング(24h?)

 データクレンジングの流れは大きく以下の通りです。
① 口コミ列だけを抽出する
② 口コミを和訳する
③ 和訳した口コミでテキストクリーニングを行う

コードは以下の通りです。

#Step1. 口コミだけを抽出
review_en=df1['Text'].to_list()

#Step1. レビューのhtml要素を除去
review_en1=[]
for r in review_en:
  changed_text=BeautifulSoup(r)
  chaged_changed_text=changed_text.get_text()
  review_en1.append(chaged_changed_text)

#Step2. 英語の口コミを日本語に翻訳する関数を定義し、翻訳

def jp_en_trans(target_list):
  translator = Translator()
  review_jp=[]
  for r in target_list:
    jp=translator.translate(r,src='en',dest='ja').text
    review_jp.append(jp)
  return review_jp

review_jp=jp_en_trans(review_en1[:200])
review_jp2=jp_en_trans(review_en1[200:400])
review_jp3=jp_en_trans(review_en1[400:600])
review_jp4=jp_en_trans(review_en1[600:800])
review_jp5=jp_en_trans(review_en1[800:913])
review_jp=review_jp+review_jp2+review_jp3+review_jp4+review_jp5
df2['review_jp']=review_jp

#Step3. テキストクリーニング
data_preprocessed=review_jp
# 正規化
data_preprocessed = [unicodedata.normalize("NFKC", text) for text in data_preprocessed]
# 句読点等の削除
punctuation_pattern = re.compile(r"[^\w\s]")
data_preprocessed = [_text for text in data_preprocessed if (_text := punctuation_pattern.sub("",text)) != ""]

#数字の削除
data_preprocessed = [re.sub(r'[0-9]+', '',text) for text in data_preprocessed]
何とか無事翻訳ができたようです。

さらっと書きましたが、翻訳がうまく行かずかなり苦労したり、データクレンジングがうまく行かず苦戦したら、様々な逆境がありました。ここだけでもまる3日はかかった気がします。

Step3. 単語のToken化

 本当にここまでくることも相当苦労しましたが、やっとクレンジングが終わり、分析の段階に入りました。ここでは、単語をToken化し、ベクトル化しております。こうすることで、人間の言語がやっと機械が理解できる言語になりますね。
 

#Step1. janomeを利用し、文書をtoken化、品詞ごとに分ける
def tokenize2(text):
    tokens = t.tokenize(text)
    token_list = []
    for token in tokens:
        if token not in stopwards:
          # 「名詞」「動詞」「形容詞」「形容動詞」を取り出してください
          part_of_speech = token.part_of_speech.split(",")[0]

          if part_of_speech == "名詞" or part_of_speech == "動詞" or part_of_speech == "形容詞" or part_of_speech == "形容動詞":
            token_list.append(token.surface)

    return token_list

#Step2. 抽出したtokenをベクトル化
t = Tokenizer()
vectorizer = TfidfVectorizer(tokenizer=tokenize2)
train_matrix = vectorizer.fit_transform(data_preprocessed)
vectorized_data = train_matrix.toarray()

Step4. K-meansモデル化

 いよいよ、分析の準備が終わりましたね!
言語をどう処理しどう分類してみるかは、様々な方法があると思いますが、今回は、自分でバイアスを持つ前に、機械に教師なし学習として、クラスター分析を依頼することで、どんな口コミがあるか、ざっくり傾向を掴むことを目標にしました。
 まずは、簡単に3つのクラスターに分類し、分布図で可視化してみました。

#Step1. Kmeansでモデリング
k = 3
kmeans = KMeans(n_clusters=k,init="random",n_init=10,max_iter=300,tol=1e-04,random_state=0)
kmeans.fit(train_matrix)

kmeans_clusters = kmeans.predict(train_matrix)
kmeans_distances = kmeans.transform(train_matrix)

#Step2. 主成分分析で次元圧縮
pca =  PCA(n_components=3)
x_pca = pca.fit_transform(vectorized_data)
x_pca = np.concatenate((x_pca, kmeans_clusters.reshape(-1, 1)), axis=1)
x_pca

#Step1. 3次元の散布図で可視化
layout = go.Layout(width=800, height=800)
fig = go.Figure(layout=layout)
for label,c in zip(np.unique(kmeans_clusters), ["red", "green", "blue"]):
  fig.add_trace(go.Scatter3d(x=x_pca[x_pca[:,3]==label][:,0], y=x_pca[x_pca[:,3]==label][:,1], z=x_pca[x_pca[:,3]==label][:,2], mode="markers", marker={"size":4, "color":c}))

fig.show()
んんんんん

んんん、何となくクラスターは作られたようですが、中身をみてみないとわからないな、と思い、次は、トピックモデリングとワードクラウドを使い、中身を見てみました。

Step5. トピックモデリングとワードクラウドで可視化(24h?)

 さて、具体的にどんな共通点を基にこのようなクラスターができたかを確認するために、今回は各クラスターに対し、トピックモデリングを行うことにしました。
 トピックモデルとは、「文書の中の潜在的なトピックを解析する手法」であり、文章の中の単語の共起性に基づき、トピックを導いてくれるそうです。
 なお、様々なトピックモデルの中、LDA(Latent Dirichlet Allocation)という手法がもっともよく使われており、文書の中で各トピックが存在する割合を表現してくれるため口コミのクラスターが主にどんな内容になっているかを把握するに適していると判断、この手法を選びました。
 なお、各トピックの存在割合を直感的にわかりやすくするため、ワードクラウドにて可視化をします!
 さて、何がおきるか、やってみます!

#Step1. トピックモデリングとwordcloudによる可視化

# boolean maskを使うためにndarray化
data = np.array(data_preprocessed)

# トピックモデル化するための関数の定義
NUM_TOPICS = 1
def get_topics(tokenized_texts):
  # テキストをトークン化して辞書を作成
  dictionary = corpora.Dictionary(tokenized_texts)
  # コーパスを作成
  corpus = [dictionary.doc2bow(tokens) for tokens in tokenized_texts]
  # LDAモデルの構築と学習
  lda_model = gensim.models.LdaModel(corpus, num_topics=NUM_TOPICS, id2word=dictionary, passes=10)
  # トピックの表示
  topics = lda_model.print_topics(num_topics=NUM_TOPICS)
  for topic in topics:
      print(topic)
  return topics

# WordCloudの作成の関数を定義
def print_wordcloud(topics):
  for topic in topics:
      topic_words = topic[1].split('+')
      wordcloud_data = {}
      for word_prob in topic_words:
          prob, word = word_prob.split('*')
          word = word.strip().strip('"')
          prob = float(prob)
          wordcloud_data[word] = prob

      wordcloud = WordCloud(width=800, height=400, background_color='white', font_path=font_path).generate_from_frequencies(wordcloud_data)
      plt.figure(figsize=(10, 5))
      plt.imshow(wordcloud, interpolation='bilinear')
      plt.axis('off')
      plt.show()

# janomeを利用した文書のtoken化関数
t=Tokenizer()
def tokenize1(text):
    tokens = t.tokenize(text)
    noun = []
    for token in tokens:
      if token.part_of_speech.split(',')[0] in ["名詞", "形容詞", "動詞"]:
        noun.append(token.surface)
   return noun

#三つのクラスターを可視化
tokenized_texts = [tokenize1(text) for text in data[kmeans_clusters==0]]
topics = get_topics(tokenized_texts)
print_wordcloud(topics)

tokenized_texts = [tokenize1(text) for text in data[kmeans_clusters==1]]
topics = get_topics(tokenized_texts)
print_wordcloud(topics)

tokenized_texts = [tokenize1(text) for text in data[kmeans_clusters==2]]
topics = get_topics(tokenized_texts)
print_wordcloud(topics)
クラスター0の様子

あれ?私、彼ら、これら、??もう全く意味がわからないクラスターが誕生しました。先生助けて!!と、チューターに相談したところ、こういったストップワードと呼ばれる意味のない形態素を除去できなかったことが原因だそうです。
 ここで、一度ストップワードを削除し、クラスタリングを行ってみます。

#ストップワード除去バージョン
tokenized_texts = [tokenize1(text) for text in data[kmeans_clusters==0]]
tokenized_texts1=[]
for i in range(0,330):
  new_list=[j for j in tokenized_texts[i] if j not in stopwards]
  tokenized_texts1.append(new_list)
topics = get_topics(tokenized_texts1)
print_wordcloud(topics)

tokenized_texts = [tokenize1(text) for text in data[kmeans_clusters==1]]
tokenized_texts2=[]
for i in range(0,195):
  new_list=[j for j in tokenized_texts[i] if j not in stopwards]
  tokenized_texts2.append(new_list)
topics = get_topics(tokenized_texts2)
print_wordcloud(topics)

tokenized_texts = [tokenize1(text) for text in data[kmeans_clusters==2]]
tokenized_texts3=[]
for i in range(0,388):
  new_list=[j for j in tokenized_texts[i] if j not in stopwards]
  tokenized_texts3.append(new_list)
topics = get_topics(tokenized_texts3)
print_wordcloud(topics)
ストップワード削除後、再誕生したクラスター0の様子

んんんんん、、、、柔らかく受け取ったそうですが、何が何か、これだけではかなり分かりづらいクラスターになりました
 やはり、クラスター3つだけでは、900件を超えるレビューをまとめることはかなり難しそうですね。もっと意味のある結果を出すため、クラスターの数を調整してみました

Step6. パラメーターの調整

#Step1-1. Kmeansのクラスターを増やす
k = 5
kmeans = KMeans(n_clusters=k,init="random",n_init=10,max_iter=300,tol=1e-04,random_state=0)
kmeans.fit(train_matrix)

kmeans_clusters = kmeans.predict(train_matrix)
kmeans_distances = kmeans.transform(train_matrix)

#各クラスターのワードクラウド作成およびトピックの抽出
for c in range(0,k):
  tokenized_texts = [tokenize1(text) for text in data[kmeans_clusters==c]]
  text_list=[]
  for i in range(0,len(data[kmeans_clusters==c])):
    new_list=[j for j in tokenized_texts[i] if j not in stopwards]
    text_list.append(new_list)
  topics = get_topics(text_list)
  print_wordcloud(topics)

最初は、クラスターの数を10個にしてみましたが、それでも、中々結果が分かりづらかったため、間の5を試してみました。
 すると!!

5クラスターにしたときのクラスター0の様子

子供がこのクッキーを愛していると、その理由は柔らかいからと、
何となくですが、ちょっとは説明できるような言葉になってきました。本当にそうなのか、クラスター内の口コミの元データを見ると、

クラスター0の元データ

お!!なるほど!!いくつかを読んでみると、
柔らかくておいしいから子供が好き」
といった内容が見えてきました!!これは使えるかも!

他のクラスターも見てみます。

5クラスターにしたときのクラスター1の様子

こちらからは、何となくですが、
外出に最適で、子供が大好きだ」と言っているようです
中身を見てみますと、

外出最適は、利便性のことを言うようです

外出する際に、持ち歩きに便利なので、すこが素晴らしいと感じられるポイントのようですね!!随分分かりやすくなりました!

3. 結論および考察

 以上で、あるクッキー製品に関し、900件を超える口コミを自然言語処理し、クラスター化させ、その口コミの傾向を見てみました。
 すると、最初3つのクラスターの時には中々傾向が見えなかったものが、パラメーターを調整していく中で、だんだん傾向が見えてき、その傾向から仮説をもって元口コミを見ることで素早く傾向が掴めたと思います。

[良かった点]

  1. クラスター分析をすることで、900件超えの口コミを全部読まなくても傾向が掴めるようになりました。これはかなりの時間短縮に繋がりそうです。

  2. クラスターを見て、頭の中で仮説を作ったうえで、元データをみることで、元データを読むときもより効率的に読むことができました。

[今後の課題]

  1. どうしても大まかなクラスターだけでは、いくつかの要素が混在されているため、パラメーターを変えながら、どれが適切かを確認してみないといけないと思いました。教師なし学習は、仮説を立てる前の軽いインサイトを得ることは可能ですが、その仮説を基に、何が何件あるかを分類までは難しいことがわかりました。

  2. 既に分類したいカテゴリーがあれば、次は、教師あり学習に変え、全口コミを分類することもやってみたいと思います。

4.感想&Thanks to..

 さらっと1つの記事でまとめましたが、ここまで来るのにほぼ3週間は経過した印象です。初めての実装だったため、とても大変で、データフレーム一つ追加することですら、相当な時間がかかりましたが、すごく成長を感じます。
 余談ですが、アメリカの食品スタートアップでは、こういった、AIを使い、素早く消費者調査を実行することにより、商品開発期間を短縮、画期的なスピードでPDCAを実行することで、消費者の心にぴったりの製品を開発するそうです。
 小さな一歩ではございますが、今回の実行により、私自身もAI技術を活用素早いPDCAを実行することで、業界に前例のない革新的なビジネスモデルを作るための、第一歩を踏み出したいなと思います。
 ここまで、毎晩21-22時の遅い時間帯に、丁寧にご指導をしてくださった、Aidemyのチューターの丹羽さん、厚く御礼申し上げます。
 どうもありがとうございました!!

この記事が気に入ったらサポートをしてみませんか?