[{"data":1,"prerenderedAt":877},["ShallowReactive",2],{"recommendation-models-towers":3},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8,17,89,108,115,141,153,258,270,282,325,370,382,388,397,406,423,513,557,576,582,593,602,738,750,788,794,804,830,848,853],{"type":9,"tag":10,"props":11,"children":13},"element","h1",{"id":12},"build-the-personalized-towers",[14],{"type":15,"value":16},"text","Build the personalized towers",{"type":9,"tag":18,"props":19,"children":20},"p",{},[21,23,29,31,38,40,45,47,52,54,59,61,66,68,73,75,80,82,87],{"type":15,"value":22},"The personalized artifact learns from past reader-item examples. It has two ",{"type":9,"tag":24,"props":25,"children":26},"strong",{},[27],{"type":15,"value":28},"towers",{"type":15,"value":30}," in\n",{"type":9,"tag":32,"props":33,"children":35},"code",{"className":34},"",[36],{"type":15,"value":37},"trainer\u002Fmodels.py",{"type":15,"value":39},": ",{"type":9,"tag":32,"props":41,"children":42},{"className":34},[43],{"type":15,"value":44},"UserModel",{"type":15,"value":46}," calculates numbers for a reader, and ",{"type":9,"tag":32,"props":48,"children":49},{"className":34},[50],{"type":15,"value":51},"ContentModel",{"type":15,"value":53}," calculates\nnumbers for an item. Each tower is a series of ",{"type":9,"tag":24,"props":55,"children":56},{},[57],{"type":15,"value":58},"layers",{"type":15,"value":60},". A layer takes numbers, performs a\ncalculation, and passes its output numbers to the next layer. For example, a dense layer can\nmultiply inputs ",{"type":9,"tag":32,"props":62,"children":63},{"className":34},[64],{"type":15,"value":65},"[1, 0]",{"type":15,"value":67}," by learned weights ",{"type":9,"tag":32,"props":69,"children":70},{"className":34},[71],{"type":15,"value":72},"[0.4, 0.7]",{"type":15,"value":74},", add a bias of ",{"type":9,"tag":32,"props":76,"children":77},{"className":34},[78],{"type":15,"value":79},"0.1",{"type":15,"value":81},", and produce\n",{"type":9,"tag":32,"props":83,"children":84},{"className":34},[85],{"type":15,"value":86},"1×0.4 + 0×0.7 + 0.1 = 0.5",{"type":15,"value":88},". Training changes those weights and biases.",{"type":9,"tag":18,"props":90,"children":91},{},[92,94,99,101,106],{"type":15,"value":93},"The final output of each tower is an ",{"type":9,"tag":24,"props":95,"children":96},{},[97],{"type":15,"value":98},"embedding",{"type":15,"value":100},", or list of numbers. ",{"type":9,"tag":32,"props":102,"children":103},{"className":34},[104],{"type":15,"value":105},"embedding_dim",{"type":15,"value":107}," sets\nhow many numbers are in that list: 64 by default for both towers. Matching lengths let the\nmodel multiply corresponding reader and item numbers and add them to score a pair. The\ntraining chapter works through that score by hand.",{"type":9,"tag":109,"props":110,"children":112},"h2",{"id":111},"first-turn-category-names-into-numbers",[113],{"type":15,"value":114},"First, turn category names into numbers",{"type":9,"tag":18,"props":116,"children":117},{},[118,120,125,127,132,134,139],{"type":15,"value":119},"Neural-network layers consume numeric arrays, not category names. ",{"type":9,"tag":32,"props":121,"children":122},{"className":34},[123],{"type":15,"value":124},"trainer\u002Ffeatures.py",{"type":15,"value":126}," builds\na ",{"type":9,"tag":24,"props":128,"children":129},{},[130],{"type":15,"value":131},"multi-hot",{"type":15,"value":133}," row: one position per known category, with ",{"type":9,"tag":32,"props":135,"children":136},{"className":34},[137],{"type":15,"value":138},"1",{"type":15,"value":140}," for each category the item has.",{"type":9,"tag":18,"props":142,"children":143},{},[144,146,151],{"type":15,"value":145},"Assume the category vocabulary is ",{"type":9,"tag":32,"props":147,"children":148},{"className":34},[149],{"type":15,"value":150},"['science', 'sports', 'technology']",{"type":15,"value":152},":",{"type":9,"tag":154,"props":155,"children":156},"table",{},[157,181],{"type":9,"tag":158,"props":159,"children":160},"thead",{},[161],{"type":9,"tag":162,"props":163,"children":164},"tr",{},[165,171,176],{"type":9,"tag":166,"props":167,"children":168},"th",{},[169],{"type":15,"value":170},"Item",{"type":9,"tag":166,"props":172,"children":173},{},[174],{"type":15,"value":175},"Categories",{"type":9,"tag":166,"props":177,"children":178},{},[179],{"type":15,"value":180},"Multi-hot row",{"type":9,"tag":182,"props":183,"children":184},"tbody",{},[185,210,234],{"type":9,"tag":162,"props":186,"children":187},{},[188,197,202],{"type":9,"tag":189,"props":190,"children":191},"td",{},[192],{"type":9,"tag":32,"props":193,"children":194},{"className":34},[195],{"type":15,"value":196},"article-9",{"type":9,"tag":189,"props":198,"children":199},{},[200],{"type":15,"value":201},"science",{"type":9,"tag":189,"props":203,"children":204},{},[205],{"type":9,"tag":32,"props":206,"children":207},{"className":34},[208],{"type":15,"value":209},"[1, 0, 0]",{"type":9,"tag":162,"props":211,"children":212},{},[213,221,226],{"type":9,"tag":189,"props":214,"children":215},{},[216],{"type":9,"tag":32,"props":217,"children":218},{"className":34},[219],{"type":15,"value":220},"article-10",{"type":9,"tag":189,"props":222,"children":223},{},[224],{"type":15,"value":225},"sports",{"type":9,"tag":189,"props":227,"children":228},{},[229],{"type":9,"tag":32,"props":230,"children":231},{"className":34},[232],{"type":15,"value":233},"[0, 1, 0]",{"type":9,"tag":162,"props":235,"children":236},{},[237,245,250],{"type":9,"tag":189,"props":238,"children":239},{},[240],{"type":9,"tag":32,"props":241,"children":242},{"className":34},[243],{"type":15,"value":244},"article-15",{"type":9,"tag":189,"props":246,"children":247},{},[248],{"type":15,"value":249},"science and technology",{"type":9,"tag":189,"props":251,"children":252},{},[253],{"type":9,"tag":32,"props":254,"children":255},{"className":34},[256],{"type":15,"value":257},"[1, 0, 1]",{"type":9,"tag":18,"props":259,"children":260},{},[261,263,268],{"type":15,"value":262},"The vocabulary sets the column order. ",{"type":9,"tag":32,"props":264,"children":265},{"className":34},[266],{"type":15,"value":267},"build_multi_hot_matrix",{"type":15,"value":269}," starts with zeros and sets\nthe positions of present categories to one:",{"type":9,"tag":271,"props":272,"children":277},"pre",{"className":273,"code":275,"filename":-1,"highlights":-1,"language":276,"meta":34},[274],"language-python","index = {key: i for i, key in enumerate(vocab)}\nmatrix = np.zeros((len(rows), len(index)), dtype=np.float32)\nfor r, row_id in enumerate(rows):\n    for key in keys_by_row.get(row_id, []):\n        col = index.get(key)\n        if col is not None:\n            matrix[r, col] = 1.0\n","python",[278],{"type":9,"tag":32,"props":279,"children":280},{"__ignoreMap":34},[281],{"type":15,"value":275},{"type":9,"tag":18,"props":283,"children":284},{},[285,290,292,296,298,302,304,309,311,316,318,323],{"type":9,"tag":32,"props":286,"children":287},{"className":34},[288],{"type":15,"value":289},"index",{"type":15,"value":291}," maps ",{"type":9,"tag":32,"props":293,"children":294},{"className":34},[295],{"type":15,"value":201},{"type":15,"value":297}," to position 0, ",{"type":9,"tag":32,"props":299,"children":300},{"className":34},[301],{"type":15,"value":225},{"type":15,"value":303}," to 1, and ",{"type":9,"tag":32,"props":305,"children":306},{"className":34},[307],{"type":15,"value":308},"technology",{"type":15,"value":310}," to 2. ",{"type":9,"tag":32,"props":312,"children":313},{"className":34},[314],{"type":15,"value":315},"r",{"type":15,"value":317}," is the item's\nrow position. ",{"type":9,"tag":32,"props":319,"children":320},{"className":34},[321],{"type":15,"value":322},"col",{"type":15,"value":324}," is the category's column position. The function also handles an empty\nvocabulary by producing a zero-width matrix; the dataset omits that optional feature.",{"type":9,"tag":18,"props":326,"children":327},{},[328,330,335,337,342,344,348,350,354,356,361,363,368],{"type":15,"value":329},"A dense layer can learn how much each position matters. Imagine one output number with learned\nweights ",{"type":9,"tag":32,"props":331,"children":332},{"className":34},[333],{"type":15,"value":334},"[0.4, -0.2, 0.7]",{"type":15,"value":336}," and bias ",{"type":9,"tag":32,"props":338,"children":339},{"className":34},[340],{"type":15,"value":341},"0",{"type":15,"value":343},". For ",{"type":9,"tag":32,"props":345,"children":346},{"className":34},[347],{"type":15,"value":244},{"type":15,"value":349},"'s ",{"type":9,"tag":32,"props":351,"children":352},{"className":34},[353],{"type":15,"value":257},{"type":15,"value":355}," input, that number is\n",{"type":9,"tag":32,"props":357,"children":358},{"className":34},[359],{"type":15,"value":360},"1×0.4 + 0×(-0.2) + 1×0.7 + 0 = 1.1",{"type":15,"value":362},". The actual category layer learns ",{"type":9,"tag":24,"props":364,"children":365},{},[366],{"type":15,"value":367},"16",{"type":15,"value":369}," outputs at\nonce, each with its own weights and bias. Its ReLU activation replaces a negative output with\nzero. Training changes these weights from their initial values.",{"type":9,"tag":18,"props":371,"children":372},{},[373,375,380],{"type":15,"value":374},"The personalized content tower uses these dense training arrays. The ",{"type":9,"tag":24,"props":376,"children":377},{},[378],{"type":15,"value":379},"content-only index",{"type":15,"value":381},"\nfrom the previous chapter stores category membership sparsely; the two paths use the same\nitem information for different computations.",{"type":9,"tag":109,"props":383,"children":385},{"id":384},"turn-ids-into-learned-vectors",[386],{"type":15,"value":387},"Turn IDs into learned vectors",{"type":9,"tag":18,"props":389,"children":390},{},[391,395],{"type":9,"tag":32,"props":392,"children":393},{"className":34},[394],{"type":15,"value":44},{"type":15,"value":396}," creates two layers for the reader ID:",{"type":9,"tag":271,"props":398,"children":401},{"className":399,"code":400,"filename":-1,"highlights":-1,"language":276,"meta":34},[274],"self.user_lookup = tf.keras.layers.StringLookup(\n    vocabulary=unique_user_ids, mask_token=None\n)\nself.user_embedding = tf.keras.layers.Embedding(\n    len(unique_user_ids) + 1, embedding_dim, embeddings_initializer=\"zeros\"\n)\n",[402],{"type":9,"tag":32,"props":403,"children":404},{"__ignoreMap":34},[405],{"type":15,"value":400},{"type":9,"tag":18,"props":407,"children":408},{},[409,414,416,421],{"type":9,"tag":32,"props":410,"children":411},{"className":34},[412],{"type":15,"value":413},"unique_user_ids",{"type":15,"value":415}," is the list of profile IDs included when the trainer builds this model.\nSuppose it contains ",{"type":9,"tag":32,"props":417,"children":418},{"className":34},[419],{"type":15,"value":420},"['reader-1', 'reader-2']",{"type":15,"value":422},". Follow the two code lines in order:",{"type":9,"tag":424,"props":425,"children":426},"ol",{},[427,483],{"type":9,"tag":428,"props":429,"children":430},"li",{},[431,436,438,443,445,449,451,456,457,462,464,468,470,474,476,481],{"type":9,"tag":32,"props":432,"children":433},{"className":34},[434],{"type":15,"value":435},"StringLookup",{"type":15,"value":437}," gives each ID a row number. In this example, ",{"type":9,"tag":32,"props":439,"children":440},{"className":34},[441],{"type":15,"value":442},"reader-1",{"type":15,"value":444}," becomes ",{"type":9,"tag":32,"props":446,"children":447},{"className":34},[448],{"type":15,"value":138},{"type":15,"value":450}," and\n",{"type":9,"tag":32,"props":452,"children":453},{"className":34},[454],{"type":15,"value":455},"reader-2",{"type":15,"value":444},{"type":9,"tag":32,"props":458,"children":459},{"className":34},[460],{"type":15,"value":461},"2",{"type":15,"value":463},". Row ",{"type":9,"tag":32,"props":465,"children":466},{"className":34},[467],{"type":15,"value":341},{"type":15,"value":469}," is reserved for an ID not in ",{"type":9,"tag":32,"props":471,"children":472},{"className":34},[473],{"type":15,"value":413},{"type":15,"value":475},"; the code\ncalls that an ",{"type":9,"tag":24,"props":477,"children":478},{},[479],{"type":15,"value":480},"out-of-vocabulary",{"type":15,"value":482}," ID.",{"type":9,"tag":428,"props":484,"children":485},{},[486,491,493,498,500,504,506,511],{"type":9,"tag":32,"props":487,"children":488},{"className":34},[489],{"type":15,"value":490},"Embedding",{"type":15,"value":492}," creates a table with ",{"type":9,"tag":32,"props":494,"children":495},{"className":34},[496],{"type":15,"value":497},"len(unique_user_ids) + 1",{"type":15,"value":499}," rows. For our two IDs, that is\nthree rows: the reserved row and one per known ID. ",{"type":9,"tag":32,"props":501,"children":502},{"className":34},[503],{"type":15,"value":105},{"type":15,"value":505}," is the number of columns,\n64 by default. ",{"type":9,"tag":32,"props":507,"children":508},{"className":34},[509],{"type":15,"value":510},"embeddings_initializer=\"zeros\"",{"type":15,"value":512}," means every number in this ID table starts\nat zero.",{"type":9,"tag":18,"props":514,"children":515},{},[516,518,523,525,529,531,536,538,542,544,548,550,555],{"type":15,"value":517},"Later, ",{"type":9,"tag":32,"props":519,"children":520},{"className":34},[521],{"type":15,"value":522},"self.user_embedding(self.user_lookup(features[\"user_id\"]))",{"type":15,"value":524}," runs those steps together.\nIf a batch contains ",{"type":9,"tag":32,"props":526,"children":527},{"className":34},[528],{"type":15,"value":420},{"type":15,"value":530},", the inner lookup produces ",{"type":9,"tag":32,"props":532,"children":533},{"className":34},[534],{"type":15,"value":535},"[1, 2]",{"type":15,"value":537},"; the outer\nlookup retrieves table rows ",{"type":9,"tag":32,"props":539,"children":540},{"className":34},[541],{"type":15,"value":138},{"type":15,"value":543}," and ",{"type":9,"tag":32,"props":545,"children":546},{"className":34},[547],{"type":15,"value":461},{"type":15,"value":549},". It produces two lists of 64 numbers, one per reader.\nThose are ",{"type":9,"tag":24,"props":551,"children":552},{},[553],{"type":15,"value":554},"ID embeddings",{"type":15,"value":556},", not finished recommendations or scores. During the first training\nstage, positive reader-item examples can change the selected ID rows. The ranking stage then\nkeeps the towers fixed while learning its own scoring layers.",{"type":9,"tag":18,"props":558,"children":559},{},[560,562,567,569,574],{"type":15,"value":561},"A ",{"type":9,"tag":24,"props":563,"children":564},{},[565],{"type":15,"value":566},"feature batch",{"type":15,"value":568}," is a dictionary where each key holds values for several examples. The\n",{"type":9,"tag":32,"props":570,"children":571},{"className":34},[572],{"type":15,"value":573},"features",{"type":15,"value":575}," dictionary can also carry configured user signals and earlier category activity.\nThose inputs help make a reader vector even when a known profile has no interaction history;\nits ID row has not learned anything yet.",{"type":9,"tag":109,"props":577,"children":579},{"id":578},"combine-the-available-user-inputs",[580],{"type":15,"value":581},"Combine the available user inputs",{"type":9,"tag":18,"props":583,"children":584},{},[585,587,592],{"type":15,"value":586},"This excerpt is from ",{"type":9,"tag":32,"props":588,"children":589},{"className":34},[590],{"type":15,"value":591},"UserModel.call",{"type":15,"value":152},{"type":9,"tag":271,"props":594,"children":597},{"className":595,"code":596,"filename":-1,"highlights":-1,"language":276,"meta":34},[274],"user_emb = self.user_embedding(self.user_lookup(features[\"user_id\"]))\nparts = [user_emb]\n# ... pooled editorial-type and collection interests are appended here when present ...\nif self.num_signal_tokens > 0 and \"user_signal_multi_hot\" in features:\n    parts.append(self.signal_dense(features[\"user_signal_multi_hot\"]))\nif self.num_categories > 0 and \"user_category_affinity\" in features:\n    affinity = self.affinity_dropout(features[\"user_category_affinity\"], training=training)\n    parts.append(self.affinity_dense(affinity))\nreturn self.dense(tf.concat(parts, axis=1) if len(parts) > 1 else parts[0], training=training)\n",[598],{"type":9,"tag":32,"props":599,"children":600},{"__ignoreMap":34},[601],{"type":15,"value":596},{"type":9,"tag":154,"props":603,"children":604},{},[605,621],{"type":9,"tag":158,"props":606,"children":607},{},[608],{"type":9,"tag":162,"props":609,"children":610},{},[611,616],{"type":9,"tag":166,"props":612,"children":613},{},[614],{"type":15,"value":615},"Expression",{"type":9,"tag":166,"props":617,"children":618},{},[619],{"type":15,"value":620},"Meaning",{"type":9,"tag":182,"props":622,"children":623},{},[624,640,663,693,716],{"type":9,"tag":162,"props":625,"children":626},{},[627,635],{"type":9,"tag":189,"props":628,"children":629},{},[630],{"type":9,"tag":32,"props":631,"children":632},{"className":34},[633],{"type":15,"value":634},"features['user_id']",{"type":9,"tag":189,"props":636,"children":637},{},[638],{"type":15,"value":639},"Read the batch of reader IDs.",{"type":9,"tag":162,"props":641,"children":642},{},[643,658],{"type":9,"tag":189,"props":644,"children":645},{},[646,651,653],{"type":9,"tag":32,"props":647,"children":648},{"className":34},[649],{"type":15,"value":650},"user_lookup",{"type":15,"value":652}," then ",{"type":9,"tag":32,"props":654,"children":655},{"className":34},[656],{"type":15,"value":657},"user_embedding",{"type":9,"tag":189,"props":659,"children":660},{},[661],{"type":15,"value":662},"Change each string ID into an integer, then into a learned vector.",{"type":9,"tag":162,"props":664,"children":665},{},[666,674],{"type":9,"tag":189,"props":667,"children":668},{},[669],{"type":9,"tag":32,"props":670,"children":671},{"className":34},[672],{"type":15,"value":673},"parts.append(...)",{"type":9,"tag":189,"props":675,"children":676},{},[677,679,684,686,691],{"type":15,"value":678},"Add another numeric signal only when it exists. ",{"type":9,"tag":32,"props":680,"children":681},{"className":34},[682],{"type":15,"value":683},"signal_dense",{"type":15,"value":685}," projects configured profile signals; ",{"type":9,"tag":32,"props":687,"children":688},{"className":34},[689],{"type":15,"value":690},"affinity_dense",{"type":15,"value":692}," projects past category interests. A dense layer learns numeric weights for combining its inputs.",{"type":9,"tag":162,"props":694,"children":695},{},[696,704],{"type":9,"tag":189,"props":697,"children":698},{},[699],{"type":9,"tag":32,"props":700,"children":701},{"className":34},[702],{"type":15,"value":703},"tf.concat(parts, axis=1)",{"type":9,"tag":189,"props":705,"children":706},{},[707,709,714],{"type":15,"value":708},"Join each reader's vectors side by side. ",{"type":9,"tag":32,"props":710,"children":711},{"className":34},[712],{"type":15,"value":713},"axis=1",{"type":15,"value":715}," is the feature dimension, not the reader-row dimension.",{"type":9,"tag":162,"props":717,"children":718},{},[719,727],{"type":9,"tag":189,"props":720,"children":721},{},[722],{"type":9,"tag":32,"props":723,"children":724},{"className":34},[725],{"type":15,"value":726},"self.dense(...)",{"type":9,"tag":189,"props":728,"children":729},{},[730,732,736],{"type":15,"value":731},"Learn a combination and return one ",{"type":9,"tag":32,"props":733,"children":734},{"className":34},[735],{"type":15,"value":105},{"type":15,"value":737},"-wide vector per reader.",{"type":9,"tag":18,"props":739,"children":740},{},[741,743,748],{"type":15,"value":742},"The elided lines pool sparse editorial-type and direct-collection interests. Category\naffinity used ",{"type":9,"tag":24,"props":744,"children":745},{},[746],{"type":15,"value":747},"during training",{"type":15,"value":749}," is built only from outcomes earlier than that example's\nfeature time, so its own answer is not fed back as an input. At serving, the saved reader row\nuses the captured history available for that model version.",{"type":9,"tag":18,"props":751,"children":752},{},[753,758,760,765,767,772,774,779,781,786],{"type":9,"tag":32,"props":754,"children":755},{"className":34},[756],{"type":15,"value":757},"affinity_dropout",{"type":15,"value":759}," is a Keras ",{"type":9,"tag":24,"props":761,"children":762},{},[763],{"type":15,"value":764},"dropout",{"type":15,"value":766}," layer. On each fitting step it sets a random 30% of\nthe affinity numbers to zero and scales the rest up so their total stays about the same. This\nguards against ",{"type":9,"tag":24,"props":768,"children":769},{},[770],{"type":15,"value":771},"memorization",{"type":15,"value":773},", also called ",{"type":9,"tag":24,"props":775,"children":776},{},[777],{"type":15,"value":778},"overfitting",{"type":15,"value":780},": learning quirks of the training\nrows that do not hold for new ones. With affinity sometimes hidden, the tower cannot rely on\naffinity alone and also learns from the reader's ID and profile signals. The ",{"type":9,"tag":32,"props":782,"children":783},{"className":34},[784],{"type":15,"value":785},"training",{"type":15,"value":787}," flag\nturns dropout off at serving time, so every recommendation sees the full input.",{"type":9,"tag":109,"props":789,"children":791},{"id":790},"build-the-item-vector",[792],{"type":15,"value":793},"Build the item vector",{"type":9,"tag":18,"props":795,"children":796},{},[797,802],{"type":9,"tag":32,"props":798,"children":799},{"className":34},[800],{"type":15,"value":801},"ContentModel.call",{"type":15,"value":803}," starts with item ID, MIME type (file type), language, and any available\ncategories, labels, editorial type, direct collections, and text embedding. It looks up short\nlearned lists for values such as language and file type. For categories, it applies a dense\nlayer to the multi-hot row you calculated above.",{"type":9,"tag":18,"props":805,"children":806},{},[807,809,814,816,821,823,828],{"type":15,"value":808},"When a text embedding is available, it is an ",{"type":9,"tag":24,"props":810,"children":811},{},[812],{"type":15,"value":813},"input",{"type":15,"value":815}," made earlier from the item's text. In\nthe code, ",{"type":9,"tag":32,"props":817,"children":818},{"className":34},[819],{"type":15,"value":820},"self.embedding_dense(features[\"embedding\"])",{"type":15,"value":822}," changes that input into 32 numbers.\nThis is called a ",{"type":9,"tag":24,"props":824,"children":825},{},[826],{"type":15,"value":827},"projection",{"type":15,"value":829},": each of the 32 outputs is a learned combination of the input\nnumbers, using the same multiply-and-add calculation and ReLU as the category example.",{"type":9,"tag":18,"props":831,"children":832},{},[833,835,839,841,846],{"type":15,"value":834},"Before joining the pieces, ",{"type":9,"tag":32,"props":836,"children":837},{"className":34},[838],{"type":15,"value":801},{"type":15,"value":840}," multiplies each property's part by that\nproperty's normalized similarity weight from the saved context. These are the same weights the\ncontent model uses. A signal whose weight is zero contributes only zeros. The learned item-ID\nvector is not scaled. The code then joins these parts and applies two more dense layers. The\nlast one produces the item's 64-number ",{"type":9,"tag":24,"props":842,"children":843},{},[844],{"type":15,"value":845},"output embedding",{"type":15,"value":847},". The text embedding and the output\nembedding are separate lists produced for different purposes.",{"type":9,"tag":18,"props":849,"children":850},{},[851],{"type":15,"value":852},"The content-only artifact from the previous chapter uses the supplied text embedding directly\nin its similarity calculation. It does not produce this learned 64-number output.",{"type":9,"tag":18,"props":854,"children":855},{},[856,861,863,868,870,875],{"type":9,"tag":24,"props":857,"children":858},{},[859],{"type":15,"value":860},"Check:",{"type":15,"value":862}," If a content item has no labels, must we invent a label for it? No. The optional\nlabel branch is skipped when the vocabulary is empty. A training ",{"type":9,"tag":24,"props":864,"children":865},{},[866],{"type":15,"value":867},"label",{"type":15,"value":869}," (engagement target)\nis also a different concept from an item's editorial ",{"type":9,"tag":24,"props":871,"children":872},{},[873],{"type":15,"value":874},"labels",{"type":15,"value":876}," (content features).",1790541170521]