LoginSignup
1
1

皆さんこんにちは。今日も引き続きChatGPT先生をお迎えして、「ChatGPTとPythonで学ぶ 協調フィルタリング」というテーマで雑談したいと思います。それではChatGPT先生、よろしくお願いします。


assistant:

こんにちは、いつもありがとうございます。今日は協調フィルタリングについて話すようですね。非常に興味深いテーマだと思います。
まず、協調フィルタリングとは何かを説明します。協調フィルタリングは、推薦システムの一種で、ユーザー間の評価のパターンを利用して推薦を行います。例えば、映画の評価や購入履歴などから、特定のユーザーがまだ評価していないアイテムに対する評価を予測します。

協調フィルタリングには主に次の二種類があります。

  • ユーザーベースの協調フィルタリング: 同じような評価パターンを持つユーザーを見つけ、そのユーザーが高評価するアイテムを推薦します。
  • アイテムベースの協調フィルタリング: 同じような評価パターンを持つアイテムを見つけ、そのアイテムを高評価するユーザーに対して他の同様のアイテムを推薦します。

Pythonで協調フィルタリングを実装する場合、アイテムやユーザーの評価データ(通常は行列形式で表されます)と、それぞれの評価パターンの類似性を比較するためのメトリクス(例えばコサイン類似性など)を用意します。その後、類似性が最も高いアイテムやユーザーを見つけ、それに基づいて評価の予測を行います。


user:

ありがとうございます。それでは早速、協調フィルタリングについて学ぶためのPythonコードを書いてください。その際に、型ヒントも導入してください。コード中に日本語での説明も可能な限り詳しくお願いします。Pythonのコーディング規約PEP8に準拠してください。


assistant:

了解しました。協調フィルタリングの一例として、アイテムベースの協調フィルタリングをPythonで実装してみましょう。まずは、ダミーデータの作成とアイテム間の類似度の計算から始めます。

なお、この例ではscikit-learnのcosine_similarity関数を使用して類似度を計算します。

from typing import List
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity

# ユーザーとアイテムの評価データを作成します(ダミーデータ)
data = {
    'User': ['User1', 'User2', 'User3', 'User4', 'User5'],
    'Item1': [5, 4, 1, 2, 1],
    'Item2': [4, 5, 1, 2, 1],
    'Item3': [1, 1, 5, 4, 4],
    'Item4': [1, 1, 4, 5, 4],
}

# DataFrameに変換して、行と列を反転させます
ratings = pd.DataFrame(data).set_index('User').T

def compute_similarity(df: pd.DataFrame) -> pd.DataFrame:
    """cosine similarity を計算します"""
    sim_matrix = cosine_similarity(df.values)
    sim_df = pd.DataFrame(sim_matrix, index=df.index, columns=df.index)
    return sim_df

# アイテム間の類似度を計算します
item_similarity = compute_similarity(ratings)
print(item_similarity)

このコードでは、各アイテムの評価パターン(各ユーザーによる評価)に基づいて、アイテム間の類似度を計算し、その結果を表示します。


user:

ありがとうございます。次の結果が得られました。

          Item1     Item2     Item3     Item4
Item1  1.000000  0.978723  0.493740  0.512730
Item2  0.978723  1.000000  0.493740  0.512730
Item3  0.493740  0.493740  1.000000  0.983051
Item4  0.512730  0.512730  0.983051  1.000000

assistant:

これはアイテムごとの評価の傾向を表す類似度のマトリクスです。数字が大きいほど2つのアイテムの評価パターンが似ていることを意味します。
例えば、Item1とItem2の類似度は0.978723であり、これは非常に高い類似度と言えます。つまり、これら2つのアイテムは同じように評価される傾向があると見ることができます。

次に、この類似度のマトリクスを使って、特定のユーザーに対するアイテムの推薦スコアを計算しましょう。

from typing import Dict

def predict_ratings(user_ratings: Dict[str, float], similarity: pd.DataFrame) -> pd.Series:
    """ユーザーのアイテムに対する推薦スコアを計算します"""
    weighted_scores = similarity.mul(user_ratings, axis=0).sum(axis=1)
    normalization = similarity.sum(axis=1)
    predictions = weighted_scores / normalization
    return predictions.sort_values(ascending=False)

# 特定のユーザーの評価データを作成します
user_ratings = {
    'Item1': 5,
    'Item2': 4,
    'Item3': 0,  # まだ評価していないアイテム
    'Item4': 0,  # まだ評価していないアイテム
}

# ユーザーのアイテムに対する推薦スコアを計算します
predictions = predict_ratings(user_ratings, item_similarity)
print(predictions)

この関数では、アイテム間の類似性とユーザーの評価データを元にして、評価の未定なアイテムに対する推薦スコアを計算しています。特に評価の未確定のアイテムに対する推薦スコアは、そのアイテムと類似の評価を持つ他のアイテムをユーザーがどう評価しているかに基づいています。また、推薦スコアは高い方から低い方へとソートされます。

この関数を実行すると、各アイテムに対する予測評価スコアが得られます。


user:

ありがとうございます。次の結果が得られました。

Item1    5.0
Item2    4.0
Item3    0.0
Item4    0.0
dtype: float64

assistant:

ここで得られた結果は、特定のユーザーに対する各アイテムの推薦スコアです。ユーザーがまだ評価していないItem3とItem4に対する予測スコアは0.0となっています。

この結果が示すのは、そのユーザーが他のアイテムへの評価履歴をもとに、Item3とItem4にどの程度の評価をするかの予測です。

ご回答の参考になれば幸いです。他に何か質問はございますか?


user:

「協調フィルタリングの一例として、アイテムベースの協調フィルタリングをPythonで実装してみましょう。」とのことでしたが、協調フィルタリングはアイテムベース以外にもあるということですね?


assistant:

はい、その通りです。協調フィルタリングには主に「ユーザーベースの協調フィルタリング」、「アイテムベースの協調フィルタリング」、そしてこれらを混合した「ハイブリッド方式」の3つの方法があります。

  1. ユーザーベースの協調フィルタリング:
    ここでは、あるユーザー(ターゲットユーザー)と他のユーザーとの間にある類似度に基づいて推薦を行います。すなわち、ターゲットユーザーと評価のパターンが似ているユーザーが高評価しているアイテムを推薦します。

  2. アイテムベースの協調フィルタリング:
    これは先ほど説明した通りであり、あるユーザーが評価したアイテムと他のアイテムとの間の類似度に基づいて推薦を行います。すなわち、あるユーザーが高評価しているアイテムと評価のパターンが似ているアイテムを推薦します。

  3. ハイブリッド方式:
    これは上述のユーザーベースとアイテムベースの協調フィルタリングを組み合わせた方法です。これにより、各方式の長所を活かし、短所を補完することが可能となります。

以上が協調フィルタリングの主要な手法となります。とはいえ、多様な種類のデータと各種のビジネスニーズに対応するために、これらの基本的な手法を発展させたり、他の推薦方法と組み合わせたりするなど、さまざまな改良が試みられています。


user:

ありがとうございます。それでは、ユーザーベースの協調フィルタリングについて学ぶためのPythonコードを書いてください。その際に、型ヒントも導入してください。コード中に日本語での説明も可能な限り詳しくお願いします。Pythonのコーディング規約PEP8に準拠してください。


assistant:

承知しました。それでは、ユーザーベースの協調フィルタリングにおける類似度計算と評価予測の計算を行うPythonコードを以下に示します。まず、ダミーデータを使用してユーザー間の類似度を計算します。

from typing import List

# ユーザーとアイテムの評価データを作成します(ダミーデータ)
data = {
    'User': ['User1', 'User2', 'User3', 'User4', 'User5'],
    'Item1': [5, 4, 1, 2, 1],
    'Item2': [4, 5, 1, 2, 1],
    'Item3': [1, 1, 5, 4, 4],
    'Item4': [1, 1, 4, 5, 4],
}

# DataFrameに変換します
ratings = pd.DataFrame(data).set_index('User')

def compute_similarity(df: pd.DataFrame) -> pd.DataFrame:
    """cosine similarity を計算します"""
    sim_matrix = cosine_similarity(df.values)
    sim_df = pd.DataFrame(sim_matrix, index=df.index, columns=df.index)
    return sim_df

# ユーザー間の類似度を計算します
user_similarity = compute_similarity(ratings)
print(user_similarity)

ここでは、データフレームをトランスポーズせず、ユーザー間の類似度を計算しています。類似度はアイテムの評価パターンに基づいており、ユーザーがどの程度同様にアイテムを評価しているかを表します。

その後、この類似度のマトリクスを使って、特定のアイテムに対するユーザーの推薦スコアを計算します。

def predict_ratings(user_similarity: pd.DataFrame, ratings: pd.DataFrame, user_id: str) -> pd.Series:
    """ユーザーのアイテムに対する推薦スコアを計算します"""
    # targetユーザー以外のユーザーについて、類似度を取得します
    similarity = user_similarity[user_id].drop(user_id)
    
    # 各アイテムについて予測評価値を計算します
    predictions = []
    for item in ratings.columns:
        # アイテムを評価したユーザーの評価値と類似度を掛け合わせて合計します(sum of weights)
        weighted_scores = ratings[item].drop(user_id) * similarity
        # 類似度の合計で割って正規化します
        prediction = weighted_scores.sum() / similarity.sum()
        predictions.append(prediction)
    
    pred_series = pd.Series(predictions, index=ratings.columns)
    return pred_series.sort_values(ascending=False)

# ユーザーのアイテムに対する推薦スコアを計算します
predictions = predict_ratings(user_similarity, ratings, 'User1')
print(predictions)

この関数では、各アイテムの推薦スコアを計算しています。特定のユーザー(この場合はUser1)と評価のパターンが似ているユーザーがどのようにアイテムを評価しているかに基づいて、User1がアイテムをどの程度評価するかを予測します。


user:

ありがとうございます。次の結果が得られました。

User      User1     User2     User3     User4     User5
User                                                   
User1  1.000000  0.976744  0.418605  0.588209  0.444606
User2  0.976744  1.000000  0.418605  0.588209  0.444606
User3  0.418605  0.418605  1.000000  0.958562  0.993825
User4  0.588209  0.588209  0.958562  1.000000  0.979992
User5  0.444606  0.444606  0.993825  0.979992  1.000000

Item4    3.035475
Item3    2.965627
Item2    2.851270
Item1    2.449013
dtype: float64

assistant:

得られた結果は、'User1'に対する各アイテムの推薦スコアです。このスコアは、ユーザーがまだ評価していないアイテムに対する予想評価値として解釈できます。

この結果から、'User1'がまだ評価していないアイテムの中では、'Item4'を最も高く評価する可能性があると予測できます。一方で、'Item1'を評価する可能性は相対的に低い、と予測できます。

以上がPythonを用いてユーザーベースの協調フィルタリングを行う一例です。他に質問があればどうぞ。


user:

ありがとうございます。それでは、ハイブリッド方式の協調フィルタリングについて学ぶためのPythonコードを書いてください。その際に、型ヒントも導入してください。コード中に日本語での説明も可能な限り詳しくお願いします。Pythonのコーディング規約PEP8に準拠してください。


assistant:

ハイブリッド方式の協調フィルタリングでは、ユーザーベースとアイテムベースの協調フィルタリングの結果を組み合わせて推薦を行います。一つの簡単なアプローチは、両者の予測スコアの平均を取ることです。以下にそのPythonコードを示します。

まずはユーザーベースの協調フィルタリングとアイテムベースの協調フィルタリングの結果を得ます。(これらの計算は既に説明した通りです)

# データフレームをアイテムベースに変換
item_ratings = ratings.T

# ユーザー間、アイテム間の類似度を計算します
user_similarity = compute_similarity(ratings)
item_similarity = compute_similarity(item_ratings)

# 特定のユーザー、特定のアイテムに対する推薦スコアを計算します
user_predictions = predict_ratings(user_similarity, ratings, 'User1')
item_predictions = predict_ratings(item_similarity, item_ratings, 'Item1')

次に、ユーザーベースとアイテムベースの推薦スコアを組み合わせてハイブリッド方式の推薦スコアを計算します。

def compute_hybrid(user_pred: pd.Series, item_pred: pd.Series) -> pd.Series:
    """ハイブリッド方式の推薦スコアを計算します"""
    return (user_pred + item_pred) / 2

# ハイブリッド方式の推薦スコアを計算します
hybrid_predictions = compute_hybrid(user_predictions, item_predictions)
print(hybrid_predictions)

この関数では、与えられたユーザーベースの推薦スコアとアイテムベースの推薦スコアの平均を取って、ハイブリッド方式の推薦スコアを計算しています。
このスコアが高いアイテムほど、そのユーザーが嗜好する可能性が高いと推測されます。


user:

ありがとうございます。次の結果が得られました。

Item1   NaN
Item2   NaN
Item3   NaN
Item4   NaN
User1   NaN
User2   NaN
User3   NaN
User4   NaN
User5   NaN
dtype: float64

assistant:

すみません、ご指摘の通り、アイテムベースとユーザーベースの予測を直接的に結合することはできません。なぜなら、ユーザーベースの予測はユーザーに対するアイテム評価を予測するものであり、一方でアイテムベースの予測はアイテムに対するユーザー評価を予測するものであるからです。

しかし、ハイブリッド方式とは異なるアプローチを用いることで、これら2つの方法を統合することが可能です。一つは、ユーザーベースとアイテムベースの評価予測をそれぞれ行い、それらの予測を結合して新たな予測を生み出すことです。具体的には、ユーザーが評価したすべてのアイテムに対してアイテムベースの予測を行い、同様に全てのユーザーに対してユーザーベースの予測を行います。その後、これらの予測を結合し(例えば、平均を取る)、各ユーザーとアイテムのペアに対する最終的な評価予測とします。

これは非常に単純な方法であり、より複雑なハイブリッド方式でも、これら2つの方法を組み合わせて学習されたモデルを用いて評価予測を行うことがあります。これはより高度な推薦システムでは一般的なアプローチで、機械学習技術(例えば、深層学習)を用いて両者の予測を最適に組み合わせることがしばしば行われます。

追加でご質問があればお知らせください。


user:

ここまでの議論を踏まえて、修正した全コードを書いてください。またその際に、型ヒントも導入してください。コード中に日本語での説明も可能な限り詳しくお願いします。Pythonのコーディング規約PEP8に準拠してください。


assistant:

ここでは、まずユーザーベースとアイテムベースの協調フィルタリングそれぞれで推薦スコアを計算します。その後、それらの平均値をとることでハイブリッド方式の推薦スコアを計算します。

以下にそのPythonコードを記載します。

まずは必要なライブラリをインポートします。

from typing import List

import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity

次に、ユーザー間、アイテム間の類似度を計算する関数を定義します。

def compute_similarity(df: pd.DataFrame) -> pd.DataFrame:
    """cosine similarity を計算します"""
    sim_matrix = cosine_similarity(df.values)
    sim_df = pd.DataFrame(sim_matrix, index=df.index, columns=df.index)
    return sim_df

次に、指定したユーザーのアイテムに対する推薦スコアを計算する関数を定義します。

def predict_ratings(similarity: pd.DataFrame, ratings: pd.DataFrame, user_id: str) -> pd.Series:
    """指定したユーザーのアイテムに対する推薦スコアを計算します"""
    # targetユーザー以外のユーザー(またはアイテム)について、類似度を取得します
    similarity_scores = similarity[user_id].drop(user_id)
    
    # 各アイテムについて予測評価値を計算します
    predictions = []
    for item in ratings.columns:
        # アイテムを評価したユーザー(またはアイテム)の評価値と類似度を掛け合わせたものの合計を求めます
        weighted_scores = ratings[item].drop(user_id) * similarity_scores
        # 上記の合計を類似度の合計で割って正規化します
        prediction = weighted_scores.sum() / similarity_scores.sum()
        predictions.append(prediction)
    
    pred_series = pd.Series(predictions, index=ratings.columns)
    return pred_series.sort_values(ascending=False)

最後に、ユーザーベースとアイテムベースの推薦スコアの平均を計算する関数を定義します。

def compute_hybrid(user_pred: pd.Series, item_pred: pd.Series) -> pd.Series:
    """ハイブリッド方式の推薦スコアを計算します"""
    return (user_pred + item_pred) / 2

以上がユーザーベースとアイテムベースの協調フィルタリング、そしてその組み合わせであるハイブリッド方式の推薦スコアを計算するための関数群です。

これらの関数を用いて、以下のように推薦スコアを計算することが可能です。

# データフレームを作成します
data = {
    'User': ['User1', 'User2', 'User3', 'User4', 'User5'],
    'Item1': [5, 4, 1, 2, 1],
    'Item2': [4, 5, 1, 2, 1],
    'Item3': [1, 1, 5, 4, 4],
    'Item4': [1, 1, 4, 5, 4],
}
ratings = pd.DataFrame(data).set_index('User')

# ユーザー間、アイテム間の類似度を計算します
user_similarity = compute_similarity(ratings)
item_similarity = compute_similarity(ratings.T)

# ユーザーベースとアイテムベースの推薦スコアを計算します
user_predictions = predict_ratings(user_similarity, ratings, 'User1')
item_predictions = predict_ratings(item_similarity, ratings.T, 'Item1')

# ハイブリッド方式の推薦スコアを計算します
hybrid_predictions = compute_hybrid(user_predictions, item_predictions)
print(hybrid_predictions)

なお、ここで計算している推薦スコアはあくまで一例であり、実際の推薦システムでは利用者の評価履歴やアイテムの情報などを考慮しながら、さらに複雑な推薦スコアを計算することもあります。また、推薦スコアの組み合わせ方も単純な平均だけでなく、異なる重み付けなど色々な方法があります。


user:

ありがとうございます。次の結果が得られました。

Item1   NaN
Item2   NaN
Item3   NaN
Item4   NaN
User1   NaN
User2   NaN
User3   NaN
User4   NaN
User5   NaN
dtype: float64

assistant:

指定したユーザー(またはアイテム)自身をドロップしているため、指定したユーザー、あるいはアイテムが評価を行っていないアイテム、あるいはユーザーに対する評価の予測がNaNになってしまう問題があります。

そのため、指定したユーザー、あるいはアイテムがすでに評価を行っているアイテム、あるいはユーザーについて予測を行わないように修正します。以下に修正したコードを記述します。

def predict_ratings(similarity: pd.DataFrame, ratings: pd.DataFrame, user_id: str) -> pd.Series:
    """
    指定したユーザーのアイテムに対する推薦スコアを計算します
    ただし、既に評価しているアイテムに対しては評価を行わない
    """
    # targetユーザー以外のユーザー(またはアイテム)について、類似度を取得します
    similarity_scores = similarity[user_id].drop(user_id)
    
    # 各アイテムについて予測評価値を計算します
    predictions = []
    items = []
    for item in ratings.columns:
        # 扱うユーザーが既に評価したアイテムは予測をスキップします
        if not pd.isnull(ratings.loc[user_id, item]):
            continue
        # アイテムを評価したユーザー(またはアイテム)の評価値と類似度を掛け合わせたものの合計を求めます
        weighted_scores = ratings[item].dropna() * similarity_scores
        # 上記の合計を類似度の合計で割って正規化します
        prediction = weighted_scores.sum() / similarity_scores.sum()
        predictions.append(prediction)
        items.append(item)
    
    pred_series = pd.Series(predictions, index=items)
    return pred_series.sort_values(ascending=False)

これで、指定したユーザーorアイテムがまだ評価をしていないアイテムorユーザーに対する予測評価値を算出できるようになります。

上記関数で予測評価値を算出する際、既に扱うユーザーが評価したアイテムに対しては予測をスキップし、評価がまだなされていないアイテムのみを対象に予測評価値を算出しています。


user:

ここまでの議論を踏まえて、修正した全コードを書いてください。またその際に、型ヒントも導入してください。コード中に日本語での説明も可能な限り詳しくお願いします。Pythonのコーディング規約PEP8に準拠してください。


assistant:

ご要望通り、ここまでの議論を踏まえたPythonのコードを以下に示します。

まずは必要なライブラリをインポートします。

from typing import List

import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity

次に、データフレームに対してcosine similarityを計算する関数を定義します。

def compute_similarity(df: pd.DataFrame) -> pd.DataFrame:
    """cosine similarity を計算します"""
    sim_matrix = cosine_similarity(df.values)
    sim_df = pd.DataFrame(sim_matrix, index=df.index, columns=df.index)
    return sim_df

次に、指定したユーザー、またはアイテムに対する推薦スコアを計算する関数を定義します。

def predict_ratings(similarity: pd.DataFrame, ratings: pd.DataFrame, user_id: str) -> pd.Series:
    """
    指定したユーザー、またはアイテムに対する推薦スコアを計算します。
    ただし、すでに評価されているアイテム、またはユーザーに対してはスコアを計算しない
    """
    similarity_scores = similarity[user_id].drop(user_id)
    predictions = []
    items = []
    for item in ratings.columns:
        if not pd.isnull(ratings.loc[user_id, item]):
            continue
        weighted_scores = ratings[item].dropna() * similarity_scores
        prediction = weighted_scores.sum() / similarity_scores.sum()
        predictions.append(prediction)
        items.append(item)
    pred_series = pd.Series(predictions, index=items)
    return pred_series.sort_values(ascending=False)

そして、ユーザーベースとアイテムベースの推薦スコアを組み合わせるための関数を定義します。

def compute_hybrid(user_pred: pd.Series, item_pred: pd.Series) -> pd.Series:
    """ハイブリッド方式の推薦スコアを計算します"""
    return (user_pred + item_pred) / 2

以上で準備が整いました。以下のようにして推薦スコアを計算することが可能です。

# データフレームを作成します
data = {
    'User': ['User1', 'User2', 'User3', 'User4', 'User5'],
    'Item1': [5, 4, 1, 2, 1],
    'Item2': [4, 5, 1, 2, 1],
    'Item3': [1, 1, 5, 4, 4],
    'Item4': [1, 1, 4, 5, 4],
}
ratings = pd.DataFrame(data).set_index('User')

# ユーザー間、アイテム間の類似度を計算します
user_similarity = compute_similarity(ratings)
item_similarity = compute_similarity(ratings.T)

# ユーザーベースとアイテムベースの推薦スコアを計算します
user_predictions = predict_ratings(user_similarity, ratings, 'User1')
item_predictions = predict_ratings(item_similarity, ratings.T, 'Item1')

# ハイブリッド方式の推薦スコアを計算します
hybrid_predictions = compute_hybrid(user_predictions, item_predictions)
print(hybrid_predictions)

これがPythonを用いてハイブリッド方式の協調フィルタリングを行う一例です。


user:

ありがとうございます。次の結果が得られました。

Series([], dtype: object)

assistant:

エラーではなく結果が空白で返ってきてしまったとのことですね。この問題は、指定したユーザー('User1')がすでにすべてのアイテムに評価をした結果、新たな評価候補のアイテムがないことから起こっている可能性があります。

同じく、アイテム('Item1')についてもすべてのユーザーから評価が得られている場合、新たな評価候補のユーザーが存在しないため、空白の結果が返る可能性があります。

協調フィルタリングは既存の評価から新たな評価を予測するため、指定したユーザーやアイテムがすでにすべてのアイテムやユーザーに評価をしている場合、新たな推薦を提供することはできません。未評価のアイテムやユーザーが存在するデータセットでエラーが生じるかどうか、再度ご確認いただけますと幸いです。


user:

自作データをもっとリッチにするか、何か練習用に使えるデータで解析できませんか?


assistant:

見本のデータフレームをより詳細にするために、新しくユーザーとアイテムを追加しましょう。まず、各ユーザーが各アイテムに対して評価をしていない状況を作ります(NaNは評価がまだなされていないことを示します)。その上で、ユーザー 'UserA'とアイテム 'Item5'に対する推薦スコアを計算してみましょう。

# データフレームを作成します
data = {
    'User': ['User1', 'User2', 'User3', 'User4', 'User5', 'UserA'],
    'Item1': [5, 4, 1, 2, 1, None],
    'Item2': [4, 5, 1, 2, 1, 4],
    'Item3': [1, 1, 5, 4, 4, None],
    'Item4': [1, 1, 4, 5, 4, 5],
    'Item5': [None, None, None, None, None, None],
}
ratings = pd.DataFrame(data).set_index('User')

# ユーザー間、アイテム間の類似度を計算します
user_similarity = compute_similarity(ratings)
item_similarity = compute_similarity(ratings.T)

# 'UserA' と 'Item5' の推薦スコアを計算します
user_pred = predict_ratings(user_similarity, ratings, 'UserA')
item_pred = predict_ratings(item_similarity, ratings.T, 'Item5')

# ハイブリッド方式の推薦スコアを計算します
hybrid_predictions_user = compute_hybrid(user_pred, item_pred)
print(hybrid_predictions_user)

このように、評価が未だ行われていないユーザーとアイテムに対して推薦スコアを予測することが可能です。


user:

上記コードをベースに、下記エラーを修正した全コードを書いてください。またその際に、型ヒントも導入してください。コード中に日本語での説明も可能な限り詳しくお願いします。Pythonのコーディング規約PEP8に準拠してください。

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[19], line 13
     10 ratings = pd.DataFrame(data).set_index('User')
     12 # ユーザー間、アイテム間の類似度を計算します
---> 13 user_similarity = compute_similarity(ratings)
     14 item_similarity = compute_similarity(ratings.T)
     16 # 'UserA' と 'Item5' の推薦スコアを計算します

Cell In[15], line 3, in compute_similarity(df)
      1 def compute_similarity(df: pd.DataFrame) -> pd.DataFrame:
      2     """cosine similarity を計算します"""
----> 3     sim_matrix = cosine_similarity(df.values)
      4     sim_df = pd.DataFrame(sim_matrix, index=df.index, columns=df.index)
      5     return sim_df

File /usr/local/lib/python3.10/site-packages/sklearn/utils/_param_validation.py:214, in validate_params.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
    208 try:
    209     with config_context(
    210         skip_parameter_validation=(
    211             prefer_skip_nested_validation or global_skip_validation
    212         )
    213     ):
--> 214         return func(*args, **kwargs)
    215 except InvalidParameterError as e:
    216     # When the function is just a wrapper around an estimator, we allow
    217     # the function to delegate validation to the estimator, but we replace
    218     # the name of the estimator by the name of the function in the error
    219     # message to avoid confusion.
    220     msg = re.sub(
    221         r"parameter of \w+ must be",
    222         f"parameter of {func.__qualname__} must be",
    223         str(e),
    224     )

File /usr/local/lib/python3.10/site-packages/sklearn/metrics/pairwise.py:1578, in cosine_similarity(X, Y, dense_output)
   1543 """Compute cosine similarity between samples in X and Y.
   1544 
   1545 Cosine similarity, or the cosine kernel, computes similarity as the
   (...)
   1574     Returns the cosine similarity between samples in X and Y.
   1575 """
   1576 # to avoid recursive import
-> 1578 X, Y = check_pairwise_arrays(X, Y)
   1580 X_normalized = normalize(X, copy=True)
   1581 if X is Y:

File /usr/local/lib/python3.10/site-packages/sklearn/metrics/pairwise.py:156, in check_pairwise_arrays(X, Y, precomputed, dtype, accept_sparse, force_all_finite, copy)
    153     dtype = dtype_float
    155 if Y is X or Y is None:
--> 156     X = Y = check_array(
    157         X,
    158         accept_sparse=accept_sparse,
    159         dtype=dtype,
    160         copy=copy,
    161         force_all_finite=force_all_finite,
    162         estimator=estimator,
    163     )
    164 else:
    165     X = check_array(
    166         X,
    167         accept_sparse=accept_sparse,
   (...)
    171         estimator=estimator,
    172     )

File /usr/local/lib/python3.10/site-packages/sklearn/utils/validation.py:957, in check_array(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, estimator, input_name)
    951         raise ValueError(
    952             "Found array with dim %d. %s expected <= 2."
    953             % (array.ndim, estimator_name)
    954         )
    956     if force_all_finite:
--> 957         _assert_all_finite(
    958             array,
    959             input_name=input_name,
    960             estimator_name=estimator_name,
    961             allow_nan=force_all_finite == "allow-nan",
    962         )
    964 if ensure_min_samples > 0:
    965     n_samples = _num_samples(array)

File /usr/local/lib/python3.10/site-packages/sklearn/utils/validation.py:122, in _assert_all_finite(X, allow_nan, msg_dtype, estimator_name, input_name)
    119 if first_pass_isfinite:
    120     return
--> 122 _assert_all_finite_element_wise(
    123     X,
    124     xp=xp,
    125     allow_nan=allow_nan,
    126     msg_dtype=msg_dtype,
    127     estimator_name=estimator_name,
    128     input_name=input_name,
    129 )

File /usr/local/lib/python3.10/site-packages/sklearn/utils/validation.py:171, in _assert_all_finite_element_wise(X, xp, allow_nan, msg_dtype, estimator_name, input_name)
    154 if estimator_name and input_name == "X" and has_nan_error:
    155     # Improve the error message on how to handle missing values in
    156     # scikit-learn.
    157     msg_err += (
    158         f"\n{estimator_name} does not accept missing values"
    159         " encoded as NaN natively. For supervised learning, you might want"
   (...)
    169         "#estimators-that-handle-nan-values"
    170     )
--> 171 raise ValueError(msg_err)

ValueError: Input contains NaN.

assistant:

エラー内容を確認すると、cosine similarityを計算する際に入力データにNaNが含まれているためエラーが起こっています。この問題を解決するためには、以下のようにNaNを0または他の値で置き換えるなどの対応が必要です。

また、predict_ratings関数もアイテムの評価スコアがNaNであるときに対応できるような仕様に修正しています。

以下で修正された全コードを示します。

from typing import List

import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity

def compute_similarity(df: pd.DataFrame) -> pd.DataFrame:
    """
    データフレームの各行間のcosine similarityを計算します。
    NaNは0として扱います。
    """
    # NaNを0で置換します
    filled_df = df.fillna(0)
    sim_matrix = cosine_similarity(filled_df.values)
    sim_df = pd.DataFrame(sim_matrix, index=df.index, columns=df.index)
    return sim_df

def predict_ratings(similarity: pd.DataFrame, ratings: pd.DataFrame, user_id: str) -> pd.Series:
    """
    指定したユーザーorアイテムに対する推薦スコアを計算します。
    ※すでに評価されているアイテムorユーザに対してはスコアを計算しない
    """
    similarity_scores = similarity[user_id].drop(user_id)
    predictions = []
    items = []
    for item in ratings.columns:
        if not pd.isnull(ratings.loc[user_id, item]):
            continue
        weighted_scores = ratings[item].fillna(0) * similarity_scores
        prediction = weighted_scores.sum() / similarity_scores.sum()
        predictions.append(prediction)
        items.append(item)
    pred_series = pd.Series(predictions, index=items)
    return pred_series.sort_values(ascending=False)

def compute_hybrid(user_pred: pd.Series, item_pred: pd.Series) -> pd.Series:
    """ハイブリッド方式の推薦スコアを計算します"""
    return (user_pred + item_pred) / 2

# データフレームを作成します
data = {
    'User': ['User1', 'User2', 'User3', 'User4', 'User5', 'UserA'],
    'Item1': [5, 4, 1, 2, 1, None],
    'Item2': [4, 5, 1, 2, 1, 4],
    'Item3': [1, 1, 5, 4, 4, None],
    'Item4': [1, 1, 4, 5, 4, 5],
    'Item5': [None, None, None, None, None, None],
}
ratings = pd.DataFrame(data).set_index('User')

# ユーザー間、アイテム間の類似度を計算します
user_similarity = compute_similarity(ratings)
item_similarity = compute_similarity(ratings.T)

# 'UserA' と 'Item5' の推薦スコアを計算します
user_pred = predict_ratings(user_similarity, ratings, 'UserA')
item_pred = predict_ratings(item_similarity, ratings.T, 'Item5')

# ハイブリッド方式の推薦スコアを計算します
hybrid_predictions_user = compute_hybrid(user_pred, item_pred)
print(hybrid_predictions_user)

上記のコードでは、cosine similarityを計算する前にデータフレームのNaNを0に置き換えています。また、評価スコアの予測を行う際にも、評価スコアがNaNの場合はその評価を0として計算しています。これにより、評価がまだなされていないアイテムの推薦スコアも算出できます。ただし、この方法では未評価のアイテムへの推薦スコアが必ずしも正確でない可能性があるため、注意が必要です。


user:

ありがとうございます。次の結果が得られました。

Item1   NaN
Item3   NaN
Item5   NaN
User1   NaN
User2   NaN
User3   NaN
User4   NaN
User5   NaN
UserA   NaN
dtype: float64
/tmp/ipykernel_52483/3143383930.py:30: RuntimeWarning: invalid value encountered in scalar divide
  prediction = weighted_scores.sum() / similarity_scores.sum()
/tmp/ipykernel_52483/3143383930.py:30: RuntimeWarning: invalid value encountered in scalar divide
  prediction = weighted_scores.sum() / similarity_scores.sum()
/tmp/ipykernel_52483/3143383930.py:30: RuntimeWarning: invalid value encountered in scalar divide
  prediction = weighted_scores.sum() / similarity_scores.sum()
/tmp/ipykernel_52483/3143383930.py:30: RuntimeWarning: invalid value encountered in scalar divide
  prediction = weighted_scores.sum() / similarity_scores.sum()
/tmp/ipykernel_52483/3143383930.py:30: RuntimeWarning: invalid value encountered in scalar divide
  prediction = weighted_scores.sum() / similarity_scores.sum()
/tmp/ipykernel_52483/3143383930.py:30: RuntimeWarning: invalid value encountered in scalar divide
  prediction = weighted_scores.sum() / similarity_scores.sum()

assistant:

エラーメッセージを確認しますと、スカラの除算処理でエラーが発生しているようです。

このエラーメッセージは、通常0で除算を試みたときに発生します。「weighted_scores.sum() / similarity_scores.sum()」において、similarity_scores.sum()が0になるケースが存在するためと考えられます。

これは、対象のユーザーまたはアイテムがすべての他のユーザーまたはアイテムとの類似度が0(つまり全く類似していない)である場合に発生します。

この問題を解消するためには、除算を行う前にsimilarity_scores.sum()が0でないことを確認する必要があります。この確認を行い、もし0であれば提案スコアを計算せずにNaNや0を返すような形にコードを修正しましょう。

修正前:

prediction = weighted_scores.sum() / similarity_scores.sum()

修正後:

if similarity_scores.sum() != 0:
    prediction = weighted_scores.sum() / similarity_scores.sum()
else:
    prediction = np.nan

これにより、類似度の合計が0となるケースを適切にハンドリングできます。

以上を反映した全コードは以下の通りです:


    def predict_ratings(similarity: pd.DataFrame, ratings: pd.DataFrame, user_id: str) -> pd.Series:
        """
        指定したユーザーorアイテムに対する推薦スコアを計算します。
        ※すでに評価されているアイテムorユーザに対してはスコアを計算しない
        """
        similarity_scores = similarity[user_id].drop(user_id)
        predictions = []
        items = []
        for item in ratings.columns:
            if not pd.isnull(ratings.loc[user_id, item]):
                continue
            weighted_scores = ratings[item].fillna(0) * similarity_scores
            if similarity_scores.sum() != 0:
                prediction = weighted_scores.sum() / similarity_scores.sum()
            else:
                prediction = np.nan
            predictions.append(prediction)
            items.append(item)
        pred_series = pd.Series(predictions, index=items)
        return pred_series.sort_values(ascending=False)
    ...

申し訳ありませんが、具体的なエラーメッセージを共有いただけますか?そのエラーメッセージを見ないと、どの部分が問題で、それをどのように修正すればよいのかを判断することが困難となります。そのエラーメッセージをもとに適切なサポートを提供します。


user:

ここまでの議論を踏まえて、修正した全コードを書いてください。またその際に、型ヒントも導入してください。コード中に日本語での説明も可能な限り詳しくお願いします。Pythonのコーディング規約PEP8に準拠してください。


assistant:

上記までの議論を受けて、以下の修正と追記を実施しました。

  1. compute_similarity関数: データフレームの各行間のcosine similarityを計算します。NaNは0として扱います。

  2. predict_ratings関数: 単に除算を行う前に「similarity_scores.sum() != 0」というチェックを行い、これが0だと「prediction」にNaNを設定します。これにより0で除算をするエラーを回避します。

修正したコードを以下に示します。

# 必要なパッケージをインポートします
from typing import List
import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity

def compute_similarity(df: pd.DataFrame) -> pd.DataFrame:
    """
    データフレームの各行間のcosine similarityを計算します。
    NaNは0として扱います。
    """
    filled_df = df.fillna(0)
    sim_matrix = cosine_similarity(filled_df.values)
    sim_df = pd.DataFrame(sim_matrix, index=df.index, columns=df.index)
    return sim_df

def predict_ratings(similarity: pd.DataFrame, ratings: pd.DataFrame, user_id: str) -> pd.Series:
    """
    指定したユーザーorアイテムに対する推薦スコアを計算します。
    """
    similarity_scores = similarity[user_id].drop(user_id)
    predictions = []
    items = []
    for item in ratings.columns:
        if not pd.isnull(ratings.loc[user_id, item]):
            continue
        weighted_scores = ratings[item].fillna(0) * similarity_scores
        # 類似度の合計が0以外の場合にのみ、予測スコアを計算します
        if similarity_scores.sum() != 0:
            prediction = weighted_scores.sum() / similarity_scores.sum()
        else:
            prediction = np.nan
        predictions.append(prediction)
        items.append(item)
    pred_series = pd.Series(predictions, index=items)
    return pred_series.sort_values(ascending=False)

def compute_hybrid(user_pred: pd.Series, item_pred: pd.Series) -> pd.Series:
    """ハイブリッド方式の推薦スコアを計算します"""
    return (user_pred + item_pred) / 2

# データフレームを作成します
data = {
    'User': ['User1', 'User2', 'User3', 'User4', 'User5', 'UserA'],
    'Item1': [5, 4, 1, 2, 1, None],
    'Item2': [4, 5, 1, 2, 1, 4],
    'Item3': [1, 1, 5, 4, 4, None],
    'Item4': [1, 1, 4, 5, 4, 5],
    'Item5': [None, None, None, None, None, None],
}
ratings = pd.DataFrame(data).set_index('User')

# ユーザー間、アイテム間の類似度を計算します
user_similarity = compute_similarity(ratings)
item_similarity = compute_similarity(ratings.T)

# 'UserA' と 'Item5' の推薦スコアを計算します
user_pred = predict_ratings(user_similarity, ratings, 'UserA')
item_pred = predict_ratings(item_similarity, ratings.T, 'Item5')

# ハイブリッド方式の推薦スコアを計算します
hybrid_predictions_user = compute_hybrid(user_pred, item_pred)
print(hybrid_predictions_user)

以上のコードは、ユーザー間およびアイテム間の類似度を計算し、推薦スコアを述べています。推薦スコアは、各ユーザーまたはアイテムがそれぞれのユーザーまたはアイテムとどれだけ類似しているかに基づいています。推薦スコアが高いほど、そのアイテムを好む可能性が高いと判断されます。


user:

ありがとうございます。次の結果が得られました。

Item1   NaN
Item3   NaN
Item5   NaN
User1   NaN
User2   NaN
User3   NaN
User4   NaN
User5   NaN
UserA   NaN
dtype: float64

assistant:

結果がすべてNaNになってしまう問題についてですが、以下の2点が考えられます。

  1. similarity_scores.sum()が0になるということは、そのユーザーやアイテムが他の全てのユーザーやアイテムと類似性がない(とみなされてしまう)ということです。このような場合、他の評価から推測することが難しいため、NaNになってしまいます。

  2. さらに、「Item5」と「UserA」については、元のデータで評価が1つもついておらず、そのためにNaNになっている可能性もあります。

なお、NaNが出てしまう場合の対処法としては、欠損値を最初に初期値(例えば0や平均値など)に置き換える方法や、入力データそのものを変更する方法などが考えられます。


user:

ここまでの議論を踏まえて、修正した全コードを書いてください。またその際に、型ヒントも導入してください。コード中に日本語での説明も可能な限り詳しくお願いします。Pythonのコーディング規約PEP8に準拠してください。


assistant:

ご指摘いただいた内容を踏まえ、修正したコードを以下に示します。各関数には型ヒントを付与し、関数の計算内容については日本語で詳しく説明しました。また、コードはPEP8準拠となるように注意して記述しました。

from typing import List
import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity


def compute_similarity(df: pd.DataFrame) -> pd.DataFrame:
    """
    各行間のコサイン類似度を計算します。
    欠損値NaNは0として計算に考慮します。
    """
    # 欠損値NaNは0で置換
    filled_df = df.fillna(0)
    # コサイン類似度を計算
    sim_matrix = cosine_similarity(filled_df.values)
    # コサイン類似度をデータフレーム形式で返す
    sim_df = pd.DataFrame(sim_matrix, index=df.index, columns=df.index)
    return sim_df


def predict_ratings(
        similarity: pd.DataFrame,
        ratings: pd.DataFrame,
        user_id: str) -> pd.Series:
    """
    指定したユーザーまたはアイテムに対する推薦スコアを計算します。
    推薦スコアは、未評価のアイテムやユーザーに対して計算されます。
    """
    # 指定したユーザーまたはアイテムと他の全てのユーザーまたはアイテムとの類似度を取得
    similarity_scores = similarity[user_id].drop(user_id)
    predictions = []
    items = []
    for item in ratings.columns:
        # すでに評価がある場合はスキップ
        if not pd.isnull(ratings.loc[user_id, item]):
            continue
        # 類似度に基づいてスコアを予測
        weighted_scores = ratings[item].fillna(0) * similarity_scores
        prediction = np.nan
        if similarity_scores.sum() != 0:
            prediction = weighted_scores.sum() / similarity_scores.sum()
        predictions.append(prediction)
        items.append(item)
    # 予測スコアをSeriesオブジェクトとして返す
    pred_series = pd.Series(predictions, index=items)
    return pred_series.sort_values(ascending=False)


def compute_hybrid(user_pred: pd.Series, item_pred: pd.Series) -> pd.Series:
    """
    ハイブリッド方式の推薦スコアを計算します。
    単純平均を取ることで、ユーザーベースとアイテムベースの間でバランスを取ります。
    """
    return (user_pred + item_pred) / 2


# レーティングデータを作成
data = {
    'User': ['User1', 'User2', 'User3', 'User4', 'User5', 'UserA'],
    'Item1': [5, 4, 1, 2, 1, None],
    'Item2': [4, 5, 1, 2, 1, 4],
    'Item3': [1, 1, 5, 4, 4, None],
    'Item4': [1, 1, 4, 5, 4, 5],
    'Item5': [None, None, None, None, None, None],
}
ratings = pd.DataFrame(data).set_index('User')

# ユーザー間、アイテム間の類似度計算
user_similarity = compute_similarity(ratings)
item_similarity = compute_similarity(ratings.T)

# ユーザーAとアイテム5の推薦スコアを計算
user_pred = predict_ratings(user_similarity, ratings, 'UserA')
item_pred = predict_ratings(item_similarity, ratings.T, 'Item5')

# ハイブリッド方式の推薦スコアを計算
hybrid_predictions_user = compute_hybrid(user_pred, item_pred)
print(hybrid_predictions_user)

以上の順番で、まず各ユーザー間やアイテム間の類似度を計算します。その上で、特定のユーザーやアイテムに対する推薦スコアを計算し、最終的にハイブリッド方式で統合します。具体的には、ユーザーベースの推薦とアイテムベースの推薦を単純に平均化します。


user:

ありがとうございます。次の結果が得られました。なんでかNaNが解消できませんね

Item1   NaN
Item3   NaN
Item5   NaN
User1   NaN
User2   NaN
User3   NaN
User4   NaN
User5   NaN
UserA   NaN
dtype: float64

assistant:

NaNが解消できない理由についてですが、類似度が全て0になっている可能性が考えられます。

この場合は、データ自体に一度目を通すなどして原因を突き止めることをおすすめします。なお、レコメンドシステムでは以下のような原因が挙げられます。

  1. 入力データの不足や偏り: データが不足していたり、特定のカテゴリばかりが評価されていたりすると、類似度が計算できない場合があります。特にレコメンドシステムでよくある問題として冷静期問題が挙げられ、新規ユーザーやアイテムに対する推薦が困難になります。
  2. データの前処理: データがスパース(稀)であったり、不適切な前処理が行われた結果として類似度が計算できない場合があります。

具体的な対策としては以下のようなものが挙げられます。

  1. データ収集: データを加えることで、評価のバリエーションを増やし、類似度が計算できるようにする。
  2. 初期値設定: 評価がない場合には全てのアイテムに対して一律に中立的な評価を入れる。

ただし、これらの対策はデータの性質やどういった結果を求めるかによりますので、一概には言えない部分もあります。詳細な原因と対策についてはデータを詳しく分析する必要があります。特にレコメンドシステムはデータの特性に強く依存するため、詳細な対策についてはデータ特性をよく理解した上で行うべきと考えます。


user:

映画のデータとかありませんでしたっけ?


assistant:

おそらく、映画のレビューや評価を集めたデータセット「MovieLens」を指しているのかもしれませんね。MovieLensには、ユーザーデータ、映画データ、そしてユーザーからの映画評価データが収録されています。

例えばPythonのライブラリである「surprise」を使ってMovieLensデータセットを読み込むことができます。

from surprise import Dataset

# MovieLens 100Kデータセットをロードします
data = Dataset.load_builtin('ml-100k')

このようにしてロードしたデータセットを使ってレコメンドシステムの構築や評価を行うことができます。ただし、このデータセットを使うにはまず「surprise」ライブラリをインストールする必要があります。

また、MovieLensデータセットは他にも「ml-latest-small」と名付けられたより小規模なデータセットや、「ml-1m」、「ml-10m」、「ml-20m」といった大規模なデータセットも提供しています。使用するデータセットは実装の目的や必要なデータの種類、量によって選択してください。

なお、MovieLensデータセットや他のデータセットを使用する際には、それぞれのデータセットが提供する使用規約やポリシーをよく読み、適切に使用するように気をつけてください。


user:

うーん、まあいいや。わかりました。おかげさまで、どんな計算なのかはだいたい分かりました。


assistant:

それは良かったです!レコメンドシステムの基本的な仕組みや計算方法について理解いただけて嬉しいです。

レコメンドシステムは少々複雑な概念ですが、実際には頻繁に我々の生活やビジネスに利用されています。具体的な実装方法やデータの取り扱い方など、今後も何かご不明な点や質問がありましたら、いつでもお気軽にお声がけください。

それでは、今後の学習やプロジェクトが成功することを祈っています!


user:

それでは、これまでの議論を踏まえて、協調フィルタリングについて詳しいまとめの文章を書いてください。


assistant:

協調フィルタリング(Collaborative Filtering)は、ユーザーやアイテム間の類似性を基にレコメンデーションを行う手法の一つです。この手法では、他のユーザーのアイテム評価のパターン(ユーザー間)または他のアイテムのユーザー評価のパターン(アイテム間)を元に、評価の未知の部分を予測します。具体的には、ユーザーベースのアプローチとアイテムベースのアプローチが存在します。

  1. ユーザーベースのアプローチ:特定のユーザーと他のユーザーの評価パターンが似ているとき、その似ている他のユーザーが高評価をつけたアイテムを特定のユーザーにも推薦する手法です。

  2. アイテムベースのアプローチ:特定のユーザーが高評価をつけたアイテムと他のアイテムの評価パターンが似ているとき、その似ている他のアイテムを特定のユーザーにも推薦する手法です。

協調フィルタリングでは、よくコサイン類似度が類似度の指標として使用されます。また、ユーザーベースのアプローチとアイテムベースのアプローチを統合したハイブリッド方式も存在します。この方式では単純平均を取ることで、ユーザーベースとアイテムベースの間でバランスを取ります。

ただし、全てのユーザーや全てのアイテムがお互いに類似性を持つわけではなく、欠損値(NaN)が生じる可能性があることには注意が必要です。こういった問題は、未評価のアイテムに対して初期値を設定したり、調整したりすることで対応可能です。

以上が、協調フィルタリングの基本的な概要と仕組みです。すべてのレコメンドシステムが協調フィルタリングに基づいているわけではありませんが、その基本的な概念や考え方を理解しておくと、新しいレコメンドシステムの概念を理解する助けになるでしょう。


user:

最後に、協調フィルタリングをネタにした面白ギャグを創作してください。


assistant:

ユーザーベースの協調フィルタリングとアイテムベースの協調フィルタリングが喫茶店で会った…

ユーザーベースがアイテムベースに、「おい、今日のコーヒー、前に好きな奴に勧められて飲んだ奴に似てるな。」と言った。

アイテムベースは、「そうじゃなくて、それは前に君が好きだと言ったコーヒーと同じ豆を使って作られてるんだよ。」と答えた。

そこで、二人が同時に「やっぱり協調フィルタリングが一番だね!」と笑いながら乾杯した!


1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1