One place for hosting & domains

      Uma Introdução às consultas no MySQL


      Introdução

      Bancos de dados são um componente chave em muitos websites e aplicações, e estão no centro de como os dados são armazenados e trocados pela Internet. Um dos aspectos mais importantes do gerenciamento de banco de dados é a prática de recuperar dados de um banco de dados, seja em uma base ad hoc ou parte de um processo codificado em um aplicativo. Existem várias maneiras de recuperar informações de um banco de dados, mas um dos métodos mais utilizados é realizado através do envio de consultas pela linha de comandos.

      Em sistemas de gerenciamento de bancos de dados relacionais, uma consulta é qualquer comando usado para recuperar dados de uma tabela. Na Linguagem de Consulta Estruturada ou Structured Query Language (SQL), consultas são feitas quase sempre usando o comando SELECT.

      Neste guia, discutiremos a sintaxe básica das consultas SQL, bem como algumas das funções e operadores mais comumente empregados. Vamos também praticar a criação de consultas SQL usando alguns dados de amostra em um banco de dados MySQL.

      O MySQL é um sistema de gerenciamento de banco de dados relacional open-source. Sendo um dos bancos de dados SQL mais amplamente implantados, o MySQL prioriza velocidade, confiabilidade e usabilidade. Em geral, ele segue o padrão SQL ANSI, embora haja alguns casos em que o MySQL executa operações de maneira diferente do padrão reconhecido.

      Pré-requisitos

      Em geral, os comandos e conceitos apresentados neste guia podem ser usados em qualquer sistema operacional baseado em Linux executando qualquer software de banco de dados SQL. No entanto, ele foi escrito especificamente com um servidor Ubuntu 18.04 executando o MySQL em mente. Para configurar isso, você precisará do seguinte:

      Com esta configuração pronta, podemos começar o tutorial.

      Criando um Banco de Dados de Amostra

      Antes de começarmos a fazer consultas no SQL, vamos primeiro criar um banco de dados e algumas tabelas, em seguida, preencher essas tabelas com alguns dados de amostra. Isso permitirá que você ganhe experiência prática quando começar a fazer consultas mais tarde.

      Para o banco de dados de amostra que usaremos ao longo deste guia, imagine o seguinte cenário:

      Você e vários de seus amigos celebram seus aniversários juntos. Em cada ocasião, os membros do grupo vão para a pista de boliche local, participam de um torneio amistoso e, em seguida, todos vão para sua casa, onde você prepara a refeição favorita do aniversariante.

      Agora que essa tradição já dura algum tempo, você decidiu começar a acompanhar os registros desses torneios. Além disso, para tornar o planejamento das refeições mais fácil, você decide criar um registro dos aniversários dos seus amigos e de suas entradas, acompanhamentos e sobremesas favoritas. Em vez de manter essas informações em um livro físico, você decide exercitar suas habilidades de banco de dados gravando-as em um banco de dados MySQL.

      Para começar, abra um prompt de MySQL como seu usuário root do MySQL:

      Note: Se você seguiu o pré-requisito do tutorial sobre Como Instalar o MySQL no Ubuntu 18.04, você pode ter configurado seu usuário root para autenticar usando uma senha. Neste caso, você irá se conectar ao prompt do MySQL com o seguinte comando:

      Em seguida, crie o banco de dados executando:

      • CREATE DATABASE `aniversarios`;

      Depois, selecione este banco de dados digitando:

      A seguir, crie duas tabelas dentro desse banco de dados. Vamos utilizar a primeira tabela para acompanhar os registros dos seus amigos na pista de boliche. O seguinte comando criará uma tabela chamada torneios com colunas para o nome de cada um de seus amigos, o número de torneios que eles ganharam (vitorias), a melhor pontuação deles em todo o tempo, e que tamanho de sapato de boliche eles usam (tamanho):

      • CREATE TABLE torneios (
      • nome varchar(30),
      • vitorias real,
      • melhor real,
      • tamanho real
      • );

      Depois de executar o comando CREATE TABLE e preenchê-lo com títulos das colunas, você receberá a seguinte saída:

      Output

      Query OK, 0 rows affected (0.00 sec)

      Preencha a tabela torneios com alguns dados de amostra:

      • INSERT INTO torneios (nome, vitorias, melhor, tamanho)
      • VALUES ('Dolly', '7', '245', '8.5'),
      • ('Etta', '4', '283', '9'),
      • ('Irma', '9', '266', '7'),
      • ('Barbara', '2', '197', '7.5'),
      • ('Gladys', '13', '273', '8');

      Você receberá uma saída como esta:

      Output

      Query OK, 5 rows affected (0.01 sec) Records: 5 Duplicates: 0 Warnings: 0

      Depois disso, crie outra tabela dentro do mesmo banco de dados que usaremos para armazenar informações sobre as refeições de aniversário favoritas dos seus amigos. O seguinte comando cria uma tabela chamada refeicoes com colunas para o nome de cada um dos seus amigos, a data_nascimento, a entrada favorita de cada um, o acompanhamento preferido, e a sobremesa favorita:

      • CREATE TABLE refeicoes (
      • nome varchar(30),
      • data_nascimento date,
      • entrada varchar(30),
      • acompanhamento varchar(30),
      • sobremesa varchar(30)
      • );

      Da mesma forma, para esta tabela, você receberá um feedback confirmando que o comando foi executado com sucesso:

      Output

      Query OK, 0 rows affected (0.01 sec)

      Preencha esta tabela com alguns dados de amostra também:

      • INSERT INTO refeicoes (nome, data_nascimento, entrada, acompanhamento, sobremesa)
      • VALUES ('Dolly', '1946-01-19', 'steak', 'salad', 'cake'),
      • ('Etta', '1938-01-25', 'chicken', 'fries', 'ice cream'),
      • ('Irma', '1941-02-18', 'tofu', 'fries', 'cake'),
      • ('Barbara', '1948-12-25', 'tofu', 'salad', 'ice cream'),
      • ('Gladys', '1944-05-28', 'steak', 'fries', 'ice cream');

      Output

      Query OK, 5 rows affected (0.00 sec) Records: 5 Duplicates: 0 Warnings: 0

      Uma vez que esse comando tenha sido concluído com êxito, você acabou de configurar seu banco de dados. A seguir, vamos falar sobre a estrutura básica de comando das consultas SELECT.

      Entendendo Comandos SELECT

      Conforme mencionado na introdução, consultas SQL quase sempre começam com o comando SELECT. SELECT é usado em consultas para especificar quais colunas de uma tabela devem ser retornadas no conjunto de resultados ou result-set. As consultas também quase sempre incluem FROM, que é usado para especificar qual tabela o comando consultará.

      Geralmente, as consultas SQL seguem essa sintaxe:

      • SELECT coluna_a_selecionar FROM tabela_a_selecionar WHERE certas_condições_a_aplicar;

      A título de exemplo, o seguinte comando retornará a coluna nome inteira da tabela refeicoes:

      • SELECT nome FROM refeicoes;
      [seconday_label Output]
      +---------+
      | nome    |
      +---------+
      | Dolly   |
      | Etta    |
      | Irma    |
      | Barbara |
      | Gladys  |
      +---------+
      5 rows in set (0.00 sec)
      

      Você pode selecionar várias colunas da mesma tabela, separando seus nomes com uma vírgula, desta forma:

      • SELECT nome, data_nascimento FROM refeicoes;

      Output

      +---------+-----------------+ | nome | data_nascimento | +---------+-----------------+ | Dolly | 1946-01-19 | | Etta | 1938-01-25 | | Irma | 1941-02-18 | | Barbara | 1948-12-25 | | Gladys | 1944-05-28 | +---------+-----------------+ 5 rows in set (0.00 sec)

      Em vez de nomear uma coluna específica ou um conjunto de colunas, você pode seguir o operador SELECT com um asterisco (*) que serve como um curinga representando todas as colunas em uma tabela. O seguinte comando retorna todas as colunas da tabela torneios:

      Output

      +---------+----------+--------+---------+ | nome | vitorias | melhor | tamanho | +---------+----------+--------+---------+ | Dolly | 7 | 245 | 8.5 | | Etta | 4 | 283 | 9 | | Irma | 9 | 266 | 7 | | Barbara | 2 | 197 | 7.5 | | Gladys | 13 | 273 | 8 | +---------+----------+--------+---------+ 5 rows in set (0.00 sec)

      WHERE é usado em consultas para filtrar registros que atendem a uma condição especificada, e todas as linhas que não atendem a essa condição são eliminadas do resultado. Uma cláusula WHERE geralmente segue esta sintaxe:

      • . . . WHERE nome_da_coluna operador_de_comparação valor

      O operador de comparação em uma cláusula WHERE define como a coluna especificada deve ser comparada com o valor. Aqui estão alguns operadores comuns de comparação em SQL:

      Operador O que ele faz
      = testa a igualdade
      != testa a desigualdade
      < testa menor que
      > testa maior que
      <= testa menor que ou igual a
      >= testa maior que ou igual a
      BETWEEN testa se um valor está dentro de um determinado intervalo
      IN testa se o valor de uma linha está contido em um conjunto de valores especificados
      EXISTS testa se existem linhas, dadas as condições especificadas
      LIKE testa se um valor corresponde a uma string especificada
      IS NULL testa valores NULL
      IS NOT NULL testa todos os valores que não sejam NULL

      Por exemplo, se você quiser encontrar o tamanho do sapato de Irma, use a seguinte consulta:

      • SELECT tamanho FROM torneios WHERE nome = 'Irma';

      Output

      +---------+ | tamanho | +---------+ | 7 | +---------+ 1 row in set (0.00 sec)

      O SQL permite o uso de caracteres curinga, e eles são especialmente úteis quando usados em cláusulas WHERE. Os sinais de porcentagem (%) representam zero ou mais caracteres desconhecidos, e os sublinhados ou underscores (_) representam um único caractere desconhecido. Eles são úteis se você estiver tentando encontrar uma informação específica em uma tabela, mas não tiver certeza de qual é exatamente essa informação. Para ilustrar, digamos que você tenha esquecido a entrada favorita de alguns de seus amigos, mas você está certo de que este prato principal começa com um "t". Você pode encontrar seu nome executando a seguinte consulta:

      • SELECT entrada FROM refeicoes WHERE entrada LIKE 't%';

      Output

      +---------+ | entrada | +---------+ | tofu | | tofu | +---------+ 2 rows in set (0.00 sec)

      Com base na saída acima, vemos que a entrada que esquecemos é tofu.

      Pode haver momentos em que você está trabalhando com bancos de dados que possuem colunas ou tabelas com nomes relativamente longos ou difíceis de ler. Nesses casos, você pode tornar esses nomes mais legíveis criando um alias ou apelido com a palavra-chave AS. Apelidos criados com AS são temporários e existem apenas durante a consulta para a qual eles foram criados:

      • SELECT nome AS n, data_nascimento AS d, sobremesa AS s FROM refeicoes;

      Output

      +---------+------------+-----------+ | n | d | s | +---------+------------+-----------+ | Dolly | 1946-01-19 | cake | | Etta | 1938-01-25 | ice cream | | Irma | 1941-02-18 | cake | | Barbara | 1948-12-25 | ice cream | | Gladys | 1944-05-28 | ice cream | +---------+------------+-----------+ 5 rows in set (0.00 sec)

      Aqui, dissemos ao SQL para exibir a coluna nome como n, a coluna data_nascimento como d e a coluna sobremesa como s.

      Os exemplos que mostramos até aqui incluem algumas das palavras-chave e cláusulas mais usadas em consultas SQL. Elas são úteis para consultas básicas, mas não são úteis se você estiver tentando realizar um cálculo ou derivar um valor escalar (um valor único, em oposição a um conjunto de vários valores diferentes) com base em seus dados. É aqui que as funções de agregação entram em ação.

      Funções de Agregação

      Muitas vezes, ao trabalhar com dados, você não necessariamente quer ver os dados em si. Em vez disso, você quer informações sobre os dados. A sintaxe SQL inclui várias funções que permitem interpretar ou executar cálculos em seus dados apenas emitindo uma consulta SELECT. Estas são conhecidas como funções de agregação.

      A função COUNT conta e retorna o número de linhas que correspondem a um determinado critério. Por exemplo, se você quiser saber quantos dos seus amigos preferem o tofu para a entrada de aniversário, você pode fazer essa consulta:

      • SELECT COUNT(entrada) FROM refeicoes WHERE entrada = 'tofu';

      Output

      +----------------+ | COUNT(entrada) | +----------------+ | 2 | +----------------+ 1 row in set (0.00 sec)

      A função AVG retorna o valor médio (média) de uma coluna. Usando nossa tabela de exemplo, você pode encontrar a melhor pontuação média entre seus amigos com esta consulta:

      • SELECT AVG(melhor) FROM torneios;

      Output

      +-------------+ | AVG(melhor) | +-------------+ | 252.8 | +-------------+ 1 row in set (0.00 sec)

      SUM é usado para encontrar a soma total de uma determinada coluna. Por exemplo, se você quiser ver quantos jogos você e seus amigos jogaram ao longo dos anos, você pode executar essa consulta:

      • SELECT SUM(vitorias) FROM torneios;

      Output

      +---------------+ | SUM(vitorias) | +---------------+ | 35 | +---------------+ 1 row in set (0.00 sec)

      Observe que as funções AVG eSUM só funcionarão corretamente quando usadas com dados numéricos. Se você tentar usá-los em dados não numéricos, isso resultará em um erro ou apenas 0, dependendo de qual SGBD você está usando:

      • SELECT SUM(entrada) FROM refeicoes;

      Output

      +--------------+ | SUM(entrada) | +--------------+ | 0 | +--------------+ 1 row in set, 5 warnings (0.00 sec)

      MIN é usado para encontrar o menor valor dentro de uma coluna especificada. Você poderia usar essa consulta para ver qual o pior registro geral de boliche até agora (em termos de número de vitórias):

      • SELECT MIN(vitorias) FROM torneios;
      [secondarylabel Output]
      +---------------+
      | MIN(vitorias) |
      +---------------+
      |             2 |
      +---------------+
      1 row in set (0.00 sec)
      

      Da mesma forma, MAX é usado para encontrar o maior valor numérico em uma determinada coluna. A consulta a seguir mostrará o melhor registro geral de boliche:

      • SELECT MAX(vitorias) FROM torneios;

      Output

      +---------------+ | MAX(vitorias) | +---------------+ | 13 | +---------------+ 1 row in set (0.00 sec)

      Ao contrário de SUM eAVG, as funções MIN eMAX podem ser usadas para tipos de dados numéricos e alfabéticos. Quando executado em uma coluna contendo valores de string, a função MIN mostrará o primeiro valor alfabeticamente:

      • SELECT MIN(nome) FROM refeicoes;

      Output

      +-----------+ | MIN(nome) | +-----------+ | Barbara | +-----------+ 1 row in set (0.00 sec)

      Da mesma forma, quando executado em uma coluna contendo valores de string, a função MAX mostrará o último valor em ordem alfabética:

      • SELECT MAX(nome) FROM refeicoes;

      Output

      +-----------+ | MAX(nome) | +-----------+ | Irma | +-----------+ 1 row in set (0.00 sec)

      As funções agregadas têm muitos usos além do que foi descrito nesta seção. Elas são particularmente úteis quando usadas com a cláusula GROUP BY, que é abordada na próxima seção junto com várias outras cláusulas de consulta que afetam como os result-sets são classificados.

      Manipulando Saídas da Consulta

      Além das cláusulas FROM eWHERE, existem várias outras cláusulas que são usadas para manipular os resultados de uma consulta SELECT. Nesta seção, explicaremos e forneceremos exemplos para algumas das cláusulas de consulta mais comumente usadas.

      Uma das cláusulas de consulta mais usadas, além de FROM e WHERE, é a cláusula GROUP BY. Ela é normalmente usada quando você está executando uma função de agregação em uma coluna, mas em relação aos valores correspondentes em outra.

      Por exemplo, digamos que você queria saber quantos de seus amigos preferem cada uma das três entradas que você faz. Você pode encontrar essa informação com a seguinte consulta:

      • SELECT COUNT(nome), entrada FROM refeicoes GROUP BY entrada;

      Output

      +-------------+----------+ | COUNT(nome) | entrada | +-------------+----------+ | 1 | chicken | | 2 | steak | | 2 | tofu | +-------------+----------+ 3 rows in set (0.00 sec)

      A cláusula ORDER BY é usada para classificar os resultados da consulta. Por padrão, os valores numéricos são classificados em ordem crescente e os valores de texto são classificados em ordem alfabética. Para ilustrar, a consulta a seguir lista as colunas nome e data_nascimento, mas classifica os resultados por data_nascimento:

      • SELECT nome, data_nascimento FROM refeicoes ORDER BY data_nascimento;

      Output

      +---------+-----------------+ | nome | data_nascimento | +---------+-----------------+ | Etta | 1938-01-25 | | Irma | 1941-02-18 | | Gladys | 1944-05-28 | | Dolly | 1946-01-19 | | Barbara | 1948-12-25 | +---------+-----------------+ 5 rows in set (0.00 sec)

      Observe que o comportamento padrão de ORDER BY é classificar o result-set em ordem crescente. Para reverter isso e ter o resultado classificado em ordem decrescente, feche a consulta com DESC:

      • SELECT nome, data_nascimento FROM refeicoes ORDER BY data_nascimento DESC;

      Output

      +---------+-----------------+ | nome | data_nascimento | +---------+-----------------+ | Barbara | 1948-12-25 | | Dolly | 1946-01-19 | | Gladys | 1944-05-28 | | Irma | 1941-02-18 | | Etta | 1938-01-25 | +---------+-----------------+ 5 rows in set (0.00 sec)

      Como mencionado anteriormente, a cláusula WHERE é usada para filtrar resultados com base em condições específicas. No entanto, se você usar a cláusula WHERE com uma função de agregação, ela retornará um erro, como é o caso da seguinte tentativa de encontrar quais acompanhamentos são os favoritos de pelo menos três de seus amigos:

      • SELECT COUNT(nome), acompanhamento FROM refeicoes WHERE COUNT(nome) >= 3;

      Output

      ERROR 1111 (HY000): Invalid use of group function

      A cláusula HAVING foi adicionada ao SQL para fornecer funcionalidade semelhante à da cláusula WHERE, além de ser compatível com funções de agregação. É útil pensar na diferença entre essas duas cláusulas como sendo que WHERE se aplica a registros individuais, enquantoHAVING se aplica a grupos de registros. Para este fim, sempre que você emitir uma cláusula HAVING, a cláusula GROUP BY também deve estar presente.

      O exemplo a seguir é outra tentativa de descobrir quais são os acompanhamentos favoritos de pelo menos três de seus amigos, embora este retorne um resultado sem erro:

      • SELECT COUNT(nome), acompanhamento FROM refeicoes GROUP BY acompanhamento HAVING COUNT(nome) >= 3;

      Output

      +-------------+----------------+ | COUNT(nome) | acompanhamento | +-------------+----------------+ | 3 | fries | +-------------+----------------+ 1 row in set (0.00 sec)

      As funções de agregação são úteis para resumir os resultados de uma determinada coluna em uma dada tabela. No entanto, há muitos casos em que é necessário consultar o conteúdo de mais de uma tabela. Na próxima seção analisaremos algumas maneiras de fazer isso.

      Consultando Várias Tabelas

      Mais frequentemente, um banco de dados contém várias tabelas, cada uma contendo diferentes conjuntos de dados. O SQL fornece algumas maneiras diferentes de executar uma única consulta em várias tabelas.

      A cláusula JOIN pode ser usada para combinar linhas de duas ou mais tabelas em um resultado de consulta. Ele faz isso localizando uma coluna relacionada entre as tabelas e classifica os resultados adequadamente na saída.

      Os comandos SELECT que incluem uma cláusula JOIN geralmente seguem esta sintaxe:

      • SELECT tabela1.coluna1, tabela2.coluna2
      • FROM tabela1
      • JOIN tabela2 ON tabela1.coluna_relacionada=tabela2.coluna_relacionada;

      Note que como cláusulas JOIN comparam o conteúdo de mais de uma tabela, o exemplo anterior especifica em qual tabela selecionar cada coluna, precedendo o nome da coluna com o nome da tabela e um ponto. Você pode especificar de qual tabela uma coluna deve ser selecionada para qualquer consulta, embora isso não seja necessário ao selecionar de uma única tabela, como fizemos nas seções anteriores. Vamos examinar um exemplo usando nossos dados de amostra.

      Imagine que você queria comprar para cada um de seus amigos um par de sapatos de boliche como presente de aniversário. Como as informações sobre datas de nascimento e tamanhos de calçados dos seus amigos são mantidas em tabelas separadas, você pode consultar as duas tabelas separadamente e comparar os resultados de cada uma delas. Com uma cláusula JOIN, no entanto, você pode encontrar todas as informações desejadas com uma única consulta:

      • SELECT torneios.nome, torneios.tamanho, refeicoes.data_nascimento
      • FROM torneios
      • JOIN refeicoes ON torneios.nome=refeicoes.nome;

      Output

      +---------+---------+------------------+ | nome | tamanho | data_nascimento | +---------+---------+------------------+ | Dolly | 8.5 | 1946-01-19 | | Etta | 9 | 1938-01-25 | | Irma | 7 | 1941-02-18 | | Barbara | 7.5 | 1948-12-25 | | Gladys | 8 | 1944-05-28 | +---------+---------+------------------+ 5 rows in set (0.00 sec)

      A cláusula JOIN usada neste exemplo, sem nenhum outro argumento, é uma cláusula inner JOIN. Isso significa que ela seleciona todos os registros que possuem valores correspondentes nas duas tabelas e os imprime no result-set, enquanto todos os registros que não tem correspondência são excluídos. Para ilustrar essa ideia, vamos adicionar uma nova linha a cada tabela que não tenha uma entrada correspondente na outra:

      • INSERT INTO torneios (nome, vitorias, melhor, tamanho)
      • VALUES ('Bettye', '0', '193', '9');
      • INSERT INTO refeicoes (nome, data_nascimento, entrada, acompanhamento, sobremesa)
      • VALUES ('Lesley', '1946-05-02', 'steak', 'salad', 'ice cream');

      Então, execute novamente a instrução SELECT anterior com a cláusula JOIN:

      • SELECT torneios.nome, torneios.tamanho, refeicoes.data_nascimento
      • FROM torneios
      • JOIN refeicoes ON torneios.nome=refeicoes.nome;

      Output

      +---------+---------+-----------------+ | nome | tamanho | data_nascimento | +---------+---------+-----------------+ | Dolly | 8.5 | 1946-01-19 | | Etta | 9 | 1938-01-25 | | Irma | 7 | 1941-02-18 | | Barbara | 7.5 | 1948-12-25 | | Gladys | 8 | 1944-05-28 | +---------+---------+-----------------+ 5 rows in set (0.00 sec)

      Observe que, como a tabela torneios não tem entrada para Lesley e a tabela refeicoes não tem entrada para Bettye, esses registros estão ausentes desta saída.

      É possível, no entanto, retornar todos os registros de uma das tabelas usando uma cláusula outer JOIN. No MySQL, as cláusulas JOIN são escritas como LEFT JOIN ou RIGHT JOIN.

      Uma cláusula LEFT JOIN retorna todos os registros da tabela da "esquerda" e apenas os registros correspondentes da tabela da direita. No contexto de outer joins, a tabela da esquerda é aquela referenciada pela cláusula FROM e a tabela da direita é qualquer outra tabela referenciada após o comando JOIN.

      Execute a consulta anterior novamente, mas desta vez use uma cláusula LEFT JOIN:

      • SELECT torneios.nome, torneios.tamanho, refeicoes.data_nascimento
      • FROM torneios
      • LEFT JOIN refeicoes ON torneios.nome=refeicoes.nome;

      Este comando retornará todos os registros da tabela da esquerda (neste caso, torneios), mesmo que não tenha um registro correspondente na tabela da direita. Toda vez que não houver um registro correspondente da tabela da direita, ele será retornado como NULL ou apenas como um valor em branco, dependendo do seu SGBD:

      Output

      +---------+---------+-----------------+ | nome | tamanho | data_nascimento | +---------+---------+-----------------+ | Dolly | 8.5 | 1946-01-19 | | Etta | 9 | 1938-01-25 | | Irma | 7 | 1941-02-18 | | Barbara | 7.5 | 1948-12-25 | | Gladys | 8 | 1944-05-28 | | Bettye | 9 | NULL | +---------+---------+-----------------+ 6 rows in set (0.00 sec)

      Agora execute a consulta novamente, desta vez com uma cláusula RIGHT JOIN:

      • SELECT torneios.nome, torneios.tamanho, refeicoes.data_nascimento
      • FROM torneios
      • RIGHT JOIN refeicoes ON torneios.nome=refeicoes.nome;

      Isso retornará todos os registros da tabela da direita (refeicoes). Como a data de nascimento de Lesley está registrada na tabela da direita, mas não há uma linha correspondente para ela na tabela da esquerda, as colunas nome e tamanho retornarão como valores NULL nessa linha:

      Output

      +---------+---------+-----------------+ | nome | tamanho | data_nascimento | +---------+---------+-----------------+ | Dolly | 8.5 | 1946-01-19 | | Etta | 9 | 1938-01-25 | | Irma | 7 | 1941-02-18 | | Barbara | 7.5 | 1948-12-25 | | Gladys | 8 | 1944-05-28 | | NULL | NULL | 1946-05-02 | +---------+---------+-----------------+ 6 rows in set (0.00 sec)

      Observe que joins à esquerda e à direita podem ser escritos como LEFT OUTER JOIN ou RIGHT OUTER JOIN, embora a parte OUTER da cláusula esteja implícita. Da mesma forma, especificar INNER JOIN produzirá o mesmo resultado que apenas escrever JOIN.

      Como uma alternativa ao uso de JOIN para consultar registros de várias tabelas, você pode usar a cláusula UNION.

      O operador UNION funciona de forma ligeiramente diferente de uma cláusula JOIN: em vez de imprimir resultados de várias tabelas como colunas únicas usando um único comando SELECT, o UNION combina os resultados de dois comandos SELECT em uma única coluna.

      Para ilustrar, execute a seguinte consulta:

      • SELECT nome FROM torneios UNION SELECT nome FROM refeicoes;

      Esta consulta removerá quaisquer entradas duplicadas, que é o comportamento padrão do operador UNION:

      Output

      +---------+ | nome | +---------+ | Dolly | | Etta | | Irma | | Barbara | | Gladys | | Bettye | | Lesley | +---------+ 7 rows in set (0.00 sec)

      Para retornar todas as entradas (incluindo as duplicadas), use o operador UNION ALL:

      • SELECT nome FROM torneios UNION ALL SELECT nome FROM refeicoes;

      Output

      +---------+ | nome | +---------+ | Dolly | | Etta | | Irma | | Barbara | | Gladys | | Bettye | | Dolly | | Etta | | Irma | | Barbara | | Gladys | | Lesley | +---------+ 12 rows in set (0.00 sec)

      Os nomes e números das colunas na tabela de resultados refletem o nome e o número de colunas consultadas pelo primeiro comando SELECT. Note que ao usar UNION para consultar múltiplas colunas de mais de uma tabela, cada comando SELECT deve consultar o mesmo número de colunas, as respectivas colunas devem ter tipos de dados similares, e as colunas em cada comando SELECT devem estar na mesma ordem. O exemplo a seguir mostra o que pode resultar se você usar uma cláusula UNION em dois comandos SELECT que consultam um número diferente de colunas:

      • SELECT nome FROM refeicoes UNION SELECT nome, vitorias FROM torneios;

      Output

      ERROR 1222 (21000): The used SELECT statements have a different number of columns

      Outra maneira de consultar várias tabelas é através do uso de subconsultas ou subqueries. As subqueries (também conhecidas como consultas internas ou aninhadas) são consultas incluídas em outra consulta. Elas são úteis nos casos em que você está tentando filtrar os resultados de uma consulta com base no resultado de uma função de agregação separada.

      Para ilustrar essa ideia, digamos que você queira saber quais dos seus amigos ganharam mais partidas do que Bárbara. Em vez de consultar quantos jogos Bárbara venceu e, em seguida, executar outra consulta para ver quem ganhou mais jogos do que isso, você pode calcular ambos com uma única consulta:

      • SELECT nome, vitorias FROM torneios
      • WHERE vitorias > (
      • SELECT vitorias FROM torneios WHERE nome = 'Barbara'
      • );

      Output

      +--------+----------+ | nome | vitorias | +--------+----------+ | Dolly | 7 | | Etta | 4 | | Irma | 9 | | Gladys | 13 | +--------+----------+ 4 rows in set (0.00 sec)

      A subquerie nesse comando foi executada apenas uma vez; ele só precisava encontrar o valor da coluna vitorias na mesma linha que Barbara na coluna nome, e os dados retornados pela subquerie e pela consulta externa são independentes um do outro. Existem casos, no entanto, em que a consulta externa deve primeiro ler todas as linhas de uma tabela e comparar esses valores com os dados retornados pela subquerie para retornar os dados desejados. Nesse caso, a subquerie é referida como uma subquerie correlacionada.

      O comando a seguir é um exemplo de uma subquerie correlacionada. Esta consulta procura descobrir quais dos seus amigos ganharam mais jogos do que a média para aqueles com o mesmo tamanho de calçado:

      • SELECT nome, tamanho FROM torneios AS t
      • WHERE vitorias > (
      • SELECT AVG(vitorias) FROM torneios WHERE tamanho = t.tamanho
      • );

      Para que a consulta seja concluída, ela deve primeiro coletar as colunas nome e tamanho da consulta externa. Em seguida, ele compara cada linha desse result-set com os resultados da consulta interna, que determina o número médio de vitórias para indivíduos com tamanhos de sapatos idênticos. Como você só tem dois amigos com o mesmo tamanho de calçado, só pode haver uma linha no result-set:

      Output

      +------+---------+ | nome | tamanho | +------+---------+ | Etta | 9 | +------+---------+ 1 row in set (0.00 sec)

      Conforme mencionado anteriormente, as subquerie podem ser usadas para consultar resultados de várias tabelas. Para ilustrar isso com um exemplo final, digamos que você queria fazer um jantar surpresa para o melhor jogador de todos os tempos do grupo. Você pode encontrar qual dos seus amigos tem o melhor registro de boliche e retornar sua refeição favorita com a seguinte consulta:

      • SELECT nome, entrada, acompanhamento, sobremesa
      • FROM refeicoes
      • WHERE nome = (SELECT nome FROM torneios
      • WHERE vitorias = (SELECT MAX(vitorias) FROM torneios));

      Output

      +--------+---------+-----------------+------------+ | nome | entrada | acompanhamento | sobremesa | +--------+---------+-----------------+------------+ | Gladys | steak | fries | ice cream | +--------+---------+-----------------+------------+ 1 row in set (0.00 sec)

      Observe que esse comando não inclui apenas uma subquerie, mas também contém uma subquerie dentro dessa subquerie.

      Conclusão

      A realização de consultas é uma das tarefas mais comuns no domínio do gerenciamento de banco de dados. Existem várias ferramentas de administração de banco de dados, como phpMyAdmin or pgAdmin, que permitem realizar consultas e visualizar os resultados, mas a execução de comandos SELECT a partir da linha de comando ainda é um fluxo de trabalho amplamente praticado que também pode fornecer maior controle.

      Se você é novato no trabalho com SQL, lhe encorajamos a usar nosso Guia de Consulta Rápida SQL como referência e a revisar a documentação oficial do MySQL. Além disso, se você quiser saber mais sobre bancos de dados relacionais e SQL, os seguintes tutoriais podem ser de seu interesse:

      Por Mark Drake



      Source link

      Como Construir uma Aplicação Node.js com o Docker


      Introdução

      A plataforma Docker permite aos desenvolvedores empacotar e executar aplicações como containers. Um container é um processo isolado que executa em um sistema operacional compartilhado, oferecendo uma alternativa mais leve às máquinas virtuais. Embora os containers não sejam novos, eles oferecem benefícios — incluindo isolamento do processo e padronização do ambiente — que estão crescendo em importância à medida que mais desenvolvedores usam arquiteturas de aplicativos distribuídos.

      Ao criar e dimensionar uma aplicação com o Docker, o ponto de partida normalmente é a criação de uma imagem para a sua aplicação, que você pode então, executar em um container. A imagem inclui o código da sua aplicação, bibliotecas, arquivos de configuração, variáveis de ambiente, e runtime. A utilização de uma imagem garante que o ambiente em seu container está padronizado e contém somente o que é necessário para construir e executar sua aplicação.

      Neste tutorial, você vai criar uma imagem de aplicação para um website estático que usa o framework Express e o Bootstrap. Em seguida, você criará um container usando essa imagem e a enviará para o Docker Hub para uso futuro. Por fim, você irá baixar a imagem armazenada do repositório do Docker Hub e criará outro container, demonstrando como você pode recriar e escalar sua aplicação.

      Pré-requisitos

      Para seguir este tutorial, você vai precisar de:

      Passo 1 — Instalando as Dependências da Sua Aplicação

      Para criar a sua imagem, primeiro você precisará produzir os arquivos de sua aplicação, que você poderá copiar para o seu container. Esses arquivos incluirão o conteúdo estático, o código e as dependências da sua aplicação.

      Primeiro, crie um diretório para o seu projeto no diretório home do seu usuário não-root. Vamos chamar o nosso de node_project, mas sinta-se à vontade para substituir isso por qualquer outra coisa:

      Navegue até esse diretório:

      Esse será o diretório raiz do projeto:

      Em seguida, crie um arquivo package.json com as dependências do seu projeto e outras informações de identificação. Abra o arquivo com o nano ou o seu editor favorito:

      Adicione as seguintes informações sobre o projeto, incluindo seu nome, autor, licença, ponto de entrada e dependências. Certifique-se de substituir as informações do autor pelo seu próprio nome e seus detalhes de contato:

      ~/node_project/package.json

      
      {
        "name": "nodejs-image-demo",
        "version": "1.0.0",
        "description": "nodejs image demo",
        "author": "Sammy the Shark <sammy@example.com>",
        "license": "MIT",
        "main": "app.js",
        "keywords": [
          "nodejs",
          "bootstrap",
          "express"
        ],
        "dependencies": {
          "express": "^4.16.4"
        }
      }
      

      Este arquivo inclui o nome do projeto, autor e a licença sob a qual ele está sendo compartilhado. O npm recomenda manter o nome do seu projeto curto e descritivo, evitando duplicidades no registro npm. Listamos a licença do MIT no campo de licença, permitindo o uso e a distribuição gratuitos do código do aplicativo.

      Além disso, o arquivo especifica:

      • "main": O ponto de entrada para a aplicação, app.js. Você criará esse arquivo em seguida.

      • "dependencies": As dependências do projeto — nesse caso, Express 4.16.4 ou acima.

      Embora este arquivo não liste um repositório, você pode adicionar um seguindo estas diretrizes em adicionando um repositório ao seu arquivo package.json. Esse é um bom acréscimo se você estiver versionando sua aplicação.

      Salve e feche o arquivo quando você terminar de fazer as alterações.

      Para instalar as dependências do seu projeto, execute o seguinte comando:

      Isso irá instalar os pacotes que você listou em seu arquivo package.json no diretório do seu projeto.

      Agora podemos passar para a construção dos arquivos da aplicação.

      Passo 2 — Criando os Arquivos da Aplicação

      Vamos criar um site que oferece aos usuários informações sobre tubarões. Nossa aplicação terá um ponto de entrada principal, app.js, e um diretório views, que incluirá os recursos estáticos do projeto. A página inicial, index.html, oferecerá aos usuários algumas informações preliminares e um link para uma página com informações mais detalhadas sobre tubarões, sharks.html. No diretório views, vamos criar tanto a página inicial quanto sharks.html.

      Primeiro, abra app.js no diretório principal do projeto para definir as rotas do projeto:

      A primeira parte do arquivo irá criar a aplicação Express e os objetos Router, e definir o diretório base, a porta, e o host como variáveis:

      ~/node_project/app.js

      
      var express = require("express");
      var app = express();
      var router = express.Router();
      
      var path = __dirname + '/views/';
      const PORT = 8080;
      const HOST = '0.0.0.0';
      

      A função require carrega o módulo express, que usamos então para criar os objetos app e router. O objeto router executará a função de roteamento do aplicativo e, como definirmos as rotas do método HTTP, iremos incluí-las nesse objeto para definir como nossa aplicação irá tratar as solicitações.

      Esta seção do arquivo também define algumas variáveis, path, PORT, e HOST:

      • path: Define o diretório base, que será o subdiretório views dentro do diretório atual do projeto.

      • HOST: Define o endereço ao qual a aplicação se vinculará e escutará. Configurar isto para 0.0.0.0 ou todos os endereços IPv4 corresponde ao comportamento padrão do Docker de expor os containers para 0.0.0.0, a menos que seja instruído de outra forma.

      • PORT: Diz à aplicação para escutar e se vincular à porta 8080.

      Em seguida, defina as rotas para a aplicação usando o objeto router:

      ~/node_project/app.js

      
      ...
      
      router.use(function (req,res,next) {
        console.log("/" + req.method);
        next();
      });
      
      router.get("/",function(req,res){
        res.sendFile(path + "index.html");
      });
      
      router.get("/sharks",function(req,res){
        res.sendFile(path + "sharks.html");
      });
      

      A função router.use carrega uma função de middleware que registrará as solicitações do roteador e as transmitirá para as rotas da aplicação. Estas são definidas nas funções subsequentes, que especificam que uma solicitação GET para a URL base do projeto deve retornar a página index.html, enquanto uma requisição GET para a rota /sharks deve retornar sharks.html.

      Finalmente, monte o middleware router e os recursos estáticos da aplicação e diga à aplicação para escutar na porta 8080:

      ~/node_project/app.js

      
      ...
      
      app.use(express.static(path));
      app.use("/", router);
      
      app.listen(8080, function () {
        console.log('Example app listening on port 8080!')
      })
      

      O arquivo app.js finalizado ficará assim:

      ~/node_project/app.js

      
      var express = require("express");
      var app = express();
      var router = express.Router();
      
      var path = __dirname + '/views/';
      const PORT = 8080;
      const HOST = '0.0.0.0';
      
      router.use(function (req,res,next) {
        console.log("/" + req.method);
        next();
      });
      
      router.get("/",function(req,res){
        res.sendFile(path + "index.html");
      });
      
      router.get("/sharks",function(req,res){
        res.sendFile(path + "sharks.html");
      });
      
      app.use(express.static(path));
      app.use("/", router);
      
      app.listen(8080, function () {
        console.log('Example app listening on port 8080!')
      })
      

      Salve e feche o arquivo quando tiver terminado.

      Em seguida, vamos adicionar algum conteúdo estático à aplicação. Comece criando o diretório views:

      Abra a página inicial, index.html:

      Adicione o seguinte código ao arquivo, que irá importar o Bootstrap e criar o componente jumbotron com um link para a página de informações mais detalhadas sharks.html

      ~/node_project/views/index.html

      
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <title>About Sharks</title>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
          <link href="css/styles.css" rel="stylesheet">
          <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css">
      </head>
      
      <body>
          <nav class="navbar navbar-dark navbar-static-top navbar-expand-md">
              <div class="container">
                  <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
                  </button> <a class="navbar-brand" href="#">Everything Sharks</a>
                  <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                      <ul class="nav navbar-nav mr-auto">
                          <li class="active nav-item"><a href="/" class="nav-link">Home</a>
                          </li>
                          <li class="nav-item"><a href="http://www.digitalocean.com/sharks" class="nav-link">Sharks</a>
                          </li>
                      </ul>
                  </div>
              </div>
          </nav>
          <div class="jumbotron">
              <div class="container">
                  <h1>Want to Learn About Sharks?</h1>
                  <p>Are you ready to learn about sharks?</p>
                  <br>
                  <p><a class="btn btn-primary btn-lg" href="http://www.digitalocean.com/sharks" role="button">Get Shark Info</a>
                  </p>
              </div>
          </div>
          <div class="container">
              <div class="row">
                  <div class="col-lg-6">
                      <h3>Not all sharks are alike</h3>
                      <p>Though some are dangerous, sharks generally do not attack humans. Out of the 500 species known to researchers, only 30 have been known to attack humans.
                      </p>
                  </div>
                  <div class="col-lg-6">
                      <h3>Sharks are ancient</h3>
                      <p>There is evidence to suggest that sharks lived up to 400 million years ago.
                      </p>
                  </div>
              </div>
          </div>
      </body>
      
      </html>
      

      A navbar de nível superior aqui, permite que os usuários alternem entre as páginas Home e Sharks. No subcomponente navbar-nav, estamos utilizando a classe active do Bootstrap para indicar a página atual ao usuário. Também especificamos as rotas para nossas páginas estáticas, que correspondem às rotas que definimos em app.js:

      ~/node_project/views/index.html

      
      ...
      <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
         <ul class="nav navbar-nav mr-auto">
            <li class="active nav-item"><a href="/" class="nav-link">Home</a>
            </li>
            <li class="nav-item"><a href="http://www.digitalocean.com/sharks" class="nav-link">Sharks</a>
            </li>
         </ul>
      </div>
      ...
      

      Além disso, criamos um link para nossa página de informações sobre tubarões no botão do nosso jumbotron:

      ~/node_project/views/index.html

      
      ...
      <div class="jumbotron">
         <div class="container">
            <h1>Want to Learn About Sharks?</h1>
            <p>Are you ready to learn about sharks?</p>
            <br>
            <p><a class="btn btn-primary btn-lg" href="http://www.digitalocean.com/sharks" role="button">Get Shark Info</a>
            </p>
         </div>
      </div>
      ...
      

      Há também um link para uma folha de estilo personalizada no cabeçalho:

      ~/node_project/views/index.html

      ...
      <link href="css/styles.css" rel="stylesheet">
      ...
      

      Vamos criar esta folha de estilo no final deste passo.

      Salve e feche o arquivo quando terminar.

      Com a página inicial da aplicação funcionando, podemos criar nossa página de informações sobre tubarões, sharks.html, que oferecerá aos usuários interessados mais informações sobre os tubarões.

      Abra o arquivo:

      Adicione o seguinte código, que importa o Bootstrap e a folha de estilo personalizada, e oferece aos usuários informações detalhadas sobre determinados tubarões:

      ~/node_project/views/sharks.html

      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <title>About Sharks</title>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
          <link href="css/styles.css" rel="stylesheet">
          <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css">
      </head>
      <nav class="navbar navbar-dark navbar-static-top navbar-expand-md">
          <div class="container">
              <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
              </button> <a class="navbar-brand" href="/">Everything Sharks</a>
              <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                  <ul class="nav navbar-nav mr-auto">
                      <li class="nav-item"><a href="/" class="nav-link">Home</a>
                      </li>
                      <li class="active nav-item"><a href="http://www.digitalocean.com/sharks" class="nav-link">Sharks</a>
                      </li>
                  </ul>
              </div>
          </div>
      </nav>
      <div class="jumbotron text-center">
          <h1>Shark Info</h1>
      </div>
      <div class="container">
          <div class="row">
              <div class="col-lg-6">
                  <p>
                      <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                      </div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
                  </p>
              </div>
              <div class="col-lg-6">
                  <p>
                      <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
                  </p>
              </div>
          </div>
      </div>
      
      </html>
      

      Observe que neste arquivo, usamos novamente a classe active para indicar a página atual.

      Salve e feche o arquivo quando tiver terminado.

      Finalmente, crie a folha de estilo CSS personalizada que você vinculou em index.html e sharks.html criando primeiro uma pasta css no diretório views:

      Abra a folha de estilo:

      • nano views/css/styles.css

      Adicione o seguinte código, que irá definir a cor desejada e a fonte para nossas páginas:

      ~/node_project/views/css/styles.css

      
      .navbar {
          margin-bottom: 0;
          background: #000000;
      }
      
      body {
          background: #000000;
          color: #ffffff;
          font-family: 'Merriweather', sans-serif;
      }
      
      h1,
      h2 {
          font-weight: bold;
      }
      
      p {
          font-size: 16px;
          color: #ffffff;
      }
      
      .jumbotron {
          background: #0048CD;
          color: white;
          text-align: center;
      }
      
      .jumbotron p {
          color: white;
          font-size: 26px;
      }
      
      .btn-primary {
          color: #fff;
          text-color: #000000;
          border-color: white;
          margin-bottom: 5px;
      }
      
      img,
      video,
      audio {
          margin-top: 20px;
          max-width: 80%;
      }
      
      div.caption: {
          float: left;
          clear: both;
      }
      

      Além de definir a fonte e a cor, esse arquivo também limita o tamanho das imagens especificando max-width ou largura máxima de 80%. Isso evitará que ocupem mais espaço do que gostaríamos na página.

      Salve e feche o arquivo quando tiver terminado.

      Com os arquivos da aplicação no lugar e as dependências do projeto instaladas, você está pronto para iniciar a aplicação.

      Se você seguiu o tutorial de configuração inicial do servidor nos pré-requisitos, você terá um firewall ativo que permita apenas o tráfego SSH. Para permitir o tráfego para a porta 8080, execute:

      Para iniciar a aplicação, certifique-se de que você está no diretório raiz do seu projeto:

      Inicie sua aplicação com node app.js:

      Dirija seu navegador para http://ip_do_seu_servidor:8080. Você verá a seguinte página inicial:

      Clique no botão Get Shark Info. Você verá a seguinte página de informações:

      Agora você tem uma aplicação instalada e funcionando. Quando estiver pronto, saia do servidor digitando CTRL + C. Agora podemos passar a criar o Dockerfile que nos permitirá recriar e escalar essa aplicação conforme desejado.

      Step 3 — Escrevendo o Dockerfile

      Seu Dockerfile especifica o que será incluído no container de sua aplicação quando for executado. A utilização de um Dockerfile permite que você defina seu ambiente de container e evite discrepâncias com dependências ou versões de runtime.

      Seguindo estas diretrizes na construção de containers otimizados, vamos tornar nossa imagem o mais eficiente possível, minimizando o número de camadas de imagem e restringindo a função da imagem a uma única finalidade — recriar nossos arquivos da aplicação e o conteúdo estático.

      No diretório raiz do seu projeto, crie o Dockerfile:

      As imagens do Docker são criadas usando uma sucessão de imagens em camadas que são construídas umas sobre as outras. Nosso primeiro passo será adicionar a imagem base para a nossa aplicação que formará o ponto inicial da construção da aplicação.

      Vamos utilizar a imagem node:10, uma vez que, no momento da escrita desse tutorial, esta é a versão LTS reomendada do Node.js. Adicione a seguinte instrução FROM para definir a imagem base da aplicação:

      ~/node_project/Dockerfile

      FROM node:10
      

      Esta imagem inclui Node.js e npm. Cada Dockerfile deve começar com uma instrução FROM.

      Por padrão, a imagem Node do Docker inclui um usuário não-root node que você pode usar para evitar a execução de seu container de aplicação como root. Esta é uma prática de segurança recomendada para evitar executar containers como root e para restringir recursos dentro do container para apenas aqueles necessários para executar seus processos. Portanto, usaremos o diretório home do usuário node como o diretório de trabalho de nossa aplicação e o definiremos como nosso usuário dentro do container. Para mais informações sobre as melhores práticas ao trabalhar com a imagem Node do Docker, veja este guia de melhores práticas.

      Para um ajuste fino das permissões no código da nossa aplicação no container, vamos criar o subdiretório node_modules em /home/node juntamente com o diretório app. A criação desses diretórios garantirá que eles tenham as permissões que desejamos, o que será importante quando criarmos módulos de node locais no container com npm install. Além de criar esses diretórios, definiremos a propriedade deles para o nosso usuário node:

      ~/node_project/Dockerfile

      ...
      RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
      

      Para obter mais informações sobre o utilitário de consolidação das instruções RUN, veja esta discussão sobre como gerenciar camadas de container.

      Em seguida, defina o diretório de trabalho da aplicação para /home/node/app:

      ~/node_project/Dockerfile

      ...
      WORKDIR /home/node/app
      

      Se WORKDIR não estiver definido, o Docker irá criar um por padrão, então é uma boa ideia defini-lo explicitamente.

      A seguir, copie os arquivos package.json e package-lock.json (para npm 5+):

      ~/node_project/Dockerfile

      ...
      COPY package*.json ./
      

      Adicionar esta instrução COPY antes de executar o npm install ou copiar o código da aplicação nos permite aproveitar o mecanismo de armazenamento em cache do Docker. Em cada estágio da compilação ou build, o Docker verificará se há uma camada armazenada em cache para essa instrução específica. Se mudarmos o package.json, esta camada será reconstruída, mas se não o fizermos, esta instrução permitirá ao Docker usar a camada de imagem existente e ignorar a reinstalação dos nossos módulos de node.

      Depois de copiar as dependências do projeto, podemos executar npm install:

      ~/node_project/Dockerfile

      ...
      RUN npm install
      

      Copie o código de sua aplicação para o diretório de trabalho da mesma no container:

      ~/node_project/Dockerfile

      ...
      COPY . .
      

      Para garantir que os arquivos da aplicação sejam de propriedade do usuário não-root node, copie as permissões do diretório da aplicação para o diretório no container:

      ~/node_project/Dockerfile

      ...
      COPY --chown=node:node . .
      

      Defina o usuário para node:

      ~/node_project/Dockerfile

      ...
      USER node
      

      Exponha a porta 8080 no container e inicie a aplicação:

      ~/node_project/Dockerfile

      ...
      EXPOSE 8080
      
      CMD [ "node", "app.js" ]
      

      EXPOSE não publica a porta, mas funciona como uma maneira de documentar quais portas no container serão publicadas em tempo de execução. CMD executa o comando para iniciar a aplicação - neste caso, node app.js. Observe que deve haver apenas uma instrução CMD em cada Dockerfile. Se você incluir mais de uma, somente a última terá efeito.

      Há muitas coisas que você pode fazer com o Dockerfile. Para obter uma lista completa de instruções, consulte a documentação de referência Dockerfile do Docker

      O Dockerfile completo estará assim:

      ~/node_project/Dockerfile

      
      FROM node:10
      
      RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
      
      WORKDIR /home/node/app
      
      COPY package*.json ./
      
      RUN npm install
      
      COPY . .
      
      COPY --chown=node:node . .
      
      USER node
      
      EXPOSE 8080
      
      CMD [ "node", "app.js" ]
      

      Salve e feche o arquivo quando terminar a edição.

      Antes de construir a imagem da aplicação, vamos adicionar um arquivo .dockerignore. Trabalhando de maneira semelhante a um arquivo .gitignore, .dockerignore especifica quais arquivos e diretórios no diretório do seu projeto não devem ser copiados para o seu container.

      Abra o arquivo .dockerignore:

      Dentro do arquivo, adicione seus módulos de node, logs npm, Dockerfile, e o arquivo .dockerignore:

      ~/node_project/.dockerignore

      node_modules
      npm-debug.log
      Dockerfile
      .dockerignore
      

      Se você estiver trabalhando com o Git, então você também vai querer adicionar o seu diretório .git e seu arquivo .gitignore.

      Salve e feche o arquivo quando tiver terminado.

      Agora você está pronto para construir a imagem da aplicação usando o comando docker build. Usar a flag -t com o docker build permitirá que você marque a imagem com um nome memorizável. Como vamos enviar a imagem para o Docker Hub, vamos incluir nosso nome de usuário do Docker Hub na tag. Vamos marcar a imagem como nodejs-image-demo, mas sinta-se à vontade para substituir isto por um nome de sua escolha. Lembre-se também de substituir seu_usuário_dockerhub pelo seu nome real de usuário do Docker Hub:

      • docker build -t seu_usuário_dockerhub/nodejs-image-demo .

      O . especifica que o contexto do build é o diretório atual.

      Levará um ou dois minutos para construir a imagem. Quando estiver concluído, verifique suas imagens:

      Você verá a seguinte saída:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE seu_usuário_dockerhub/nodejs-image-demo latest 1c723fb2ef12 8 seconds ago 895MB node 10 f09e7c96b6de 17 hours ago 893MB

      É possível criar um container com essa imagem usando docker run. Vamos incluir três flags com esse comando:

      • -p: Isso publica a porta no container e a mapeia para uma porta em nosso host. Usaremos a porta 80 no host, mas sinta-se livre para modificá-la, se necessário, se tiver outro processo em execução nessa porta. Para obter mais informações sobre como isso funciona, consulte esta discussão nos documentos do Docker sobre port binding.

      • -d: Isso executa o container em segundo plano.

      • --name: Isso nos permite dar ao container um nome memorizável.

      Execute o seguinte comando para construir o container:

      • docker run --name nodejs-image-demo -p 80:8080 -d seu_usuário_dockerhub/nodejs-image-demo

      Depois que seu container estiver em funcionamento, você poderá inspecionar uma lista de containers em execução com docker ps:

      Você verá a seguinte saída:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e50ad27074a7 seu_usuário_dockerhub/nodejs-image-demo "node app.js" 8 seconds ago Up 7 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo

      Com seu container funcionando, você pode visitar a sua aplicação apontando seu navegador para http://ip_do_seu_servidor. Você verá a página inicial da sua aplicação novamente:

      Agora que você criou uma imagem para sua aplicação, você pode enviá-la ao Docker Hub para uso futuro.

      Ao enviar sua imagem de aplicação para um registro como o Docker Hub, você a torna disponível para uso subsequente à medida que cria e escala seus containers. Vamos demonstrar como isso funciona, enviando a imagem da aplicação para um repositório e, em seguida, usando a imagem para recriar nosso container.

      A primeira etapa para enviar a imagem é efetuar login na conta do Docker Hub que você criou nos pré-requisitos:

      • docker login -u seu_usuário_dockerhub -p senha_do_usuário_dockerhub

      Efetuando o login dessa maneira será criado um arquivo ~/.docker/config.json no diretório home do seu usuário com suas credenciais do Docker Hub.

      Agora você pode enviar a imagem da aplicação para o Docker Hub usando a tag criada anteriormente, seu_usuário_dockerhub/nodejs-image-demo:

      • docker push seu_usuário_dockerhub/nodejs-image-demo

      Vamos testar o utilitário do registro de imagens destruindo nosso container e a imagem de aplicação atual e reconstruindo-os com a imagem em nosso repositório.

      Primeiro, liste seus containers em execução:

      Você verá a seguinte saída:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e50ad27074a7 seu_usuário_dockerhub/nodejs-image-demo "node app.js" 3 minutes ago Up 3 minutes 0.0.0.0:80->8080/tcp nodejs-image-demo

      Usando o CONTAINER ID listado em sua saída, pare o container da aplicação em execução. Certifique-se de substituir o ID destacado abaixo por seu próprio CONTAINER ID:

      Liste todas as suas imagens com a flag -a:

      Você verá a seguinte saída com o nome da sua imagem, seuusuáriodockerhub/nodejs-image-demo, juntamente com a imagem node e outras imagens do seu build.

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE seu_usuário_dockerhub/nodejs-image-demo latest 1c723fb2ef12 7 minutes ago 895MB <none> <none> e039d1b9a6a0 7 minutes ago 895MB <none> <none> dfa98908c5d1 7 minutes ago 895MB <none> <none> b9a714435a86 7 minutes ago 895MB <none> <none> 51de3ed7e944 7 minutes ago 895MB <none> <none> 5228d6c3b480 7 minutes ago 895MB <none> <none> 833b622e5492 8 minutes ago 893MB <none> <none> 5c47cc4725f1 8 minutes ago 893MB <none> <none> 5386324d89fb 8 minutes ago 893MB <none> <none> 631661025e2d 8 minutes ago 893MB node 10 f09e7c96b6de 17 hours ago 893MB

      Remova o container parado e todas as imagens, incluindo imagens não utilizadas ou pendentes, com o seguinte comando:

      Digite y quando solicitado na saída para confirmar que você gostaria de remover o container e as imagens parados. Esteja ciente de que isso também removerá seu cache de compilação.

      Agora você removeu o container que está executando a imagem da sua aplicação e a própria imagem. Para obter mais informações sobre como remover containers, imagens e volumes do Docker, consulte How To Remove Docker Images, Containers, and Volumes.

      Com todas as suas imagens e containers excluídos, agora você pode baixar a imagem da aplicação do Docker Hub:

      • docker pull seu_usuário_dockerhub/nodejs-image-demo

      Liste suas imagens mais uma vez:

      Você verá a imagem da sua aplicação:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE seu_usuário_dockerhub/nodejs-image-demo latest 1c723fb2ef12 11 minutes ago 895MB

      Agora você pode reconstruir seu container usando o comando do Passo 3:

      • docker run --name nodejs-image-demo -p 80:8080 -d seu_usuário_dockerhub/nodejs-image-demo

      Liste seus containers em execução:

      docker ps
      

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f6bc2f50dff6 seu_usuário_dockerhub/nodejs-image-demo "node app.js" 4 seconds ago Up 3 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo

      Visite http://ip_do_seu_servidor mais uma vez para ver a sua aplicação em execução.

      Conclusão

      Neste tutorial, você criou uma aplicação web estática com Express e Bootstrap, bem como uma imagem do Docker para esta aplicação. Você utilizou essa imagem para criar um container e enviou a imagem para o Docker Hub. A partir daí, você conseguiu destruir sua imagem e seu container e recriá-los usando seu repositório do Docker Hub.

      Se você estiver interessado em aprender mais sobre como trabalhar com ferramentas como o Docker Compose e o Docker Machine para criar configurações de vários containers, consulte os seguintes guias:

      Para dicas gerais sobre como trabalhar com dados de container, consulte:

      Se você estiver interessado em outros tópicos relacionados ao Docker, consulte nossa biblioteca completa de tutoriais do Docker.

      Por Kathleen Juell



      Source link

      Como Construir uma Rede Neural para Reconhecer Dígitos Manuscritos com o TensorFlow


      Introdução

      Redes neurais são usadas como um método de deep learning ou aprendizado profundo, um dos vários subcampos da inteligência artificial. Elas foram propostas pela primeira vez há cerca de 70 anos como uma tentativa de simular a maneira como o cérebro humano funciona, embora de uma forma muito mais simplificada. Os “neurônios” individuais são conectados em camadas, com pesos designados para determinar como o neurônio responde quando os sinais são propagados pela rede. Anteriormente, as redes neurais eram limitadas no número de neurônios que elas eram capazes de simular e, portanto, a complexidade do aprendizado que podiam alcançar. Mas nos últimos anos, devido aos avanços no desenvolvimento de hardware, pudemos construir redes muito profundas e treiná-las em enormes datasets ou conjuntos de dados para obter avanços na inteligência de máquinas.

      Essas inovações permitiram que as máquinas correspondessem e excedessem as capacidades dos humanos em realizar certas tarefas. Uma dessas tarefas é o reconhecimento de objetos. Embora as máquinas tenham sido historicamente incapazes de corresponder à visão humana, avanços recentes em deep learning tornaram possível construir redes neurais capazes de reconhecer objetos, rostos, textos e até mesmo emoções.

      Neste tutorial, você implementará uma pequena subseção de reconhecimento de objeto recognition—digit. Utilizando o TensorFlow, uma biblioteca Python open-source desenvolvida pelos laboratórios do Google Brain para pesquisa em deep learning, você pegará imagens desenhadas à mão dos números de 0 a 9 e construirá e treinará uma rede neural para reconhecer e prever o rótulo correto para o dígito exibido.

      Embora você não precise de experiência prévia em deep learning prático ou de uso do TensorFlow para acompanhar este tutorial, vamos assumir alguma familiaridade com termos e conceitos de machine learning, como treinamento e testes, recursos e rótulos, otimização e avaliação. Você pode aprender mais sobre esses conceitos em Uma Introdução ao Machine Learning.

      Pré-requisitos

      Para completar esse tutorial, você vai precisar de:

      Passo 1 — Configurando o Projeto

      Antes de desenvolver o programa de reconhecimento, você precisará instalar algumas dependências e criar um espaço de trabalho para armazenar seus arquivos.

      Usaremos um ambiente virtual do Python 3 para gerenciar as dependências do nosso projeto. Crie um novo diretório para o seu projeto e navegue até o novo diretório:

      • mkdir tensorflow-demo
      • cd tensorflow-demo

      Execute os seguintes comandos para configurar o ambiente virtual para este tutorial:

      • python3 -m venv tensorflow-demo
      • source tensorflow-demo/bin/activate

      Em seguida, instale as bibliotecas que você usará neste tutorial. Usaremos versões específicas dessas bibliotecas criando um arquivo requirements.txt no diretório do projeto, que especifica o requisito e a versão que precisamos. Crie o arquivo requirements.txt:

      Abra o arquivo em seu editor de textos e adicione as seguintes linhas para especificar as bibliotecas Image, NumPy, e TensorFlow e suas versões:

      requirements.txt

      image==1.5.20
      numpy==1.14.3
      tensorflow==1.4.0
      

      Salve o arquivo e saia do editor. Em seguida instale estas bibliotecas com o seguinte comando:

      • pip install -r requirements.txt

      Com as dependências instaladas, podemos começar a trabalhar no nosso projeto.

      Passo 2 — Importando o Dataset MNIST

      O dataset que estaremos utilizando neste tutorial é chamado de dataset MNIST, e ele é um clássico na comunidade de machine learning. Este dataset é composto de imagens de dígitos manuscritos, com 28x28 pixels de tamanho. Aqui estão alguns exemplos dos dígitos incluídos no dataset:

      Vamos criar um programa Python para trabalhar com este dataset. Usaremos um arquivo para todo o nosso trabalho neste tutorial. Crie um novo arquivo chamado main.py:

      Agora abra este arquivo no editor de textos de sua preferência e adicione esta linha de código ao arquivo para importar a biblioteca do TensorFlow:

      main.py

      import tensorflow as tf
      

      Adicione as seguintes linhas de código ao seu arquivo para importar o dataset MNIST e armazenar os dados da imagem na variável mnist:

      main.py

      from tensorflow.examples.tutorials.mnist import input_data
      mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) # y labels are oh-encoded
      

      Ao ler os dados, estamos usando one-hot-encoding para representar os rótulos (o dígito real desenhado, por exemplo "3") das imagens. O one-hot-encoding utiliza um vetor de valores binários para representar valores numéricos ou categóricos. Como nossos rótulos são para os dígitos de 0 a 9, o vetor contém dez valores, um para cada dígito possível. Um desses valores é definido como 1, para representar o dígito nesse índice do vetor, e o restante é difinido como 0. Por exemplo, o dígito 3 é representado usando o vetor [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]. Como o valor no índice 3 está armazenado como 1, o vetor representa o dígito 3.

      Para representar as imagens, os 28x28 pixels são achatados em um vetor 1D com 784 pixels de tamanho. Cada um dos 784 pixels que compõem a imagem é armazenado como um valor entre 0 e 255. Isso determina a escala de cinza do pixel, pois nossas imagens são apresentadas apenas em preto e branco. Portanto, um pixel preto é representado por 255 e um pixel branco por 0, com os vários tons de cinza em algum lugar entre eles.

      Podemos usar a variável mnist para descobrir o tamanho do dataset que acabamos de importar. Observando os num_examples para cada um dos três subconjuntos, podemos determinar que o dataset foi dividido em 55.000 imagens para treinamento, 5000 para validação e 10.000 para teste. Adicione as seguintes linhas ao seu arquivo:

      main.py

      
      n_train = mnist.train.num_examples # 55,000
      n_validation = mnist.validation.num_examples # 5000
      n_test = mnist.test.num_examples # 10,000
      

      Agora que temos nossos dados importados, é hora de pensar sobre a rede neural.

      Passo 3 — Definindo a Arquitetura da Rede Neural

      A arquitetura da rede neural refere-se a elementos como o número de camadas na rede, o número de unidades em cada camada e como as unidades são conectadas entre as camadas. Como as redes neurais são vagamente inspiradas no funcionamento do cérebro humano, aqui o termo unidade é usado para representar o que seria biologicamente um neurônio. Assim como os neurônios transmitem sinais pelo cérebro, as unidades tomam alguns valores das unidades anteriores como entrada, realizam uma computação e, em seguida, transmitem o novo valor como saída para outras unidades. Essas unidades são colocadas em camadas para formar a rede, iniciando no mínimo com uma camada para entrada de valores e uma camada para valores de saída. O termo hidden layer ou camada oculta é usado para todas as camadas entre as camadas de entrada e saída, ou seja, aquelas "ocultas" do mundo real.

      Arquiteturas diferentes podem produzir resultados drasticamente diferentes, já que o desempenho pode ser pensado como uma função da arquitetura entre outras coisas, como os parâmetros, os dados e a duração do treinamento.

      Adicione as seguintes linhas de código ao seu arquivo para armazenar o número de unidades por camada nas variáveis globais. Isso nos permite alterar a arquitetura de rede em um único lugar e, no final do tutorial, você pode testar por si mesmo como diferentes números de camadas e unidades afetarão os resultados de nosso modelo:

      main.py

      
      n_input = 784   # input layer (28x28 pixels)
      n_hidden1 = 512 # 1st hidden layer
      n_hidden2 = 256 # 2nd hidden layer
      n_hidden3 = 128 # 3rd hidden layer
      n_output = 10   # output layer (0-9 digits)
      

      O diagrama a seguir mostra uma visualização da arquitetura que projetamos, com cada camada totalmente conectada às camadas adjacentes:

      O termo "deep neural network" ou rede neural profunda se relaciona com o número de camadas ocultas, com "superficial" geralmente significando apenas uma camada oculta e "profunda", referindo-se a várias camadas ocultas. Fornecidos dados de treinamento suficientes, uma rede neural superficial com um número suficiente de unidades deve teoricamente ser capaz de representar qualquer função que uma rede neural profunda possa. Mas é mais eficiente computacionalmente usar uma rede neural profunda menor para realizar a mesma tarefa que exigiria uma rede superficial com exponencialmente mais unidades ocultas. Redes neurais superficiais também freqüentemente encontram overfitting, onde a rede essencialmente memoriza os dados de treinamento que viu e não é capaz de generalizar o conhecimento para novos dados. É por isso que as redes neurais profundas são mais comumente usadas: as várias camadas entre os dados brutos de entrada e o rótulo de saída permitem que a rede aprenda recursos em vários níveis de abstração, tornando a própria rede mais capaz de generalizar.

      Outros elementos da rede neural que precisam ser definidos aqui são os hiperparâmetros. Ao contrário dos parâmetros que serão atualizados durante o treinamento, esses valores são definidos inicialmente e permanecem constantes durante todo o processo. No seu arquivo, defina as seguintes variáveis e valores:

      main.py

      
      learning_rate = 1e-4
      n_iterations = 1000
      batch_size = 128
      dropout = 0.5
      

      A taxa de aprendizado, learningrate, representa o quanto os parâmetros serão ajustados em cada etapa do processo de aprendizado. Esses ajustes são um componente-chave do treinamento: depois de cada passagem pela rede, ajustamos os pesos ligeiramente para tentar reduzir a perda. Taxas de aprendizado maiores podem convergir mais rapidamente, mas também têm o potencial de ultrapassar os valores ideais à medida que são atualizados. O número de iterações, niterations, refere-se a quantas vezes passamos pela etapa de treinamento e o tamanho do lote ou batch_size se refere a quantos exemplos de treinamento estamos usando em cada etapa. A variável dropout representa um limiar no qual eliminamos algumas unidades aleatoriamente. Estaremos usando dropout em nossa última camada oculta para dar a cada unidade 50% de chance de ser eliminada em cada etapa de treinamento. Isso ajuda a evitar o overfitting.

      Agora já definimos a arquitetura de nossa rede neural e os hiperparâmetros que impactam o processo de aprendizagem. O próximo passo é construir a rede como um gráfico do TensorFlow.

      Passo 4 — Construindo o Gráfico do TensorFlow

      Para construir nossa rede, vamos configurará-la como um gráfico computacional para o TensorFlow executar. O conceito central do TensorFlow é o tensor, uma estrutura de dados semelhante a uma matriz ou lista inicializada, manipulada à medida que passa pelo gráfico e atualizada através do processo de aprendizado.

      Começaremos definindo três tensores como placeholders ou marcadores de posição, que são tensores nos quais alimentaremos os valores posteriormente. Adicione o seguinte ao seu arquivo:

      main.py

      
      X = tf.placeholder("float", [None, n_input])
      Y = tf.placeholder("float", [None, n_output])
      keep_prob = tf.placeholder(tf.float32) ^
      

      O único parâmetro que precisa ser especificado em sua declaração é o tamanho dos dados os quais estaremos alimentando. Para X usamos um formato [None, 784], onde None representa qualquer quantidade, pois estaremos alimentando em um número indefinido de imagens de 784 pixels. O formato de Y é [None, 10] pois iremos usá-lo para um número indefinido de saídas de rótulo, com 10 classes possíveis. O tensor keep_prob é usado para controlar a taxa de dropout, e nós o inicializamos como um placeholder ao invés de uma variável imutável porque queremos usar o mesmo tensor tanto para treinamento (quando dropout é definido para 0.5) quanto para testes (quando dropout é definido como 1.0).

      Os parâmetros que a rede atualizará no processo de treinamento são os valores weight e bias, portanto, precisamos definir um valor inicial em vez de um placeholder vazio. Esses valores são essencialmente onde a rede faz seu aprendizado, pois são utilizados nas funções de ativação dos neurônios, representando a força das conexões entre as unidades.

      Como os valores são otimizados durante o treinamento, podemos defini-los para zero por enquanto. Mas o valor inicial realmente tem um impacto significativo na precisão final do modelo. Usaremos valores aleatórios de uma distribuição normal truncada para os pesos. Queremos que eles estejam próximos de zero, para que possam se ajustar em uma direção positiva ou negativa, e um pouco diferente, para que gerem erros diferentes. Isso garantirá que o modelo aprenda algo útil. Adicione estas linhas:

      main.py

      
      weights = {
          'w1': tf.Variable(tf.truncated_normal([n_input, n_hidden1], stddev=0.1)),
          'w2': tf.Variable(tf.truncated_normal([n_hidden1, n_hidden2], stddev=0.1)),
          'w3': tf.Variable(tf.truncated_normal([n_hidden2, n_hidden3], stddev=0.1)),
          'out': tf.Variable(tf.truncated_normal([n_hidden3, n_output], stddev=0.1)),
      }
      

      Para o bias ou tendência, usamos um pequeno valor constante para garantir que os tensores se ativem nos estágios iniciais e, portanto, contribuam para a propagação. Os pesos e tensores de bias são armazenados em objetos de dicionário para facilitar o acesso. Adicione este código ao seu arquivo para definir cada bias:

      main.py

      
      biases = {
          'b1': tf.Variable(tf.constant(0.1, shape=[n_hidden1])),
          'b2': tf.Variable(tf.constant(0.1, shape=[n_hidden2])),
          'b3': tf.Variable(tf.constant(0.1, shape=[n_hidden3])),
          'out': tf.Variable(tf.constant(0.1, shape=[n_output]))
      }
      

      Em seguida, configure as camadas da rede definindo as operações que manipularão os tensores. Adicione estas linhas ao seu arquivo:

      main.py

      
      layer_1 = tf.add(tf.matmul(X, weights['w1']), biases['b1'])
      layer_2 = tf.add(tf.matmul(layer_1, weights['w2']), biases['b2'])
      layer_3 = tf.add(tf.matmul(layer_2, weights['w3']), biases['b3'])
      layer_drop = tf.nn.dropout(layer_3, keep_prob)
      output_layer = tf.matmul(layer_3, weights['out']) + biases['out']
      

      Cada camada oculta executará a multiplicação da matriz nas saídas da camada anterior e os pesos da camada atual e adicionará o bias a esses valores. Na última camada oculta, aplicaremos uma operação de eliminação usando nosso valor keep_prob de 0.5.

      O passo final na construção do gráfico é definir a função de perda que queremos otimizar. Uma escolha popular da função de perda nos programas do TensorFlow é a cross-entropy ou entropia cruzada, também conhecida como log-loss, que quantifica a diferença entre duas distribuições de probabilidade (as predições e os rótulos). Uma classificação perfeita resultaria em uma entropia cruzada de 0, com a perda completamente minimizada.

      Também precisamos escolher o algoritmo de otimização que será usado para minimizar a função de perda. Um processo denominado otimização gradiente descendente é um método comum para encontrar o mínimo (local) de uma função, tomando etapas iterativas ao longo do gradiente em uma direção negativa (descendente). Existem várias opções de algoritmos de otimização de gradiente descendente já implementados no TensorFlow, e neste tutorial vamos usar o otimizador Adam. Isso se estende à otimização de gradiente descendente usando o momento para acelerar o processo através do cálculo de uma média exponencialmente ponderada dos gradientes e usando isso nos ajustes. Adicione o seguinte código ao seu arquivo:

      main.py

      
      cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=Y, logits=output_layer))
      train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
      

      Agora acabamos de definir a rede e a construímos com o TensorFlow. O próximo passo é alimentar os dados através do gráfico para treiná-los e, em seguida, testar se realmente aprendeu alguma coisa.

      Passo 5 — Treinando e Testando

      O processo de treinamento envolve alimentar o dataset de treinamento através do gráfico e otimizar a função de perda. Toda vez que a rede itera um lote de mais imagens de treinamento, ela atualiza os parâmetros para reduzir a perda, a fim de prever com mais precisão os dígitos exibidos. O processo de teste envolve a execução do nosso dataset de teste através do gráfico treinado e o acompanhamento do número de imagens que são corretamente previstas, para que possamos calcular a precisão.

      Antes de iniciar o processo de treinamento, definiremos nosso método de avaliação da precisão para que possamos imprimi-lo em mini-lotes de dados enquanto treinamos. Estas declarações impressas nos permitem verificar que, da primeira iteração até a última, a perda diminui e a precisão aumenta; elas também nos permitem rastrear se executamos ou não repetições suficientes para alcançar um resultado consistente e ideal:

      main.py

      correct_pred = tf.equal(tf.argmax(output_layer, 1), tf.argmax(Y, 1))
      accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
      

      Em correct_pred, usamos a função arg_max para comparar quais imagens estão sendo previstas corretamente observando output_layer (predições) e Y (labels), e usamos a função equal para retornar isso como uma lista de Booleanos. Podemos, então, converter essa lista em floats e calcular a média para obter uma pontuação total da precisão.

      Agora estamos prontos para inicializar uma sessão para executar o gráfico. Nesta sessão, vamos alimentar a rede com nossos exemplos de treinamento e, uma vez treinados, alimentamos o mesmo gráfico com novos exemplos de teste para determinar a precisão do modelo. Adicione as seguintes linhas de código ao seu arquivo:

      main.py

      
      init = tf.global_variables_initializer()
      sess = tf.Session()
      sess.run(init)
      

      A essência do processo de treinamento em deep learning é otimizar a função de perda. Aqui, pretendemos minimizar a diferença entre os rótulos previstos das imagens e os rótulos verdadeiros das imagens. O processo envolve quatro etapas que são repetidas para um número definido de iterações:

      Em cada etapa de treinamento, os parâmetros são ligeiramente ajustados para tentar reduzir a perda para a próxima etapa. À medida que o aprendizado avança, devemos ver uma redução na perda e, eventualmente, podemos parar de treinar e usar a rede como um modelo para testar nossos novos dados.

      Adicione este código ao arquivo:

      main.py

      
      # train on mini batches
      for i in range(n_iterations):
          batch_x, batch_y = mnist.train.next_batch(batch_size)
          sess.run(train_step, feed_dict={X: batch_x, Y: batch_y, keep_prob:dropout})
      
          # print loss and accuracy (per minibatch)
          if i%100==0:
              minibatch_loss, minibatch_accuracy = sess.run([cross_entropy, accuracy], feed_dict={X: batch_x, Y: batch_y, keep_prob:1.0})
              print("Iteration", str(i), "t| Loss =", str(minibatch_loss), "t| Accuracy =", str(minibatch_accuracy))
      

      Após 100 iterações de cada etapa de treinamento em que alimentamos um mini-lote de imagens através da rede, imprimimos a perda e a precisão desse lote. Observe que não devemos esperar uma perda decrescente e uma precisão crescente aqui, pois os valores são por lote, não para o modelo inteiro. Usamos mini-lotes de imagens em vez de alimentá-las individualmente para acelerar o processo de treinamento e permitir que a rede veja vários exemplos diferentes antes de atualizar os parâmetros.

      Quando o treinamento estiver concluído, podemos executar a sessão nas imagens de teste. Desta vez estamos usando uma taxa de dropout keep_prob de 1.0 para garantir que todas as unidades estejam ativas no processo de teste.

      Adicione este código ao arquivo:

      main.py

      
      test_accuracy = sess.run(accuracy, feed_dict={X: mnist.test.images, Y: mnist.test.labels, keep_prob:1.0})
      print("nAccuracy on test set:", test_accuracy)
      

      Agora é hora de executar nosso programa e ver com que precisão nossa rede neural pode reconhecer esses dígitos manuscritos. Salve o arquivo main.py e execute o seguinte comando no terminal para executar o script:

      Você verá uma saída semelhante à seguinte, embora os resultados individuais de perda e precisão possam variar um pouco:

      Output

      Iteration 0 | Loss = 3.67079 | Accuracy = 0.140625 Iteration 100 | Loss = 0.492122 | Accuracy = 0.84375 Iteration 200 | Loss = 0.421595 | Accuracy = 0.882812 Iteration 300 | Loss = 0.307726 | Accuracy = 0.921875 Iteration 400 | Loss = 0.392948 | Accuracy = 0.882812 Iteration 500 | Loss = 0.371461 | Accuracy = 0.90625 Iteration 600 | Loss = 0.378425 | Accuracy = 0.882812 Iteration 700 | Loss = 0.338605 | Accuracy = 0.914062 Iteration 800 | Loss = 0.379697 | Accuracy = 0.875 Iteration 900 | Loss = 0.444303 | Accuracy = 0.90625 Accuracy on test set: 0.9206

      Para tentar melhorar a precisão do nosso modelo, ou para saber mais sobre o impacto dos hiperparâmetros de ajuste, podemos testar o efeito de alterar a taxa de aprendizado, o limite de dropout, o tamanho do lote e o número de iterações. Também podemos alterar o número de unidades em nossas camadas ocultas e alterar a quantidade das próprias camadas ocultas, para ver como diferentes arquiteturas aumentam ou diminuem a precisão do modelo.

      Para demonstrar que a rede está realmente reconhecendo as imagens desenhadas à mão, vamos testá-la em uma única imagem nossa.

      Primeiro, faça o download dessa amostra de imagem de teste ou abra um editor gráfico e crie sua própria imagem de 28x28 pixels de um dígito.

      Abra o arquivo main.py no seu editor e adicione as seguintes linhas de código ao topo do arquivo para importar duas bibliotecas necessárias para a manipulação de imagens.

      main.py

      
      import numpy as np
      from PIL import Image
      ...
      

      Em seguida, no final do arquivo, adicione a seguinte linha de código para carregar a imagem de teste do dígito manuscrito:

      main.py

      
      img = np.invert(Image.open("test_img.png").convert('L')).ravel()
      

      A função open da bibliotecaImage carrega a imagem de teste como um array 4D contendo os três canais de cores RGB e a transparência Alpha. Esta não é a mesma representação que usamos anteriormente ao ler o dataset com o TensorFlow, portanto, precisamos fazer algum trabalho extra para corresponder ao formato.

      Primeiro, usamos a função convert com o parâmetro L para reduzir a representação 4D RGBA para um canal de cor em escala de cinza. Aarmazenamos isso como um array numpy e o invertemos usando np.invert, porque a matriz atual representa o preto como 0 e o branco como 255, porém, precisamos do oposto. Finalmente, chamamos ravel para achatar o array.

      Agora que os dados da imagem estão estruturados corretamente, podemos executar uma sessão da mesma forma que anteriormente, mas desta vez apenas alimentando uma imagem única para teste. Adicione o seguinte código ao seu arquivo para testar a imagem e imprimir o rótulo de saída.

      [labe main.py]
      prediction = sess.run(tf.argmax(output_layer,1), feed_dict={X: [img]})
      print ("Prediction for test image:", np.squeeze(prediction))
      

      A função np.squeeze é chamada na predição para retornar o único inteiro da matriz (ou seja, para ir de [2] para 2). A saída resultante demonstra que a rede reconheceu essa imagem como o dígito 2.

      Output

      Prediction for test image: 2

      Você pode tentar testar a rede com imagens mais complexas - dígitos que se parecem com outros dígitos, por exemplo, ou dígitos que foram mal desenhados ou desenhados incorretamente - para ver como ela se sai.

      Conclusão

      Neste tutorial você treinou com sucesso uma rede neural para classificar o dataset MNIST com cerca de 92% de precisão e testou em uma imagem sua. O estado da arte em pesquisa atual alcança cerca de 99% neste mesmo problema, usando arquiteturas de rede mais complexas envolvendo camadas convolucionais. Elas usam a estrutura 2D da imagem para melhor representar o conteúdo, ao contrário do nosso método que achata todos os pixels em um vetor de 784 unidades. Você pode ler mais sobre esse tópico no website do TensorFlow, e ver os documentos de pesquisa detalhando os resultados mais precisos no wesite do MNIST.

      Agora que você sabe como construir e treinar uma rede neural, pode tentar usar essa implementação em seus próprios dados ou testá-la em outros datasets populares, como o Google StreetView House Numbers, ou o dataset CIFAR-10 para um reconhecimento de imagem mais genérico.

      Por Ellie Birbeck



      Source link