[{"data":1,"prerenderedAt":1106},["ShallowReactive",2],{"recommendation-models-training":3},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8,17,52,59,90,102,133,230,260,284,317,327,334,346,468,556,575,581,593,602,626,631,640,673,684,690,728,842,847,856,909,915,1004,1035,1045,1051,1090],{"type":9,"tag":10,"props":11,"children":13},"element","h1",{"id":12},"train-retrieval-and-ranking",[14],{"type":15,"value":16},"text","Train retrieval and ranking",{"type":9,"tag":18,"props":19,"children":20},"p",{},[21,28,30,36,38,43,45,50],{"type":9,"tag":22,"props":23,"children":25},"code",{"className":24},"",[26],{"type":15,"value":27},"trainer\u002Ftraining.py → train_model",{"type":15,"value":29}," fits the personalized model in ",{"type":9,"tag":31,"props":32,"children":33},"strong",{},[34],{"type":15,"value":35},"two stages",{"type":15,"value":37},". The datasets\nchapter explains why the first stage sees only positive rows while the second sees all labeled\nrows. ",{"type":9,"tag":31,"props":39,"children":40},{},[41],{"type":15,"value":42},"Retrieval",{"type":15,"value":44}," learns to bring plausible articles forward from the eligible catalog.\n",{"type":9,"tag":31,"props":46,"children":47},{},[48],{"type":15,"value":49},"Ranking",{"type":15,"value":51}," gives those articles a more detailed order. Here is what each stage does.",{"type":9,"tag":53,"props":54,"children":56},"h2",{"id":55},"stage-1-fit-the-two-towers-for-retrieval",[57],{"type":15,"value":58},"Stage 1: fit the two towers for retrieval",{"type":9,"tag":18,"props":60,"children":61},{},[62,67,69,74,76,81,83,88],{"type":9,"tag":22,"props":63,"children":64},{"className":24},[65],{"type":15,"value":66},"BoscaRecommender.compute_loss",{"type":15,"value":68}," receives a ",{"type":9,"tag":31,"props":70,"children":71},{},[72],{"type":15,"value":73},"batch",{"type":15,"value":75},"—several training examples processed\ntogether—from ",{"type":9,"tag":22,"props":77,"children":78},{"className":24},[79],{"type":15,"value":80},"retrieval_dataset",{"type":15,"value":82},". In this function,\n",{"type":9,"tag":22,"props":84,"children":85},{"className":24},[86],{"type":15,"value":87},"features",{"type":15,"value":89}," is the dictionary of batched arrays introduced in chapter 3:",{"type":9,"tag":91,"props":92,"children":97},"pre",{"className":93,"code":95,"filename":-1,"highlights":-1,"language":96,"meta":24},[94],"language-python","user_embeddings = self.user_model(_user_features(features), training=training)\ncontent_embeddings = self.content_model(content_features, training=training)\nloss = self.task(user_embeddings, content_embeddings, candidate_ids=features[\"content_id\"],\n                 sample_weight=features.get(\"sample_weight\"), compute_metrics=not training)\n","python",[98],{"type":9,"tag":22,"props":99,"children":100},{"__ignoreMap":24},[101],{"type":15,"value":95},{"type":9,"tag":18,"props":103,"children":104},{},[105,110,112,117,119,124,126,131],{"type":9,"tag":22,"props":106,"children":107},{"className":24},[108],{"type":15,"value":109},"_user_features",{"type":15,"value":111}," selects the user tower's inputs from the batch. ",{"type":9,"tag":22,"props":113,"children":114},{"className":24},[115],{"type":15,"value":116},"content_features",{"type":15,"value":118}," is another\ndictionary, built between the two tower calls and omitted here, with item ID, type, language,\nand optional item features. The two tower calls output arrays of vectors: if a batch had 32\nrows and ",{"type":9,"tag":22,"props":120,"children":121},{"className":24},[122],{"type":15,"value":123},"embedding_dim",{"type":15,"value":125}," is 64, each output would have shape ",{"type":9,"tag":31,"props":127,"children":128},{},[129],{"type":15,"value":130},"32 × 64",{"type":15,"value":132},". The default batch size\nis 8,192 rows.",{"type":9,"tag":134,"props":135,"children":136},"table",{},[137,161],{"type":9,"tag":138,"props":139,"children":140},"thead",{},[141],{"type":9,"tag":142,"props":143,"children":144},"tr",{},[145,156],{"type":9,"tag":146,"props":147,"children":148},"th",{},[149,151],{"type":15,"value":150},"Argument to ",{"type":9,"tag":22,"props":152,"children":153},{"className":24},[154],{"type":15,"value":155},"self.task",{"type":9,"tag":146,"props":157,"children":158},{},[159],{"type":15,"value":160},"What it tells the retrieval task",{"type":9,"tag":162,"props":163,"children":164},"tbody",{},[165,182,198,214],{"type":9,"tag":142,"props":166,"children":167},{},[168,177],{"type":9,"tag":169,"props":170,"children":171},"td",{},[172],{"type":9,"tag":22,"props":173,"children":174},{"className":24},[175],{"type":15,"value":176},"user_embeddings",{"type":9,"tag":169,"props":178,"children":179},{},[180],{"type":15,"value":181},"Where each reader is in the learned vector space.",{"type":9,"tag":142,"props":183,"children":184},{},[185,193],{"type":9,"tag":169,"props":186,"children":187},{},[188],{"type":9,"tag":22,"props":189,"children":190},{"className":24},[191],{"type":15,"value":192},"content_embeddings",{"type":9,"tag":169,"props":194,"children":195},{},[196],{"type":15,"value":197},"Where each observed item is in the same vector space.",{"type":9,"tag":142,"props":199,"children":200},{},[201,209],{"type":9,"tag":169,"props":202,"children":203},{},[204],{"type":9,"tag":22,"props":205,"children":206},{"className":24},[207],{"type":15,"value":208},"candidate_ids",{"type":9,"tag":169,"props":210,"children":211},{},[212],{"type":15,"value":213},"Which item each observed vector belongs to.",{"type":9,"tag":142,"props":215,"children":216},{},[217,225],{"type":9,"tag":169,"props":218,"children":219},{},[220],{"type":9,"tag":22,"props":221,"children":222},{"className":24},[223],{"type":15,"value":224},"sample_weight",{"type":9,"tag":169,"props":226,"children":227},{},[228],{"type":15,"value":229},"How strongly each selected positive row should affect the loss. In dataset preparation, this was multiplied by the positive row's label.",{"type":9,"tag":18,"props":231,"children":232},{},[233,237,239,244,246,251,253,258],{"type":9,"tag":22,"props":234,"children":235},{"className":24},[236],{"type":15,"value":155},{"type":15,"value":238}," is a TensorFlow Recommenders ",{"type":9,"tag":31,"props":240,"children":241},{},[242],{"type":15,"value":243},"task",{"type":15,"value":245},": a ready-made object that holds a loss\nformula plus optional ",{"type":9,"tag":31,"props":247,"children":248},{},[249],{"type":15,"value":250},"metrics",{"type":15,"value":252},". A metric is a number reported so people can watch progress.\nUnlike the loss, it does not change any weights. ",{"type":9,"tag":22,"props":254,"children":255},{"className":24},[256],{"type":15,"value":257},"compute_metrics=not training",{"type":15,"value":259}," skips the\nmetrics while fitting, because this task's metric scores every item in the catalog and would\nslow each step.",{"type":9,"tag":18,"props":261,"children":262},{},[263,268,270,275,277,282],{"type":9,"tag":31,"props":264,"children":265},{},[266],{"type":15,"value":267},"Loss",{"type":15,"value":269}," is a number the optimizer tries to reduce by changing trainable layer weights. The\nretrieval task teaches observed positive pairs to match more strongly than the other items in\nthe same batch. Those other items act as the wrong answers. When two readers in one batch both\ncompleted ",{"type":9,"tag":22,"props":271,"children":272},{"className":24},[273],{"type":15,"value":274},"article-9",{"type":15,"value":276},", it appears twice. ",{"type":9,"tag":22,"props":278,"children":279},{"className":24},[280],{"type":15,"value":281},"remove_accidental_hits=True",{"type":15,"value":283}," stops each copy from\ncounting as a wrong answer for the other reader.",{"type":9,"tag":18,"props":285,"children":286},{},[287,289,294,296,301,303,308,310,315],{"type":15,"value":288},"The trainer uses ",{"type":9,"tag":31,"props":290,"children":291},{},[292],{"type":15,"value":293},"Adagrad",{"type":15,"value":295},", an ",{"type":9,"tag":31,"props":297,"children":298},{},[299],{"type":15,"value":300},"optimizer",{"type":15,"value":302},": the algorithm that turns gradients into actual\nweight changes. Each step moves a weight against its gradient by a small amount. The base size\nof that step is the ",{"type":9,"tag":31,"props":304,"children":305},{},[306],{"type":15,"value":307},"learning rate",{"type":15,"value":309},", ",{"type":9,"tag":22,"props":311,"children":312},{"className":24},[313],{"type":15,"value":314},"0.01",{"type":15,"value":316}," by default. Adagrad also keeps a running total of\neach weight's squared gradients and divides that weight's step by the total's square root.\nWeights that have already received large or frequent updates take smaller steps, while rarely\nupdated ones take larger steps. That property makes Adagrad a common choice for embedding\ntables, where most ID rows appear in only a few batches.",{"type":9,"tag":18,"props":318,"children":319},{},[320,325],{"type":9,"tag":22,"props":321,"children":322},{"className":24},[323],{"type":15,"value":324},"mean_loss",{"type":15,"value":326}," is the average loss per example over an epoch; it is tracked to decide when to stop\nfitting. It is a training measure, not proof that live recommendations improved.",{"type":9,"tag":328,"props":329,"children":331},"h3",{"id":330},"do-the-retrieval-math-with-two-numbers",[332],{"type":15,"value":333},"Do the retrieval math with two numbers",{"type":9,"tag":18,"props":335,"children":336},{},[337,339,344],{"type":15,"value":338},"The retrieval task scores a reader-item pair with a ",{"type":9,"tag":31,"props":340,"children":341},{},[342],{"type":15,"value":343},"dot product",{"type":15,"value":345},": multiply corresponding\nvector positions and add them. Use tiny vectors only to see the arithmetic; real outputs are\nusually 64 numbers wide.",{"type":9,"tag":134,"props":347,"children":348},{},[349,376],{"type":9,"tag":138,"props":350,"children":351},{},[352],{"type":9,"tag":142,"props":353,"children":354},{},[355,360,365,370],{"type":9,"tag":146,"props":356,"children":357},{},[358],{"type":15,"value":359},"Pair",{"type":9,"tag":146,"props":361,"children":362},{},[363],{"type":15,"value":364},"Toy vectors",{"type":9,"tag":146,"props":366,"children":367},{},[368],{"type":15,"value":369},"Dot-product arithmetic",{"type":9,"tag":146,"props":371,"children":373},{"align":372},"right",[374],{"type":15,"value":375},"Score",{"type":9,"tag":162,"props":377,"children":378},{},[379,427],{"type":9,"tag":142,"props":380,"children":381},{},[382,396,411,419],{"type":9,"tag":169,"props":383,"children":384},{},[385,390,392],{"type":9,"tag":22,"props":386,"children":387},{"className":24},[388],{"type":15,"value":389},"reader-1",{"type":15,"value":391}," with its observed positive ",{"type":9,"tag":22,"props":393,"children":394},{"className":24},[395],{"type":15,"value":274},{"type":9,"tag":169,"props":397,"children":398},{},[399,404,406],{"type":9,"tag":22,"props":400,"children":401},{"className":24},[402],{"type":15,"value":403},"[2, 1]",{"type":15,"value":405}," and ",{"type":9,"tag":22,"props":407,"children":408},{"className":24},[409],{"type":15,"value":410},"[1, 1]",{"type":9,"tag":169,"props":412,"children":413},{},[414],{"type":9,"tag":22,"props":415,"children":416},{"className":24},[417],{"type":15,"value":418},"2×1 + 1×1",{"type":9,"tag":169,"props":420,"children":421},{"align":372},[422],{"type":9,"tag":22,"props":423,"children":424},{"className":24},[425],{"type":15,"value":426},"3",{"type":9,"tag":142,"props":428,"children":429},{},[430,439,452,460],{"type":9,"tag":169,"props":431,"children":432},{},[433,437],{"type":9,"tag":22,"props":434,"children":435},{"className":24},[436],{"type":15,"value":389},{"type":15,"value":438}," with another item's vector",{"type":9,"tag":169,"props":440,"children":441},{},[442,446,447],{"type":9,"tag":22,"props":443,"children":444},{"className":24},[445],{"type":15,"value":403},{"type":15,"value":405},{"type":9,"tag":22,"props":448,"children":449},{"className":24},[450],{"type":15,"value":451},"[0, 1]",{"type":9,"tag":169,"props":453,"children":454},{},[455],{"type":9,"tag":22,"props":456,"children":457},{"className":24},[458],{"type":15,"value":459},"2×0 + 1×1",{"type":9,"tag":169,"props":461,"children":462},{"align":372},[463],{"type":9,"tag":22,"props":464,"children":465},{"className":24},[466],{"type":15,"value":467},"1",{"type":9,"tag":18,"props":469,"children":470},{},[471,473,478,480,485,487,492,494,499,501,506,507,512,514,519,521,526,528,533,535,540,542,547,549,554],{"type":15,"value":472},"The task compares the observed item with other candidates in the training batch. It converts\nthese scores into a share for the observed item using ",{"type":9,"tag":31,"props":474,"children":475},{},[476],{"type":15,"value":477},"softmax",{"type":15,"value":479},". First it applies an\n",{"type":9,"tag":31,"props":481,"children":482},{},[483],{"type":15,"value":484},"exponential",{"type":15,"value":486}," to each score: ",{"type":9,"tag":22,"props":488,"children":489},{"className":24},[490],{"type":15,"value":491},"e",{"type":15,"value":493}," is about ",{"type":9,"tag":22,"props":495,"children":496},{"className":24},[497],{"type":15,"value":498},"2.718",{"type":15,"value":500},", so ",{"type":9,"tag":22,"props":502,"children":503},{"className":24},[504],{"type":15,"value":505},"e³ ≈ 20.09",{"type":15,"value":405},{"type":9,"tag":22,"props":508,"children":509},{"className":24},[510],{"type":15,"value":511},"e¹ ≈ 2.72",{"type":15,"value":513},". Then\nit divides the observed item's value by the total: ",{"type":9,"tag":22,"props":515,"children":516},{"className":24},[517],{"type":15,"value":518},"20.09 \u002F (20.09 + 2.72) ≈ 0.88",{"type":15,"value":520},".\nLarger scores receive a larger share. The loss for this example is ",{"type":9,"tag":22,"props":522,"children":523},{"className":24},[524],{"type":15,"value":525},"−ln(0.88) ≈ 0.13",{"type":15,"value":527},";\n",{"type":9,"tag":22,"props":529,"children":530},{"className":24},[531],{"type":15,"value":532},"ln",{"type":15,"value":534}," is the natural logarithm, the reverse of the exponential. You can read ",{"type":9,"tag":22,"props":536,"children":537},{"className":24},[538],{"type":15,"value":539},"−ln(share)",{"type":15,"value":541},"\nas a penalty: a share near one gives a small penalty, and a share near zero gives a large one.\nIf this row's retrieval sample weight is ",{"type":9,"tag":22,"props":543,"children":544},{"className":24},[545],{"type":15,"value":546},"2",{"type":15,"value":548},", its contribution is about ",{"type":9,"tag":22,"props":550,"children":551},{"className":24},[552],{"type":15,"value":553},"2 × 0.13 = 0.26",{"type":15,"value":555},".\nReal batches contain more candidates, and the task sums their weighted losses. The optimizer\nadjusts tower weights so observed pairs tend to score higher than alternatives.",{"type":9,"tag":18,"props":557,"children":558},{},[559,561,566,568,573],{"type":15,"value":560},"This penalty has a name: ",{"type":9,"tag":31,"props":562,"children":563},{},[564],{"type":15,"value":565},"cross-entropy",{"type":15,"value":567},". It is ",{"type":9,"tag":22,"props":569,"children":570},{"className":24},[571],{"type":15,"value":572},"−ln",{"type":15,"value":574}," of the share the model gave to the\nanswer that actually happened. Here the answer is “which of the batch's items did this reader\nengage with?”, so there are many possible answers and softmax splits the shares among them.\nThe ranking stage below uses a two-answer version of the same idea.",{"type":9,"tag":53,"props":576,"children":578},{"id":577},"stage-2-freeze-the-towers-and-fit-ranking",[579],{"type":15,"value":580},"Stage 2: freeze the towers and fit ranking",{"type":9,"tag":18,"props":582,"children":583},{},[584,586,591],{"type":15,"value":585},"After retrieval, the code changes the towers' ",{"type":9,"tag":22,"props":587,"children":588},{"className":24},[589],{"type":15,"value":590},"trainable",{"type":15,"value":592}," flags:",{"type":9,"tag":91,"props":594,"children":597},{"className":595,"code":596,"filename":-1,"highlights":-1,"language":96,"meta":24},[94],"user_model.trainable = False\ncontent_model.trainable = False\nranking_model = RankingModel(\n    user_model, content_model, weights, vocabs.get(\"editorial_types_by_id\"),\n    source_query_model=build_item_query_model(content_model, content_dataset),\n    content_similarity=content_similarity,\n    behavior_model=behavior_model,\n)\nranker = BoscaRanker(ranking_model)\n",[598],{"type":9,"tag":22,"props":599,"children":600},{"__ignoreMap":24},[601],{"type":15,"value":596},{"type":9,"tag":18,"props":603,"children":604},{},[605,610,612,617,619,624],{"type":9,"tag":31,"props":606,"children":607},{},[608],{"type":15,"value":609},"Freeze",{"type":15,"value":611}," means the next optimizer step does not change the towers' learned weights. The item\nand reader vectors remain the ones retrieval just learned. ",{"type":9,"tag":22,"props":613,"children":614},{"className":24},[615],{"type":15,"value":616},"RankingModel",{"type":15,"value":618}," adds scoring logic\nthat can use a content baseline, the tower match, source-item relevance, and captured\nbehavioral signals. ",{"type":9,"tag":22,"props":620,"children":621},{"className":24},[622],{"type":15,"value":623},"behavior_model",{"type":15,"value":625}," is built from the saved behavior snapshot for this run.",{"type":9,"tag":18,"props":627,"children":628},{},[629],{"type":15,"value":630},"For the basic reader-item match, the ranking model uses a dot product plus a learned\ncorrection:",{"type":9,"tag":91,"props":632,"children":635},{"className":633,"code":634,"filename":-1,"highlights":-1,"language":96,"meta":24},[94],"relevance = tf.reduce_sum(user_embedding * content_embedding, axis=1, keepdims=True)\nreturn relevance + self.score(tf.concat([user_embedding, content_embedding], axis=1), training=training)\n",[636],{"type":9,"tag":22,"props":637,"children":638},{"__ignoreMap":24},[639],{"type":15,"value":634},{"type":9,"tag":18,"props":641,"children":642},{},[643,645,650,652,657,659,664,666,671],{"type":15,"value":644},"Multiplication pairs each number in the reader vector with the number in the same position\nof the item vector. ",{"type":9,"tag":22,"props":646,"children":647},{"className":24},[648],{"type":15,"value":649},"reduce_sum",{"type":15,"value":651}," adds those products into one score per pair. The ",{"type":9,"tag":22,"props":653,"children":654},{"className":24},[655],{"type":15,"value":656},"self.score",{"type":15,"value":658},"\nnetwork looks at both full vectors and learns a correction. The complete ",{"type":9,"tag":22,"props":660,"children":661},{"className":24},[662],{"type":15,"value":663},"score_pairs",{"type":15,"value":665},"\nfunction combines this with the saved context's content and behavior terms. A score is used to\n",{"type":9,"tag":31,"props":667,"children":668},{},[669],{"type":15,"value":670},"order",{"type":15,"value":672}," eligible items; a larger score comes first.",{"type":9,"tag":18,"props":674,"children":675},{},[676,678,682],{"type":15,"value":677},"The dot-product table above explains the ",{"type":9,"tag":22,"props":679,"children":680},{"className":24},[681],{"type":15,"value":649},{"type":15,"value":683}," part of this code. The ranking score is\nmore than that dot product: the learned correction, content baseline, and available behavior\nsignals can raise or lower the final result.",{"type":9,"tag":53,"props":685,"children":687},{"id":686},"learn-from-all-labels-including-rejection",[688],{"type":15,"value":689},"Learn from all labels, including rejection",{"type":9,"tag":18,"props":691,"children":692},{},[693,698,700,705,707,712,714,719,721,726],{"type":9,"tag":22,"props":694,"children":695},{"className":24},[696],{"type":15,"value":697},"BoscaRanker",{"type":15,"value":699}," receives ",{"type":9,"tag":22,"props":701,"children":702},{"className":24},[703],{"type":15,"value":704},"ranking_dataset",{"type":15,"value":706},", so the dismissal of ",{"type":9,"tag":22,"props":708,"children":709},{"className":24},[710],{"type":15,"value":711},"article-10",{"type":15,"value":713}," and the weak rating\nof ",{"type":9,"tag":22,"props":715,"children":716},{"className":24},[717],{"type":15,"value":718},"article-11",{"type":15,"value":720}," are present. Its task uses weighted ",{"type":9,"tag":31,"props":722,"children":723},{},[724],{"type":15,"value":725},"binary cross-entropy",{"type":15,"value":727},".",{"type":9,"tag":729,"props":730,"children":731},"ul",{},[732,763,805,826],{"type":9,"tag":733,"props":734,"children":735},"li",{},[736,741,743,747,749,754,756,761],{"type":9,"tag":31,"props":737,"children":738},{},[739],{"type":15,"value":740},"Binary",{"type":15,"value":742}," refers to two endpoints: positive engagement (",{"type":9,"tag":22,"props":744,"children":745},{"className":24},[746],{"type":15,"value":467},{"type":15,"value":748},") and no engagement (",{"type":9,"tag":22,"props":750,"children":751},{"className":24},[752],{"type":15,"value":753},"0",{"type":15,"value":755},"). A\nprepared label can also fall between them, like Sam's ",{"type":9,"tag":22,"props":757,"children":758},{"className":24},[759],{"type":15,"value":760},"0.25",{"type":15,"value":762}," rating. Ranking scores each\nreader-article pair; retrieval compares an observed item with other items in a batch.",{"type":9,"tag":733,"props":764,"children":765},{},[766,771,773,777,779,784,786,790,792,797,799,803],{"type":9,"tag":31,"props":767,"children":768},{},[769],{"type":15,"value":770},"Cross-entropy",{"type":15,"value":772}," penalizes a score that does not fit the label. For label ",{"type":9,"tag":22,"props":774,"children":775},{"className":24},[776],{"type":15,"value":467},{"type":15,"value":778},", the penalty\nis ",{"type":9,"tag":22,"props":780,"children":781},{"className":24},[782],{"type":15,"value":783},"−ln(p)",{"type":15,"value":785},"; for label ",{"type":9,"tag":22,"props":787,"children":788},{"className":24},[789],{"type":15,"value":753},{"type":15,"value":791},", it is ",{"type":9,"tag":22,"props":793,"children":794},{"className":24},[795],{"type":15,"value":796},"−ln(1 − p)",{"type":15,"value":798},". A label between them blends those penalties.\nHere ",{"type":9,"tag":22,"props":800,"children":801},{"className":24},[802],{"type":15,"value":18},{"type":15,"value":804}," is a temporary 0–1 value calculated from the score for the loss, not a measured\nchance of engagement. Retrieval uses a related penalty across many items.",{"type":9,"tag":733,"props":806,"children":807},{},[808,813,815,819,820,824],{"type":9,"tag":31,"props":809,"children":810},{},[811],{"type":15,"value":812},"What it is used for:",{"type":15,"value":814}," the loss works with both the ",{"type":9,"tag":22,"props":816,"children":817},{"className":24},[818],{"type":15,"value":753},{"type":15,"value":405},{"type":9,"tag":22,"props":821,"children":822},{"className":24},[823],{"type":15,"value":467},{"type":15,"value":825}," endpoints and the partial\nlabels made during observation preparation. A score strongly opposed to an endpoint label\nreceives a large penalty.",{"type":9,"tag":733,"props":827,"children":828},{},[829,834,836,840],{"type":9,"tag":31,"props":830,"children":831},{},[832],{"type":15,"value":833},"Weighted",{"type":15,"value":835}," means each row's penalty is multiplied by its ",{"type":9,"tag":22,"props":837,"children":838},{"className":24},[839],{"type":15,"value":224},{"type":15,"value":841}," before rows are\naveraged.",{"type":9,"tag":18,"props":843,"children":844},{},[845],{"type":15,"value":846},"The next subsection works through the arithmetic. Here is the code:",{"type":9,"tag":91,"props":848,"children":851},{"className":849,"code":850,"filename":-1,"highlights":-1,"language":96,"meta":24},[94],"self.task = tfrs.tasks.Ranking(\n    loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n    metrics=[tf.keras.metrics.BinaryCrossentropy(from_logits=True)],\n)\nloss = self.task(labels=labels, predictions=predictions, sample_weight=sample_weight)\n",[852],{"type":9,"tag":22,"props":853,"children":854},{"__ignoreMap":24},[855],{"type":15,"value":850},{"type":9,"tag":18,"props":857,"children":858},{},[859,861,866,868,873,875,879,881,886,888,893,895,900,902,907],{"type":15,"value":860},"The ",{"type":9,"tag":22,"props":862,"children":863},{"className":24},[864],{"type":15,"value":865},"label",{"type":15,"value":867}," is the known training target from observations; ",{"type":9,"tag":22,"props":869,"children":870},{"className":24},[871],{"type":15,"value":872},"predictions",{"type":15,"value":874}," are current ranking\nscores. ",{"type":9,"tag":22,"props":876,"children":877},{"className":24},[878],{"type":15,"value":224},{"type":15,"value":880}," controls each row's influence. A ",{"type":9,"tag":31,"props":882,"children":883},{},[884],{"type":15,"value":885},"logit",{"type":15,"value":887}," is a raw score that may be\nnegative or positive. ",{"type":9,"tag":22,"props":889,"children":890},{"className":24},[891],{"type":15,"value":892},"from_logits=True",{"type":15,"value":894}," tells the loss function it will receive those raw\nscores. The served score remains a logit: use it to order articles, not as a claim that the\nnumber is a measured chance of engagement. ",{"type":9,"tag":22,"props":896,"children":897},{"className":24},[898],{"type":15,"value":899},"metrics=[...]",{"type":15,"value":901}," reports the same cross-entropy for\nmonitoring; only ",{"type":9,"tag":22,"props":903,"children":904},{"className":24},[905],{"type":15,"value":906},"loss=",{"type":15,"value":908}," changes weights. The optimizer changes the ranking head's weights to\nreduce that loss.",{"type":9,"tag":328,"props":910,"children":912},{"id":911},"what-does-that-penalty-do",[913],{"type":15,"value":914},"What does that penalty do?",{"type":9,"tag":18,"props":916,"children":917},{},[918,920,925,927,932,934,939,941,946,948,953,954,959,961,965,967,971,973,977,979,983,985,990,992,996,998,1003],{"type":15,"value":919},"For calculating loss, the function temporarily converts a logit ",{"type":9,"tag":22,"props":921,"children":922},{"className":24},[923],{"type":15,"value":924},"s",{"type":15,"value":926}," to a number between zero\nand one with ",{"type":9,"tag":22,"props":928,"children":929},{"className":24},[930],{"type":15,"value":931},"sigmoid(s) = 1 \u002F (1 + e^(−s))",{"type":15,"value":933},". A score of ",{"type":9,"tag":22,"props":935,"children":936},{"className":24},[937],{"type":15,"value":938},"+2",{"type":15,"value":940}," gives about ",{"type":9,"tag":22,"props":942,"children":943},{"className":24},[944],{"type":15,"value":945},"0.88",{"type":15,"value":947},"; a score of\n",{"type":9,"tag":22,"props":949,"children":950},{"className":24},[951],{"type":15,"value":952},"−2",{"type":15,"value":940},{"type":9,"tag":22,"props":955,"children":956},{"className":24},[957],{"type":15,"value":958},"0.12",{"type":15,"value":960},". A completed item with label ",{"type":9,"tag":22,"props":962,"children":963},{"className":24},[964],{"type":15,"value":467},{"type":15,"value":966}," gets a small penalty if the score is\n",{"type":9,"tag":22,"props":968,"children":969},{"className":24},[970],{"type":15,"value":938},{"type":15,"value":972},": ",{"type":9,"tag":22,"props":974,"children":975},{"className":24},[976],{"type":15,"value":525},{"type":15,"value":978},". A dismissed item with label ",{"type":9,"tag":22,"props":980,"children":981},{"className":24},[982],{"type":15,"value":753},{"type":15,"value":984}," gets a large penalty for that same\nscore: ",{"type":9,"tag":22,"props":986,"children":987},{"className":24},[988],{"type":15,"value":989},"−ln(1 − 0.88) ≈ 2.13",{"type":15,"value":991},". Moving the dismissal's score toward ",{"type":9,"tag":22,"props":993,"children":994},{"className":24},[995],{"type":15,"value":952},{"type":15,"value":997}," would reduce its\npenalty to about ",{"type":9,"tag":22,"props":999,"children":1000},{"className":24},[1001],{"type":15,"value":1002},"0.13",{"type":15,"value":727},{"type":9,"tag":18,"props":1005,"children":1006},{},[1007,1009,1013,1015,1020,1022,1027,1029,1033],{"type":15,"value":1008},"For a partial target such as the ",{"type":9,"tag":22,"props":1010,"children":1011},{"className":24},[1012],{"type":15,"value":760},{"type":15,"value":1014}," rating, the penalty blends both sides:\n",{"type":9,"tag":22,"props":1016,"children":1017},{"className":24},[1018],{"type":15,"value":1019},"−[0.25 × ln(p) + 0.75 × ln(1 − p)]",{"type":15,"value":1021},", where ",{"type":9,"tag":22,"props":1023,"children":1024},{"className":24},[1025],{"type":15,"value":1026},"p = sigmoid(score)",{"type":15,"value":1028},". Each row's result is\nmultiplied by its ",{"type":9,"tag":22,"props":1030,"children":1031},{"className":24},[1032],{"type":15,"value":224},{"type":15,"value":1034},". The model therefore learns from both strong responses and\nweaker ratings. The conversion is internal to the loss calculation; serving returns the\noriginal logit for ordering.",{"type":9,"tag":18,"props":1036,"children":1037},{},[1038,1043],{"type":9,"tag":31,"props":1039,"children":1040},{},[1041],{"type":15,"value":1042},"Why two stages?",{"type":15,"value":1044}," Retrieval learns broadly useful vectors from positive examples. Ranking\nthen learns a more detailed order from both positive and negative evidence without changing\nthe vectors and indexes built by retrieval.",{"type":9,"tag":53,"props":1046,"children":1048},{"id":1047},"stop-when-training-loss-stops-improving",[1049],{"type":15,"value":1050},"Stop when training loss stops improving",{"type":9,"tag":18,"props":1052,"children":1053},{},[1054,1056,1061,1063,1068,1070,1075,1077,1082,1084,1088],{"type":15,"value":1055},"Each stage is fitted by ",{"type":9,"tag":22,"props":1057,"children":1058},{"className":24},[1059],{"type":15,"value":1060},"_fit_to_convergence",{"type":15,"value":1062},", which calls TensorFlow's ",{"type":9,"tag":22,"props":1064,"children":1065},{"className":24},[1066],{"type":15,"value":1067},"fit",{"type":15,"value":1069}," (the prologue\ndefines ",{"type":9,"tag":31,"props":1071,"children":1072},{},[1073],{"type":15,"value":1074},"fitting",{"type":15,"value":1076},"). An ",{"type":9,"tag":31,"props":1078,"children":1079},{},[1080],{"type":15,"value":1081},"epoch",{"type":15,"value":1083}," is one pass over the training examples. The helper caches\nthe examples in memory after the first pass and reshuffles them before every epoch.\nReshuffling matters for retrieval: the wrong answers for each row are the other items in its\nbatch, so new batches give it new comparisons. The configured epoch count, 50 by default, is a\nmaximum. Early stopping watches ",{"type":9,"tag":22,"props":1085,"children":1086},{"className":24},[1087],{"type":15,"value":324},{"type":15,"value":1089},", waits up to five epochs without sufficient improvement, and restores the best\nweights seen during that fit. This controls fitting time; it is not a recommendation-quality\ntest.",{"type":9,"tag":18,"props":1091,"children":1092},{},[1093,1098,1100,1104],{"type":9,"tag":31,"props":1094,"children":1095},{},[1096],{"type":15,"value":1097},"Check:",{"type":15,"value":1099}," Why is ",{"type":9,"tag":22,"props":1101,"children":1102},{"className":24},[1103],{"type":15,"value":711},{"type":15,"value":1105}," in ranking but absent from retrieval? Its dismissal is useful\nnegative evidence for ordering, but it must not teach the retrieval towers that it is a\ndesirable reader-item pair.",1790541170522]