Junho de 2019

Volume 34 – Número 6

[ASP.NET Core 3.0]

Segurança biométrica com IA no ASP.NET Core

Por Stefano Tempesta

Este artigo, em duas partes, apresenta o modelo de autorização baseado em política do ASP.NET Core 3, que tem por objetivo dissociar a lógica de autorização das funções básicas de usuário. Ele apresenta um exemplo específico desse processo de autorização com base em informações biométricas, como reconhecimento facial ou de voz. Neste caso, o acesso a um prédio é restringido quando é detectada uma intrusão. A gravidade da intrusão é avaliada por um serviço de detecção de anomalias incorporado ao Azure Machine Learning.

Acesso ao local

O contexto é um local extremamente seguro. Pense em uma área militar, um hospital ou um data center. O acesso é restrito a pessoas autorizadas, com algumas limitações. As etapas a seguir descrevem o fluxo de segurança imposto na porta de cada prédio para registrar a entrada das pessoas:

  1. Uma pessoa que solicita acesso a um prédio passa seu cartão de acesso no leitor de cartão da porta.
  2. Câmeras detectam movimento e capturam o rosto e o corpo da pessoa; isso deve impedir que uma foto impressa, por exemplo, engane uma câmera que tenha somente reconhecimento facial.
  3. O leitor de cartão e as câmeras são registrados como dispositivos Internet das Coisas (IoT) e transmitem dados gravados para o Hub IoT do Azure.
  4. Os Serviços Cognitivos da Microsoft comparam a pessoa com um banco de dados de pessoas que têm autorização para acessar o prédio.
  5. Um fluxo de autorização compara as informações biométricas coletadas pelos dispositivos IoT com a identidade da pessoa no passe de acesso.
  6. Um Serviço do Azure Machine Learning é invocado para avaliar o nível de risco da solicitação de acesso e se ela é uma intrusão.
  7. A autorização é concedida por uma API Web do ASP.NET Core, verificando os requisitos específicos da política pertencentes ao perfil definido nas etapas anteriores.

Se houver uma incompatibilidade entre a identidade detectada da pessoa e o passe de acesso, o acesso ao local é bloqueado imediatamente. Caso contrário, o fluxo continuará verificando se alguma destas anomalias foi encontrada:

  • Frequência atípica de acesso ao prédio.
  • Se a pessoa saiu do prédio mais cedo (check-out).
  • Número de acessos permitidos por dia.
  • Se a pessoa está em serviço.
  • Nível crítico do prédio (talvez você não queira restringir o acesso a um refeitório, mas impor uma política mais rígida ao acesso a um datacenter de servidor).
  • Se a pessoa está trazendo alguém ou algo.
  • Ocorrências anteriores de tipologias de acesso semelhantes no mesmo prédio.
  • Alterações no nível de risco medido no passado.
  • Número de intrusões detectadas no passado.

O serviço de detecção de anomalias é executado no Azure Machine Learning e retorna uma pontuação, expressa como uma probabilidade de que o acesso é um desvio do padrão ou não. A pontuação é expressa em um intervalo entre zero e um, onde zero é "nenhum risco detectado", tudo bem, confiança total concedida; e um é "alerta vermelho", bloquear o acesso imediatamente! O nível de risco de cada prédio determina o limite considerado aceitável para permitir o acesso a ele por qualquer valor maior que zero.

Autorização no ASP.NET Core

O ASP.NET Core fornece uma função declarativa simples de autorização e um modelo avançado baseado em política. A autorização é expressa em requisitos e manipuladores avaliam as declarações do usuário em relação a tais requisitos. Com a finalidade de autorizar o acesso de usuários a um local, descreverei como gerar requisitos de política personalizados e o respectivo manipulador de autorização. Para obter mais informações sobre o modelo de autorização no ASP.NET Core, consulte a documentação em bit.ly/2UYZaJh.

Conforme observado, um mecanismo de autorização baseado em política personalizado consiste em requisitos e, normalmente, em um manipulador de autorização. Conceder acesso a um prédio consiste em invocar uma API que desbloqueia a porta de entrada. Os dispositivos IoT transmitem informações biométricas para um Hub IoT do Azure que, por sua vez, dispara o fluxo de trabalho de verificação postando a ID do local, um identificador exclusivo do local. O método POST da API Web simplesmente retorna um HTTP código 200 e uma mensagem JSON com o nome do usuário e a ID do local, caso a autorização seja obtida. Caso contrário, ele gera o código de erro esperado: HTTP 401 Acesso não autorizado. Mas, vamos por ordem: Vou começar com a classe Startup da API Web, especificamente o método ConfigureServices, que contém as instruções de configuração dos serviços necessários para executar o aplicativo ASP.NET Core. As políticas de autorização são adicionadas chamando o método AddAuthorization no objeto de serviços. O método AddAuthorization aceita uma coleção de políticas que a função da API deve possuir quando invocado para autorizar sua execução. Neste caso, eu só preciso de uma política, que eu chamo de "AuthorizedUser". Essa política, no entanto, tem vários requisitos a serem cumpridos, os quais refletem as características biométricas de uma pessoa que eu quero verificar: rosto, corpo e voz. Os três requisitos são individualmente representados por uma classe específica que implementa a interface IAuthorizationRequirement, conforme mostrado na Figura 1. Ao listar os requisitos da política AuthorizedUser, eu também especifico o nível de confiança necessário para atender ao requisito. Conforme observado anteriormente, esse valor, entre zero e um, expressa a precisão da identificação do respectivo atributo biométrico. Retomarei esse assunto posteriormente, ao discutir o reconhecimento biométrico com os Serviços Cognitivos.

Figura 1 - Configuração dos requisitos de autorização na API Web

public void ConfigureServices(IServiceCollection services)
{
  var authorizationRequirements = new List<IAuthorizationRequirement>
  {
    new FaceRecognitionRequirement(confidence: 0.9),
    new BodyRecognitionRequirement(confidence: 0.9),
    new VoiceRecognitionRequirement(confidence: 0.9)
  };
  services
    .AddAuthorization(options =>
    {
      options.AddPolicy("AuthorizedUser", policy => policy.Requirements =
        authorizationRequirements);
    })

A política de autorização AuthorizedUser contém vários requisitos de autorização, sendo que todos eles devem ser aprovados na ordem para que a avaliação da política seja bem-sucedida. Ou seja, vários requisitos de autorização adicionados a uma única política de autorização são tratados em uma base AND.

Os três requisitos de política que implantei na solução são todos classes que implementam a interface IAuthorizationRequirement. Na verdade, essa interface está vazia, ou seja, ela não dita a implementação de um método. Implementei os três requisitos consistentemente, especificando uma propriedade pública ConfidenceScore para capturar o nível esperado de confiança que a API de reconhecimento deve satisfazer para considerar esse requisito como bem-sucedido. A classe FaceRecognitionRequirement tem esta aparência:

public class FaceRecognitionRequirement : IAuthorizationRequirement
{
  public double ConfidenceScore { get; }
  public FaceRecognitionRequirement(double confidence) =>
    ConfidenceScore = confidence;
}

Da mesma maneira, os outros requisitos de reconhecimento de voz e corpo são implementados, respectivamente, nas classes BodyRecognitionRequirement e VoiceRecognitionRequirement.

A autorização para executar uma ação da API Web é controlada por meio do atributo Authorize. Em termos simples, aplicar AuthorizeAttribute a um controlador ou ação limita o acesso desse controlador ou ação a qualquer usuário autenticado. A API Web que controla o acesso a um local expõe um controlador de acesso único, que contém somente a ação Post. Essa ação será autorizada se todos os requisitos da política "AuthorizedUser" especificada forem atendidos:

[ApiController]
public class AccessController : ControllerBase
{
  [HttpPost]
  [Authorize(Policy = "AuthorizedUser")]
  public IActionResult Post([FromBody] string siteId)
  {
    var response = new
    {
      User = HttpContext.User.Identity.Name,
      SiteId = siteId
    };
    return new JsonResult(response);
  }
}

Cada requisito é gerenciado por um manipulador de autorização, como o mostrado na Figura 2, que é responsável pela avaliação de um requisito da política. Você pode optar por ter um único manipulador para todos os requisitos ou manipuladores separados para cada requisito. Essa última abordagem é mais flexível, pois permite que configure um gradiente de requisitos de autorização que você pode definir facilmente na classe Startup. Os manipuladores de requisito de rosto, corpo e voz estendem a classe abstrata AuthorizationHandler<TRequirement>, onde TRequirement é o requisito a ser manipulado. Como eu quero avaliar os três requisitos, preciso escrever um manipulador personalizado que estenda AuthorizationHandler para FaceRecognitionRequirement, BodyRecognitionRequirement e VoiceRecognitionRequirement. Especificamente, o método HandleRequirementAsync, que determina se um requisito de autorização é atendido. Esse método, por ser assíncrono, não retorna um valor real, exceto para indicar que a tarefa foi concluída. A manipulação da autorização consiste em marcar um requisito como "bem-sucedido", invocando o método Succeed no contexto do manipulador de autorização. Na verdade, isso é verificado por um objeto "reconhecedor", que usa a API dos Serviços Cognitivos internamente (mais informações na próxima seção). A ação de reconhecimento, executada pelo método Recognize, obtém o nome da pessoa identificada e retorna um valor (pontuação) que expressa o nível de confiança de que a identificação é mais (valor mais próximo a um) ou menos (valor mais próximo a zero) precisa. Um nível esperado foi especificado na configuração da API. Você pode ajustar esse valor para qualquer limite apropriado à sua solução.

Figura 2 - O manipulador de autorização personalizado

public class FaceRequirementHandler :
  AuthorizationHandler<FaceRecognitionRequirement>
{
  protected override Task HandleRequirementAsync(
    AuthorizationHandlerContext context,
      FaceRecognitionRequirement requirement)
  {
    string siteId =
      (context.Resource as HttpContext).Request.Query["siteId"];
    IRecognition recognizer = new FaceRecognition();
    if (recognizer.Recognize(siteId, out string name) >=
      requirement.ConfidenceScore)
    {
      context.User.AddIdentity(new ClaimsIdentity(
        new GenericIdentity(name)));
      context.Succeed(requirement);
    }
    return Task.CompletedTask;
  }
}

Além de avaliar o requisito específico, o manipulador de autorização também adiciona uma declaração de identidade ao usuário atual. Quando uma identidade é criada, ela pode ser atribuída a uma ou mais declarações emitidas por uma entidade confiável. Uma declaração é um par nome-valor que representa o titular. Neste caso, estou atribuindo a declaração de identidade ao usuário em contexto. Essa declaração é então recuperada na ação Post do controlador de acesso e retornada como parte da resposta da API.

A última etapa a executar para habilitar esse processo de autorização personalizado é o registro do manipulador na API Web. Os manipuladores são registrados na coleção de serviços durante a configuração:

services.AddSingleton<IAuthorizationHandler, FaceRequirementHandler>();
services.AddSingleton<IAuthorizationHandler, BodyRequirementHandler>();
services.AddSingleton<IAuthorizationHandler, VoiceRequirementHandler>();

Esse código registra cada manipulador de requisito como um singleton, usando a estrutura interna de DI (injeção de dependência) do ASP.NET Core. Uma instância do manipulador será criada quando o aplicativo for iniciado e DI injetará a classe registrada no objeto relevante.

Identificação facial

A solução usa os Serviços Cognitivos do Azure na API da Pesquisa Visual para identificar o rosto e o corpo de uma pessoa. Para obter mais informações sobre os Serviços Cognitivos e detalhes da API, visite bit.ly/2sxsqry.

A API da Pesquisa Visual fornece detecção de atributo de rosto e verificação facial. A detecção facial refere-se à capacidade de detectar rostos humanos em uma imagem. A API retorna as coordenadas do retângulo de localização do rosto na imagem processada e, opcionalmente, pode extrair uma série de atributos relacionados ao rosto, como pose da cabeça, sexo, idade, emoções, pelos faciais e óculos. A verificação facial, por outro lado, executa a autenticação do rosto detectado em relação ao rosto previamente salvo da pessoa. Na prática, ela avalia se dois rostos pertencem à mesma pessoa. Essa é a API específica que uso neste projeto de segurança. Para começar, adicione o seguinte pacote NuGet à sua solução do Visual Studio: Microsoft.Azure.Cognitive­Services.Vision.Face 2.2.0-preview

O pacote gerenciado do .NET está na versão prévia; portanto, verifique se você marcou a opção "Incluir pré-lançamento" ao procurar o NuGet, conforme mostrado na Figura 3.

Pacote NuGet da API de Detecção Facial
Figura 3 - Pacote NuGet da API de Detecção Facial

Usar o pacote .NET, assim como detecção e reconhecimento faciais é simples. Genericamente, o reconhecimento facial descreve o trabalho de comparação de dois rostos diferentes para determinar se são parecidos ou se pertencem à mesma pessoa. As operações de reconhecimento usam principalmente as estruturas de dados listadas na Figura 4.

Figura 4 - Estruturas de dados da API de Detecção Facial

Name Descrição
DetectedFace É uma única representação de rosto recuperada pela operação de detecção facial. Sua ID expira 24 horas após a criação.
PersistedFace Quando objetos DetectedFace são adicionados a um grupo (como FaceList ou Person), eles se tornam objetos de PersistedFace, que podem ser recuperados a qualquer momento e não expiram.
FaceList/LargeFaceList É uma lista variada de objetos PersistedFace. Um FaceList tem uma ID exclusiva, uma cadeia de caracteres de nome e, opcionalmente, uma cadeia de caracteres de dados do usuário.
Person É uma lista de objetos PersistedFace que pertencem à mesma pessoa. Ela tem uma ID exclusiva, uma cadeia de caracteres de nome e, opcionalmente, uma cadeia de caracteres de dados do usuário.
PersonGroup/LargePersonGroup É uma lista variada de objetos Person. Ela tem uma ID exclusiva, uma cadeia de caracteres de nome e, opcionalmente, uma cadeia de caracteres de dados do usuário. Um PersonGroup deve ser treinado para que possa ser usado em operações de reconhecimento.

A operação de verificação usa uma Face ID de uma lista de rostos detectados em uma imagem (a coleção DetectedFace) e determina se os rostos pertencem à mesma pessoa, comparando a ID com uma coleção de rostos persistentes (PersistedFace). Imagens de rosto persistentes que possuem ID e nome exclusivos identificam uma pessoa (Person). Um grupo de pessoas pode, opcionalmente, ser coletado em um PersonGroup para melhorar o desempenho do reconhecimento. Basicamente, uma pessoa é uma unidade básica de identidade e o objeto Person pode ter um ou mais rostos conhecidos registrados. Cada pessoa está definida em um determinado PersonGroup (uma coleção de pessoas) e a identificação é feita em relação a um PersonGroup. O sistema de segurança criaria um ou mais objetos PersonGroup e, em seguida, associaria as pessoas a eles. Após a criação de um grupo, a coleção PersonGroup deve ser treinada para que possa ser usada na identificação. Além disso, ela deverá ser treinada novamente após a adição ou remoção de uma pessoa ou se a face registrada de alguma pessoa for editada. O treinamento é feito pela API de treinamento de PersonGroup. Ao usar a biblioteca de clientes, isso é simplesmente uma chamada do método TrainPersonGroupAsync:

await faceServiceClient.TrainPersonGroupAsync(personGroupId);

O treinamento é um processo assíncrono. Ele pode não ser concluído, mesmo após o retorno do método TrainPersonGroupAsync. Talvez seja necessário consultar o status de treinamento com o método GetPersonGroupTrainingStatusAsync até que esteja pronto, antes de fazer a detecção ou verificação facial.

Ao executar a verificação facial, a API de Detecção Facial computa a semelhança de um rosto detectado entre todos os rostos de um grupo e retorna a pessoa mais parecida com esse rosto de teste. Isso é feito por meio do método IdentifyAsync da biblioteca de clientes. O rosto de teste precisa ser detectado usando as etapas mencionadas anteriormente, e a Face ID será então passada para a API de Identificação como um segundo argumento. Várias Face IDs podem ser identificadas ao mesmo tempo e o resultado conterá todos os resultados de Identify. Por padrão, Identify retorna apenas uma pessoa que corresponde ao melhor rosto de teste. Se preferir, você pode especificar o parâmetro opcional maxNumOfCandidatesReturned para permitir que Identify retorne mais candidatos. O código na Figura 5 demonstra o processo de identificação e verificação facial:

Figura 5 - O processo de reconhecimento facial

public class FaceRecognition : IRecognition
{
  public double Recognize(string siteId, out string name)
  {
    FaceClient faceClient = new FaceClient(
      new ApiKeyServiceClientCredentials("<Subscription Key>"))
    {
      Endpoint = "<API Endpoint>"
    };
    ReadImageStream(siteId, out Stream imageStream);
    // Detect faces in the image
    IList<DetectedFace> detectedFaces =
      faceClient.Face.DetectWithStreamAsync(imageStream).Result;
    // Too many faces detected
    if (detectedFaces.Count > 1)
    {
      name = string.Empty;
      return 0;
    }
    IList<Guid> faceIds = detectedFaces.Select(f => f.FaceId.Value).ToList();
    // Identify faces
    IList<IdentifyResult> identifiedFaces =
      faceClient.Face.IdentifyAsync(faceIds, "<Person Group ID>").Result;
    // No faces identified
    if (identifiedFaces.Count == 0)
    {
      name = string.Empty;
      return 0;
    }
    // Get the first candidate (candidates are ranked by confidence)
    IdentifyCandidate candidate =
      identifiedFaces.Single().Candidates.FirstOrDefault();
    // Find the person
    Person person =
      faceClient.PersonGroupPerson.GetAsync("", candidate.PersonId).Result;
    name = person.Name;
    return candidate.Confidence;
  }

Primeiro, você precisa obter um objeto cliente para a API de Detecção Facial, passando a sua chave de assinatura e o ponto de extremidade de API. Você pode obter ambos os valores no seu Portal do Azure, onde provisionou o serviço de API de Detecção Facial. Em seguida, você detecta qualquer rosto visível em uma imagem, passado como um fluxo para o método DetectWithStreamAsync do objeto Face do cliente. O objeto Face implementa as operações de detecção e verificação da API de Detecção Facial. Dos rostos detectados, garanto que apenas um seja realmente detectado e obtenho sua ID, ou seja, seu identificador exclusivo na coleção de rostos registrados de todas as pessoas autorizadas a acessar o local. Em seguida, o método IdentifyAsync executa a identificação do rosto detectado em um PersonGroup e retorna uma lista de melhores correspondências, ou candidatos, classificados por nível de confiança. Com a ID do primeiro candidato, recupero o nome da pessoa que, eventualmente, é retornado à API Web de Acesso. O requisito de autorização por detecção facial é atendido.

Reconhecimento de voz

A API de Reconhecimento do Locutor dos Serviços Cognitivos do Azure fornece algoritmos de verificação e identificação de locutor. As vozes têm características exclusivas que podem ser usadas para identificar uma pessoa, como uma impressão digital. A solução de segurança neste artigo usa voz como um sinal de controle de acesso, onde a pessoa diz uma frase secreta em um microfone registrado como um dispositivo IoT. Assim como no reconhecimento facial, o reconhecimento de voz também requer uma pré-inscrição de pessoas autorizadas. A API de Reconhecimento de Locutor chama uma pessoa inscrita de “Profile”. Ao inscrever um perfil, a voz do locutor é gravada dizendo uma frase específica. Em seguida, algumas características são extraídas e a frase escolhida é reconhecida. Juntas, as características extraídas e a frase escolhida formam uma assinatura de voz exclusiva. Durante a verificação, voz e frase de entrada são comparadas com a assinatura de voz e a frase da inscrição, para verificar se elas são da mesma pessoa e se a frase está correta.

Analisando a implementação do código, a API de Reconhecimento do Locutor não se beneficia de um pacote gerenciado do NuGet, como a API de Detecção Facial. Portanto, a abordagem que usarei é invocar a API REST diretamente com um mecanismo de solicitação e resposta de cliente HTTP. A primeira etapa é criar uma instância de um HttpClient com os parâmetros necessários de autenticação e tipo de dados:

public VoiceRecognition()
{
  _httpClient = new HttpClient();
  _httpClient.BaseAddress = new Uri("<API Endpoint>");
  _httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key",
    "<Subscription Key>");
  _httpClient.DefaultRequestHeaders.Accept.Add(
     new MediaTypeWithQualityHeaderValue("application/json"));
}

O método Recognize na Figura 6 desenvolve-se em várias etapas, da seguinte maneira: Depois de obter o fluxo de áudio do dispositivo IoT no local, ele tenta identificar esse áudio em relação a uma coleção de perfis inscritos. A identificação é codificada no método IdentifyAsync. Esse método assíncrono prepara uma mensagem de solicitação com várias partes, que contém o fluxo de áudio e as IDs de perfil de identificação e envia uma solicitação POST para um ponto de extremidade específico. Se a resposta da API for HTTP Código 202 (Aceito), o valor retornado será um URI da operação executada em segundo plano. Essa operação, no URI identificado, é verificada pelo método Recognize a cada 100 ms para conclusão. Quando é bem-sucedida, você obtém a ID do perfil da pessoa identificada. Com essa ID, você poderá continuar a verificação do fluxo de áudio, que é a confirmação final de que a voz gravada pertence à pessoa identificada. Isso é implementado no método VerifyAsync, que funciona de maneira semelhante ao método IdentifyAsync, exceto que ele retorna um objeto VoiceVerificationResponse, que contém o perfil da pessoa e, portanto, seu nome. A resposta da verificação inclui um nível de confiança, que também é retornado para a API Web de Acesso, assim como acontece com a API de Detecção Facial.

Figura 6 - Reconhecimento de voz

public double Recognize(string siteId, out string name)
{
  ReadAudioStream(siteId, out Stream audioStream);
  Guid[] enrolledProfileIds = GetEnrolledProfilesAsync();
  string operationUri =
    IdentifyAsync(audioStream, enrolledProfileIds).Result;
  IdentificationOperation status = null;
  do
  {
    status = CheckIdentificationStatusAsync(operationUri).Result;
    Thread.Sleep(100);
  } while (status == null);
  Guid profileId = status.ProcessingResult.IdentifiedProfileId;
  VoiceVerificationResponse verification =
    VerifyAsync(profileId, audioStream).Result;
  if (verification == null)
  {
    name = string.Empty;
    return 0;
  }
  Profile profile = GetProfileAsync(profileId).Result;
  name = profile.Name;
  return ToConfidenceScore(verification.Confidence);
}

Quero adicionar alguns comentários sobre essa API, indicando como ela difere da API de Detecção Facial. A API de verificação de voz retorna um objeto JSON que contém o resultado geral da operação de verificação (aceitar ou rejeitar), nível de confiança (baixa, normal ou alta) e a frase reconhecida:

{
  "result" : "Accept", // [Accept | Reject]
  "confidence" : "Normal", // [Low | Normal | High]
  "phrase": "recognized phrase"
}

Esse objeto é mapeado até a classe VoiceVerificationResponse C#, por conveniência de operação no método VerifyAsync, mas seu nível de confiança é expresso como um texto:

public class VoiceVerificationResponse
{
  [JsonConverter(typeof(StringEnumConverter))]
  public Result Result { get; set; }
  [JsonConverter(typeof(StringEnumConverter))]
  public Confidence Confidence { get; set; }
  public string Phrase { get; set; }
}

Em vez disso, a API Web de Acesso espera um valor decimal (tipo de dados double) entre zero e um. Portanto, especifiquei alguns valores numéricos para a enumeração Confidence:

public enum Confidence
{
  Low = 1,
  Normal = 50,
  High = 99
}

Em seguida, converti esses valores em “double”, antes de retornar para a API Web de Acesso:

private double ToConfidenceScore(Confidence confidence)
{
  return (double)confidence / 100.0d;
}

Conclusão

Assim termina a primeira parte, na qual discuti o fluxo geral de segurança no acesso ao local e abordei a implementação do mecanismo de autorização na API Web do ASP.NET Core utilizando políticas e requisitos personalizados. Em seguida, ilustrei o reconhecimento facial e de voz, usando a API relevante de Serviços Cognitivos, como um mecanismo para restringir o acesso com base em informações biométricas de perfis de pessoas previamente autorizadas ou inscritas. Na segunda parte deste artigo, analisarei o streaming de dados de dispositivos IoT como um ponto de disparo da solicitação de acesso, bem como a confirmação final da API de Acesso para desbloquear (ou bloquear!) a porta de acesso. Também falarei de um serviço de detecção de anomalias com base em aprendizado de máquina que será executado em qualquer tentativa de acesso para identificar seu risco.

O código-fonte desta parte inicial da solução está disponível no GitHub, em bit.ly/2IXPZCo.


Stefano Tempestaé Diretor Regional da Microsoft, MVP em Inteligência Artificial e Aplicativos de Negócios e membro do Blockchain Council. Um palestrante regular em conferências internacionais de TI, incluindo Microsoft Ignite e Tech Summit, os interesses do Tempesta vão além das tecnologias de blockchain e relacionadas com IA. Ele criou o Blogchain Space (blogchain.space), um blog sobre tecnologias de blockchain, escreve para a MSDN Magazine e MS Dynamics World e publica experimentos de aprendizado de máquina na Azure AI Gallery (Gallery.Azure.AI).

Agradecemos ao seguinte especialista técnico da Microsoft pela revisão deste artigo: Barry Dorrans


Discuta esse artigo no fórum do MSDN Magazine