Sentiment Analysis Part 3 (Interface)

This is the project where we are going to build an interface to communicate with the users, I could do this very easily with a console application, but I wanted to make it better than that, I wanted to show that ML.Net can be used for the real applications very easily.

You can download the source code from here.

So lets start to build our "Interface" project.

Let's add new project to the our solution, please select "Windows Forms App" as below, and click "Next".

And then name it as "MoviewReiews", and click to "Create" to create and add a new project to our solution.

Now we have Interface Project which we named "MovieReviews", now we can start to work on it.
 
Before starting, let me explain what this "Interface" project is going to do. Here we are going to use our trained model to make predictions of the user reviews of any movie.

As you already guess we need new "User Reviews" to be able to predict if it is "Positive" or "Negative". We are going to make it easy for the users to type any movie name, and get general information of it with the user reviews. We are going to use Html Agility Pack to get general information of the movie and the user reviews from the IMDB.

Please pay attention that, this is educational article and the tutorial of it, there is nothing about parsing and I do not suggest parsing any website.

Now we have Interface Project which we named "MovieReviews", now we can start to work on it.
 
Before starting, let me explain what this "Interface" project is going to do. Here we are going to use our trained model to make predictions of the user reviews of any movie.

As you already guess we need new "User Reviews" to be able to predict if it is "Positive" or "Negative". We are going to make it easy for the users to type any movie name, and get general information of it with the user reviews. We are going to use Html Agility Pack to get general information of the movie and the user reviews from the IMDB.

Now we have Interface Project which we named "MovieReviews", now we can start to work on it.
 
Before starting, let me explain what this "Interface" project is going to do. Here we are going to use our trained model to make predictions of the user reviews of any movie.

As you already guess we need new "User Reviews" to be able to predict if it is "Positive" or "Negative". We are going to make it easy for the users to type any movie name, and get general information of it with the user reviews. We are going to use Html Agility Pack to get general information of the movie and the user reviews from the IMDB.

Now we have Interface Project which we named "MovieReviews", now we can start to work on it.
 
Before starting, let me explain what this "Interface" project is going to do. Here we are going to use our trained model to make predictions of the user reviews of any movie.

As you already guess we need new "User Reviews" to be able to predict if it is "Positive" or "Negative". We are going to make it easy for the users to type any movie name, and get general information of it with the user reviews. We are going to use Html Agility Pack to get general information of the movie and the user reviews from the IMDB.

So let's start with Settings.cs, this is a very simple static class which we are going to keep some constant settings, below you can put your API Key which you got it from OMDB API, replace it with [APIKEY] section.


public static class Settings
    {
        public static string BASE_MOVIE_URL = @"https://www.imdb.com/title/{0}";
        public static string BASE_MOVIE_URL_REVIEWS =  @"https://www.imdb.com/title/{0}/reviews?ref_=tt_ov_rt";
        public static string API_SEARCH_URL =  @"https://www.omdbapi.com/?t={0}&apikey=[APIKEY]";
    }
    

Now we are going to talk about WebHelper class, which we are going to use it most of the process, this class is going to be our helper class, which we will use for searching movies, getting general information about them, and getting user reviews about any movies.

The Web Helper class have some helper functions, let's start by defining them first. Below you can see five helper functions which are going to help us for getting information and user reviews for the movies, as you can guess here we use Html Agility Pack , I am not going to talk about it because this article is not covering it, and one more time I want to specify that, this is educational article and tutorial project of it, because of I do not recommend to anyone to make scraping on any website.


#region HtmlHelpers
        
        //Gets reviews nodes
        private static IEnumerable GetReviewsNodes(HtmlDocument doc)
        {
            return doc.DocumentNode.SelectNodes("//div[contains(@class,  'lister-item-content')]");
        }

        //Gets rating value
        private static string GetRating(HtmlNode node)
        {
            var mainNode = node.SelectSingleNode(".//span[contains(@class,  'rating-other-user-rating')]");
            return mainNode != null ? mainNode.ChildNodes[3].InnerHtml : "N/A";
        }

        //Gets title
        private static string GetTitle(HtmlNode node)
        {
            return node.SelectSingleNode(".//a[contains(@class,  'title')]").InnerHtml;
        }

        //Gets user and date
        private static string[] GetUserNameAndData(HtmlNode node)
        {
            var mainNode = node.SelectSingleNode(".//div[contains(@class,  'display-name-date')]");
            var userNode = mainNode.SelectSingleNode(".//span[contains(@class,  'display-name-link')]");
            var user = userNode.SelectSingleNode("a").InnerHtml;
            var date = mainNode.SelectSingleNode(".//span[contains(@class,  'review-date')]").InnerHtml;
            return new[] {user, date};
        }

        //Gets title
        private static string GetReview(HtmlNode node)
        {
            return node.SelectSingleNode(".//div[contains(@class, 'text  show-more__control')]").InnerHtml;
        }

   #endregion
    

No we need to create a "User Control" and name it as "ReviewItem", we are going to use this control to display Movie. Reviews. As you can see below there is some fields which we are going to use to display information about the review.

OK, now it is time to create our models, we are going to create two classes for our models which we are going to use on this tutorial project.
 
let's start with MovieSearchResultModel class, this is the class which we need to use for reading JSON response of OMDB API, we are not going to use all fields, but as you can see the structure is like below.


 public class Rating
    {
        public string Source { get; set; }
        public string Value { get; set; }
    }

    public class MovieSearchResultModel
    {
        public string Title { get; set; }
        public string Year { get; set; }
        public string Rated { get; set; }
        public string Released { get; set; }
        public string Runtime { get; set; }
        public string Genre { get; set; }
        public string Director { get; set; }
        public string Writer { get; set; }
        public string Actors { get; set; }
        public string Plot { get; set; }
        public string Language { get; set; }
        public string Country { get; set; }
        public string Awards { get; set; }
        public string Poster { get; set; }
        public List Ratings { get; set; }
        public string Metascore { get; set; }
        public string imdbRating { get; set; }
        public string imdbVotes { get; set; }
        public string imdbID { get; set; }
        public string Type { get; set; }
        public string DVD { get; set; }
        public string BoxOffice { get; set; }
        public string Production { get; set; }
        public string Website { get; set; }
        public bool Response { get; set; }
        public string PageUrl { get; set; }
        public string ReviewsPageUrl { get; set; }
    }
    

and second mode, ReviewModel which is the representation model of the reviews.


    public class ReviewsModel
    {
        public string Title { get; set; }
        public string Review { get; set; }
        public string Rating { get; set; }
        public string User { get; set; }
        public string Date { get; set; }
    }
    

and second mode, ReviewModel which is the representation model of the reviews.

Here the code part of the main screen, we start by definition of our Debug variable which we already use on "Trainer" project too. Please pay attention that if you set this variable "true" on the "Trainer" project you need to set it "true" here too, they should be same, otherwise "Trainer" project saves model to the different path, and the "Movie Reviews" project searches model in different path where most likely there won't be any model.


private static readonly bool _debugMode = true;
    

Next, we define SentimentAnalyst which we are going to use, and assign model path information to it according to "debug" value.


  private SentimentAnalyst _sentimentAnalyst;
       
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            var modelDataFile = Path.Combine(GetParentDirectory(),
                _debugMode
                    ? $@"Movie Reviews\\Movie Reviews\\bin\\{"Debug"}\\Data"
                    : $@"Movie Reviews\\Movie Reviews\\bin\\{"Release"}\\Data",
                "model.zip");
            SetColors();
            _sentimentAnalyst = new SentimentAnalyst(null, modelDataFile);
            _sentimentAnalyst.LoadTrainedModel();
        }
    

On the main screen, we have some helper functions, let's define them too, those functions are going to help us for loading user reviews, getting parent directory and setting some UI elements colors.


#region Helpers
       
         //Load user reviews into the list
        private void LoadReviews(IEnumerable reviews)
        {
            var top = 0;
            reviewList.Controls.Clear();
            if(reviews==null) return;
            
            foreach (var review in reviews)
            {
                var data = new Data {Review = review.Review};
                var status = _sentimentAnalyst.Predicate(data);
                var reviewItem = new ReviewItem
                {
                    Width = 485,
                    Top = top,
                    lblTitle = {Text = review.Title},
                    lblDate = {Text = review.Date},
                    lblReview = {Text = review.Review},
                    lblUser = {Text = review.User},
                    lblRank = {Text = $@"{review.Rating}/10"},
                    lblStatus = { Text =   status.PredictionValue==true?"Positive":"Negative" }
                };
                reviewItem.lblStatus.BackColor = status.PredictionValue ?  Color.Green : Color.DarkRed;
                reviewList.Controls.Add(reviewItem);
                top += 180;
            }
        }

        //Gets solution path
        private static string GetParentDirectory()
        {
            var directoryInfo =  Directory.GetParent(Directory.GetCurrentDirectory()).Parent;
            if (directoryInfo?.Parent?.Parent != null)
                return directoryInfo.Parent.Parent
                    .FullName;
            return string.Empty;
        }

        //Set colors for the UI elements
        private void SetColors()
        {
            lblInfo1.ForeColor=Color.FromArgb(170,170,170);
            lblInfo2.ForeColor = Color.FromArgb(170, 170, 170);
            lblInfo3.ForeColor = Color.FromArgb(170, 170, 170);
            lblInfo4.ForeColor = Color.FromArgb(170, 170, 170);
            lblInfo5.ForeColor = Color.FromArgb(170, 170, 170);
            lblInfo6.ForeColor = Color.FromArgb(170, 170, 170);
            lblInfo7.ForeColor = Color.FromArgb(170, 170, 170);
            lblInfo8.ForeColor = Color.FromArgb(170, 170, 170);
            lblInfo9.ForeColor = Color.FromArgb(170, 170, 170);
            lblInfo10.ForeColor = Color.FromArgb(170, 170, 170);
            lblTitle.ForeColor = Color.FromArgb(170, 170, 170);
            lblYear.ForeColor = Color.FromArgb(170, 170, 170);
            lblReleased.ForeColor = Color.FromArgb(170, 170, 170);
            lblRated.ForeColor = Color.FromArgb(170, 170, 170);
            lblRuntime.ForeColor = Color.FromArgb(170, 170, 170);
            lblGenre.ForeColor = Color.FromArgb(170, 170, 170);
            lblLanguage.ForeColor = Color.FromArgb(170, 170, 170);
            lblCountry.ForeColor = Color.FromArgb(170, 170, 170);
            imdbRating.ForeColor = Color.FromArgb(170, 170, 170);
            lblInfo11.ForeColor = Color.FromArgb(170, 170, 170);
            lblPlot.ForeColor = Color.FromArgb(170, 170, 170);
        }

        #endregion Helpers
    

so, as a final, we are going to write code for btnAnalyze's click event, this function checks if the user enters any Movie Name if there is a movie name it calls helper class which we defined above to get general information like title, yea, released date, etc.

And after that, it calls WebHelper again to get movie reviews and pass them as parameters to another helper class which is "LoadReviews" for creating and loading user reviews as user controls on the interface.


   private void btnAnalyze_Click(object sender, EventArgs e)
        {
            if (txtMoviewName.Text == string.Empty)
            {
                MessageBox.Show(this, "Please type a movie name", "Warning",  MessageBoxButtons.OK, MessageBoxIcon.Warning);
                txtMoviewName.Focus();
                return;
            }
            var movieInfo= WebHelper.GetMovieGeneralInfo(txtMoviewName.Text);
            if (movieInfo.Response)
            {
                lblTitle.Text = movieInfo.Title;
                lblYear.Text = movieInfo.Year;
                lblReleased.Text = movieInfo.Released;
                lblRated.Text = $@"{movieInfo.Rated}/10";
                lblRuntime.Text = movieInfo.Runtime;
                lblGenre.Text = movieInfo.Genre;
                lblPlot.Text = movieInfo.Plot;
                lblLanguage.Text = movieInfo.Language;
                lblCountry.Text = movieInfo.Country;
                imdbRating.Text = movieInfo.imdbRating;
                lblInfo11.Text = $@"{movieInfo.imdbRating}/10";
                if (movieInfo.Poster != null)
                    if(movieInfo.Poster!= "N/A")
                        moviePicture.Load(movieInfo.Poster);
                //Get movie reviews
                LoadReviews(WebHelper.GetMovieReviews(movieInfo.ReviewsPageUrl));
            }
            else
            {
                lblTitle.Text = string.Empty;
                lblYear.Text = string.Empty;
                lblReleased.Text = string.Empty;
                lblRated.Text = string.Empty;
                lblRuntime.Text = string.Empty;
                lblGenre.Text = string.Empty;
                lblPlot.Text = string.Empty;
                lblLanguage.Text = string.Empty;
                lblCountry.Text = string.Empty;
                imdbRating.Text = string.Empty;
                moviePicture.Image = null;
                reviewList.Controls.Clear();
                MessageBox.Show(this, "Cannot find the movie, please check the  name and try again.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                txtMoviewName.Focus();
            }
}
    

   private void btnAnalyze_Click(object sender, EventArgs e)
        {
            if (txtMoviewName.Text == string.Empty)
            {
                MessageBox.Show(this, "Please type a movie name", "Warning",  MessageBoxButtons.OK, MessageBoxIcon.Warning);
                txtMoviewName.Focus();
                return;
            }
            var movieInfo= WebHelper.GetMovieGeneralInfo(txtMoviewName.Text);
            if (movieInfo.Response)
            {
                lblTitle.Text = movieInfo.Title;
                lblYear.Text = movieInfo.Year;
                lblReleased.Text = movieInfo.Released;
                lblRated.Text = $@"{movieInfo.Rated}/10";
                lblRuntime.Text = movieInfo.Runtime;
                lblGenre.Text = movieInfo.Genre;
                lblPlot.Text = movieInfo.Plot;
                lblLanguage.Text = movieInfo.Language;
                lblCountry.Text = movieInfo.Country;
                imdbRating.Text = movieInfo.imdbRating;
                lblInfo11.Text = $@"{movieInfo.imdbRating}/10";
                if (movieInfo.Poster != null)
                    if(movieInfo.Poster!= "N/A")
                        moviePicture.Load(movieInfo.Poster);
                //Get movie reviews
                LoadReviews(WebHelper.GetMovieReviews(movieInfo.ReviewsPageUrl));
            }
            else
            {
                lblTitle.Text = string.Empty;
                lblYear.Text = string.Empty;
                lblReleased.Text = string.Empty;
                lblRated.Text = string.Empty;
                lblRuntime.Text = string.Empty;
                lblGenre.Text = string.Empty;
                lblPlot.Text = string.Empty;
                lblLanguage.Text = string.Empty;
                lblCountry.Text = string.Empty;
                imdbRating.Text = string.Empty;
                moviePicture.Image = null;
                reviewList.Controls.Clear();
                MessageBox.Show(this, "Cannot find the movie, please check the  name and try again.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                txtMoviewName.Focus();
            }
}
    

Wait a minute, where is the ML.Net here?

It is a very good question, actually as we already talked we defined all process in the SentimentAnalyst project, here in the one of our helper, we just ask to SentimentAnalyst to make predictions for us according to the trained model.

Here, inside to LoadReviews helper function, we called for prediction for each Movie Review.


    var status = _sentimentAnalyst.Predicate(data);
    

It is a very good question, actually as we already talked we defined all process in the SentimentAnalyst project, here in the one of our helper, we just ask to SentimentAnalyst to make predictions for us according to the trained model.

Here, inside to LoadReviews helper function, we called for prediction for each Movie Review.

That's all for today about Sentiment Analysis Project, I hope you found something useful and you learned something. Next, we are going to talk and build an example application about Multi-Classification.