ระบบแนะนำ

ข้อมูลเบื้องต้นเกี่ยวกับระบบผู้แนะนำแบบฝัง

เรียนรู้วิธีสร้างตัวแนะนำการแยกตัวประกอบเมทริกซ์อย่างง่ายใน TensorFlow

มีทุกที่: บางครั้งก็ยอดเยี่ยม บางครั้งก็แย่ และบางครั้งก็มีคำแนะนำตลกๆ บนเว็บไซต์หลักๆ เช่น Amazon, Netflix หรือ Spotify เพื่อบอกคุณว่าจะซื้อ ดู หรือฟังอะไรต่อไป แม้ว่าระบบผู้แนะนำจะสะดวกสำหรับผู้ใช้อย่างเรา แต่เราได้รับแรงบันดาลใจในการลองสิ่งใหม่ๆ บริษัทต่างๆ ได้รับประโยชน์เป็นพิเศษจากสิ่งเหล่านั้น

เพื่อให้เข้าใจถึงขอบเขต เรามาดูตัวเลขบางส่วนจากรายงานเรื่อง Measuring the Business Value of Recommender Systems โดย Dietmar Jannach และ Michael Jugovac [1] จากรายงานของพวกเขา:

  • Netflix: “75 % ของสิ่งที่ผู้คนดูมาจากคำแนะนำบางประเภท” (อันนี้มาจากสื่อด้วยซ้ำ!)
  • YouTube: “การคลิกบนหน้าจอหลัก 60% เป็นไปตามคำแนะนำ”
  • Amazon: “ประมาณ 35 % ของยอดขายของพวกเขามาจากการขายต่อเนื่อง (เช่น คำแนะนำ)” โดยที่ ของพวกเขา หมายถึง Amazon

ในเอกสารนี้ [1] คุณจะพบข้อความที่น่าสนใจเพิ่มเติมเกี่ยวกับ CTR ที่เพิ่มขึ้น การมีส่วนร่วม และยอดขายที่คุณจะได้รับจากการจ้างระบบผู้แนะนำ

ดูเหมือนว่าผู้แนะนำจะเป็นสิ่งที่ยิ่งใหญ่ที่สุดนับตั้งแต่ขนมปังหั่นเป็นชิ้น และฉันก็เห็นด้วยว่าผู้แนะนำเป็นหนึ่งในสิ่งที่ดีที่สุดและน่าสนใจที่สุดที่เกิดขึ้นจากสาขาการเรียนรู้ของเครื่อง นั่นเป็นเหตุผลที่ฉันอยากจะแสดงให้คุณเห็นในบทความนี้

  • วิธีการออกแบบผู้แนะนำการทำงานร่วมกันที่ง่ายดาย (การแยกตัวประกอบเมทริกซ์)
  • วิธีการใช้งานใน TensorFlow
  • ข้อดีและข้อเสียคืออะไร

คุณสามารถค้นหารหัสได้ที่ my Github

ก่อนที่เราจะเริ่ม ให้เรารวบรวมข้อมูลที่เราสามารถเล่นได้ก่อน

การรับข้อมูล

หากคุณยังไม่มี ให้รับ tensorflow_datasets ผ่าน pip install tensorflow-datasets คุณสามารถดาวน์โหลดชุดข้อมูลใดๆ ที่พวกเขาเสนอได้ แต่เราจะยึดถือชุดข้อมูลคลาสสิกที่แท้จริง: movielens! เราใช้ข้อมูลเลนส์ภาพยนตร์ในเวอร์ชันที่เล็กที่สุดซึ่งประกอบด้วย 1,000,000 แถว ดังนั้นการฝึกอบรมจึงเร็วขึ้นในภายหลัง

import tensorflow_datasets as tfds


data = tfds.load("movielens/1m-ratings")

data เป็นพจนานุกรมที่มีชุดข้อมูล TensorFlow ซึ่งดีมาก แต่เพื่อให้ง่ายขึ้น มาส่งมันลงใน dataframe ของ panda เพื่อให้ทุกคนเข้าใจตรงกัน

หมายเหตุ: โดยปกติ คุณจะเก็บมันเป็นชุดข้อมูล TensorFlow โดยเฉพาะอย่างยิ่งหากข้อมูลมีขนาดใหญ่ขึ้นเนื่องจากแพนด้ากิน RAM ของคุณอย่างมาก อย่าพยายามแปลงเป็น dataframe ของ pandas สำหรับชุดข้อมูล movielens เวอร์ชัน 25,000,000!

df = tfds.as_dataframe(data["train"])
print(df.head(5))

⚠️ คำเตือน: อย่าพิมพ์ทั้งดาต้าเฟรม เนื่องจากนี่คือดาต้าเฟรมที่มีสไตล์ซึ่งได้รับการกำหนดค่าให้แสดงทั้งหมด 1,000,000 แถวตามค่าเริ่มต้น!

เราจะเห็นข้อมูลมากมาย แต่ละแถวประกอบด้วย

  • ผู้ใช้ (user_id)
  • ภาพยนตร์ (movie_id)
  • การให้คะแนนที่ผู้ใช้ให้กับภาพยนตร์ (user_rating) ซึ่งแสดงเป็นจำนวนเต็มระหว่าง 1 ถึง 5 (ดาว) และ
  • คุณสมบัติอื่นๆ อีกมากมายเกี่ยวกับผู้ใช้และภาพยนตร์

ในบทช่วยสอนนี้ ให้เราใช้เฉพาะค่าขั้นต่ำเปล่า: user_id, movie_id, และ user_rating เนื่องจากบ่อยครั้งมากที่ข้อมูลนี้เป็นเพียงข้อมูลเดียว เรามี. การมีคุณลักษณะเพิ่มเติมเกี่ยวกับผู้ใช้และภาพยนตร์มักจะเป็นเรื่องฟุ่มเฟือย ดังนั้นให้เราจัดการกับกรณีที่ยากกว่าแต่สามารถนำไปใช้ได้อย่างกว้างขวางโดยตรง
ผู้แนะนำที่ได้รับการฝึกอบรมเกี่ยวกับข้อมูลการโต้ตอบประเภทนี้เรียกว่า การทำงานร่วมกัน —ก โมเดลได้รับการฝึกอบรมเกี่ยวกับการโต้ตอบของผู้ใช้จำนวนมากเพื่อให้คำแนะนำสำหรับผู้ใช้คนเดียว
หนึ่งเดียวเพื่อทุกคน ทั้งหมดเพื่อหนึ่งเดียว!

นอกจากนี้เรายังจะเก็บการประทับเวลาไว้เพื่อดำเนินการแยกการทดสอบรถไฟชั่วคราว เนื่องจากสิ่งนี้คล้ายกับวิธีที่เราฝึกในชีวิตจริง นั่นคือ เราฝึกตอนนี้ แต่เราต้องการให้โมเดลทำงานได้ดีในวันพรุ่งนี้ ดังนั้นเราควรประเมินคุณภาพโมเดลแบบนี้ด้วย

filtered_data = (
    df
    .filter(["timestamp", "user_id", "movie_id", "user_rating"])
    .sort_values("timestamp")
    .astype({"user_id": int, "movie_id": int, "user_rating": int}) # nicer types
    .drop(columns=["timestamp"]) # don't need the timestamp anymore
)

train = filtered_data.iloc[:900000] # chronologically first 90% of the dataset
test = filtered_data.iloc[900000:]  # chronologically last 10% of the dataset

filtered_dataประกอบด้วย

ปัญหาสตาร์ทเย็น

หากเราแบ่งข้อมูลด้วยวิธีใดก็ตาม เราอาจพบสิ่งที่เรียกว่าปัญหาการสตาร์ทไม่ติด ซึ่งหมายความว่าผู้ใช้หรือภาพยนตร์บางรายการมีอยู่ในชุดทดสอบเท่านั้น แต่ไม่มีอยู่ในชุดการฝึก ในกรณีของเรา น่าตลกดีที่ผู้ใช้ 1 เป็นตัวอย่าง

print(train.query("user_id == 1").shape[0])
print(test.query("user_id == 1").shape[0])

# Output:
# 0
# 53

มันเหมือนกับหมวดหมู่ของคุณลักษณะเฉพาะที่ปรากฏเฉพาะในชุดทดสอบเท่านั้น มันทำให้การเรียนรู้ยากขึ้น แต่ถึงกระนั้น โมเดลก็ยังต้องจัดการกับมันอยู่ดี ผู้แนะนำที่เราจะสร้างในเร็วๆ นี้ค่อนข้างมีแนวโน้มที่จะเกิดปัญหา Cold Start แต่มีผู้แนะนำประเภทอื่นๆ ที่สามารถจัดการกับผู้ใช้หรือภาพยนตร์ใหม่ได้ในวิธีที่ดีกว่า นี่เป็นบางอย่างสำหรับบทความอื่น

มาสร้างรถไฟและทดสอบดาต้าเฟรมแล้วเดินหน้าต่อไป

X_train = train.drop(columns=["user_rating"])
y_train = train["user_rating"]

X_test = test.drop(columns=["user_rating"])
y_test = test["user_rating"]

หลักสูตรความผิดพลาดของการฝัง

ตอนนี้เรารู้แล้วว่าข้อมูลมีลักษณะอย่างไร ให้เรากำหนด ลายเซ็น ของโมเดล ซึ่งหมายถึงสิ่งที่เข้าไปและสิ่งที่ออกมา ในกรณีของเรา มันค่อนข้างง่าย: อินพุตควรเป็น user_id และ movie_id และเอาต์พุตควรเป็น user_rating เช่น อย่างไร ผู้ใช้ให้คะแนนภาพยนตร์

แต่โมเดลดังกล่าวจะมีลักษณะเป็นอย่างไร? นี่เป็นเรื่องยาก โดยเฉพาะอย่างยิ่งสำหรับผู้เริ่มต้นด้านวิทยาศาสตร์ข้อมูล ผู้ใช้และภาพยนตร์เป็นหมวดหมู่ แม้ว่าเราจะเข้ารหัสเป็นจำนวนเต็มก็ตาม ดังนั้นการปฏิบัติต่อพวกเขาเหมือนเป็นตัวเลขและเพียงฝึกแบบจำลองกับพวกเขาจึงไม่มีวัตถุประสงค์

มีบางอย่างที่น่ากลัว!

สำหรับผู้อ่านที่อยากรู้อยากเห็นฉันจะทำมันต่อไป ต่อไปนี้เป็นตัวอย่างว่า ไม่ ทำอย่างไร:

# BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD

from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.metrics import mean_absolute_error

hgb = HistGradientBoostingRegressor(random_state=0)
hgb.fit(X_train, y_train)
print(hgb.score(X_test, y_test), mean_absolute_error(y_test, hgb.predict(X_test)))

# Output:
# 0.07018701410615702 0.8508620798953698

# BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD BAD

r² มีค่าประมาณ 0.07 ซึ่งดีพอๆ กับตัวถดถอยที่ส่งออกเฉพาะค่าเฉลี่ยของเรตติ้งเท่านั้น โดยไม่ขึ้นอยู่กับอินพุตของผู้ใช้และภาพยนตร์ ค่าเฉลี่ยความคลาดเคลื่อนสัมบูรณ์คือประมาณ 0.85 ซึ่งหมายความว่าเราพลาดการให้คะแนนที่แท้จริงโดยเฉลี่ยประมาณ 0.85 ดาว

แทนที่จะทำเช่นนี้ ฉันจะแสดงวิธีใช้การฝังเพื่อสร้างโมเดลที่มีความหมายและดีขึ้น

การเข้ารหัสแบบร้อนแรงเป็นกรณีพิเศษของการฝัง

วิธีหนึ่งในการเข้ารหัสตัวแปรหมวดหมู่ เช่น ผู้ใช้หรือภาพยนตร์ของเราคือการใช้เวกเตอร์ เช่น ตัวเลขสองชุด — เรียกว่า การฝัง ในบริบทนี้ นี่เป็นเทคนิคที่มีประโยชน์ที่ควรคำนึงถึง ไม่เพียงแต่สำหรับระบบผู้แนะนำเท่านั้น แต่ยังเมื่อใดก็ตามที่คุณจัดการกับข้อมูลที่เป็นหมวดหมู่

ตัวอย่างง่ายๆ ในการเปลี่ยนหมวดหมู่ให้เป็นตัวเลขคือ การเข้ารหัสแบบร้อนแรง/แบบหลอกๆ อย่างไรก็ตามผลลัพธ์ของการฝังนั้นมีมิติสูงสำหรับคุณลักษณะตามหมวดหมู่ที่มีคาร์ดินัลลิตีสูง ซึ่งนำเราไปสู่กับดัก "คำสาปแห่งมิติ" เมื่อพยายามทำงานกับ พวกเขา.

ข้อเสียเปรียบอีกประการหนึ่งคือเวกเตอร์สองตัวแต่ละคู่มีระยะห่างจากกันเท่ากัน ตามตัวอย่าง หากคุณใช้ฟีเจอร์ที่มีสามหมวดหมู่ที่เข้ารหัสเป็น [1, 0, 0], [0, 1, 0] และ [0, 0, 1] แต่ละหมวดหมู่จะมีระยะห่างระหว่างกันเท่ากัน หมวดหมู่ในหน่วยเมตริกทั่วไป เช่น แบบยุคลิดและ "ระยะทางมิโคว์สกี้" อื่นๆ หรือความคล้ายคลึงโคไซน์ สิ่งนี้อาจใช้ได้ดีสำหรับคุณสมบัติเล็กน้อย แต่สำหรับคุณสมบัติ ลำดับ เช่น สภาพอากาศ ร้อน ไม่รุนแรง หรือ เย็น คงจะดีกว่านี้ถ้า ร้อน อยู่ใกล้ เบา มากกว่า เย็น

เห็นได้ชัดว่านี่เป็นสิ่งที่ไม่ดี ดังนั้นเราจึงต้องคิดถึงสิ่งที่แตกต่างออกไป

เรื่องจริง

การฝังทำให้เราสามารถสร้างเวกเตอร์ที่สั้นลงและมีความหมายมากกว่าเวกเตอร์ที่เข้ารหัสแบบร้อนแรงเพียงตัวเดียว

พร้อมใช้งานภายในเฟรมเวิร์กการเรียนรู้เชิงลึก เช่น TensorFlow และ PyTorch ในระดับที่สูงมาก พวกมันทำงานดังนี้:

  1. คุณระบุมิติข้อมูลการฝัง เช่น เวกเตอร์ควรมีความยาวเท่าใด นี่คือไฮเปอร์พารามิเตอร์ที่คุณปรับแต่งได้ และอื่นๆ อีกมากมาย
  2. การฝังสำหรับแต่ละหมวดหมู่จะได้รับการเริ่มต้นแบบสุ่ม เช่นเดียวกับน้ำหนักอื่นๆ ในโครงข่ายประสาทเทียมของคุณ
  3. การฝึกอบรมผลักดันการฝังให้มีประโยชน์ต่อโมเดลมากขึ้น

จริงๆ แล้วนี่ไม่ใช่การดำเนินการใหม่ในเชิงแนวคิด เนื่องจากคุณสามารถ จำลอง ได้โดย การเข้ารหัสแบบร้อนแรงครั้งแรก หมวดหมู่ แล้ว จากนั้นใช้เลเยอร์เชิงเส้น (หนาแน่น) ไม่มีฟังก์ชันการเปิดใช้งานและความลำเอียง เลเยอร์ที่ฝังมีประสิทธิภาพมากกว่าเนื่องจากเป็นเพียงการค้นหาแทนการคำนวณผลิตภัณฑ์เมทริกซ์เหมือนกับที่ทำในเลเยอร์เชิงเส้น

การสร้างแบบจำลอง

ตอนนี้เรามีส่วนผสมครบแล้ว เรามาสร้างแบบจำลองกันดีกว่า! ขั้นแรก เราจะกำหนดสถาปัตยกรรมระดับสูงของโมเดล จากนั้นเราจะสร้างมันขึ้นมาใน TensorFlow แม้ว่ามันจะง่ายเช่นเดียวกันใน PyTorch หากคุณต้องการสิ่งนี้

สถาปัตยกรรม

เอาล่ะ ตัวแปรหมวดหมู่สองตัว (user_id และ movie_id) เข้ามาในโมเดล จากนั้นเราก็ฝังพวกมัน เราจะได้เวกเตอร์สองตัว ซึ่งควรจะมีความยาวเท่ากัน ท้ายที่สุดแล้ว เราอยากจะได้ตัวเลขเพียงตัวเดียว ซึ่งก็คือ user_rating

หมายเหตุ: เราจะจำลองมันเป็นปัญหาการถดถอย แต่คุณยังสามารถมองว่ามันเป็นงานการจัดหมวดหมู่ได้

แล้วเราจะสร้างตัวเลขตัวเดียวจากเวกเตอร์สองตัวที่มีความยาวเท่ากันได้อย่างไร? มีหลายวิธี แต่วิธีหนึ่งที่ง่ายและมีประสิทธิภาพที่สุดคือการใช้ ผลิตภัณฑ์ดอท

หมายเหตุ: แนวทางที่เราจะดำเนินการต่อไปนี้เรียกอีกอย่างว่า การแยกตัวประกอบเมทริกซ์ เนื่องจากเราคำนวณผลคูณดอททั่วสถานที่ เหมือนกับว่าคุณคูณสอง เมทริกซ์

ไม่มีอะไรบ้าเกินไปฉันจะเถียง ตอนนี้เรามาดูกันว่าโมเดลควรทำงานอย่างไร:

ตามสูตรเราได้สร้างสิ่งนี้:

ซึ่งอ่านว่า “การจัดอันดับภาพยนตร์ m จากผู้ใช้ u เท่ากับการฝังของผู้ใช้ u dot product การฝังภาพยนตร์ m .

การใช้งานใน TensorFlow เวอร์ชันหนึ่ง

จริงๆ แล้วการนำไปปฏิบัติก็เป็นเรื่องง่ายหากคุณรู้จัก TensorFlow พื้นฐาน สิ่งเดียวที่ต้องใส่ใจคือเลเยอร์ที่ฝังต้องการให้หมวดหมู่แสดงเป็นจำนวนเต็มตั้งแต่ 1 ถึง number_of_categories บ่อยครั้งที่คุณพบว่ามีคนเติมพจนานุกรมบางคำเช่น {“user_8323”: 1, “user_1122”: 2, …} และพจนานุกรมผกผันเช่น {1: “user_8323”, 2: “user_1122”, …} เพื่อให้บรรลุเป้าหมายนี้ แต่ TensorFlow มีชั้นดี ๆ ให้ดูแลเช่นกัน เราจะใช้ IntegerLookup ที่นี่ คุณลักษณะที่ดีของเลเยอร์นี้: หมวดหมู่ที่ไม่รู้จักจะถูกแมปเป็น 0 ตามค่าเริ่มต้น

ก่อนที่เราจะเริ่ม เราต้องรวบรวมผู้ใช้และภาพยนตร์ที่ไม่ซ้ำทั้งหมด จากชุดการฝึก ก่อน

all_users = train["user_id"].unique()
all_movies = train["movie_id"].unique()

เมื่อใช้ API การทำงานของ Keras คุณสามารถนำแนวคิดข้างต้นไปใช้ดังนี้:

import tensorflow as tf

# user pipeline
user_input = tf.keras.layers.Input(shape=(1,), name="user")
user_as_integer = tf.keras.layers.IntegerLookup(vocabulary=all_users)(user_input)
user_embedding = tf.keras.layers.Embedding(input_dim=len(all_users)+1, output_dim=32)(user_as_integer)

# movie pipeline
movie_input = tf.keras.layers.Input(shape=(1,), name="movie")
movie_as_integer = tf.keras.layers.IntegerLookup(vocabulary=all_movies)(movie_input)
movie_embedding = tf.keras.layers.Embedding(input_dim=len(all_movies)+1, output_dim=32)(movie_as_integer)

# dot product
dot = tf.keras.layers.Dot(axes=2)([user_embedding, movie_embedding])
flatten = tf.keras.layers.Flatten()(dot)

# model input/output definition
model = tf.keras.Model(inputs=[user_input, movie_input], outputs=flatten)

model.compile(loss="mse", metrics=[tf.keras.metrics.MeanAbsoluteError()])

เนื่องจากเราตั้งชื่อที่ดีให้กับผู้ใช้และเลเยอร์อินพุตภาพยนตร์ เราจึงสามารถฝึกโมเดลได้ดังนี้:

model.fit(
    x={
        "user": X_train["user_id"],
        "movie": X_train["movie_id"]
    },
    y=y_train.values,
    batch_size=256,
    epochs=100,
    validation_split=0.1, # for early stopping
    callbacks=[
        tf.keras.callbacks.EarlyStopping(patience=1, restore_best_weights=True)
    ],
)

# Output (for me):
# ...
# Epoch 18/100
# 3165/3165 [==============================] - 8s 3ms/step - loss: 0.7357 - mean_absolute_error: 0.6595 - val_loss: 11.4699 - val_mean_absolute_error: 2.9923

เราสามารถประเมินแบบจำลองนี้ในชุดทดสอบได้ในตอนนี้ แต่เราเห็นแล้วว่ามันอาจจะค่อนข้างแย่เพราะ val_mean_absolute_error มีค่าประมาณ 3 นั่นหมายความว่าเราจะได้รับดาวเฉลี่ย 3 ดาว ซึ่งถือว่าแย่มากในระบบ 5 ดาว นี่แย่ยิ่งกว่าโมเดลที่ไม่ดีของเราเมื่อก่อนซึ่งถือเป็นความสำเร็จทีเดียว 😅 แต่ทำไมล่ะ? มาสำรวจเรื่องนี้กันในส่วนถัดไป

การใช้งานใน TensorFlow เวอร์ชัน 2

เราได้สร้างแบบจำลองการถดถอยที่สามารถส่งออกจำนวนจริงใดๆ ออกมาได้ เป็นเรื่องยากมากสำหรับแบบจำลองที่จะเรียนรู้ว่าควรส่งออกตัวเลขในช่วงพิเศษระหว่าง 1 ถึง 5 แต่เราสามารถทำให้แบบจำลองง่ายขึ้นด้วยเคล็ดลับง่ายๆ: เพียงสควอชช่วงเอาต์พุตในขณะที่เราทำเพื่อการถดถอยโลจิสติก แทนที่จะเป็นช่วง [0, 1] ให้ปรับขนาดและเลื่อนไปที่ [1, 5]

user_input = tf.keras.layers.Input(shape=(1,), name="user")
user_as_integer = tf.keras.layers.IntegerLookup(vocabulary=all_users)(user_input)
user_embedding = tf.keras.layers.Embedding(input_dim=len(all_users) + 1, output_dim=32)(user_as_integer)

movie_input = tf.keras.layers.Input(shape=(1,), name="movie")
movie_as_integer = tf.keras.layers.IntegerLookup(vocabulary=all_movies)(movie_input)
movie_embedding = tf.keras.layers.Embedding(input_dim=len(all_movies) + 1, output_dim=32)(movie_as_integer)

dot = tf.keras.layers.Dot(axes=2)([user_embedding, movie_embedding])
flatten = tf.keras.layers.Flatten()(dot)

# this is new!
squash = tf.keras.layers.Lambda(lambda x: 4*tf.nn.sigmoid(x) + 1)(flatten) 

model = tf.keras.Model(inputs=[user_input, movie_input], outputs=squash)

model.compile(loss="mse", metrics=[tf.keras.metrics.MeanAbsoluteError()])

เป็นสูตร:

โดยที่ σ คือฟังก์ชันซิกมอยด์ การฝึกอบรมเป็นไปตามข้างต้นและการประเมินในชุดทดสอบทำให้เราได้รับ

model.evaluate(
    x={"user": X_test["user_id"], "movie": X_test["movie_id"]},
    y=y_test
)

# Output:
# [...] loss: 0.9701 - mean_absolute_error: 0.7683

นี่ดีกว่ารุ่นก่อนมากและยังเป็นพื้นฐานที่ไม่ดีอีกด้วย ในกรณีที่คุณต้องการคะแนน r² เช่นกัน:

from sklearn.metrics import r2_score

r2_score(
    y_test,
    model.predict(
        {"user": X_test["user_id"], "movie": X_test["movie_id"]}
    ).ravel()
)
# Output:
# 0.1767611765807019

เรามาปรับแต่งขั้นสุดท้ายเล็กน้อยเพื่อให้ได้โมเดลที่ดียิ่งขึ้น

การใช้งานใน TensorFlow เวอร์ชันสุดท้าย

นอกจากการฝังแล้ว เรายังสามารถเชื่อมโยง คำอคติ กับภาพยนตร์และผู้ใช้แต่ละเรื่องได้ ข้อมูลนี้สรุปได้ว่าผู้ใช้บางรายมักจะให้คะแนนเชิงบวก (หรือลบ) ค่อนข้างมาก และภาพยนตร์บางเรื่องมักจะได้รับบทวิจารณ์เชิงบวก (หรือลบ) เท่านั้น ด้วยวิธีนี้ อคติ จึงสามารถการทำงานคร่าวๆ ในขณะที่การฝัง ทำหน้าที่ การปรับแต่งอย่างละเอียด ตัวอย่างเช่น ผู้ใช้ที่ให้ 4 ดาวเป็นส่วนใหญ่สำหรับทุกสิ่งจะมีอคติคงที่ ตัวเลขตัวเดียว และไม่มีเวกเตอร์ การฝังจะต้องเน้นไปที่การอธิบายว่าทำไมบางครั้งผู้ใช้รายนี้จึงให้ 3 หรือ 5 ดาว

สูตรจึงกลายเป็น

โดยที่ bᵤและbₘเป็นอคติของผู้ใช้ uและภาพยนตร์ m ตามลำดับ เป็นรหัส:

user_input = tf.keras.layers.Input(shape=(1,), name="user")
user_as_integer = tf.keras.layers.IntegerLookup(vocabulary=all_users)(user_input)
user_embedding = tf.keras.layers.Embedding(input_dim=len(all_users) + 1, output_dim=32)(user_as_integer)
user_bias = tf.keras.layers.Embedding(input_dim=len(all_users) + 1, output_dim=1)(user_as_integer)

movie_input = tf.keras.layers.Input(shape=(1,), name="movie")
movie_as_integer = tf.keras.layers.IntegerLookup(vocabulary=all_movies)(movie_input)
movie_embedding = tf.keras.layers.Embedding(input_dim=len(all_movies) + 1, output_dim=32)(movie_as_integer)
movie_bias = tf.keras.layers.Embedding(input_dim=len(all_movies) + 1, output_dim=1)(movie_as_integer)

dot = tf.keras.layers.Dot(axes=2)([user_embedding, movie_embedding])
add = tf.keras.layers.Add()([dot, user_bias, movie_bias])
flatten = tf.keras.layers.Flatten()(add)
squash = tf.keras.layers.Lambda(lambda x: 4 * tf.nn.sigmoid(x) + 1)(flatten)

model = tf.keras.Model(inputs=[user_input, movie_input], outputs=squash)


model.compile(loss="mse", metrics=[tf.keras.metrics.MeanAbsoluteError()])

หากคุณชอบ plot_model เอาต์พุตของ Keras:

ตามที่ระบุไว้แล้ว ประสิทธิภาพของโมเดลจะดีขึ้นอีกครั้ง

  • เอ็มเอสอี γ 0.89
  • แม่ อยู่ที่ 0.746
  • r² ≈ 0.245

ดี! เราได้ MAE และ MSE ต่ำสุด (และด้วยเหตุนี้จึงเป็น r² สูงสุด) ในเวอร์ชันนี้

การคาดการณ์

เพื่อตอบคำถามเช่น “ผู้ใช้ 1 จะให้คะแนนภาพยนตร์ 2 และภาพยนตร์ 3 อย่างไร” คุณสามารถตอบคำถามง่ายๆ ได้

model.predict({"user": tf.constant([[1], [1]]), "movie": tf.constant([[2], [3]])})

# Output:
# array([[3.0344076],
#        [2.9140234]], dtype=float32)

เพื่อให้ได้คะแนนทั้งหมดจากผู้ใช้ 1 คุณสามารถทำได้

model.predict({
    "user": tf.ones_like(all_movies.reshape(-1, 1)), # fill user 1 in many times
    "movie": all_movies.reshape(-1, 1)
})

บทสรุป

ในบทความนี้ เราพบว่าผู้แนะนำมีผลกระทบอย่างมากต่อธุรกิจ จึงมีการใช้กันอย่างแพร่หลาย การสร้างผู้แนะนำที่ดีนั้นไม่ได้ตรงไปตรงมาเหมือนรุ่นอื่นๆ เนื่องจากคุณมักจะต้องจัดการกับคุณสมบัติตามหมวดหมู่ที่มีคาร์ดินาลลิตีสูง ซึ่งทำให้เคล็ดลับง่ายๆ เช่น การเข้ารหัสแบบร้อนแรงเดียวนั้นไร้ประโยชน์

เราเรียนรู้วิธีหลีกเลี่ยงปัญหานี้โดยใช้การฝังในสถาปัตยกรรมโครงข่ายประสาทเทียมของเรา เราได้เพิ่มเทคนิคง่ายๆ บางอย่างเพื่อจบลงด้วยโมเดลการแยกตัวประกอบเมทริกซ์ที่ไม่โทรม แม้ว่าจะไม่ได้ปรับไฮเปอร์พารามิเตอร์ใดๆ ก็ตาม เราสามารถปรับปรุงโมเดลให้ดียิ่งขึ้นไปอีกโดย

  • ปรับมิติการฝังให้เหมาะสม (ที่เราเพิ่งตั้งค่าเป็น 32 จนถึงตอนนี้)
  • ใช้การทำให้เป็นมาตรฐานกับการฝัง
  • การสร้างชุดการตรวจสอบความถูกต้องแบบแบ่งเวลาที่เหมาะสม ไม่ใช่ชุดสุ่มเหมือนที่เราทำ
  • ฝึกอบรมใหม่ในชุดข้อมูลการฝึกอบรมที่สมบูรณ์ (รวมถึงชุดการตรวจสอบความถูกต้อง) หลังจากที่เราทราบไฮเปอร์พารามิเตอร์ที่ดีที่สุดแล้ว

ข้อดีที่ใหญ่ที่สุดประการหนึ่งคือเราสามารถใช้โมเดลในบริบทส่วนใหญ่ได้ เนื่องจากเราต้องการเพียงข้อมูลการโต้ตอบ (เรตติ้ง) ของผู้ใช้และภาพยนตร์เท่านั้น เราไม่จำเป็นต้องรู้อะไรเพิ่มเติมเกี่ยวกับผู้ใช้และภาพยนตร์ เช่น อายุ เพศ ประเภท ... ดังนั้นโดยปกติแล้วเราจะดำเนินการได้ทันที

ราคาที่เราจ่ายสำหรับสิ่งนี้คือ เราไม่สามารถส่งออกการฝังที่มีความหมายสำหรับผู้ใช้หรือภาพยนตร์ที่ไม่รู้จักได้ — ปัญหาการสตาร์ทเย็น โมเดลจะส่งออก บางอย่าง แต่คุณภาพจะแย่มาก

อย่างไรก็ตาม หากเรามีข้อมูลผู้ใช้และภาพยนตร์ เราก็สามารถทำสิ่งที่ชาญฉลาดขึ้นและรวมคุณสมบัติเหล่านี้เข้าด้วยกันได้อย่างตรงไปตรงมาเช่นกัน สิ่งนี้จะช่วยลดปัญหาการสตาร์ทขณะเครื่องเย็น และอาจปรับปรุงโมเดลสำหรับผู้ใช้และภาพยนตร์ที่รู้จักด้วยซ้ำ คุณสามารถอ่านเกี่ยวกับเรื่องนี้ได้ที่นี่:



อ้างอิง

[1] D. Jannach และ M. Jugovac, การวัดมูลค่าทางธุรกิจของระบบผู้แนะนำ (2019), ธุรกรรม ACM บนระบบสารสนเทศเพื่อการจัดการ (TMIS) 10.4 (2019): 1–23

ฉันหวังว่าคุณจะได้เรียนรู้สิ่งใหม่ ๆ ที่น่าสนใจและมีประโยชน์ในวันนี้ ขอบคุณที่อ่าน!

ตามประเด็นสุดท้าย หากคุณ

  1. ต้องการสนับสนุนฉันในการเขียนเพิ่มเติมเกี่ยวกับแมชชีนเลิร์นนิงและ
  2. วางแผนที่จะสมัครสมาชิกแบบ Medium ต่อไป

ทำไมไม่ทำ ผ่านลิงก์นี้? นี่จะช่วยฉันได้มาก! 😊

เพื่อความโปร่งใส ราคาของคุณจะไม่เปลี่ยนแปลง แต่ค่าธรรมเนียมการสมัครสมาชิกประมาณครึ่งหนึ่งจะตกเป็นของฉันโดยตรง

ขอบคุณมาก หากคุณพิจารณาสนับสนุนฉัน!

หากคุณมีคำถามใดๆ เขียนถึงฉันที่ LinkedIn!