Dans l'article précédent (https://qiita.com/WKudo/items/efd0841959822572fac1), j'ai présenté un aperçu du langage de programmation probabiliste Pyro et comment échantillonner des échantillons à l'aide de Pyro. Cette fois, en utilisant Pyro, "Introduction à l'analyse des données par modélisation statistique bayésienne à partir de Practical Data Science Series R et Stan" (ci-après dénommé livre de référence) Sur la base du premier exemple (chapitre 3, partie 2, modèle de régression simple) dans (Masu.), Nous allons l'implémenter dans Pyro. L'implémentation est disponible sur 3-2 Simple Regression Model (Google Colaboratory).
Les données traitées cette fois (voir livre 3-2) sont ** Relation entre les ventes de bière et la température **. La modélisation statistique construit un modèle qui explique les ventes en fonction de la température. Des exemples de données réelles et de diagrammes de dispersion sont les suivants.
#Lecture des données
beer_sales_2 = pd.read_csv(
'https://raw.githubusercontent.com/logics-of-blue/book-r-stan-bayesian-model-intro/master/book-data/3-2-1-beer-sales-2.csv'
) # shape = (100, 2)
beer_sales_2.head()
sales | temperature | |
---|---|---|
0 | 41.68 | 13.7 |
1 | 110.99 | 24 |
2 | 65.32 | 21.5 |
3 | 72.64 | 13.4 |
4 | 76.54 | 28.9 |
#Visualisation
beer_sales_2.plot.scatter('temperature', 'sales')
plt.title("Relation entre les ventes de bière et la température")
Il semble y avoir une corrélation positive en quelque sorte.
Ce qui suit est une description d'un modèle qui explique la vente en termes de température à l'aide d'un modèle de régression simple.
Dans Pyro, la description du modèle et l'estimation des paramètres sont effectuées selon la procédure suivante.
Dans Pyro, le processus par lequel le modèle statistique supposé génère des données est décrit dans une méthode. Le nom de la méthode est généralement «model». À ce moment-là, veillez à respecter les points suivants.
import torch
import pyro
import pyro.distributions as dist
def model(temperature: torch.Tensor, sales: torch.Tensor=None):
intercept = pyro.sample("intercept", dist.Normal(0, 1000))
beta = pyro.sample("beta", dist.Normal(0, 1000))
sigma2 = pyro.sample("sigma2", dist.Uniform(0, 1000))
with pyro.plate("plate", size=temperature.size(0)):
y_ = intercept + beta * temperature
y = pyro.sample("y", dist.Normal(y_, sigma2), obs=sales)
return y
Les quatre variables ($ Intercept $, $ \ beta $, $ \ sigma ^ 2 $, $ y $) définies par pyro.sample
dans model
sont traitées comme des variables stochastiques. Ensuite, parmi les variables de probabilité, celles qui ne correspondent pas aux données observées (autres que $ y $) sont des variables latentes, et seront la cible pour estimer ultérieurement la distribution a posteriori.
Après avoir écrit la méthode du modèle pour le modèle statistique, la distribution a posteriori de la variable latente est estimée. Les deux méthodes d'estimation suivantes sont principalement fournies.
MCMC Les méthodes requises pour l'estimation sont fournies dans «pyro.infer». Pour faire une estimation à l'aide du noyau NUTS, écrivez comme suit.
import pyro.infer as infer
#Variables explicatives / variables observées torche.Convertir en tenseur
temperature = torch.Tensor(beer_sales_2.temperature)
sales = torch.Tensor(beer_sales_2.sales)
#Estimation de la distribution postérieure
nuts_kernel = infer.NUTS(model, adapt_step_size=True, jit_compile=True, ignore_jit_warnings=True)
mcmc = infer.MCMC(nuts_kernel, num_samples=3000, warmup_steps=200, num_chains=3)
mcmc.run(temperature, sales)
mean | std | median | 5.0% | 95.0% | n_eff | r_hat | |
---|---|---|---|---|---|---|---|
intercept | 21.00 | 6.03 | 20.99 | 11.01 | 30.80 | 2869.59 | 1.00 |
beta | 2.47 | 0.29 | 2.47 | 1.98 | 2.94 | 2866.62 | 1.00 |
sigma2 | 17.06 | 1.22 | 16.98 | 15.02 | 19.00 | 4014.61 | 1.00 |
Parmi les variables stochastiques de la méthode du modèle, la distribution a posteriori de la section (intersection), de la pente (bêta) et de la variance (sigma2) à estimer a été estimée. La section est d'environ 21 et la pente est d'environ 2,5, ce qui est presque le même que le résultat indiqué dans le livre de référence.
Pyro peut également estimer par inférence variable. Dans l'inférence de variable, la variable latente que vous voulez trouverguide
)Si vous écrivezmodel
, nous pouvons voir qu'il y a trois variables latentes que nous voulons estimer pour ce problème: ʻintercept,
betaet
sigma2. Le processus de génération de ces trois variables latentes peut être décrit dans la méthode «guide». Cependant, si vous rédigez le guide de manière appropriée$q(Z)$À$p(Z|X)$Je ne peux pas m'en approcher. Dans Pyro
pyro.param`Les paramètres déclarés par
pyro.param
.guide
. Les contraintes sont spécifiées par dist.constraints
.dist. Distribution de probabilité (paramètre cible de mise à jour)
et pyro.sample
.model
.Voici un exemple de l'implémentation de «guide» pour ce problème. Cette fois, nous avons supposé que les trois variables latentes étaient indépendantes et avons choisi la distribution delta comme forme fonctionnelle.
#Déclaration des variables et réglage des valeurs initiales
pyro.param("intercept_q", torch.tensor(0.))
pyro.param("beta_q", torch.tensor(0.))
pyro.param("sigma2_q", torch.tensor(10.))
# q(Z)Implémentation de
def guide(temperature, sales=None):
intercept_q = pyro.param("intercept_q")
beta_q = pyro.param("beta_q")
sigma2_q = pyro.param("sigma2_q", constraint=dist.constraints.positive)
intercept = pyro.sample("intercept", dist.Delta(intercept_q))
beta = pyro.sample("beta", dist.Delta(beta_q))
sigma2 = pyro.sample("sigma2", dist.Delta(sigma2_q))
Par exemple, si vous voulez une distribution normale au lieu d'une distribution delta, définissez la moyenne ($ \ mu pyro.sample
. De cette manière, vous pouvez modifier de manière flexible la méthode d'estimation de la distribution postérieure en modifiant la pièce de guidage.
À propos, si toutes les variables latentes ont une distribution delta indépendante comme dans l'exemple de code,
guide = infer.autoguide.guides.AutoDelta(model)
C'est la même chose même si vous écrivez. ʻInfer.autogide.guides fournit une fonction pratique qui crée automatiquement une méthode
guide`. (http://docs.pyro.ai/en/0.2.1-release/contrib.autoguide.html)
Si vous écrivez les méthodes model
et guide
, vous pouvez facilement trouver la distribution postérieure.
#Une méthode pour conserver la valeur du paramètre à estimer pour chaque époque
def update_param_dict(param_dict):
for name in pyro.get_param_store():
param_dict[name].append(pyro.param(name).item())
#Exécution de l'inférence de variantes
adam_params = {"lr": 1e-1, "betas": (0.95, 0.999)}
oprimizer = pyro.optim.Adam(adam_params)
svi = infer.SVI(model, guide, oprimizer, loss=infer.JitTrace_ELBO(),)
n_steps = 10000
losses = []
param_dict = defaultdict(list)
for step in tqdm(range(n_steps)):
loss = svi.step(temperature, sales,)
update_param_dict(param_dict)
losses.append(loss)
for name in pyro.get_param_store():
print("{}: {}".format(name, pyro.param(name)))
intercept_q: 21.170936584472656
beta_q: 2.459129810333252
sigma2_q: 16.684457778930664
Avec environ 21 sections, environ 2,5 pentes et environ 17 dispersions, des résultats d'estimation raisonnables ont été obtenus. D'après la figure ci-dessous, on peut voir que la perte et chaque paramètre cible de mise à jour sont également convergés.
Maintenant que nous avons trouvé que la distribution postérieure peut être estimée par inférence de variables, nous aimerions considérer l'évolutivité en utilisant le GPU, qui est le plus grand avantage de Pyro.
Le moyen le plus simple d'utiliser le GPU avec Pyro est au début du code
torch.set_default_tensor_type(torch.cuda.FloatTensor)
En écrivant, tous les Tensors générés sont placés sur le GPU depuis le début.
À propos, dans l'exemple, le nombre d'échantillons était de 100, mais si cela augmente à 1000, 10000, ..., comment le temps nécessaire à l'inférence de variables augmentera-t-il?
Ici, en supposant que les sections, les pentes et les variances estimées à partir de 100 échantillons sont des valeurs vraies, 1000, 10000, ... échantillons sont générés à partir de la distribution vraie (provisoire), et les échantillons sont générés. Nous avons comparé le temps requis pour l'inférence de variables avec le CPU et le GPU comme données d'observation.
Le nombre d'échantillons | CPU(Secondes) | GPU(Secondes) |
---|---|---|
1.34 | 3.66 | |
1.4 | 1.95 | |
1.51 | 1.95 | |
4.2 | 1.96 | |
30.41 | 2.26 | |
365.36 | 11.41 | |
3552.44 | 104.62 |
Comme vous pouvez le voir dans le tableau, dans la zone où le nombre d'échantillons est de 10 000 ou moins, l'avantage de la parallélisation n'est pas reçu et le processeur est plus rapide. Cependant, la différence a commencé à s'ouvrir lorsque le nombre d'échantillons dépassait 100 000, et le résultat était qu'il y avait une différence de 30 fois ou plus pour des pièces de 10 $ ^ 8 $. On peut voir que la modélisation statistique peut être effectuée sans stress, même lorsqu'il s'agit de données extrêmement volumineuses à l'aide de GPU.
Cette fois, j'ai introduit une méthode d'analyse par un modèle de régression simple utilisant Pyro. Le processus par lequel le modèle statistique supposé génère des données peut être décrit dans la méthode du «modèle», et la distribution postérieure peut être estimée par MCMC ou par inférence différentielle. Nous avons également mené des expériences sur l'accélération de l'utilisation de GPU et confirmé que Pyro peut effectuer une analyse évolutive.
Recommended Posts