[{"data":1,"prerenderedAt":830},["ShallowReactive",2],{"recommendation-models-content-model":3},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8,17,54,61,94,119,125,142,154,266,278,284,296,308,317,369,376,428,445,612,644,703,713,761,767,776,820],{"type":9,"tag":10,"props":11,"children":13},"element","h1",{"id":12},"build-the-content-model",[14],{"type":15,"value":16},"text","Build the content model",{"type":9,"tag":18,"props":19,"children":20},"p",{},[21,23,29,31,38,40,45,47,52],{"type":15,"value":22},"The content artifact answers: ",{"type":9,"tag":24,"props":25,"children":26},"strong",{},[27],{"type":15,"value":28},"“Given an item, which eligible items are similar?”",{"type":15,"value":30}," It can be\nbuilt even when no reader has interacted with anything. This is the path through\n",{"type":9,"tag":32,"props":33,"children":35},"code",{"className":34},"",[36],{"type":15,"value":37},"trainer\u002Fcontent_only.py",{"type":15,"value":39},", ",{"type":9,"tag":32,"props":41,"children":42},{"className":34},[43],{"type":15,"value":44},"trainer\u002Fcontent_similarity.py",{"type":15,"value":46},", and\n",{"type":9,"tag":32,"props":48,"children":49},{"className":34},[50],{"type":15,"value":51},"trainer\u002Fexport.py → export_content_model",{"type":15,"value":53},".",{"type":9,"tag":55,"props":56,"children":58},"h2",{"id":57},"what-goes-into-this-path",[59],{"type":15,"value":60},"What goes into this path?",{"type":9,"tag":18,"props":62,"children":63},{},[64,66,71,73,78,80,85,87,92],{"type":15,"value":65},"It uses the ",{"type":9,"tag":32,"props":67,"children":68},{"className":34},[69],{"type":15,"value":70},"content",{"type":15,"value":72}," and ",{"type":9,"tag":32,"props":74,"children":75},{"className":34},[76],{"type":15,"value":77},"categories",{"type":15,"value":79}," tables from chapter 1. MIME type says what kind of\nfile an item is, for example ",{"type":9,"tag":32,"props":81,"children":82},{"className":34},[83],{"type":15,"value":84},"text\u002Fhtml",{"type":15,"value":86},". Other item features include language, categories,\nlabels, editorial type, direct collection memberships, and a semantic\nembedding. A ",{"type":9,"tag":24,"props":88,"children":89},{},[90],{"type":15,"value":91},"semantic embedding",{"type":15,"value":93}," is a list of numbers made by a separate text model from\nthe item's extracted text. The text model has learned to place related passages in similar\ndirections in this number space. No one number is a named topic; the whole list is compared\nwith other lists. If a long item was split into pieces, the trainer combines the piece vectors\ninto one item vector. The content-only path compares these supplied numbers; it does not train\nthe text model that made them.",{"type":9,"tag":18,"props":95,"children":96},{},[97,99,104,106,111,112,117],{"type":15,"value":98},"The ",{"type":9,"tag":32,"props":100,"children":101},{"className":34},[102],{"type":15,"value":103},"run_content_only_training",{"type":15,"value":105}," function accepts ",{"type":9,"tag":32,"props":107,"children":108},{"className":34},[109],{"type":15,"value":110},"feedback",{"type":15,"value":72},{"type":9,"tag":32,"props":113,"children":114},{"className":34},[115],{"type":15,"value":116},"signals",{"type":15,"value":118}," so its call shape\nmatches the personalized path. It does not use either one: this model is item-keyed, with no\nreader tower.",{"type":9,"tag":55,"props":120,"children":122},{"id":121},"build-an-index-of-eligible-items",[123],{"type":15,"value":124},"Build an index of eligible items",{"type":9,"tag":18,"props":126,"children":127},{},[128,133,135,140],{"type":9,"tag":32,"props":129,"children":130},{"className":34},[131],{"type":15,"value":132},"export_content_model",{"type":15,"value":134}," removes duplicate IDs and sorts the catalog. It also identifies each\ncontext and language ",{"type":9,"tag":24,"props":136,"children":137},{},[138],{"type":15,"value":139},"facet",{"type":15,"value":141},": the subset of items eligible for that combination. It then\nconstructs the index:",{"type":9,"tag":143,"props":144,"children":149},"pre",{"className":145,"code":147,"filename":-1,"highlights":-1,"language":148,"meta":34},[146],"language-python","item_index = ContentSimilarityIndex(\n    content,\n    categories,\n    context_types,\n    candidate_indexes,\n    item_top_k,\n    weights=weights,\n)\n","python",[150],{"type":9,"tag":32,"props":151,"children":152},{"__ignoreMap":34},[153],{"type":15,"value":147},{"type":9,"tag":155,"props":156,"children":157},"table",{},[158,177],{"type":9,"tag":159,"props":160,"children":161},"thead",{},[162],{"type":9,"tag":163,"props":164,"children":165},"tr",{},[166,172],{"type":9,"tag":167,"props":168,"children":169},"th",{},[170],{"type":15,"value":171},"Argument",{"type":9,"tag":167,"props":173,"children":174},{},[175],{"type":15,"value":176},"Meaning",{"type":9,"tag":178,"props":179,"children":180},"tbody",{},[181,202,218,234,250],{"type":9,"tag":163,"props":182,"children":183},{},[184,197],{"type":9,"tag":185,"props":186,"children":187},"td",{},[188,192,193],{"type":9,"tag":32,"props":189,"children":190},{"className":34},[191],{"type":15,"value":70},{"type":15,"value":39},{"type":9,"tag":32,"props":194,"children":195},{"className":34},[196],{"type":15,"value":77},{"type":9,"tag":185,"props":198,"children":199},{},[200],{"type":15,"value":201},"The item properties to compare.",{"type":9,"tag":163,"props":203,"children":204},{},[205,213],{"type":9,"tag":185,"props":206,"children":207},{},[208],{"type":9,"tag":32,"props":209,"children":210},{"className":34},[211],{"type":15,"value":212},"context_types",{"type":9,"tag":185,"props":214,"children":215},{},[216],{"type":15,"value":217},"The saved context-and-language facets available for serving.",{"type":9,"tag":163,"props":219,"children":220},{},[221,229],{"type":9,"tag":185,"props":222,"children":223},{},[224],{"type":9,"tag":32,"props":225,"children":226},{"className":34},[227],{"type":15,"value":228},"candidate_indexes",{"type":9,"tag":185,"props":230,"children":231},{},[232],{"type":15,"value":233},"Which item positions are eligible in each facet.",{"type":9,"tag":163,"props":235,"children":236},{},[237,245],{"type":9,"tag":185,"props":238,"children":239},{},[240],{"type":9,"tag":32,"props":241,"children":242},{"className":34},[243],{"type":15,"value":244},"item_top_k",{"type":9,"tag":185,"props":246,"children":247},{},[248],{"type":15,"value":249},"How many results to keep for an item query.",{"type":9,"tag":163,"props":251,"children":252},{},[253,261],{"type":9,"tag":185,"props":254,"children":255},{},[256],{"type":9,"tag":32,"props":257,"children":258},{"className":34},[259],{"type":15,"value":260},"weights",{"type":9,"tag":185,"props":262,"children":263},{},[264],{"type":15,"value":265},"The saved importance of each similarity signal.",{"type":9,"tag":18,"props":267,"children":268},{},[269,271,276],{"type":15,"value":270},"An ",{"type":9,"tag":24,"props":272,"children":273},{},[274],{"type":15,"value":275},"index",{"type":15,"value":277}," here is stored item features plus lookup structures. It is not a fitted neural\nnetwork. Sparse membership structures store categories, labels, and collections that an item\nactually has, rather than a huge row with a column for every possible category.",{"type":9,"tag":55,"props":279,"children":281},{"id":280},"score-one-source-item-against-candidates",[282],{"type":15,"value":283},"Score one source item against candidates",{"type":9,"tag":18,"props":285,"children":286},{},[287,289,294],{"type":15,"value":288},"Suppose the source is ",{"type":9,"tag":32,"props":290,"children":291},{"className":34},[292],{"type":15,"value":293},"article-9",{"type":15,"value":295}," (English, science). A candidate also in English gets a\nlanguage match. A candidate also in science gets category similarity. Each signal contributes\naccording to its saved weight.",{"type":9,"tag":18,"props":297,"children":298},{},[299,301,306],{"type":15,"value":300},"This excerpt from ",{"type":9,"tag":32,"props":302,"children":303},{"className":34},[304],{"type":15,"value":305},"ContentSimilarityIndex._scores",{"type":15,"value":307}," shows how terms are added:",{"type":9,"tag":143,"props":309,"children":312},{"className":310,"code":311,"filename":-1,"highlights":-1,"language":148,"meta":34},[146],"scores = (\n    self.weights[\"mime\"] * tf.cast(self.content_types == self.content_types[item], tf.float32)\n    + self.weights[\"language\"] * tf.cast(self.languages == self.languages[item], tf.float32)\n    + self.weights[\"categories\"] * self.categories.scores(item)\n    + self.weights[\"labels\"] * self.labels.scores(item)\n    + self.weights[\"type\"] * tf.cast(\n        (self.editorial_types == self.editorial_types[item]) & (self.editorial_types != \"\"), tf.float32,\n    )\n    + self.weights[\"collections\"] * self.collections.scores(item)\n)\n",[313],{"type":9,"tag":32,"props":314,"children":315},{"__ignoreMap":34},[316],{"type":15,"value":311},{"type":9,"tag":18,"props":318,"children":319},{},[320,325,327,332,334,339,341,346,348,353,355,360,362,367],{"type":9,"tag":32,"props":321,"children":322},{"className":34},[323],{"type":15,"value":324},"item",{"type":15,"value":326}," is the source item's numeric position in the index.\n",{"type":9,"tag":32,"props":328,"children":329},{"className":34},[330],{"type":15,"value":331},"self.languages == self.languages[item]",{"type":15,"value":333}," compares every candidate language with the source language. The result is\na true\u002Ffalse array; ",{"type":9,"tag":32,"props":335,"children":336},{"className":34},[337],{"type":15,"value":338},"tf.cast(..., tf.float32)",{"type":15,"value":340}," turns matches into ",{"type":9,"tag":32,"props":342,"children":343},{"className":34},[344],{"type":15,"value":345},"1.0",{"type":15,"value":347}," and nonmatches into ",{"type":9,"tag":32,"props":349,"children":350},{"className":34},[351],{"type":15,"value":352},"0.0",{"type":15,"value":354},".\nMultiplying by the language weight gives the language contribution. Category, label, and\ncollection methods calculate overlap scores instead of exact string equality.\nThese ",{"type":9,"tag":24,"props":356,"children":357},{},[358],{"type":15,"value":359},"similarity weights",{"type":15,"value":361}," set the importance of item properties. They are different from\n",{"type":9,"tag":32,"props":363,"children":364},{"className":34},[365],{"type":15,"value":366},"sample_weight",{"type":15,"value":368},", which sets the influence of one training observation in the personalized path.",{"type":9,"tag":370,"props":371,"children":373},"h3",{"id":372},"work-through-the-addition-by-hand",[374],{"type":15,"value":375},"Work through the addition by hand",{"type":9,"tag":18,"props":377,"children":378},{},[379,381,385,387,392,394,398,400,405,407,412,414,419,421,426],{"type":15,"value":380},"For this teaching example, suppose the saved settings give raw similarity weights ",{"type":9,"tag":32,"props":382,"children":383},{"className":34},[384],{"type":15,"value":345},{"type":15,"value":386}," for\nMIME type, ",{"type":9,"tag":32,"props":388,"children":389},{"className":34},[390],{"type":15,"value":391},"0.5",{"type":15,"value":393}," for language, ",{"type":9,"tag":32,"props":395,"children":396},{"className":34},[397],{"type":15,"value":391},{"type":15,"value":399}," for categories, and ",{"type":9,"tag":32,"props":401,"children":402},{"className":34},[403],{"type":15,"value":404},"0",{"type":15,"value":406}," for the other four signals.\n",{"type":9,"tag":32,"props":408,"children":409},{"className":34},[410],{"type":15,"value":411},"normalized_similarity",{"type":15,"value":413}," divides each weight by their sum, ",{"type":9,"tag":32,"props":415,"children":416},{"className":34},[417],{"type":15,"value":418},"2.0",{"type":15,"value":420},". The weights used in the score\nare therefore ",{"type":9,"tag":24,"props":422,"children":423},{},[424],{"type":15,"value":425},"0.50, 0.25, and 0.25",{"type":15,"value":427},". Normalizing keeps their proportions while making the\nwhole similarity group sum to one.",{"type":9,"tag":18,"props":429,"children":430},{},[431,433,437,439,443],{"type":15,"value":432},"Let the source ",{"type":9,"tag":32,"props":434,"children":435},{"className":34},[436],{"type":15,"value":293},{"type":15,"value":438}," be ",{"type":9,"tag":32,"props":440,"children":441},{"className":34},[442],{"type":15,"value":84},{"type":15,"value":444},", English, science. Each feature comparison produces\na number between zero and one. With one category per item, a matching category scores one\nand a different category scores zero:",{"type":9,"tag":155,"props":446,"children":447},{},[448,485],{"type":9,"tag":159,"props":449,"children":450},{},[451],{"type":9,"tag":163,"props":452,"children":453},{},[454,459,465,470,475,480],{"type":9,"tag":167,"props":455,"children":456},{},[457],{"type":15,"value":458},"Candidate",{"type":9,"tag":167,"props":460,"children":462},{"align":461},"right",[463],{"type":15,"value":464},"MIME match",{"type":9,"tag":167,"props":466,"children":467},{"align":461},[468],{"type":15,"value":469},"Language match",{"type":9,"tag":167,"props":471,"children":472},{"align":461},[473],{"type":15,"value":474},"Category match",{"type":9,"tag":167,"props":476,"children":477},{},[478],{"type":15,"value":479},"Arithmetic",{"type":9,"tag":167,"props":481,"children":482},{"align":461},[483],{"type":15,"value":484},"Similarity score",{"type":9,"tag":178,"props":486,"children":487},{},[488,530,571],{"type":9,"tag":163,"props":489,"children":490},{},[491,501,506,510,514,522],{"type":9,"tag":185,"props":492,"children":493},{},[494,499],{"type":9,"tag":32,"props":495,"children":496},{"className":34},[497],{"type":15,"value":498},"article-12",{"type":15,"value":500},": same MIME, English, science",{"type":9,"tag":185,"props":502,"children":503},{"align":461},[504],{"type":15,"value":505},"1",{"type":9,"tag":185,"props":507,"children":508},{"align":461},[509],{"type":15,"value":505},{"type":9,"tag":185,"props":511,"children":512},{"align":461},[513],{"type":15,"value":505},{"type":9,"tag":185,"props":515,"children":516},{},[517],{"type":9,"tag":32,"props":518,"children":519},{"className":34},[520],{"type":15,"value":521},"0.50×1 + 0.25×1 + 0.25×1",{"type":9,"tag":185,"props":523,"children":524},{"align":461},[525],{"type":9,"tag":24,"props":526,"children":527},{},[528],{"type":15,"value":529},"1.00",{"type":9,"tag":163,"props":531,"children":532},{},[533,543,547,551,555,563],{"type":9,"tag":185,"props":534,"children":535},{},[536,541],{"type":9,"tag":32,"props":537,"children":538},{"className":34},[539],{"type":15,"value":540},"article-10",{"type":15,"value":542},": same MIME, English, sports",{"type":9,"tag":185,"props":544,"children":545},{"align":461},[546],{"type":15,"value":505},{"type":9,"tag":185,"props":548,"children":549},{"align":461},[550],{"type":15,"value":505},{"type":9,"tag":185,"props":552,"children":553},{"align":461},[554],{"type":15,"value":404},{"type":9,"tag":185,"props":556,"children":557},{},[558],{"type":9,"tag":32,"props":559,"children":560},{"className":34},[561],{"type":15,"value":562},"0.50×1 + 0.25×1 + 0.25×0",{"type":9,"tag":185,"props":564,"children":565},{"align":461},[566],{"type":9,"tag":24,"props":567,"children":568},{},[569],{"type":15,"value":570},"0.75",{"type":9,"tag":163,"props":572,"children":573},{},[574,584,588,592,596,604],{"type":9,"tag":185,"props":575,"children":576},{},[577,582],{"type":9,"tag":32,"props":578,"children":579},{"className":34},[580],{"type":15,"value":581},"article-13",{"type":15,"value":583},": different MIME, English, science",{"type":9,"tag":185,"props":585,"children":586},{"align":461},[587],{"type":15,"value":404},{"type":9,"tag":185,"props":589,"children":590},{"align":461},[591],{"type":15,"value":505},{"type":9,"tag":185,"props":593,"children":594},{"align":461},[595],{"type":15,"value":505},{"type":9,"tag":185,"props":597,"children":598},{},[599],{"type":9,"tag":32,"props":600,"children":601},{"className":34},[602],{"type":15,"value":603},"0.50×0 + 0.25×1 + 0.25×1",{"type":9,"tag":185,"props":605,"children":606},{"align":461},[607],{"type":9,"tag":24,"props":608,"children":609},{},[610],{"type":15,"value":611},"0.50",{"type":9,"tag":18,"props":613,"children":614},{},[615,617,622,624,629,631,636,638,642],{"type":15,"value":616},"That is a ",{"type":9,"tag":24,"props":618,"children":619},{},[620],{"type":15,"value":621},"weighted sum",{"type":15,"value":623},": multiply each comparison by its importance and add the results.\nThese settings and items are illustrative; the actual weights come from the saved context.\nIf an item has multiple categories, ",{"type":9,"tag":32,"props":625,"children":626},{"className":34},[627],{"type":15,"value":628},"self.categories.scores(item)",{"type":15,"value":630}," uses overlap divided by\nthe square root of the two category counts. For example, one shared category between a\none-category source and a two-category candidate scores ",{"type":9,"tag":32,"props":632,"children":633},{"className":34},[634],{"type":15,"value":635},"1 \u002F √(1×2) ≈ 0.71",{"type":15,"value":637},", rather than a\nfull ",{"type":9,"tag":32,"props":639,"children":640},{"className":34},[641],{"type":15,"value":345},{"type":15,"value":643}," match. This keeps “one shared category” from counting as identical category sets.",{"type":9,"tag":18,"props":645,"children":646},{},[647,649,654,656,661,663,668,669,674,676,680,682,687,689,694,696,701],{"type":15,"value":648},"If semantic embeddings exist, ",{"type":9,"tag":32,"props":650,"children":651},{"className":34},[652],{"type":15,"value":653},"_scores",{"type":15,"value":655}," also adds their nonnegative ",{"type":9,"tag":24,"props":657,"children":658},{},[659],{"type":15,"value":660},"cosine similarity",{"type":15,"value":662},":\nhow closely two lists of numbers point in the same direction. Use the invented two-number\nvectors from the first chapter. ",{"type":9,"tag":32,"props":664,"children":665},{"className":34},[666],{"type":15,"value":667},"[1, 0]",{"type":15,"value":72},{"type":9,"tag":32,"props":670,"children":671},{"className":34},[672],{"type":15,"value":673},"[0.8, 0.6]",{"type":15,"value":675}," point more alike than ",{"type":9,"tag":32,"props":677,"children":678},{"className":34},[679],{"type":15,"value":667},{"type":15,"value":681}," and\n",{"type":9,"tag":32,"props":683,"children":684},{"className":34},[685],{"type":15,"value":686},"[0, 1]",{"type":15,"value":688},". Multiply matching positions and add: ",{"type":9,"tag":32,"props":690,"children":691},{"className":34},[692],{"type":15,"value":693},"1×0.8 + 0×0.6 = 0.8",{"type":15,"value":695}," for the first pair,\nversus ",{"type":9,"tag":32,"props":697,"children":698},{"className":34},[699],{"type":15,"value":700},"1×0 + 0×1 = 0",{"type":15,"value":702}," for the second. Those vectors all have length 1, so these sums are\ntheir cosine similarities. The code makes the same kind of comparison with the real,\nlonger vectors and treats a negative result as zero. A missing semantic embedding leaves the\nother terms available. The illustrative settings above gave this term weight zero so the\nearlier arithmetic stays small.",{"type":9,"tag":18,"props":704,"children":705},{},[706,711],{"type":9,"tag":32,"props":707,"children":708},{"className":34},[709],{"type":15,"value":710},"default_scores",{"type":15,"value":712}," then applies the context's overall content weight and editorial type\npreference. A zero type preference reduces a score; it does not make an item ineligible. Facet\nmembership, not preference, controls which items can appear.",{"type":9,"tag":18,"props":714,"children":715},{},[716,718,723,725,729,731,735,737,741,743,747,749,753,754,759],{"type":15,"value":717},"Its multiplier is ",{"type":9,"tag":32,"props":719,"children":720},{"className":34},[721],{"type":15,"value":722},"content_weight × (1 + type_preference) \u002F 2",{"type":15,"value":724},". If the content weight is ",{"type":9,"tag":32,"props":726,"children":727},{"className":34},[728],{"type":15,"value":505},{"type":15,"value":730},"\nand the type preference is ",{"type":9,"tag":32,"props":732,"children":733},{"className":34},[734],{"type":15,"value":391},{"type":15,"value":736},", a similarity score of ",{"type":9,"tag":32,"props":738,"children":739},{"className":34},[740],{"type":15,"value":529},{"type":15,"value":742}," becomes ",{"type":9,"tag":32,"props":744,"children":745},{"className":34},[746],{"type":15,"value":570},{"type":15,"value":748},"; a score of\n",{"type":9,"tag":32,"props":750,"children":751},{"className":34},[752],{"type":15,"value":570},{"type":15,"value":742},{"type":9,"tag":32,"props":755,"children":756},{"className":34},[757],{"type":15,"value":758},"0.5625",{"type":15,"value":760},". This changes the score scale while preserving this example's order.",{"type":9,"tag":55,"props":762,"children":764},{"id":763},"why-this-path-exists",[765],{"type":15,"value":766},"Why this path exists",{"type":9,"tag":18,"props":768,"children":769},{},[770,774],{"type":9,"tag":32,"props":771,"children":772},{"className":34},[773],{"type":15,"value":498},{"type":15,"value":775}," may be newly published with no activity. On the next content-model build, its\nknown features can still place it near related items. The personalized path cannot learn an\nitem's behavioral history before any behavior exists.",{"type":9,"tag":18,"props":777,"children":778},{},[779,781,786,787,792,794,798,800,805,807,811,813,818],{"type":15,"value":780},"The content SavedModel exports ",{"type":9,"tag":32,"props":782,"children":783},{"className":34},[784],{"type":15,"value":785},"similar",{"type":15,"value":72},{"type":9,"tag":32,"props":788,"children":789},{"className":34},[790],{"type":15,"value":791},"similar_page",{"type":15,"value":793},". Both accept a source item plus\ncontext and language; ",{"type":9,"tag":32,"props":795,"children":796},{"className":34},[797],{"type":15,"value":791},{"type":15,"value":799}," also accepts an offset for another page and leaves the\nsource item out of its results. The exported ",{"type":9,"tag":32,"props":801,"children":802},{"className":34},[803],{"type":15,"value":804},"serving_default",{"type":15,"value":806}," is an alias for the item-keyed\n",{"type":9,"tag":32,"props":808,"children":809},{"className":34},[810],{"type":15,"value":785},{"type":15,"value":812}," function, not a user feed. An ",{"type":9,"tag":32,"props":814,"children":815},{"className":34},[816],{"type":15,"value":817},"explain",{"type":15,"value":819}," function reports the content contribution\nfor a source-candidate pair.",{"type":9,"tag":18,"props":821,"children":822},{},[823,828],{"type":9,"tag":24,"props":824,"children":825},{},[826],{"type":15,"value":827},"Check:",{"type":15,"value":829}," Does the content model learn a new 64-number item vector from feedback? No. This\ncontent-only artifact calculates exact weighted similarity from supplied item features. The\nnext chapter introduces the separate learned content tower inside the personalized artifact.",1790541170520]