diff --git a/next_steps/core_use_cases/filters/promotions/Filtro_de_promocao.ipynb b/next_steps/core_use_cases/filters/promotions/Filtro_de_promocao.ipynb new file mode 100644 index 0000000..00cd4ff --- /dev/null +++ b/next_steps/core_use_cases/filters/promotions/Filtro_de_promocao.ipynb @@ -0,0 +1,2051 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f760989d", + "metadata": {}, + "source": [ + "# Promovendo produtos em suas recomendações\n", + "\n", + "Este notebook vai guiar você nas etapas de criação do filtro de promoção utilizando o [Amazon Personalize](https://aws.amazon.com/personalize/). Essa funcionalidade retorna recomendações que contenham resultados com produtos ou serviços promovidos. \n", + "\n", + "Recomendações personalizadas aumentam o engajamento de seu website ou aplicativo, e frequentemente existe a necessidade de aplicar regras adicionais de negócio no momento de apresentar itens para seus clientes. [O Filtro de Promoção do Amazon Personalize ](https://docs.aws.amazon.com/pt_br/personalize/latest/dg/promoting-items.html) pode ajudar nos casos que é necessário mostrar itens de determinadas categorias ou marcas. Por exemplo, um aplicativo de video sob demanda pode promover uma nova série ou um website de e-commerce pode promover produtos que estão em liquidação.\n", + "\n", + "![promotions-overview-pt-br.png](images/promotions-overview-pt-br.png \"Diagrama demonstrando como o filtro de promoção muda o resultado da recomendação de produtos\")\n", + "\n", + "Primeiro precisamos seguir os passos para criar o grupo do conjunto de dados, em seguida criamos um recomendador que retornará produtos de um site de e-commerce ficticio. O objetivo é gerar recomendações que sejam relevantes para o usuário informado.\n", + "\n", + "Depois, vamos criar o filtro de promoção para promover produtos que estarão presentes na lista de recomendações para o usuário.\n", + "\n", + "Para finalizar, vamos fazer a limpeza dos recursos criados para evitar custos desnecessários.\n", + "\n", + "O tempo estimado para executar todos os passos deste notebook é de aproximadamente 75 minutos.\n", + "\n", + "## Como usar este notebook\n", + "\n", + "O código está dividido em células como o exemplo abaixo. Você pode usar o botão triangular executar (Run) no topo desta página para executar cada célula e avançar para a seguinte. Ou dentro da célula você também pode usar a combinação de teclas \"Shift + Enter\" para a executar e avançar para a próxima célula.\n", + "\n", + "Durante a execução do código na célula, observe uma linha que mostra um `*` enquanto a execução esta em andamento. Esta linha será atualizada para um número indicando que a execução do código desta célula finalizou.\n", + "\n", + "Para começar, siga as instruções abaixo e execute as células do notebook.\n", + "\n", + "## Introdução ao Amazon Personalize\n", + "\n", + "[O Amazon Personalize](https://aws.amazon.com/pt/personalize//) simplifica o desenvolvimento de sistemas de recomendação, incluindo casos de recomendações de produtos em tempo real e marketing direto personalizado. O Amazon Personalize oferece a mesma tecnologia utilizada pela Amazon.com, sem necessidade de experiência prévia com aprendizado de máquina. Com o Amazon Personalize você paga pelo o que consome, sem taxas mínimas ou compromisso inicial. \n", + "\n", + "Para começar a utilizar o Amazon Personalize, basta seguir o processo com três passos que requer apenas alguns cliques na console AWS, ou algumas chamadas à API.\n", + "\n", + "Primeiro, informe os dados de usuários, os dados do catálogo de produtos e dados de interações (como cliques, compras, etc). É possivel carregados os dados do Amazon S3 ou utilizar uma chamada de API.\n", + "\n", + "Segundo, com um clique na console (ou uma chamada de API), treine o modelo de recomendação com os seus dados. Lembrando que todos os seus dados permanecem protegidos e seguros através de criptografia, e são utilizados somente para criar recomendações para seus cliente ou usuários.\n", + "\n", + "Por último, receba recomendações personalizadas para qualquer usuário, criando um recomendador e utilizando a API GetRecommendations.\n", + "\n", + "Caso queira conhecer mais sobre o Amazon Personalize, ou ter acesso a mais exemplos como este acesse: [Github Sample Notebooks](https://github.com/aws-samples/amazon-personalize-samples) e [Documentação do produto](https://docs.aws.amazon.com/pt_br/personalize/latest/dg/what-is-personalize.html)." + ] + }, + { + "cell_type": "markdown", + "id": "6a7ca987", + "metadata": {}, + "source": [ + "## Importação\n", + "O Python fornece uma coleção de bibliotecas e precisamos importá-las, como o [boto3](https://aws.amazon.com/sdk-for-python/) (AWS SDK para python) e [Pandas](https://pandas.pydata.org/)/[Numpy](https://numpy.org/), que são ferramentas essenciais para a ciência de dados." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86eb0420", + "metadata": {}, + "outputs": [], + "source": [ + "# Instale a versão mais recente do botocore para garantir que temos os recursos mais atualizados no SDK\n", + "import sys\n", + "!{sys.executable} -m pip install --upgrade pip\n", + "!{sys.executable} -m pip install --upgrade --no-deps --force-reinstall botocore" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fca0fd16", + "metadata": {}, + "outputs": [], + "source": [ + "# Importação de bibliotecas\n", + "import boto3\n", + "import json\n", + "import numpy as np\n", + "import pandas as pd\n", + "import time\n", + "import datetime" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9af0b2bb", + "metadata": {}, + "outputs": [], + "source": [ + "# Configure o SDK para o Personalize:\n", + "personalize = boto3.client('personalize')\n", + "personalize_runtime = boto3.client('personalize-runtime')" + ] + }, + { + "cell_type": "markdown", + "id": "12983cae", + "metadata": {}, + "source": [ + "Utilize a célula abaixo para validar que o seu ambiente esta comunicando com Amazon Personalize." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df13c737", + "metadata": {}, + "outputs": [], + "source": [ + "personalize.list_dataset_groups()" + ] + }, + { + "cell_type": "markdown", + "id": "479e3ff5", + "metadata": {}, + "source": [ + "Informe o nome dos arquivos com os dados de itens e interações a serem utilizados." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e44a59b7", + "metadata": {}, + "outputs": [], + "source": [ + "interactions_file_path = 'cleaned_interactions_training_data.csv'\n", + "items_file_path = 'cleaned_item_training_data.csv'" + ] + }, + { + "cell_type": "markdown", + "id": "0765d343", + "metadata": {}, + "source": [ + "## Especifique um bucket S3 e uma localização de saída dos dados\n", + "\n", + "O Amazon Personalize precisa de um bucket S3 como fonte de dados. O código abaixo irá criar um bucket com um `bucket_name` único.\n", + "\n", + "O bucket do Amazon S3 precisa estar na mesma região que os recursos do Amazon Personalize." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ff5e864", + "metadata": {}, + "outputs": [], + "source": [ + "# Define a mesma região que o Notebook atual do Amazon SageMaker\n", + "with open('/opt/ml/metadata/resource-metadata.json') as notebook_info:\n", + " data = json.load(notebook_info)\n", + " resource_arn = data['ResourceArn']\n", + " region = resource_arn.split(':')[3]\n", + "print('region:', region)\n", + "\n", + "# Ou você pode especificar a região onde o seu bucket e modelo serão utilizados\n", + "# region = \"us-east-1\" \n", + "\n", + "s3 = boto3.client('s3')\n", + "account_id = boto3.client('sts').get_caller_identity().get('Account')\n", + "bucket_name = account_id + \"-\" + region + \"-\" + \"personalizemanagedretailers\"\n", + "print('bucket_name:', bucket_name)\n", + "\n", + "try: \n", + " if region == \"us-east-1\":\n", + " s3.create_bucket(Bucket=bucket_name)\n", + " else:\n", + " s3.create_bucket(\n", + " Bucket = bucket_name,\n", + " CreateBucketConfiguration={'LocationConstraint': region}\n", + " )\n", + "except s3.exceptions.BucketAlreadyOwnedByYou:\n", + " print(\"O bucket já existe. O nome será definido como: \", bucket_name)" + ] + }, + { + "cell_type": "markdown", + "id": "9b430601", + "metadata": {}, + "source": [ + "## Download, preparação, e upload dos dados de treinamento\n", + "\n", + "Produzimos dados sintéticos com base no código do projeto [Retail Demo Store] (https://github.com/aws-samples/retail-demo-store). Clique no link para maiores informações e possíveis usos.\n", + "\n", + "Primeiro, faremos o download dos dados (dados de treinamento). Neste exemplo, utilizaremos o histórico de compras de um conjunto de dados de um e-commerce. O conjunto de dados contém o user_id, o item_id, e as interações entre dos clientes e itens, e também a hora em que essa interação ocorreu (Timestamp).\n", + "\n", + "### Download e exploração do conjunto de dados de interações" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ec851d2", + "metadata": {}, + "outputs": [], + "source": [ + "!aws s3 cp s3://retail-demo-store-us-east-1/csvs/interactions.csv ." + ] + }, + { + "cell_type": "markdown", + "id": "ae49d831", + "metadata": {}, + "source": [ + "O conjunto de dados foi transferido com sucesso com o nome: interactions.csv\n", + "\n", + "Vamos explorar este conjunto de dados para identificar as suas caracteristicas." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "475cb0a3", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv('./interactions.csv')\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "711a3292", + "metadata": {}, + "outputs": [], + "source": [ + "df.info()" + ] + }, + { + "cell_type": "markdown", + "id": "66032111", + "metadata": {}, + "source": [ + "Das células acima, descobrimos que os nossos dados têm 5 colunas, 675004 linhas e os campos são: ITEM_ID, USER_ID, EVENT_TYPE, TIMESTAMP e DISCOUNT.\n", + "\n", + "Para que seja compativel com o esquema de interação do Amazon Personalize, as colunas precisam ter o nome esperado pelo Amazon Personalize (leia sobre nomes de coluna [aqui](https://docs.aws.amazon.com/pt_br/personalize/latest/dg/how-it-works-dataset-schema.html) )\n", + "\n", + "Os recomendadores ECOMMERCE requerem que dados do EVENT_TYPE sejam específicados para entender o contexto da interação. Vamos analisar os tipos de eventos que estão atualmente no nosso conjunto de dados:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "383fa167", + "metadata": {}, + "outputs": [], + "source": [ + "df.EVENT_TYPE.value_counts()" + ] + }, + { + "cell_type": "markdown", + "id": "9033359c", + "metadata": {}, + "source": [ + "Verificamos que os eventos \"View\" e \"Purchase\" estão presentes. Eles serão utilizados em nosso exemplo." + ] + }, + { + "cell_type": "markdown", + "id": "0bee3c96", + "metadata": {}, + "source": [ + "### Preparação dos dados de interação\n" + ] + }, + { + "cell_type": "markdown", + "id": "7d7b067f", + "metadata": {}, + "source": [ + "### Remova colunas\n", + "\n", + "Algumas colunas deste conjunto de dados não adicionam valor ao nosso modelo e, por isso, devem ser removidas do conjunto de dados. Como exemplo a coluna com o nome *discount*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65efe415", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "test=df.drop(columns=['DISCOUNT'])\n", + "df=test\n", + "df.sample(10)" + ] + }, + { + "cell_type": "markdown", + "id": "7e13d9db", + "metadata": {}, + "source": [ + "Na célula abaixo, vamos salvar os dados limpos em um arquivo com o nome \"final_training_data.csv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c8ad8ac", + "metadata": {}, + "outputs": [], + "source": [ + "df.to_csv(interactions_file_path)" + ] + }, + { + "cell_type": "markdown", + "id": "2fd3b13d", + "metadata": {}, + "source": [ + "### Download e exploração dos dados de itens" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89324993", + "metadata": {}, + "outputs": [], + "source": [ + "!aws s3 cp s3://retail-demo-store-us-east-1/csvs/items.csv ." + ] + }, + { + "cell_type": "markdown", + "id": "3bd1fa09", + "metadata": {}, + "source": [ + "O conjunto de dados foi transferido com sucesso com o nome items.csv\n", + "\n", + "Vamos explorar este conjunto de dados para identificar as suas caracteristicas." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9588db31", + "metadata": {}, + "outputs": [], + "source": [ + "items_df = pd.read_csv('./items.csv')\n", + "items_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d25b6c88", + "metadata": {}, + "outputs": [], + "source": [ + "items_df.info()" + ] + }, + { + "cell_type": "markdown", + "id": "4fda300e", + "metadata": {}, + "source": [ + "Vamos analisar os tipos de itens que estão incluídos no conjunto de dados." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c3cf7bc", + "metadata": {}, + "outputs": [], + "source": [ + "items_df.CATEGORY_L1.unique()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57a631e4", + "metadata": {}, + "outputs": [], + "source": [ + "items_df.CATEGORY_L2.unique()" + ] + }, + { + "cell_type": "markdown", + "id": "30d55069", + "metadata": {}, + "source": [ + "### Remova colunas\n", + "\n", + "Algumas colunas deste conjunto de dados poderiam acrescentar valor ao nosso modelo, mas não serão relevantes em nosso exemplo. Para simplificar, vamos remover a coluna com o nome *product_decription*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f79effaa", + "metadata": {}, + "outputs": [], + "source": [ + "test=items_df.drop(columns=['PRODUCT_DESCRIPTION'])\n", + "items_df=test\n", + "items_df.sample(10)" + ] + }, + { + "cell_type": "markdown", + "id": "ef827e5b", + "metadata": {}, + "source": [ + "Salve os dados limpos em um arquivo .csv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6dcf134f", + "metadata": {}, + "outputs": [], + "source": [ + "items_df.to_csv(items_file_path)" + ] + }, + { + "cell_type": "markdown", + "id": "d966bbd6", + "metadata": {}, + "source": [ + "## Configure o bucket S3 e funções IAM\n", + "\n", + "Até agora, baixamos, limpamos e salvamos os dados em nosso notebook Jupyter utilizado. Mas o Amazon Personalize precisa de um bucket S3 para obter os dados, e também das permissões (função IAM) para acessar esse bucket. Vamos configurar isso na célula abaixo." + ] + }, + { + "cell_type": "markdown", + "id": "d88b13fe", + "metadata": {}, + "source": [ + "## Configure a politica do bucket S3\n", + "O Amazon Personalize precisa das pemissões necessárias para ler o conteúdo do seu bucket S3. Para isso adicione uma política de bucket.\n", + "\n", + "Observação: verifique se a função que você está usando para executar o código deste notebook tem as permissões necessárias para modificar a política do bucket S3." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a81b8a76", + "metadata": {}, + "outputs": [], + "source": [ + "s3 = boto3.client(\"s3\")\n", + "policy = {\n", + " \"Version\": \"2012-10-17\",\n", + " \"Id\": \"PersonalizeS3BucketAccessPolicy\",\n", + " \"Statement\": [\n", + " {\n", + " \"Sid\": \"PersonalizeS3BucketAccessPolicy\",\n", + " \"Effect\": \"Allow\",\n", + " \"Principal\": {\n", + " \"Service\": \"personalize.amazonaws.com\"\n", + " },\n", + " \"Action\": [\n", + " \"s3:GetObject\",\n", + " \"s3:ListBucket\"\n", + " ],\n", + " \"Resource\": [\n", + " \"arn:aws:s3:::{}\".format(bucket_name),\n", + " \"arn:aws:s3:::{}/*\".format(bucket_name)\n", + " ]\n", + " }\n", + " ]\n", + "}\n", + "\n", + "s3.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(policy))" + ] + }, + { + "cell_type": "markdown", + "id": "a2884e83", + "metadata": {}, + "source": [ + "### Upload dos dados de interação para o S3\n", + "Agora que os dados de treinamento estão preparados para o Amazon Personalize, precisamos carregá-los no bucket s3 criado acima" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "049ee211", + "metadata": {}, + "outputs": [], + "source": [ + "boto3.Session().resource('s3').Bucket(bucket_name).Object(interactions_file_path).upload_file(interactions_file_path)\n", + "interactions_s3DataPath = \"s3://\"+bucket_name+\"/\"+interactions_file_path\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "7cb04ad2", + "metadata": {}, + "source": [ + "### Upload dos dados de itens para S3\n", + "Agora que os dados de treinamento estão preparados para o Amazon Personalize, precisamos carregá-los no bucket s3 criado acima" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38e3e584", + "metadata": {}, + "outputs": [], + "source": [ + "boto3.Session().resource('s3').Bucket(bucket_name).Object(items_file_path).upload_file(items_file_path)\n", + "items_s3DataPath = \"s3://\"+bucket_name+\"/\"+items_file_path" + ] + }, + { + "cell_type": "markdown", + "id": "cfbe117d", + "metadata": {}, + "source": [ + "## Crie o grupo de conjunto de dados\n", + "O maior agrupamento no Personalize é um grupo de conjunto de dados, que isola seus dados, rastreadores de eventos, soluções, recomendadores e campanhas. Se preferir, pode alterar o nome abaixo.\n", + "\n", + "Durante a criação é necessário informar o dominio dos dados, isso determina os esquemas e casos de utilização disponíveis para os recomendadores.\n", + "\n", + "Para ter mais informações, acesse: [Documentação](https://docs.aws.amazon.com/pt_br/personalize/latest/dg/data-prep-ds-group.html).\n", + "\n", + "### Crie o grupo de conjunto de dados" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1aeed98c", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "response = personalize.create_dataset_group(\n", + " name='personalize_ecomemerce_ds_group',\n", + " domain='ECOMMERCE'\n", + ")\n", + "\n", + "dataset_group_arn = response['datasetGroupArn']\n", + "print(json.dumps(response, indent=2))" + ] + }, + { + "cell_type": "markdown", + "id": "757bcc10", + "metadata": {}, + "source": [ + "Antes de utilizarmos o grupo de conjunto de dados em qualquer etapa abaixo ele deve estar ativo (ACTIVE), execute a célula abaixo e aguarde que ele chegue nesse status para prosseguir." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01ca00c7", + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "\n", + "max_time = time.time() + 3*60*60 # 3 horas\n", + "while time.time() < max_time:\n", + " describe_dataset_group_response = personalize.describe_dataset_group(\n", + " datasetGroupArn = dataset_group_arn\n", + " )\n", + " status = describe_dataset_group_response[\"datasetGroup\"][\"status\"]\n", + " print(\"Grupo de conjunto de dados: {}\".format(status))\n", + " \n", + " if status == \"ACTIVE\" or status == \"CREATE FAILED\":\n", + " break\n", + " \n", + " time.sleep(60)" + ] + }, + { + "cell_type": "markdown", + "id": "c11b6c37", + "metadata": {}, + "source": [ + "## Crie o esquema de interações\n", + "É através do esquema que o Amazon Personalize entende os seus dados. Esta configuração informa ao serviço como processar os dados do arquivo CSV. Observe que as colunas e tipos estão igual ao arquivo criado na etapa acima." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "591cd9ae", + "metadata": {}, + "outputs": [], + "source": [ + "interactions_schema = {\n", + " \"type\": \"record\",\n", + " \"name\": \"Interactions\",\n", + " \"namespace\": \"com.amazonaws.personalize.schema\",\n", + " \"fields\": [\n", + " {\n", + " \"name\": \"USER_ID\",\n", + " \"type\": \"string\"\n", + " },\n", + " {\n", + " \"name\": \"ITEM_ID\",\n", + " \"type\": \"string\"\n", + " },\n", + " {\n", + " \"name\": \"TIMESTAMP\",\n", + " \"type\": \"long\"\n", + " },\n", + " {\n", + " \"name\": \"EVENT_TYPE\",\n", + " \"type\": \"string\"\n", + " \n", + " }\n", + " \n", + " ],\n", + " \"version\": \"1.0\"\n", + "}\n", + "\n", + "\n", + "create_schema_response = personalize.create_schema(\n", + " name = \"personalize-ecommerce-interatn_group\",\n", + " domain = \"ECOMMERCE\",\n", + " schema = json.dumps(interactions_schema)\n", + ")\n", + "\n", + "interaction_schema_arn = create_schema_response['schemaArn']\n", + "print(json.dumps(create_schema_response, indent=2))" + ] + }, + { + "cell_type": "markdown", + "id": "7e58dc0b", + "metadata": {}, + "source": [ + "## Crie o esquema de itens\n", + "É através do esquema que o Amazon Personalize entende os seus dados. Esta configuração informa ao serviço como processar os dados do arquivo CSV. Observe que as colunas e tipos estão igual ao arquivo criado na etapa acima." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a791d503", + "metadata": {}, + "outputs": [], + "source": [ + "items_schema = {\n", + " \"type\": \"record\",\n", + " \"name\": \"Items\",\n", + " \"namespace\": \"com.amazonaws.personalize.schema\",\n", + " \"fields\": [\n", + " {\n", + " \"name\": \"ITEM_ID\",\n", + " \"type\": \"string\"\n", + " },\n", + " {\n", + " \"name\": \"PRICE\",\n", + " \"type\": \"float\"\n", + " },\n", + " {\n", + " \"name\": \"CATEGORY_L1\",\n", + " \"type\": [\"string\"],\n", + " \"categorical\": True\n", + " },\n", + " {\n", + " \"name\": \"CATEGORY_L2\",\n", + " \"type\": [\"string\"],\n", + " \"categorical\": True\n", + " \n", + " },\n", + " {\n", + " \"name\": \"GENDER\",\n", + " \"type\": [\"string\"],\n", + " \"categorical\": True\n", + " \n", + " }\n", + " ],\n", + " \"version\": \"1.0\"\n", + "}\n", + "\n", + "create_schema_response = personalize.create_schema(\n", + " name = \"personalize-ecommerce-item_group\",\n", + " domain = \"ECOMMERCE\",\n", + " schema = json.dumps(items_schema)\n", + ")\n", + "\n", + "items_schema_arn = create_schema_response['schemaArn']\n", + "\n", + "print(json.dumps(create_schema_response, indent=2))" + ] + }, + { + "cell_type": "markdown", + "id": "77dd41ad", + "metadata": {}, + "source": [ + "## Crie o conjunto de dados\n", + "Após a criação do grupo, a próxima etapa será criar o conjunto de dados e depois carregá-los no Amazon Personalize." + ] + }, + { + "cell_type": "markdown", + "id": "bad88e7d", + "metadata": {}, + "source": [ + "### Crie o conjunto de dados de interação" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "885bb616", + "metadata": {}, + "outputs": [], + "source": [ + "dataset_type = \"INTERACTIONS\"\n", + "\n", + "create_dataset_response = personalize.create_dataset(\n", + " name = \"personalize_ecommerce_demo_interactions\",\n", + " datasetType = dataset_type,\n", + " datasetGroupArn = dataset_group_arn,\n", + " schemaArn = interaction_schema_arn\n", + ")\n", + "\n", + "interactions_dataset_arn = create_dataset_response['datasetArn']\n", + "print(json.dumps(create_dataset_response, indent=2))" + ] + }, + { + "cell_type": "markdown", + "id": "cb7001e1", + "metadata": {}, + "source": [ + "### Crie o conjunto de dados de itens" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96bb28e9", + "metadata": {}, + "outputs": [], + "source": [ + "dataset_type = \"ITEMS\"\n", + "\n", + "create_dataset_response = personalize.create_dataset(\n", + " name = \"personalize_ecommerce_demo_items\",\n", + " datasetType = dataset_type,\n", + " datasetGroupArn = dataset_group_arn,\n", + " schemaArn = items_schema_arn\n", + ")\n", + "\n", + "items_dataset_arn = create_dataset_response['datasetArn']\n", + "print(json.dumps(create_dataset_response, indent=2))" + ] + }, + { + "cell_type": "markdown", + "id": "a9c72c02", + "metadata": {}, + "source": [ + "## Crie uma função IAM para o Personalize\n", + "O Amazon Personalize também precisa da acesso de assumir funções IAM na AWS para ter permissões de execução de determinadas tarefas.\n", + "\n", + "Observação: verifique se a função utilizada para executar o código deste notebook tem as permissões necessárias para criar uma nova função IAM." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3eec5608", + "metadata": {}, + "outputs": [], + "source": [ + "iam = boto3.client(\"iam\")\n", + "\n", + "role_name = \"PersonalizeRoleEcommerceDemoRecommender\"\n", + "assume_role_policy_document = {\n", + " \"Version\": \"2012-10-17\",\n", + " \"Statement\": [\n", + " {\n", + " \"Effect\": \"Allow\",\n", + " \"Principal\": {\n", + " \"Service\": \"personalize.amazonaws.com\"\n", + " },\n", + " \"Action\": \"sts:AssumeRole\"\n", + " }\n", + " ]\n", + "}\n", + "\n", + "create_role_response = iam.create_role(\n", + " RoleName = role_name,\n", + " AssumeRolePolicyDocument = json.dumps(assume_role_policy_document)\n", + ")\n", + "\n", + "# O AmazonPersonalizeFullAccess permite o acesso a qualquer bucket S3 com um nome que inclua \"personalize\" ou \" Personalize\" \n", + "# se quiser utilizar um bucket com um nome diferente, considere a possibilidade de criar e anexar uma nova política\n", + "# que forneça acesso de leitura ao seu bucket ou anexe a política AmazonS3ReadOnlyAccess à função\n", + "policy_arn = \"arn:aws:iam::aws:policy/service-role/AmazonPersonalizeFullAccess\"\n", + "iam.attach_role_policy(\n", + " RoleName = role_name,\n", + " PolicyArn = policy_arn\n", + ")\n", + "\n", + "# Agora adicione o suporte ao S3\n", + "iam.attach_role_policy(\n", + " PolicyArn='arn:aws:iam::aws:policy/AmazonS3FullAccess',\n", + " RoleName=role_name\n", + ")\n", + "time.sleep(60) # aguarde por 1 minuto para que a politica da função IAM se propague\n", + "\n", + "role_arn = create_role_response[\"Role\"][\"Arn\"]\n", + "print(role_arn)\n" + ] + }, + { + "cell_type": "markdown", + "id": "616f86ce", + "metadata": {}, + "source": [ + "## Importe os dados\n", + "Anteriormente, foi criado o grupo e o conjunto de dados para armazenar os dados. A próxima tarefa será importar os dados que serão utilizados na construção do modelo.\n", + "### Crie a tarefa de importação do conjunto de dados de interações" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e902d38", + "metadata": {}, + "outputs": [], + "source": [ + "create_interactions_dataset_import_job_response = personalize.create_dataset_import_job(\n", + " jobName = \"personalize_ecommerce_demo_interactions_import\",\n", + " datasetArn = interactions_dataset_arn,\n", + " dataSource = {\n", + " \"dataLocation\": \"s3://{}/{}\".format(bucket_name, interactions_file_path)\n", + " },\n", + " roleArn = role_arn\n", + ")\n", + "\n", + "dataset_interactions_import_job_arn = create_interactions_dataset_import_job_response['datasetImportJobArn']\n", + "print(json.dumps(create_interactions_dataset_import_job_response, indent=2))" + ] + }, + { + "cell_type": "markdown", + "id": "64d20685", + "metadata": {}, + "source": [ + "### Crie a tarefa de importação do conjunto de dados de itens" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "432da810", + "metadata": {}, + "outputs": [], + "source": [ + "create_items_dataset_import_job_response = personalize.create_dataset_import_job(\n", + " jobName = \"personalize_ecommerce_demo_items_import\",\n", + " datasetArn = items_dataset_arn,\n", + " dataSource = {\n", + " \"dataLocation\": \"s3://{}/{}\".format(bucket_name, items_file_path)\n", + " },\n", + " roleArn = role_arn\n", + ")\n", + "\n", + "dataset_items_import_job_arn = create_items_dataset_import_job_response['datasetImportJobArn']\n", + "print(json.dumps(create_items_dataset_import_job_response, indent=2))" + ] + }, + { + "cell_type": "markdown", + "id": "06519930", + "metadata": {}, + "source": [ + "### Aguarde para que a tarefa de importação fique com o status ACTIVE\n", + "Pode levar alguns minutos para a tarefa de importação seja concluída. Aguarde até que as tarefas fiquem com o status ativo (ACTIVE) na execução abaixo." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26369f92", + "metadata": {}, + "outputs": [], + "source": [ + "max_time = time.time() + 3*60*60 # 3 horas\n", + "\n", + "while time.time() < max_time:\n", + " describe_dataset_import_job_response = personalize.describe_dataset_import_job(\n", + " datasetImportJobArn = dataset_items_import_job_arn\n", + " )\n", + " status = describe_dataset_import_job_response[\"datasetImportJob\"]['status']\n", + " print(\"ConjuntodeDadosdeItensImport: {}\".format(status))\n", + " \n", + " if status == \"ACTIVE\" or status == \"CREATE FAILED\":\n", + " break\n", + " \n", + " time.sleep(60)\n", + "\n", + "while time.time() < max_time:\n", + " describe_dataset_import_job_response = personalize.describe_dataset_import_job(\n", + " datasetImportJobArn = dataset_interactions_import_job_arn\n", + " )\n", + " status = describe_dataset_import_job_response[\"datasetImportJob\"]['status']\n", + " print(\"ConjuntodeDadosdeInteracoesImport: {}\".format(status))\n", + " \n", + " if status == \"ACTIVE\" or status == \"CREATE FAILED\":\n", + " break\n", + " \n", + " time.sleep(60)" + ] + }, + { + "cell_type": "markdown", + "id": "8df7d504", + "metadata": {}, + "source": [ + "## Escolha um recomendador\n", + "\n", + "Cada domínio tem casos de uso distintos. Abaixo temos uma lista de receitas para o dominio de e-commerce." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd6bdb74", + "metadata": {}, + "outputs": [], + "source": [ + "available_recipes = personalize.list_recipes(domain='ECOMMERCE') # Veja a lista de receitas para o dominio ecommerce\n", + "display (available_recipes['recipes'])" + ] + }, + { + "cell_type": "markdown", + "id": "2960a2b6", + "metadata": {}, + "source": [ + "Vamos criar um recomendador do tipo \"Recomendado para você\". Este recomendador oferece recomendações personalizadas para itens com base em um usuário especifico. Com este recomendador, o Amazon Personalize filtra automaticamente os itens que o usuário comprou com base no userId especificado e nos eventos `Purchase`.\n", + "\n", + "[Informações adicionais para casos de dominio](https://docs.aws.amazon.com/pt_br/personalize/latest/dg/domain-use-cases.html)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef90d282", + "metadata": {}, + "outputs": [], + "source": [ + "create_recommender_response = personalize.create_recommender(\n", + " name = 'recomendado_para_voce_demo',\n", + " recipeArn = 'arn:aws:personalize:::recipe/aws-ecomm-recommended-for-you',\n", + " datasetGroupArn = dataset_group_arn\n", + ")\n", + "recommended_for_you_arn = create_recommender_response[\"recommenderArn\"]\n", + "print (json.dumps(create_recommender_response))" + ] + }, + { + "cell_type": "markdown", + "id": "78af7483", + "metadata": {}, + "source": [ + "Precisamos aguardar a criação do recomendador e fique com o status `ACTIVE`. Na célula abaixo verificamos o status a cada 60 segundos." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "244a86c2", + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "\n", + "max_time = time.time() + 10*60*60 # 10 horas\n", + " \n", + "while time.time() < max_time:\n", + "\n", + " version_response = personalize.describe_recommender(\n", + " recommenderArn = recommended_for_you_arn\n", + " )\n", + " status = version_response[\"recommender\"][\"status\"]\n", + " print(status)\n", + "\n", + " if status == \"ACTIVE\":\n", + " print(\"Criação com sucesso para {}\".format(recommended_for_you_arn))\n", + " \n", + " elif status == \"CREATE FAILED\":\n", + " print(\"Criação falhou para {}\".format(recommended_for_you_arn))\n", + " break\n", + "\n", + " if status == \"ACTIVE\" or status == \"CREATE FAILED\":\n", + " break\n", + " else:\n", + " print('A criação do recomendador \"Recomendado para você\" ainda esta em progresso')\n", + " \n", + " time.sleep(60)" + ] + }, + { + "cell_type": "markdown", + "id": "e1146c9e", + "metadata": {}, + "source": [ + "## Obtendo recomendações\n", + "Agora que o recomendador foi treinado, vamos analisar as recomendações que conseguimos dar aos nossos usuários!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4aaa6533", + "metadata": {}, + "outputs": [], + "source": [ + "# lendo os dados originais para ter um Dataframe que tenha ambos os item_ids \n", + "# e os títulos correspondentes para facilitar a leitura das nossas recomendações.\n", + "items_df = pd.read_csv('./items.csv')\n", + "items_df.sample(10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "583e35f5", + "metadata": {}, + "outputs": [], + "source": [ + "def get_item_by_id(item_id, item_df):\n", + " \"\"\"\n", + " Este processo recebe um item_id de uma recomendação em formato de string, \n", + " converte-o para um int e, em seguida, faz uma pesquisa em um dataframe que retorna a descrição do item.\n", + " \n", + " Em caso de erro temos uma excessão criada.\n", + " \n", + " Fique a vontade para adicionar outros controles e debugs para melhorar o resultado caso tenha algum erro.\n", + " \"\"\"\n", + " try:\n", + " return items_df.loc[items_df[\"ITEM_ID\"]==str(item_id)]['PRODUCT_DESCRIPTION'].values[0]\n", + " except:\n", + " print (item_id)\n", + " return \"Erro ao obter a descrição do item\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "848b1cc0", + "metadata": {}, + "outputs": [], + "source": [ + "def get_category_by_id(item_id, item_df):\n", + " \"\"\"\n", + " Este processo recebe um item_id de uma recomendação em formato de string, \n", + " converte-o para um int e, em seguida, faz uma pesquisa em um dataframe e retorna a descrição do item.\n", + " \n", + " Em caso de erro temos uma excessão criada.\n", + " \"\"\"\n", + " \n", + " try:\n", + " return items_df.loc[items_df[\"ITEM_ID\"]==str(item_id)]['CATEGORY_L2'].values[0]\n", + " except:\n", + " print (item_id)\n", + " return \"Erro ao obter a categoria do item\"\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "13cb379f", + "metadata": {}, + "source": [ + "Vamos receber algumas recomendações do recomendador \"Recomendado para você\":" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a47b1a3", + "metadata": {}, + "outputs": [], + "source": [ + "# Primeiro selecione um usuário\n", + "test_user_id = \"777\" \n", + "\n", + "# Solicite a recomentação para o usuário\n", + "get_recommendations_response = personalize_runtime.get_recommendations(\n", + " recommenderArn = recommended_for_you_arn,\n", + " userId = test_user_id,\n", + " numResults = 20\n", + ")\n", + "\n", + "# Crie um novo dataframe para as recomendações\n", + "item_list = get_recommendations_response['itemList']\n", + "recommendation_id_list = []\n", + "recommendation_description_list = []\n", + "recommendation_category_list = []\n", + "\n", + "for item in item_list:\n", + " description = get_item_by_id(item['itemId'], items_df)\n", + " recommendation_description_list.append(description)\n", + " recommendation_id_list.append(item['itemId'])\n", + " recommendation_category_list.append(get_category_by_id(item['itemId'], items_df))\n", + "\n", + "user_recommendations_df = pd.DataFrame(recommendation_id_list, columns = [\"ID\"])\n", + "user_recommendations_df[\"description\"] = recommendation_description_list\n", + "user_recommendations_df[\"category level 2\"] = recommendation_category_list\n", + "\n", + "pd.options.display.max_rows =20\n", + "display(user_recommendations_df)" + ] + }, + { + "cell_type": "markdown", + "id": "bff9ddd9", + "metadata": {}, + "source": [ + "## Utilizando filtro de promoção com um recomendador\n", + "Vamos criar um filtro de promoção para garantir que as recomendações para os usuários contêm itens que queremos promover. A nossa loja de e-commerce está promovendo produtos para o Halloween, e queremos garantir que os usuários vejam produtos desta categoria.\n", + "\n", + "Para maiores informações sobre filtro de promoção, acesse: [documentação](https://docs.aws.amazon.com/pt_br/personalize/latest/dg/promoting-items.html). \n", + "\n", + "### Crie um filtro para promover itens\n", + "\n", + "Primeiro, precisamos de criar um filtro que defina quais os produtos que serão promovidos. Utilizaremos um filtro dinâmico [filtro](https://docs.aws.amazon.com/pt_br/personalize/latest/dg/filter-expressions.html) que recebe um valor em tempo de inferência. \n", + "\n", + "Este filtro inclui apenas os itens que têm o valor especificado para a coluna *Items.CATEGORY_L2*.\n", + "\n", + "Para obter os mesmos resultados, também poderíamos utilizar um filtro estático da seguinte forma:\n", + "\n", + "```\n", + "'INCLUDE ItemID WHERE Items.CATEGORY_L2 IN (\"halloween\")'\n", + "```\n", + "\n", + "Com o uso do filtro dinâmico temos maior flexibilidade, e caso seja necessário promover produtos de uma outra categoria não será necessário criar um novo filtro." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed562d1f", + "metadata": {}, + "outputs": [], + "source": [ + "create_filter_response = personalize.create_filter(\n", + " name = 'filtro_categoria',\n", + " datasetGroupArn = dataset_group_arn,\n", + " filterExpression = 'INCLUDE ItemID WHERE Items.CATEGORY_L2 IN ($CATEGORY)'\n", + ") \n", + "filter_arn = create_filter_response[\"filterArn\"]\n", + "print(\"Filter ARN: \" + filter_arn)" + ] + }, + { + "cell_type": "markdown", + "id": "59294595", + "metadata": {}, + "source": [ + "Aguarde até que o filtro criado fique com o status: \"Active\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e0da60e", + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "\n", + "max_time = time.time() + 10*60*60 # 10 horas\n", + " \n", + "while time.time() < max_time:\n", + " version_response = personalize.describe_filter(\n", + " filterArn = filter_arn\n", + " )\n", + " status = version_response[\"filter\"][\"status\"]\n", + "\n", + " if status == \"ACTIVE\":\n", + " print(\"Criação concluída para {}\".format(filter_arn))\n", + " \n", + " elif status == \"CREATE FAILED\":\n", + " print(\"Criação falhou para {}\".format(filter_arn))\n", + " break\n", + "\n", + " if status == \"ACTIVE\" or status == \"CREATE FAILED\":\n", + " break\n", + " else:\n", + " print('A criação do filtro está em progresso')\n", + " \n", + " time.sleep(30)" + ] + }, + { + "cell_type": "markdown", + "id": "40388118", + "metadata": {}, + "source": [ + "### Receba recomentações para o filtro criado\n", + "\n", + "Agora que o filtro foi criado, podemos receber recomendações para um usuário utilizando o filtro. Vamos usar o usuário de teste previamente configurado." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6dda3682", + "metadata": {}, + "outputs": [], + "source": [ + "print(test_user_id)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be991bd9", + "metadata": {}, + "outputs": [], + "source": [ + "get_recommendations_response = personalize_runtime.get_recommendations(\n", + " recommenderArn = recommended_for_you_arn,\n", + " userId = test_user_id,\n", + " numResults = 20,\n", + " filterArn = filter_arn,\n", + " filterValues={\"CATEGORY\" : \"\\\"halloween\\\"\"}\n", + ")\n", + "user_recommendations_df =[]\n", + "\n", + "# Criar um novo dataframe para as recomendações\n", + "item_list = get_recommendations_response['itemList']\n", + "recommendation_id_list = []\n", + "recommendation_description_list = []\n", + "recommendation_category_list = []\n", + "\n", + "for item in item_list:\n", + " description = get_item_by_id(item['itemId'], items_df)\n", + " recommendation_description_list.append(description)\n", + " recommendation_id_list.append(item['itemId'])\n", + " recommendation_category_list.append(get_category_by_id(item['itemId'], items_df))\n", + "\n", + "user_recommendations_df = pd.DataFrame(recommendation_id_list, columns = [\"ID\"])\n", + "user_recommendations_df[\"description\"] = recommendation_description_list\n", + "user_recommendations_df[\"category level 2\"] = recommendation_category_list\n", + "\n", + "pd.options.display.max_rows =20\n", + "display(user_recommendations_df)" + ] + }, + { + "cell_type": "markdown", + "id": "3984415e", + "metadata": {}, + "source": [ + "Podemos observar que todos os itens retornados têm \"category l2\" como \"halloween\"." + ] + }, + { + "cell_type": "markdown", + "id": "37bbc039", + "metadata": {}, + "source": [ + "### Recebendo recomendações com o uso do filtro de promoção\n", + "\n", + "Queremos fazer recomendações personalizadas para cada usuário, mas em vez de ter todos os itens de uma determinada categoria, queremos ainda incluir alguns itens para a promover a categoria \"Halloween\". Para isso vamos usar o filtro de promoção!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8a4a0f4e", + "metadata": {}, + "outputs": [], + "source": [ + "get_recommendations_response = personalize_runtime.get_recommendations(\n", + " recommenderArn = recommended_for_you_arn,\n", + " userId = test_user_id,\n", + " numResults = 20,\n", + " promotions = [{\n", + " \"name\" : \"halloween_promotion\",\n", + " \"percentPromotedItems\" : 20,\n", + " \"filterArn\": filter_arn,\n", + " \"filterValues\": {\n", + " \"CATEGORY\" : \"\\\"halloween\\\"\"\n", + " }\n", + " }]\n", + ")\n", + "\n", + "\n", + "# Criar um novo dataframe para as recomendações\n", + "item_list = get_recommendations_response['itemList']\n", + "recommendation_id_list = []\n", + "recommendation_description_list = []\n", + "recommendation_category_list = []\n", + "\n", + "for item in item_list:\n", + " description = get_item_by_id(item['itemId'], items_df)\n", + " recommendation_description_list.append(description)\n", + " recommendation_id_list.append(item['itemId'])\n", + " recommendation_category_list.append(get_category_by_id(item['itemId'], items_df))\n", + "\n", + "user_recommendations_df = pd.DataFrame(recommendation_id_list, columns = [\"ID\"])\n", + "user_recommendations_df[\"description\"] = recommendation_description_list\n", + "user_recommendations_df[\"category level 2\"] = recommendation_category_list\n", + "\n", + "pd.options.display.max_rows =20\n", + "display(user_recommendations_df)" + ] + }, + { + "cell_type": "markdown", + "id": "cdbab006", + "metadata": {}, + "source": [ + "### Promovendo produtos enquanto aplicamos o filtro\n", + "\n", + "É possível utilizar um filtro combinado com a ação de promover produtos. O filtro no nível superior do bloco de parâmetros é aplicado apenas aos itens não promovidos. O filtro para selecionar os produtos promovidos é especificado no bloco de parâmetros `promotions`. O exemplo a seguir utiliza o mesmo filtro dinâmico utilizado anteriormente. O primeiro é aplicado aos produtos não promovidos, selecionando os produtos do nível 2 da categoria \"decorative\", e o segundo é aplicado ao filtro de promoção, promovendo produtos do nível 2 da categoria \"halloween\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7fc31280", + "metadata": {}, + "outputs": [], + "source": [ + "get_recommendations_response = personalize_runtime.get_recommendations(\n", + " recommenderArn=recommended_for_you_arn,\n", + " userId=test_user_id,\n", + " numResults=20,\n", + " filterArn=filter_arn,\n", + " filterValues={\n", + " \"CATEGORY\": \"\\\"decorative\\\"\"\n", + " },\n", + " promotions=[{\n", + " \"name\": \"halloween_promotion\",\n", + " \"percentPromotedItems\": 20,\n", + " \"filterArn\": filter_arn,\n", + " \"filterValues\": {\n", + " \"CATEGORY\": \"\\\"halloween\\\"\"\n", + " }\n", + " }]\n", + ")\n", + "\n", + "# Criar um novo dataframe para as recomendações\n", + "item_list = get_recommendations_response['itemList']\n", + "recommendation_id_list = []\n", + "recommendation_description_list = []\n", + "recommendation_category_list = []\n", + "\n", + "for item in item_list:\n", + " description = get_item_by_id(item['itemId'], items_df)\n", + " recommendation_description_list.append(description)\n", + " recommendation_id_list.append(item['itemId'])\n", + " recommendation_category_list.append(\n", + " get_category_by_id(item['itemId'], items_df))\n", + "\n", + "user_recommendations_df = pd.DataFrame(recommendation_id_list, columns=[\"ID\"])\n", + "user_recommendations_df[\"description\"] = recommendation_description_list\n", + "user_recommendations_df[\"category level 2\"] = recommendation_category_list\n", + "\n", + "pd.options.display.max_rows = 20\n", + "display(user_recommendations_df)" + ] + }, + { + "cell_type": "markdown", + "id": "610a4725", + "metadata": {}, + "source": [ + "## Conclusão\n", + "Usando este notebook, foi possível treinar com sucesso um modelo de aprendizado de máquina (Machine Learning) para gerar recomendações de produtos com base no comportamento passado do usuário. Você criou um recomendador para um cenário de uso básico e usou filtros e [Filtro de Promoção](https://docs.aws.amazon.com/pt_br/personalize/latest/dg/promoting-items.html) para aplicar regras de negócio adicionais aos produtos que devem ser apresentados aos usuários.\n", + "\n", + "Como próximo passo, é possível adaptar este código para criar outros recomendadores de acordo com as suas necessidades.\n", + "\n", + "Após concluir este exemplo, certifique-se de seguir os passos da próxima sessão para remover os recursos criados neste notebook." + ] + }, + { + "cell_type": "markdown", + "id": "aa077309", + "metadata": {}, + "source": [ + "## Remoção dos recursos\n", + "Esta seção fornece instruções sobre como remover os recursos criados neste notebook" + ] + }, + { + "cell_type": "markdown", + "id": "231035cc", + "metadata": {}, + "source": [ + "### Salve as informações do recurso para a remoção:\n", + "Salve a informação dos recursos criados para utilizar no próximo notebook `Limpeza_de_Recursos.ipynb`.\n", + "\n", + "Importante: Isto substituirá qualquer dado armazenado para estas variáveis e as definirá com os valores neste notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c480f708", + "metadata": {}, + "outputs": [], + "source": [ + "# Salve as informações dos recursos criados para posterior limpeza\n", + "%store dataset_group_arn\n", + "%store role_name\n", + "%store region\n", + "print ('dataset_group_arn:', dataset_group_arn)\n", + "print ('role_name:', role_name)\n", + "print ('region:', region)" + ] + }, + { + "cell_type": "markdown", + "id": "31a9eb53", + "metadata": {}, + "source": [ + "### Execute o notebook de remoção de recursos\n", + "\n", + "Prossiga para [Limpeza_de_Recursos.ipynb](Limpeza_de_Recursos.ipynb) limpar os recursos." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dee5b89d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "availableInstances": [ + { + "_defaultOrder": 0, + "_isFastLaunch": true, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 4, + "name": "ml.t3.medium", + "vcpuNum": 2 + }, + { + "_defaultOrder": 1, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.t3.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 2, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.t3.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 3, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.t3.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 4, + "_isFastLaunch": true, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.m5.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 5, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.m5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 6, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.m5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 7, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.m5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 8, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.m5.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 9, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.m5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 10, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.m5.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 11, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.m5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 12, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.m5d.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 13, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.m5d.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 14, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.m5d.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 15, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.m5d.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 16, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.m5d.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 17, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.m5d.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 18, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.m5d.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 19, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.m5d.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 20, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": true, + "memoryGiB": 0, + "name": "ml.geospatial.interactive", + "supportedImageNames": [ + "sagemaker-geospatial-v1-0" + ], + "vcpuNum": 0 + }, + { + "_defaultOrder": 21, + "_isFastLaunch": true, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 4, + "name": "ml.c5.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 22, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.c5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 23, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.c5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 24, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.c5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 25, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 72, + "name": "ml.c5.9xlarge", + "vcpuNum": 36 + }, + { + "_defaultOrder": 26, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 96, + "name": "ml.c5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 27, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 144, + "name": "ml.c5.18xlarge", + "vcpuNum": 72 + }, + { + "_defaultOrder": 28, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.c5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 29, + "_isFastLaunch": true, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.g4dn.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 30, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.g4dn.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 31, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.g4dn.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 32, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.g4dn.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 33, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.g4dn.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 34, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.g4dn.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 35, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 61, + "name": "ml.p3.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 36, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 244, + "name": "ml.p3.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 37, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 488, + "name": "ml.p3.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 38, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 768, + "name": "ml.p3dn.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 39, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.r5.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 40, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.r5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 41, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.r5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 42, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.r5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 43, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.r5.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 44, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.r5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 45, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 512, + "name": "ml.r5.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 46, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 768, + "name": "ml.r5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 47, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.g5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 48, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.g5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 49, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.g5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 50, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.g5.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 51, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.g5.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 52, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.g5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 53, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.g5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 54, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 768, + "name": "ml.g5.48xlarge", + "vcpuNum": 192 + }, + { + "_defaultOrder": 55, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 1152, + "name": "ml.p4d.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 56, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 1152, + "name": "ml.p4de.24xlarge", + "vcpuNum": 96 + } + ], + "instance_type": "ml.c5.large", + "kernelspec": { + "display_name": "Python 3 (Data Science 3.0)", + "language": "python", + "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-east-1:081325390199:image/sagemaker-data-science-310-v1" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/next_steps/core_use_cases/filters/promotions/Limpeza_de_Recursos.ipynb b/next_steps/core_use_cases/filters/promotions/Limpeza_de_Recursos.ipynb new file mode 100644 index 0000000..43728e5 --- /dev/null +++ b/next_steps/core_use_cases/filters/promotions/Limpeza_de_Recursos.ipynb @@ -0,0 +1,1003 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f2ed4510", + "metadata": {}, + "source": [ + "# Remoção dos recursos\n", + "Este notebook detalha o processo de remover os recursos criados no notebook anterior \"Filtro_de_promocao.ipynb\" carregando as informações dos recuros já salvos.\n", + "\n", + "(!) Se você criou recursos utilizando múltiplos notebooks e tem múltiplos grupos de conjuntos de dados, execute a célula que começa com `# store for cleanup` no final do notebook e logo em seguida execute este notebook para cada um deles (ex: caso tenha criado dois grupos de conjuntos de dados, terá que executar este notebook duas vezes). Este código remove apenas os recursos especificados em `# store for cleanup`, corresponderão ao último notebook executado).\n", + "\n", + "Você pode utilizar este notebook para remover recursos do Amazon Personalize. Os ARNs dos recursos são definidos no notebook anterior, e também é possível adicioná-los de forma manual abaixo." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a771e164", + "metadata": {}, + "outputs": [], + "source": [ + "%store -r" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24882c55", + "metadata": {}, + "outputs": [], + "source": [ + "# Se não puder/não quiser usar \"%store -r\" para carregar os recursos a serem apagados, \n", + "# pode retirar o comentário do código abaixo e inseri-los manualmente\n", + "\n", + "# dataset_group_arn='XXXXX'\n", + "# role_name='XXXXX'\n", + "# region='XXXXX'" + ] + }, + { + "cell_type": "markdown", + "id": "d317efa0", + "metadata": {}, + "source": [ + "Valide se as informações correspondem aos recursos a serem removidos. \n", + "Importante: Esta operação não pode ser desfeita." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a856088b", + "metadata": {}, + "outputs": [], + "source": [ + "print ('dataset_group_arn:', dataset_group_arn)\n", + "print ('role_name:', role_name)\n", + "print ('region:', region)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d2adadd", + "metadata": {}, + "outputs": [], + "source": [ + "schema_arns = []" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4013ba1b", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import getopt\n", + "import logging\n", + "import botocore\n", + "import boto3\n", + "import time\n", + "from packaging import version\n", + "from time import sleep\n", + "from botocore.exceptions import ClientError\n", + "\n", + "logger = logging.getLogger()\n", + "handler = logging.StreamHandler(sys.stdout)\n", + "handler.setLevel(logging.INFO)\n", + "logger.setLevel(logging.INFO)\n", + "logger.addHandler(handler)\n", + "\n", + "personalize = None" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f358d77f", + "metadata": {}, + "outputs": [], + "source": [ + "def _delete_event_trackers(dataset_group_arn):\n", + " event_tracker_arns = []\n", + "\n", + " event_trackers_paginator = personalize.get_paginator('list_event_trackers')\n", + " for event_tracker_page in event_trackers_paginator.paginate(datasetGroupArn = dataset_group_arn):\n", + " for event_tracker in event_tracker_page['eventTrackers']:\n", + " if event_tracker['status'] in [ 'ACTIVE', 'CREATE FAILED' ]:\n", + " logger.info('Removendo o event tracker {}'.format(event_tracker['eventTrackerArn']))\n", + " personalize.delete_event_tracker(eventTrackerArn = event_tracker['eventTrackerArn'])\n", + " elif event_tracker['status'].startswith('DELETE'):\n", + " logger.warning('Event tracker {} esta sendo removido e vai aguardar até completar'.format(event_tracker['eventTrackerArn']))\n", + " else:\n", + " raise Exception('Solução {} esta com o status {} e não pode ser removida'.format(event_tracker['eventTrackerArn'], event_tracker['status']))\n", + "\n", + " event_tracker_arns.append(event_tracker['eventTrackerArn'])\n", + "\n", + " max_time = time.time() + 30*60 # 30 minutos\n", + " while time.time() < max_time:\n", + " for event_tracker_arn in event_tracker_arns:\n", + " try:\n", + " describe_response = personalize.describe_event_tracker(eventTrackerArn = event_tracker_arn)\n", + " logger.debug('Event tracker {} esta com o status {}'.format(event_tracker_arn, describe_response['eventTracker']['status']))\n", + " except ClientError as e:\n", + " error_code = e.response['Error']['Code']\n", + " if error_code == 'ResourceNotFoundException':\n", + " event_tracker_arns.remove(event_tracker_arn)\n", + "\n", + " if len(event_tracker_arns) == 0:\n", + " logger.info('Todos os event trackers foram removidos ou não existem para o dataset group')\n", + " break\n", + " else:\n", + " logger.info('Agurdando para {} event tracker(s) ser(em) removido(s)'.format(len(event_tracker_arns)))\n", + " time.sleep(20)\n", + "\n", + " if len(event_tracker_arns) > 0:\n", + " raise Exception('Timed out enquanto aguardava a remoção dos event trackers')\n", + "\n", + "def _delete_filters(dataset_group_arn):\n", + " filter_arns = []\n", + "\n", + " filters_response = personalize.list_filters(datasetGroupArn = dataset_group_arn, maxResults = 100)\n", + " for filter in filters_response['Filters']:\n", + " logger.info('Removendo filtro ' + filter['filterArn'])\n", + " personalize.delete_filter(filterArn = filter['filterArn'])\n", + " filter_arns.append(filter['filterArn'])\n", + "\n", + " max_time = time.time() + 30*60 # 30 minutos\n", + " while time.time() < max_time:\n", + " for filter_arn in filter_arns:\n", + " try:\n", + " describe_response = personalize.describe_filter(filterArn = filter_arn)\n", + " logger.debug('Filtro {} esta com o status {}'.format(filter_arn, describe_response['filter']['status']))\n", + " except ClientError as e:\n", + " error_code = e.response['Error']['Code']\n", + " if error_code == 'ResourceNotFoundException':\n", + " filter_arns.remove(filter_arn)\n", + "\n", + " if len(filter_arns) == 0:\n", + " logger.info('Todos os filtros foram removidos ou não existem para o dataset group')\n", + " break\n", + " else:\n", + " logger.info('Aguardando para {} filtro(s) ser(em) removido(s)'.format(len(filter_arns)))\n", + " time.sleep(20)\n", + "\n", + " if len(filter_arns) > 0:\n", + " raise Exception('Time out enquanto aguardava a remoção dos filtros')\n", + " \n", + "def _delete_recommenders(dataset_group_arn):\n", + " recommender_arns = []\n", + " recommenders_response = personalize.list_recommenders(datasetGroupArn = dataset_group_arn, maxResults = 100)\n", + " for recommender in recommenders_response['recommenders']:\n", + " logger.info('Removendo recomendador ' + recommender['recommenderArn'])\n", + " personalize.delete_recommender(recommenderArn = recommender['recommenderArn'])\n", + " recommender_arns.append(recommender['recommenderArn'])\n", + " max_time = time.time() + 30*60 # 30 minutos\n", + " while time.time() < max_time:\n", + " for recommender_arn in recommender_arns:\n", + " try:\n", + " describe_response = personalize.describe_recommender(recommenderArn = recommender_arn)\n", + " logger.debug('Recomendados {} esta com o status {}'.format(recommender_arn, describe_response['recommender']['status']))\n", + " except ClientError as e:\n", + " error_code = e.response['Error']['Code']\n", + " if error_code == 'ResourceNotFoundException':\n", + " recommender_arns.remove(recommender_arn)\n", + "\n", + " if len(recommender_arns) == 0:\n", + " logger.info('Todos os recomendadores foram removidos ou não existem para o dataset group')\n", + " break\n", + " else:\n", + " logger.info('Aguardando para {} recomendador(es) ser(em) removido(s)'.format(len(recommender_arns)))\n", + " time.sleep(20)\n", + "\n", + " if len(recommender_arns) > 0:\n", + " raise Exception('Timed out enquanto aguardava a remoção dos recomendadores')\n", + " \n", + "\n", + "def _delete_datasets_and_schemas(dataset_group_arn, schema_arns):\n", + " dataset_arns = []\n", + " \n", + " dataset_paginator = personalize.get_paginator('list_datasets')\n", + " for dataset_page in dataset_paginator.paginate(datasetGroupArn = dataset_group_arn):\n", + " for dataset in dataset_page['datasets']:\n", + " describe_response = personalize.describe_dataset(datasetArn = dataset['datasetArn'])\n", + " schema_arns.append(describe_response['dataset']['schemaArn'])\n", + "\n", + " if dataset['status'] in ['ACTIVE', 'CREATE FAILED']:\n", + " logger.info('Removendo conjunto de dados ' + dataset['datasetArn'])\n", + " personalize.delete_dataset(datasetArn = dataset['datasetArn'])\n", + " elif dataset['status'].startswith('DELETE'):\n", + " logger.warning('Conjunto de dados {} esta sendo removido e vai aguardar até completar'.format(dataset['datasetArn']))\n", + " else:\n", + " raise Exception('Conjunto de dados {} esta com o status {} e não pode ser removido'.format(dataset['datasetArn'], dataset['status']))\n", + "\n", + " dataset_arns.append(dataset['datasetArn'])\n", + "\n", + " max_time = time.time() + 30*60 # 30 minutos\n", + " while time.time() < max_time:\n", + " for dataset_arn in dataset_arns:\n", + " try:\n", + " describe_response = personalize.describe_dataset(datasetArn = dataset_arn)\n", + " logger.debug('Dataset {} status is {}'.format(dataset_arn, describe_response['dataset']['status']))\n", + " except ClientError as e:\n", + " error_code = e.response['Error']['Code']\n", + " if error_code == 'ResourceNotFoundException':\n", + " dataset_arns.remove(dataset_arn)\n", + "\n", + " if len(dataset_arns) == 0:\n", + " logger.info('Todos os conjuntos de dados foram removidos ou não existem para o dataset group')\n", + " break\n", + " else:\n", + " logger.info('Aguardando para {} conjunto(s) de dados ser(em) removido(s)'.format(len(dataset_arns)))\n", + " time.sleep(20)\n", + "\n", + " if len(dataset_arns) > 0:\n", + " raise Exception('Timed out enquanto aguardava a remoção dos conjuntos de dados')\n", + "\n", + " for schema_arn in schema_arns:\n", + " try:\n", + " logger.info('Removendo o esquema ' + schema_arn)\n", + " personalize.delete_schema(schemaArn = schema_arn)\n", + " except ClientError as e:\n", + " error_code = e.response['Error']['Code']\n", + " if error_code == 'ResourceInUseException':\n", + " logger.info('Esquema {} ainda esta sendo utilizado por outro conjunto de dados'.format(schema_arn))\n", + " else:\n", + " raise e\n", + "\n", + " logger.info('Todos os esquemas utilizados por este conjunto de dados foram removidos ou não existem mais')\n", + "\n", + "def _delete_dataset_group(dataset_group_arn):\n", + " logger.info('Removendo o grupo de conjunto de dados ' + dataset_group_arn)\n", + " personalize.delete_dataset_group(datasetGroupArn = dataset_group_arn)\n", + "\n", + " max_time = time.time() + 30*60 # 30 minutos\n", + " while time.time() < max_time:\n", + " try:\n", + " describe_response = personalize.describe_dataset_group(datasetGroupArn = dataset_group_arn)\n", + " logger.debug('Grupo de conjunto de dados {} status é {}'.format(dataset_group_arn, describe_response['datasetGroup']['status']))\n", + " break\n", + " except ClientError as e:\n", + " error_code = e.response['Error']['Code']\n", + " if error_code == 'ResourceNotFoundException':\n", + " logger.info('Grupo de conjunto de dados {} foi removido'.format(dataset_group_arn))\n", + " else:\n", + " raise e\n", + "\n", + " logger.info('Aguardando o grupo de conjunto de dados ser removido')\n", + " time.sleep(20)\n", + "\n", + "def delete_dataset_groups(dataset_group_arns, schema_arns, region = None):\n", + " global personalize\n", + " personalize = boto3.client(service_name = 'personalize', region_name = region)\n", + "\n", + " for dataset_group_arn in dataset_group_arns:\n", + " logger.info('Dataset Group ARN: ' + dataset_group_arn)\n", + "\n", + " # 1. Remove recomendadores\n", + " _delete_recommenders(dataset_group_arn)\n", + " \n", + " # 2. Remove event trackers\n", + " _delete_event_trackers(dataset_group_arn)\n", + "\n", + " # 3. Remove filtros\n", + " _delete_filters(dataset_group_arn)\n", + "\n", + " # 4. Remove conjuntos de dados e esquemas\n", + " _delete_datasets_and_schemas(dataset_group_arn, schema_arns)\n", + "\n", + " # 5. Remove grupo de conjunto de dados\n", + " _delete_dataset_group(dataset_group_arn)\n", + "\n", + " logger.info(f'Grupo de conjunto de dados {dataset_group_arn} foi removido')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3632a568", + "metadata": {}, + "outputs": [], + "source": [ + "delete_dataset_groups([dataset_group_arn], schema_arns, region)" + ] + }, + { + "cell_type": "markdown", + "id": "84e94a02", + "metadata": {}, + "source": [ + "## Remoção da função IAM\n", + "Comece removendo a função IAM." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6372bd68", + "metadata": {}, + "outputs": [], + "source": [ + "iam = boto3.client('iam')" + ] + }, + { + "cell_type": "markdown", + "id": "31952392", + "metadata": {}, + "source": [ + "Identifique o nome da função que será removida.\n", + "\n", + "Não é possível remover uma função de IAM que ainda tenha políticas anexadas. Então depois de identificar a função, vamos listar as políticas associadas a ela." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f3e1c048", + "metadata": {}, + "outputs": [], + "source": [ + "iam.list_attached_role_policies(\n", + " RoleName = role_name\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b646683f", + "metadata": {}, + "outputs": [], + "source": [ + "# desanexar as políticas da função IAM\n", + "iam.detach_role_policy(\n", + " RoleName = role_name,\n", + " PolicyArn = \"arn:aws:iam::aws:policy/service-role/AmazonPersonalizeFullAccess\"\n", + ")\n", + "iam.detach_role_policy(\n", + " RoleName = role_name,\n", + " PolicyArn = 'arn:aws:iam::aws:policy/AmazonS3FullAccess'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "88ac5ea3", + "metadata": {}, + "source": [ + "Em seguida, é possível remover a função IAM." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "306e1bb4", + "metadata": {}, + "outputs": [], + "source": [ + "iam.delete_role(\n", + " RoleName = role_name\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "4fa0cf14", + "metadata": {}, + "source": [ + "## Remoção do bucket S3\n", + "Para excluir um bucket do S3 ele precisa estar vazio. A maneira mais fácil de excluir um bucket S3 é navegar até o console do S3, excluir os objetos no bucket e depois excluir próprio bucket S3." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff15d740", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "availableInstances": [ + { + "_defaultOrder": 0, + "_isFastLaunch": true, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 4, + "name": "ml.t3.medium", + "vcpuNum": 2 + }, + { + "_defaultOrder": 1, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.t3.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 2, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.t3.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 3, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.t3.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 4, + "_isFastLaunch": true, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.m5.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 5, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.m5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 6, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.m5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 7, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.m5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 8, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.m5.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 9, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.m5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 10, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.m5.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 11, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.m5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 12, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.m5d.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 13, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.m5d.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 14, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.m5d.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 15, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.m5d.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 16, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.m5d.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 17, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.m5d.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 18, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.m5d.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 19, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.m5d.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 20, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": true, + "memoryGiB": 0, + "name": "ml.geospatial.interactive", + "supportedImageNames": [ + "sagemaker-geospatial-v1-0" + ], + "vcpuNum": 0 + }, + { + "_defaultOrder": 21, + "_isFastLaunch": true, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 4, + "name": "ml.c5.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 22, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.c5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 23, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.c5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 24, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.c5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 25, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 72, + "name": "ml.c5.9xlarge", + "vcpuNum": 36 + }, + { + "_defaultOrder": 26, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 96, + "name": "ml.c5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 27, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 144, + "name": "ml.c5.18xlarge", + "vcpuNum": 72 + }, + { + "_defaultOrder": 28, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.c5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 29, + "_isFastLaunch": true, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.g4dn.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 30, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.g4dn.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 31, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.g4dn.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 32, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.g4dn.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 33, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.g4dn.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 34, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.g4dn.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 35, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 61, + "name": "ml.p3.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 36, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 244, + "name": "ml.p3.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 37, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 488, + "name": "ml.p3.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 38, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 768, + "name": "ml.p3dn.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 39, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.r5.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 40, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.r5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 41, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.r5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 42, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.r5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 43, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.r5.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 44, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.r5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 45, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 512, + "name": "ml.r5.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 46, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 768, + "name": "ml.r5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 47, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.g5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 48, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.g5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 49, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.g5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 50, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.g5.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 51, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.g5.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 52, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.g5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 53, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.g5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 54, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 768, + "name": "ml.g5.48xlarge", + "vcpuNum": 192 + }, + { + "_defaultOrder": 55, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 1152, + "name": "ml.p4d.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 56, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 1152, + "name": "ml.p4de.24xlarge", + "vcpuNum": 96 + } + ], + "instance_type": "ml.t3.medium", + "kernelspec": { + "display_name": "Python 3 (Data Science 3.0)", + "language": "python", + "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-east-1:081325390199:image/sagemaker-data-science-310-v1" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/next_steps/core_use_cases/filters/promotions/images/promotions-overview-pt-br.png b/next_steps/core_use_cases/filters/promotions/images/promotions-overview-pt-br.png new file mode 100644 index 0000000..c7f6c1c Binary files /dev/null and b/next_steps/core_use_cases/filters/promotions/images/promotions-overview-pt-br.png differ