Categories: ASP.NET CoreAzure

.NET Core ve Azure Text Analytics API Kullanarak Ürün Yorumları Üzerinde Sentiment Analizi Gerçekleştirme

Merhaba arkadaşlar.

Bir süredir büyük ilgi alanlarım arasında olan “Machine Learning” ve “Natural Language Processing” konuları üzerinde araştırmalar ve denemeler yapmaktayım. Bu araştırmalarım ve denemelerim sırasında ise iş hayatımda bulunuyor olduğum domain içerisinde, bu konuları nasıl ve nerede implemente edebilirim sorularını da düşünmekteyim. (Günün sonunda, implemente ettiğimiz bir fikrin son kullanıcıya olan güzel etkisini görebilmek, bir developer olarak mutluluk veriyor değil mi?)

Bu makale kapsamında ise .NET Core ve Azure Text Analytics API kullanarak, bir e-ticaret firmasındaki ürün yorumları üzerinde sentiment analizi gerçekleştireceğiz. Sentiment analizindeki amacımız ise bir son kullanıcının bir ürün hakkındaki yorumları okumadan, hızlı bir şekilde fikir sahibi olabilmesini sağlamak yönünde olacaktır.

Öncelikle sentiment analizi nedir, kısaca bir giriş yapmak istiyorum.

1) Sentiment Analizi Nedir?

Kısaca sentiment analizi için:

Bir yazı parçasından olumlu veya olumsuz görüşlerin belirlenmesi sürecidir diyebiliriz.

Ayrıca bir konuşmacının düşüncesini veya tutumunu inceleyen, fikir madenciliği olarak da bilinir. Özellikle machine learning‘in hızla ilerlemesiyle beraber, günümüz teknoloji çağında sentiment analizleri üzerinde de büyük çalışmalar yapılmaktadır.. Geçmişe dönüp bir 10 yıl öncesini hatırlarsak, forex firmaları içerisinde de piyasaların sentiment analizlerinin çıkartılıp, bu analizler doğrultusunda alım ve satım işlemlerinin yapıldığını görebiliriz. (Oynayanlar bilir)

Günümüzde ise firmalar, kendi ürünleri hakkında sosyal medya üzerinde ne düşünülüyor gibi fikirleri öğrenebilmek için, oldukça yaygın olarak sentiment analizi gibi hizmetleri kullanmaktadırlar.

Örneğin twitter veya instagram üzerinde yediğimiz-içtiğimiz şeyleri, duygularımızı genelde paylaşmaktayız. Bu paylaşım işlemleri basit bir olay gibi gözükebilir, fakat bir çok firma hangi ürünlere veya hangi yöne ağırlık vermeleri gerektiği bilgilerini bu paylaşımlardan ve analizlerinden yola çıkarak belirleyebilmektedir.

Biz ise bu makale kapsamında sentiment analizini, kullanıcıların ürünler hakkında ne hissettiklerini keşfedip, bir sonraki kullanıcıya tercih edeceği ürün hakkında daha hızlı karar verip, satın alabilmesi amacıyla esneklik sağlamak için kullanacağız.

Sentiment analizini gerçekleştirebilmenin bir çok farklı yöntemi mevcut. Örneğin, Python‘ın VADER NLTK paketi ile kendi sentiment analyser’ınızı oluşturabilirsiniz (Üzerinde çalıştığım bir tool, ilerleyen makale konusu olarak değerlendirebilirim) veya bizi hızlandıracak bir cloud provider API‘ı tercih edebilirsiniz.

Biz bu makale kapsamında ise sentiment analizini, Azure Text Analytics API‘ı kullanarak gerçekleştireceğiz.

2) Azure Text Analytics API

Peki nedir bu Azure Text Analytics API ve bize neler sunuyor?

Azure Text Analytics API, raw text üzerinden advanced natural language processing(gelişmiş doğal dil işleme) gerçekleştirebilmemize olanak sağlayan bir cloud-based hizmettir. Özellikle “time to market” söz konusu olduğunda, Azure Text Analytics API gibi cloud hizmetleri bizlere oldukça fazla “hız” ve “zaman” kazandırmaktadır.

Azure Text Analytics API, aşağıdaki 4 ana fonksiyonu içermektedir:

  1. Sentiment Analysis: Makalemizin de konusu olan, kullanıcıların ürünler hakkında ne düşündüklerini analiz edebilmemizi sağlayan hizmeti.
  2. Key Phrase Extraction: Bir cümle içerisindeki ana noktaları hızlıca tanımlayabilmemiz için anahtar sözcükleri çıkartabilmemizi sağlayan hizmeti.
  3. Language Detection: 120 dile kadar giriş metninin hangi dilde yazıldığını tespit edebilmemizi sağlayan hizmeti.
  4. Entity Linking: Metin içerisindeki bilinen varlıkların hakkında daha fazla bilgiye ulaşılabilmesi için, linkleyebilmemizi sağlayan hizmeti. (Henüz preview durumda)

NOT: Free tier’ı seçerek, aylık 5000 transaction’a kadar ücretsiz olarak kullanabilmek mümkündür.

2.1) Text Analytics Resource’unu Oluşturmak

Text Analytics API‘ı kullanabilmek için Azure marketplace üzerinden “AI + Machine Learning” sekmesine girelim ve “Text Analytics” i seçelim.

Ardından aşağıdaki gerekli alanları, kendi seçimlerimize göre dolduralım ve create butonuna basalım.

Artık API hazır durumda.

Aşağıdaki overview ekran’ı üzerinden Azure Text Analytics API‘ının, makalenin ilerleyen bölümlerinde kullanabilmek için “endpoint” ve “key” bilgilerine erişebilmek mümkündür.

3) .NET Core ile Azure Text Analytics API Kullanımı

Bir e-ticaret firmasında çalıştığımızı ve kullanıcıların satın aldıkları ürünler için yorum yapabildiklerini varsayalım. Sanırım bir ürünü satın almadan önce o ürün için yapılmış olan yorumları okuyabilmek, hem son kullanıcı açısından hem de ilgili firma açısından önemli bir fonksiyonalitedir.

Peki, son kullanıcıların tüm yorumları okuyup bir ürün seçimi yapmalarının yerine, biz onlar için tüm ürün yorumları üzerinde bir sentiment analizi gerçekleştirerek, her bir ürün için ortalama bir son kullanıcı puanı gösterebilsek, harika olmaz mı? Hem son kullanıcılar tüm yorumları okuyarak fazla vakit kaybetmemiş olurlar hem de biz son kullanıcının ziyaretini hızlıca satışa çevirebilme imkanı elde etmiş olabiliriz.

Haydi ozaman iş başına.

İlk olarak aşağıdaki gibi “SentimentAnalysisWithNETCoreExample” isminde bir .NET Core “webapi” projesi oluşturalım.

dotnet new webapi -n SentimentAnalysisWithNETCoreExample

Ardından projeye aşağıdaki komut satırı ile “Microsoft.EntityFrameworkCore” paketini dahil edelim.

dotnet add package Microsoft.EntityFrameworkCore

Artık domain modellerimizi tanımlayabiliriz.

Models” isminde bir klasör oluşturarak, içerisinde “Domain” isminde yeni bir klasör daha oluşturalım.

-> SentimentAnalysisWithNETCoreExample
--> Models
---> Domain

Şimdi “Domain” klasörü içerisinde ise, aşağıdaki gibi bir “Product” class’ı ekleyelim.

using System.Collections.Generic;

namespace SentimentAnalysisWithNETCoreExample.Models.Domain
{
    public class Product
    {
        public Product()
        {
            this.Comments = new List<Comment>();
        }

        public int ID { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public double Price { get; set; }
        public double CustomerRating { get; set; }

        public ICollection<Comment> Comments { get; set; }
    }
}

Bizim için buradaki önemli nokta, “CustomerRating” property’si. Bu property’nin value’sunu, product comment’lerinin sentiment analizi sonucunda çıkan score’ların ortalaması ile dolduracağız.

Şimdi ise “Domain” klasörü içerisine, “Comment” isminde bir class daha ekleyelim.

namespace SentimentAnalysisWithNETCoreExample.Models.Domain
{
    public class Comment
    {
        public int ID { get; set; }
        public int ProductID { get; set; }
        public string Content { get; set; }
        public double? SentimentScore { get; set; }
    }
}

Bu class içerisindeki “SentimentScore” property’sinin value’sunda ise, her bir comment’in kendi sentiment score’unu tutacağız.

Artık data context’i ve sample dataset’i oluşturabiliriz. “Data” isminde root dizinde bir klasör oluşturalım ve içerisinde “ProductDBContext” isminde aşağıdaki gibi bir class ekleyelim.

using Microsoft.EntityFrameworkCore;
using SentimentAnalysisWithNETCoreExample.Models.Domain;

namespace SentimentAnalysisWithNETCoreExample.Data
{
    public class ProductDBContext : DbContext
    {
        public ProductDBContext(DbContextOptions<ProductDBContext> options)
            : base(options)
        {

        }

        public DbSet<Product> Products { get; set; }
        public DbSet<Comment> Comments { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            #region Products
            var product_1 = new Product()
            {
                ID = 1,
                Name = "Samsung Note 9",
                Category = "Cep Telefonu",
                Price = 600
            };

            var product_2 = new Product()
            {
                ID = 2,
                Name = "Samsung Galaxy 9",
                Category = "Cep Telefonu",
                Price = 550
            };

            modelBuilder.Entity<Product>().HasData(product_1, product_2);
            #endregion

            #region Comments
            var comment_1 = new Comment()
            {
                ID = 1,
                ProductID = 1,
                Content = "Çok harika bir telefon. Çok memnunum. Herkese tavsiye ederim."
            };

            var comment_2 = new Comment()
            {
                ID = 2,
                ProductID = 1,
                Content = "Güzel telefon, gayet kullanışlı."
            };

            var comment_3 = new Comment()
            {
                ID = 3,
                ProductID = 1,
                Content = "Hızlı telefon, beğendim."
            };

            var comment_4 = new Comment()
            {
                ID = 4,
                ProductID = 1,
                Content = "Bataryası çok iyi dayanıyor tavsiye ederim."
            };

            var comment_5 = new Comment()
            {
                ID = 5,
                ProductID = 2,
                Content = "Fena değil. Biraz ısınıyor. Daha iyi olabilirdi."
            };

            var comment_6 = new Comment()
            {
                ID = 6,
                ProductID = 2,
                Content = "Kasasını beğenmedim. Elden çok kayıyor."
            };

            var comment_7 = new Comment()
            {
                ID = 7,
                ProductID = 2,
                Content = "Memnunum, kamerası çok iyi."
            };

            var comment_8 = new Comment()
            {
                ID = 8,
                ProductID = 2,
                Content = "Kamerasının gece çekimini beğenmedim."
            };

            modelBuilder.Entity<Comment>().HasData(comment_1, comment_2, comment_3, comment_4, comment_5, comment_6, comment_7, comment_8);
            #endregion

            base.OnModelCreating(modelBuilder);
        }
    }
}

Data context’i standart olarak “DbContext” class’ından inherit alarak oluşturduk. İçerisinde “Products” ve “Comments” dbset’lerini tanımladık. Örnek dataset’e sahip olabilmek için, “OnModelCreating” method’u içerisinde bir kaç product ve onlara comment’ler ekledik.

Şimdi ise, business service’leri kodlamaya başlamadan önce request & response modellerimizi tanımlayalım.

Bunun için “Models” klasörü altında, “Requests” ve “Responses” klasörülerini oluşturalım. Ardından “Requests” klasörünün içerisinde aşağıdaki gibi “GetSentimentAnalysisRequest” ve “GetSentimentAnalysisRequestItem” class’larını tanımlayalım.

using System.Collections.Generic;

namespace SentimentAnalysisWithNETCoreExample.Models.Requests
{
    public class GetSentimentAnalysisRequest
    {
        public GetSentimentAnalysisRequest()
        {
            Documents = new List<GetSentimentAnalysisRequestItem>();
        }

        public IList<GetSentimentAnalysisRequestItem> Documents { get; set; }
    }
}
namespace SentimentAnalysisWithNETCoreExample.Models.Requests
{
    public class GetSentimentAnalysisRequestItem
    {
        public string Language { get; set; }
        public string Id { get; set; }
        public string Text { get; set; }
    }
}

GetSentimentAnalysisRequest” ve “GetSentimentAnalysisRequestItem” class’larını, Azure Text Analytics API‘ın sentiment endpoint’ini call edebilmek için kullanacağız.

Sentiment endpoint’i aşağıdaki gibi bir request’i bizden beklemektedir:

{
  "documents": [
    {
      "language": "comment lang",
      "id": "comment id",
      "text": "comment"
    },
    {
      "language": "comment lang",
      "id": "comment id",
      "text": "comment"
    }
  ]
}

NOT: Azure Text Analytics API‘ın sentiment endpoint’ini kullanabilmek için hazır bir NuGet package’ı mevcut. Fakat preview durumdadır ve henüz .NET Standard 1.4’ü desteklemektedir. Bu sebeple .NET Core 2.1 uygulamasında kullanabilmek için, kendi request modellerimizi ve service’imizi implemente edeceğiz.

Sentiment endpoint’inin response modellerini ise, oluşturmuş olduğumuz “Responses” klasörü altında tanımlayacağız.

GetSentimentAnalysisResponse” ve “GetSentimentAnalysisResponseItem” ismindeki iki adet class’ı, “Responses” klasörü altında aşağıdaki gibi oluşturalım.

using System.Collections.Generic;

namespace SentimentAnalysisWithNETCoreExample.Models.Responses
{
    public class GetSentimentAnalysisResponse
    {
        public IList<GetSentimentAnalysisResponseItem> Documents { get; set; }
    }
}
namespace SentimentAnalysisWithNETCoreExample.Models.Responses
{
    public class GetSentimentAnalysisResponseItem
    {
        public string Id { get; set; }
        public double? Score { get; set; }
    }
}

Artık sentiment sonuçlarını alabileceğimiz response modellerini de tanımladığımıza göre, service’i implemente etmeye başlayabiliriz.

Bunun için root dizinde “Services” isminde yeni bir klasör oluşturalım ve içerisinde “ITextAnalyticsService” isminde bir interface tanımlayalım.

using System.Threading.Tasks;
using SentimentAnalysisWithNETCoreExample.Models.Requests;
using SentimentAnalysisWithNETCoreExample.Models.Responses;

namespace SentimentAnalysisWithNETCoreExample.Services
{
    public interface ITextAnalyticsService
    {
         Task<GetSentimentAnalysisResponse> GetSentimentAsync(GetSentimentAnalysisRequest request);
    }
}

Ardından “Services” klasörü altında “Implementations” isminde bir yeni klasör daha oluşturalım ve içerisine “TextAnalyticsService” isminde bir class yaratıp, “ITextAnalyticsService” interface’ini aşağıdaki gibi implemente edelim.

using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using SentimentAnalysisWithNETCoreExample.Models.Requests;
using SentimentAnalysisWithNETCoreExample.Models.Responses;

namespace SentimentAnalysisWithNETCoreExample.Services.Implementations
{
    public class TextAnalyticsService : ITextAnalyticsService
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IConfiguration _configuration;

        public TextAnalyticsService(IHttpClientFactory httpClientFactory, IConfiguration configuration)
        {
            _httpClientFactory = httpClientFactory;
            _configuration = configuration;
        }

        public async Task<GetSentimentAnalysisResponse> GetSentimentAsync(GetSentimentAnalysisRequest request)
        {
            HttpClient client = _httpClientFactory.CreateClient("TextAnalyticsAPI");

            var sentimentResponse = await client.PostAsJsonAsync(
                    requestUri: _configuration.GetValue<string>("TextAnalyticsAPISentimentResourceURI"), 
                    value: request);

            GetSentimentAnalysisResponse getSentimentAnalysisResponse = null;

            if(sentimentResponse.StatusCode == HttpStatusCode.OK)
            {
                getSentimentAnalysisResponse = await sentimentResponse.Content.ReadAsAsync<GetSentimentAnalysisResponse>();
            }

            return getSentimentAnalysisResponse;
        }
    }
}

TextAnalyticsService” içerisinde yaptığımız işlem gayet basit.

HttpClientFactory‘i “TextAnalyticsAPI” key’i üzerinden named-client yöntemi ile oluşturduk. Ardından “IConfiguration” service’i üzerinden ilgili Azure Text Analytics API‘ın sentiment resource URI‘ını alarak, POST işlemini gerçekleştirdik. Eğer işlem başarılıysa tamamlanıyorsa, response’u daha önce oluşturmuş olduğumuz “GetSentimentAnalysisResponse” model’i ile map’liyoruz.

Şimdi ise product comment’leri ile ilgili işlemleri implemente edeceğimiz service’in interface’ini, aşağıdaki gibi “Services” klasörü altında tanımlayalım.

IProductCommentService

using System.Collections.Generic;
using System.Threading.Tasks;
using SentimentAnalysisWithNETCoreExample.Models.Domain;

namespace SentimentAnalysisWithNETCoreExample.Services
{
    public interface IProductCommentService
    {
        Task<List<Comment>> GetComments(int productId);
        Task<List<Comment>> CalculateCommentsSentimentScoreAsync(List<Comment> comments);
    }
}

Ardından “Services” klasörü altındaki “Implementations” klasörü içerisinde, “ProductCommentService” isminde bir class daha oluşturalım ve “IProductCommentService” interface’ini aşağıdaki gibi implemente edelim.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using SentimentAnalysisWithNETCoreExample.Data;
using SentimentAnalysisWithNETCoreExample.Models.Domain;
using SentimentAnalysisWithNETCoreExample.Models.Requests;
using SentimentAnalysisWithNETCoreExample.Models.Responses;

namespace SentimentAnalysisWithNETCoreExample.Services.Implementations
{
    public class ProductCommentService : IProductCommentService
    {
        private const string LANG = "tr";
        private readonly ProductDBContext _productDBContext;
        private readonly ITextAnalyticsService _textAnalyticsService;

        public ProductCommentService(ProductDBContext productDBContext, ITextAnalyticsService textAnalyticsService)
        {
            _productDBContext = productDBContext;
            _textAnalyticsService = textAnalyticsService;
        }

        public async Task<List<Comment>> GetComments(int productId)
        {
            List<Comment> comments = await _productDBContext.Comments.Where(c => c.ProductID == productId)
                                        .ToListAsync();

            return comments;
        }

        public async Task<List<Comment>> CalculateCommentsSentimentScoreAsync(List<Comment> comments)
        {
            var getSentimentAnalysisRequest = new GetSentimentAnalysisRequest();

            comments.ForEach(comment =>
            {
                var multiLanguageInput = new GetSentimentAnalysisRequestItem()
                {
                    Language = LANG,
                    Id = comment.ID.ToString(),
                    Text = comment.Content
                };

                getSentimentAnalysisRequest.Documents.Add(multiLanguageInput);
            });

            GetSentimentAnalysisResponse getSentimentAnalysisResponse = await _textAnalyticsService.GetSentimentAsync(getSentimentAnalysisRequest);

            if (getSentimentAnalysisResponse != null && getSentimentAnalysisResponse.Documents.Count > 0)
            {
                // Add sentiment analysis result to the comments
                foreach (GetSentimentAnalysisResponseItem getSentimentAnalysisResponseItem in getSentimentAnalysisResponse.Documents)
                {
                    Comment comment = comments.FirstOrDefault(c => c.ID == Convert.ToInt32(getSentimentAnalysisResponseItem.Id));

                    comment.SentimentScore = getSentimentAnalysisResponseItem.Score;
                }
            }

            return comments;
        }
    }
}

GetCommentsAsync” method’u ile ilgili product’ın comment’lerini, database’den alıyoruz. “CalculateCommentsSentimentScoreAsync” method’u içerisinde ise oluşturmuş olduğumuz Azure Text Analytics API‘ın service’ini kullanarak, tüm comment’lerin sentiment score’larını hesaplıyoruz. Eğer API call’ı sırasında herhangi bir problem oluşmazsa, comment’lerin sentiment score’larını map’liyoruz.

Şimdi ise product’lar ile ilgili işlemleri gerçekleştirebileceğimiz bir service’e daha ihtiyacımız var.

Öncelikle dışarıya domain model’ini expose etmemek adına product response modelini, daha önce oluşturmuş olduğumuz “Models/Responses” klasörü içerisinde tanımlamamız gerekmektedir.

GetProductResponse” ve “GetProductCommentResponse” class’larını aşağıdaki gibi “Responses” klasörü içerisinde tanımlayalım.

using System.Collections.Generic;

namespace SentimentAnalysisWithNETCoreExample.Models.Responses
{
    public class GetProductResponse
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public double Price { get; set; }
        public double CustomerRating { get; set; }
        public List<GetProductCommentResponse> Comments { get; set; }
    }
}
namespace SentimentAnalysisWithNETCoreExample.Models.Responses
{
    public class GetProductCommentResponse
    {
        public string Content { get; set; }
    }
}

Model’leri tanımlamanın ardından, “Services” klasörü altında “IProductService” isminde aşağıdaki gibi yeni bir interface oluşturalım.

using System.Collections.Generic;
using System.Threading.Tasks;
using SentimentAnalysisWithNETCoreExample.Models.Domain;
using SentimentAnalysisWithNETCoreExample.Models.Responses;

namespace SentimentAnalysisWithNETCoreExample.Services
{
    public interface IProductService
    {
         Task<GetProductResponse> GetProductAsync(int id);
    }
}

Ardından “Services” klasörü altındaki “Implementations” klasörü içerisinde, “ProductService” isminde bir class daha oluşturarak, aşağıdaki gibi implemente edelim.

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using SentimentAnalysisWithNETCoreExample.Data;
using SentimentAnalysisWithNETCoreExample.Models.Domain;
using SentimentAnalysisWithNETCoreExample.Models.Responses;

namespace SentimentAnalysisWithNETCoreExample.Services.Implementations
{
    public class ProductService : IProductService
    {
        private readonly ProductDBContext _productDBContext;
        private readonly IProductCommentService _productCommentService;

        public ProductService(ProductDBContext productDBContext, IProductCommentService productCommentService)
        {
            _productDBContext = productDBContext;
            _productCommentService = productCommentService;
        }

        public async Task<GetProductResponse> GetProductAsync(int id)
        {
            Product product =  await _productDBContext.Products.Where(p => p.ID == id)
                                    .FirstOrDefaultAsync();

            GetProductResponse productResponse = null;

            if (product != null)
            {
                productResponse = new GetProductResponse();
                
                productResponse.ID = product.ID;
                productResponse.Name = product.Name;
                productResponse.Category = product.Category;
                productResponse.Price = product.Price;

                List<Comment> comments = await _productCommentService.GetComments(id);

                productResponse.Comments = comments.Select(c => new GetProductCommentResponse()
                {
                    Content = c.Content
                }).ToList();

                if (comments.Count > 0)
                {
                    List<Comment> commentsWithSentimentAnalysis = await _productCommentService.CalculateCommentsSentimentScoreAsync(comments);

                    productResponse.CustomerRating = await CalculateProductCustomerRatingScore(commentsWithSentimentAnalysis);
                }
            }

            return productResponse;
        }

        private async Task<double> CalculateProductCustomerRatingScore(List<Comment> comments)
        {
            double sentimentScores = 0;
            double customerRating = 0;

            comments.ForEach(_comment =>
            {
                sentimentScores += _comment.SentimentScore.Value;
            });

            customerRating = (sentimentScores / comments.Count());

            return await Task.FromResult(customerRating);
        }
    }
}

Oluşturduğumuz “GetProductAsync” method’una bakarsak, ilgili product’ın comment’lerini inject etmiş olduğumuz “IProductCommentService” vasıtasıyla çekiyoruz.

Eğer ilgili product’ın comment’leri boş değilse “IProductCommentService” içerisindeki “CalculateCommentsSentimentScoreAsync” method’unu kullanarak, ilgili comment’lerin sentiment score’larını hesaplıyoruz.

Ardından kullanıcıların ilgili ürün hakkında ne hissettiklerinin (olumlu/olumsuz) bir ortalamasını alabilmek için ise, “CalculateProductCustomerRatingScoreAsync” isimli private method’u kullanıyoruz.

Sonunda service’lerin tanımlanması ve implemente edilmesi işlemlerini tamamladık.

Şimdi, “Controllers” klasörü altında “ProductsController” isminde aşağıdaki gibi bir controller tanımlayalım.

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SentimentAnalysisWithNETCoreExample.Models.Domain;
using SentimentAnalysisWithNETCoreExample.Models.Responses;
using SentimentAnalysisWithNETCoreExample.Services;

namespace SentimentAnalysisWithNETCoreExample.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    {
        private readonly IProductService _productService;

        public ProductsController(IProductService productService)
        {
            _productService = productService;
        }

        [HttpGet("{id}")]
        public async Task<ActionResult> GetProduct(int id)
        {
            GetProductResponse product = await _productService.GetProductAsync(id);

            if (product != null)
            {
                return Ok(product);
            }
            else
            {
                return NotFound();
            }
        }
    }
}

Yukarıdaki controller içerisinde ise “IProductService” interface’ini inject ettikten sonra, dışarıya bir GET endpoint’i expose ettik. Artık product’ları, “id” bazlı alabileceğimiz bir endpoint’e sahibiz.

Şimdi ise injection vb. gibi gerekli işlemleri sağlayabilmek adına, “Startup” class’ını da aşağıdaki gibi güncelleyelim.

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SentimentAnalysisWithNETCoreExample.Data;
using SentimentAnalysisWithNETCoreExample.Services;
using SentimentAnalysisWithNETCoreExample.Services.Implementations;

namespace SentimentAnalysisWithNETCoreExample
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.AddHttpClient("TextAnalyticsAPI", c =>
            {
                c.BaseAddress = new Uri(Configuration.GetValue<string>("TextAnalyticsAPIBaseAddress"));
                c.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", Configuration.GetValue<string>("TextAnalyticsAPIKey"));
            });

            //Services
            services.AddTransient<ITextAnalyticsService, TextAnalyticsService>();
            services.AddTransient<IProductService, ProductService>();
            services.AddTransient<IProductCommentService, ProductCommentService>();

            //Data
            services.AddDbContext<ProductDBContext>(option => option.UseInMemoryDatabase("ProductDBContext"));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            using (var serviceScope = app.ApplicationServices.CreateScope())
            {
                var context = serviceScope.ServiceProvider.GetService<ProductDBContext>();
                context.Database.EnsureCreated();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

Yukarıdaki kod bloğuna bakarsak HttpClient‘ı, Azure subscription key’i ve “TextAnalyticsAPI” name’i ile inject ettik. Ardından oluşturmuş olduğumuz “ITextAnalyticsService“, “IProductService” ve “IProductCommentService” interface’lerinin injection işlemlerini gerçekleştirdik.

DbContext’i ise, in-memory olarak belirledik. “Configure” method’u içerisinde ise DbContext’e erişerek, sample dataset’imizin initialize edilmesini sağladık.

Şimdi ise ilgili cofiguration key’lerini, “appsettings” json file’ı içerisine aşağıdaki gibi ekleyelim.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "TextAnalyticsAPIBaseAddress" : "https://westcentralus.api.cognitive.microsoft.com",
  "TextAnalyticsAPISentimentResourceURI" : "text/analytics/v2.0/sentiment",
  "TextAnalyticsAPIKey" : "YOUR_KEY"
}

Burada eklemiş olduğumuz “TextAnalyticsAPIBaseAddress“, “TextAnalyticsAPISentimentResourceURI” ve “TextAnalyticsAPIKey” bilgilerine, makalenin giriş bölümünde Azure Portal üzerinden oluşturmuş olduğumuz Text Analytics resource’u içerisinden ulaşabilirsiniz.

Artık test etmeye hazırız.

Öncelikle API‘ı, “dotnet run” komutu ile çalıştıralım. Ardından olumlu comment’ler ile hazırlamış olduğumuz “1” numaralı örnek product’ı, “https://localhost:5001/api/products/1” endpoint’i üzerinden GET edelim.

Gelen response’a bakarsak, 4 comment’in sentiment sonuçlarından yola çıkarak ortalama bir “customerRating” oranı elde ettiğimizi görebiliriz. “0” ile “1” aralığında değerlendirilen bu sonuç ile, kullanıcıların bu ürünü ortalama %77 lik bir oran ile olumlu değerlendirdiklerini söyleyebiliriz.

Şimdi ise biraz olumsuz comment’ler içeren “2” numaralı örnek product’ı GET edelim ve sonucuna bir göz atalım.

Bu senaryoda ise product biraz olumsuz comment’ler içerdiği için, sentiment analizi sonucunda ortalama %49 luk bir “customerRating” oranı ile response olarak karşımıza gelmektedir.

4) Sonuç

Makalenin giriş bölümünde de bahsettiğim gibi, dilerseniz kendi sentiment analyser’ınızı Python‘ın VADER NLTK paketi gibi farklı dil ve tool’lar ile oluşturabilirsiniz. İsterseniz de bizlere bir çok açıdan hız katan hazır cloud provider’larından yararlanabilirsiniz. Bu makale kapsamında ise Azure Text Analytics API‘ını kullanarak, hızlı bir şekilde .NET Core 2.1 ortamında bir e-ticaret platformundaki ürün yorumları üzerinde sentiment analizinden nasıl faydalanabiliriz konusuna bakmaya çalıştık.

https://github.com/GokGokalp/AzureTextAnalyticsAPI-Sentiment-Sample

Referanslar

https://docs.microsoft.com/en-us/azure/cognitive-services/text-analytics/quickstarts/csharp
https://docs.microsoft.com/en-us/azure/cognitive-services/text-analytics/overview

Gökhan Gökalp

View Comments

  • Asp.NET'te yazılan bir ERP projesine doğal dil işleme eklenmek istense, ve azurenin yapıtğı olayı farklı bir bakış acısı ile yazılması öngörülse,

    Bu algoritmayı sadece ar-ge olabilecek kısımlarının Pyhton ya da Core tabanlı olarak

    hangisinde yazılması daha isabetli olur. Bu noktada görüşünüzü alabilir miyim.

    • Merhaba ar-ge kapsamında değerlendirmek istiyorsanız, tabi ki bu logic'i kendiniz istediğiniz bir dil ile yazmanız isabetli olacaktır. Bu konuda Python diyebilirim, hem community hemde kullanabileceğiniz library'ler konusunda daha zengin bir seçim olacaktır.

      Teşekkürler.

  • Merhaba. Gece 3 te okudum. Uykumdan ettin :) kalktım denedim. Güzel bir anlatım olmuş. Teşekkürler

  • Python‘ın VADER NLTK paketi hakkında bir makale yazmayı düşünüyor musunuz ?
    Ve bu paket hakkında önerebileceğiniz bir kaynak var mı ?

Recent Posts

Containerized Uygulamaların Supply Chain’ini Güvence Altına Alarak Güvenlik Risklerini Azaltma (Güvenlik Taraması, SBOM’lar, Artifact’lerin İmzalanması ve Doğrulanması) – Bölüm 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 ay ago

Identity & Access Management İşlemlerini Azure AD B2C ile .NET Ortamında Gerçekleştirmek

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 yıl ago

Azure Service Bus Kullanarak Microservice’lerde Event’ler Nasıl Sıralanır (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 yıl ago

.NET Microservice’lerinde Outbox Pattern’ı ile Eventual Consistency için Atomicity Sağlama

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 yıl ago

Dapr ve .NET Kullanarak Minimum Efor ile Microservice’ler Geliştirmek – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 yıl ago