<jupyter_start><jupyter_text>Learning Virtual ValuesIn this tutorial, we will extend the ideas from the [previous tutorial](learning-position-auctions.ipynb).  We will consider position auctions, like those found in paid search marketplaces, but focus on virtual value transformations rather than payment and allocation networks. Motivating exampleConsider a two-bidder, two-slot position auction where the values for the two bidders are correlated.  There is a signal $c\sim U[0,1]$, which we intepret as a _conversion rate_.  The value of the item for bidder 1 is a random variable $v_1 = x_1 c$ where $x_1 \sim U[0,1]$, similarly for bidder 2.The first slot has a click-through-rate (quality) of 1.  The second slot has a click-through-rate of 0.5.  A bidder may purchase one slot only, so we can consider this a special case of a multi-item, unit-demand scenario. Architectures and supporting functionsIn this tutorial, we will make use of Monotonic networks. PreliminariesWe will make heavy use of numpy, pandas, and pytorch.<jupyter_code>import numpy as np
import pandas as pd

import torch
import torch.nn as nn
import torch.nn.functional as F

device = torch.device("cuda:1") if torch.cuda.is_available() else torch.device('cpu')<jupyter_output><empty_output><jupyter_text>We will also make use of matplotlib and seaborn for visualization:<jupyter_code>import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Common componentsWe add common `dmch` components:<jupyter_code>import dmch
from dmch import Mechanism
from dmch import SequentialMechanism
from dmch import build_spa, create_spa_mechanism<jupyter_output><empty_output><jupyter_text>Now we define the auction scenario:<jupyter_code># Number of bidders
bidders = 2

# Pr(click|position)
slot_weights = [1, 0.5]

# Number of slots
slots = len(slot_weights)<jupyter_output><empty_output><jupyter_text>GSPFor comparison, we define the GSP mechanism by using sequantial second-price auctions (SPA):<jupyter_code>def create_gsp_mechanism():
    mbuilder = dmch.build_spa(bidders, context_features=1)
    return mbuilder.build_sequential(slots,weights=slot_weights)<jupyter_output><empty_output><jupyter_text>MyersonNetThe allocation network is defined as follows:<jupyter_code>def create_myerson_net(context_features=0, hidden_features=100, linear_functions=1, groups=1):
    mbuilder = dmch.build_spa(bidders, context_features=context_features)
    
    mbuilder.set_virtual_function(
        hidden_features=hidden_features,
        linear_functions=linear_functions,
        groups=groups)
    
    return mbuilder.build_sequential(slots,weights=slot_weights)<jupyter_output><empty_output><jupyter_text>Auction for the motivating example The networks will train on data that is sampled from the value distribution, which is loaded into a `DataLoader`.<jupyter_code>import torch.utils.data as data_utils

epochs = 500
sample_size = 2**15
batch_size = 2**12

indepedent_components = torch.rand(sample_size, bidders)
common_components = torch.rand(sample_size, 1)
inputs = indepedent_components * common_components
inputs_with_common = torch.cat([indepedent_components * common_components, common_components], dim=1)

inputs_loader=data_utils.DataLoader(
    data_utils.TensorDataset(inputs),
    batch_size=batch_size)

inputs_with_common_loader=data_utils.DataLoader(
    data_utils.TensorDataset(inputs_with_common),
    batch_size=batch_size)<jupyter_output><empty_output><jupyter_text>Before training the networks, let's establish a GSP baseline.<jupyter_code>gsp = create_gsp_mechanism()
gsp_report = pd.DataFrame(
    dmch.evaluate(
        gsp,
        inputs_loader,
        bidders,
        epochs=epochs,
        device=device,
        misreport_lr=1e-1,
        misreport_epochs=100))<jupyter_output><empty_output><jupyter_text>We now create a simple MyersonNet instance.<jupyter_code>myerson_net = create_myerson_net().to(device)<jupyter_output><empty_output><jupyter_text>We loop over the data for a number of epochs and record traces of the networks learning.<jupyter_code>myerson_net_report = pd.DataFrame(dmch.train(
    myerson_net, # the mechanism
    inputs_loader, # the bid inputs
    bidders, # the number of bidders
    epochs=epochs, # the total number of loops over the data
    device=device, # the device
    rho=1e2, # the rho parameter for the augmented Lagrangian method
    mechanism_lr=1e-3, # the learning rate for the mechanism networks
    consider_dsic=False,
    consider_ir=False))<jupyter_output>100%|██████████| 500/500 [01:59<00:00,  3.94it/s]<jupyter_text>We can also test supplying MyersonNet with the common signal:<jupyter_code>myerson_net_with_common = create_myerson_net(context_features=1).to(device)

myerson_net_with_common_report = pd.DataFrame(dmch.train(
    myerson_net_with_common, # the mechanism
    inputs_with_common_loader, # the bid inputs
    bidders, # the number of bidders
    epochs=epochs, # the total number of loops over the data
    device=device, # the device
    rho=1e2, # the rho parameter for the augmented Lagrangian method
    mechanism_lr=1e-3, # the learning rate for the mechanism networks
    consider_dsic=False,
    consider_ir=False))<jupyter_output>100%|██████████| 500/500 [02:25<00:00,  3.41it/s]<jupyter_text>Let's review the revenue of the network: _MyersonNet_ exceeds _GSP_ revenue and _MyersonNet with common_.  Adding the common signal as context allows MyersonNet to condition on that context, which reduces a problem of finding an optimal reserve for two IID bidders from U[0,common].<jupyter_code>fig, ax = plt.subplots(figsize=(8,6));
ax.axhline(y=gsp_report.mean()[['revenue']].values, color='g', label='GSP');
ax.plot(myerson_net_report.groupby('epoch')[['revenue']].mean(), label='MyersonNet');
ax.plot(myerson_net_with_common_report.groupby('epoch')[['revenue']].mean(), label='MyersonNet with common');
ax.legend();
def plot_mechanism(mechanism, common=1):
    X, Y = np.meshgrid(
        np.arange(0.0, common, 0.01),
        np.arange(0.0, common, 0.01))
    
    n = X.shape[0]*X.shape[1]
    
    inputs = torch.cat(
        (torch.from_numpy(np.reshape(X, (n,1))),
         torch.from_numpy(np.reshape(Y, (n,1)))),
        dim=1).float().to(device)
    
    inputs = torch.cat((inputs,torch.zeros(n,1).float().to(device)),dim=1)
    inputs[:,2] = common
    
    allocation, payment = mechanism(inputs)
    
    allocation_levels = np.arange(0, 1.5, 0.01)
    bid_levels = np.arange(0, 1.0, 0.01)
    fig, axes = plt.subplots(nrows=2, ncols=bidders+1, figsize=(20,10));
    
    def plot_contour(tensor,axis_index,bidder_title,main_title,levels):
        for bidder in range(bidders):
            CS = axes[axis_index,bidder].tricontourf(
                inputs[:,0].cpu().numpy(),
                inputs[:,1].cpu().numpy(),
                tensor[:,bidder].detach().cpu().numpy(),
                levels=levels,
                cmap="RdBu_r",
                extend='both');
            fig.colorbar(CS, ax=axes[axis_index,bidder]);
            axes[axis_index,bidder].set_title(bidder_title+str(bidder));
        CS = axes[axis_index,bidders].tricontourf(
            inputs[:,0].cpu().numpy(),
            inputs[:,1].cpu().numpy(),
            tensor.sum(dim=1).detach().cpu().numpy(),
            levels=levels,
            cmap="RdBu_r",
            extend='both');
        fig.colorbar(CS, ax=axes[axis_index,bidders]);
        axes[axis_index,bidders].set_title(main_title);
    plot_contour(allocation,0,'Allocation to bidder ','Allocation to all bidders',allocation_levels)
    plot_contour(payment,1,'Payment from bidder ','Payment from all bidders',allocation_levels)
plot_mechanism(gsp,common=1)
plot_mechanism(myerson_net,common=1)
plot_mechanism(myerson_net_with_common,common=1)
plot_mechanism(gsp,common=0.66)
plot_mechanism(myerson_net,common=0.66)
plot_mechanism(myerson_net_with_common,common=0.66)
plot_mechanism(gsp,common=0.33)
plot_mechanism(myerson_net,common=0.33)
plot_mechanism(myerson_net_with_common,common=0.33)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10655636498333128, 'fraction_numerical': 0.019385109272749723, 'mean_word_length': 4.908096280087527, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 47, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '6661', 'license': 'Apache-2.0', 'n_tokens_mistral': 2813, 'n_tokens_neox': 2709, 'n_words': 611, 'path': 'learning-virtual-values.ipynb', 'repo_name': 'pjordan/learned-position-auctions', 'size': '8099'}