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.

 
And the second 3rd Part tool which we are going to use is Metro Set UI, this is our key tool to develop better interface.
 
Please install Html Agility Pack and Metro Set UI from Nuget Package Manager.
 
Make sure that you select "Browse" tab, and then find the Html Agility Pack and Metro Set UI packages as shown below, and install it to the project we just created.

 
Please install Html Agility Pack as shown below.


Please install Metro Set UI as shown below.


If you completed the installations, please get free API account from OMDB API we are going to get general information about movies by using this API. Please save your API Key, we will need it for calling API.

 
After installing 3rd party tools and getting free API account, we are ready to continue for coding. We are going to create some classes which we are going to use as a helper for our example project, general structure will be as shown below when we finish creating helper classes and control.


 
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<HtmlNode> 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<Rating> 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; }
    }

after we create those models and helper classes, we can jump to design our main screen. The design and layout will be as you see below, there is nothing complicated, simple as possible as simple.

We use Metro Set UI here for the UI components. it is very easy to implement Metro Set UI to your project. You can check from the source code or you can check it from Metro Set UI's website.


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<ReviewsModel> 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();
            }
}

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);

When we run the application and write a movie name and then click "Start Analyze", we got results like below which each user review has "Green" or "Red" color with "Positive" or "Negative" label.

 
When you read the user reviews you see that ML.Net works really very good and gives very accurate predictions.










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.



 

Recommended Articles

Check out some recommended articles based on the article which you read

K-Nearest Neighbor
K-Nearest Neighbor

K-Nearest Neighbor

Classification

K-Nearest Neighbor is the one of the well-known and easy machine algorithm ...

K-Nearest Neighbor

K-Nearest Neighbor is the one of the well-known and easy machine algorithm which is very suitable for a lot of real world problems such product recommendation, social media friend recommendation based on interest or social network of person.

Naive Bayes
Naive Bayes

Naive Bayes

Classification

Naive Bayes Algorithm is the algorithm which makes machines to be able to m...

Naive Bayes

Naive Bayes Algorithm is the algorithm which makes machines to be able to make predictions about the events which they don't have any knowledge about only by looking priors knowledge.

Sentiment Analysis Part 1
Sentiment Analysis Part 1

Sentiment Analysis Part 1

Classification

The super strong wind of the Machine Learning is turning our heads incredib...

Sentiment Analysis Part 1

The super strong wind of the Machine Learning is turning our heads incredibly, we see an example of the machine learning usage almost every area of the technology. Face detection, voice recognition, text recognition, etc. there are plenty of them, and each of them has a different type of approach to machine learning.

Sentiment Analysis Part 2
Sentiment Analysis Part 2

Sentiment Analysis Part 2

Classification

In the previous article, we finished the first part of our example project,...

Sentiment Analysis Part 2

In the previous article, we finished the first part of our example project, now we have SentimentAnalyst class which we can use for training data and making a prediction by passing real data to it. Today we are going to work on Part 2 which is going to be a trainer project, the project which is going to handle the training process.