Quantcast
Channel: Planet Python
Viewing all 22645 articles
Browse latest View live

Robin Wilson: I give talks – on science, programming and more

$
0
0

The quick summary of this post is: I give talks. You might like them. Here are some details of talks I’ve done. Feel free to invite me to speak to your group – contact me at robin@rtwilson.com. Read on for more details.

I enjoy giving talks on a variety of subjects to a range of groups. I’ve mentioned some of my programming talks on my blog before, but I haven’t mentioned anything about my other talks so far. I’ve spoken at amateur science groups (Cafe Scientifique or U3A science groups and similar), programming conferences (EuroSciPy, PyCon UK etc), schools (mostly to sixth form students), unconferences (including short talks made up on the day) and at academic conferences.

Feedback from audiences has been very good. I’ve won the ‘best talk’ prize at a number of events including the Computational Modelling Group at the University of Southampton, the Student Conference on Complexity Science, and EuroSciPy. A local science group recently wrote:

“The presentation that Dr Robin Wilson gave on Complex systems in the world around us to our Science group was excellent. The clever animated video clips, accompanied by a clear vocal description gave an easily understood picture of the underlining principles involved. The wide range of topics taken from situations familiar to everyone made the examples pertinent to all present and maintained their interest throughout. A thoroughly enjoyable and thought provoking talk.”

A list of talks I’ve done, with a brief summary for each talk, is at the end of this post. I would be happy to present any of these talks at your event – whether that is a science group, a school Geography class, a programming meet-up or something else appropriate. Just get in touch on robin@rtwilson.com.

Science talks

All of these are illustrated with lots of images and videos – and one even has live demonstrations of complex system models. They’re designed for people with an interest in science, but they don’t assume any specific knowledge – everything you need is covered from the ground up.

Monitoring the environment from space

Hundreds of satellites orbit the Earth every day, collecting data that is used for monitoring almost all aspects of the environment. This talk will introduce to you the world of satellite imaging, take you beyond the ‘pretty pictures’ to the scientific data behind them, and show you how the data can be applied to monitor plant growth, air pollution and more.

From segregation to sand dunes: complex systems in the world around us

‘Complex’ systems are all around us, and are often difficult to understand and control. In this talk you will be introduced to a range of complex systems including segregation in cities, sand dune development, traffic jams, weather forecasting, the cold war and more – and will show how looking at these systems in a decentralised way can be useful in understanding and controlling them. I’m also working on a talk for a local science and technology group on railway signalling, which should be fascinating. I’m happy to come up with new talks in areas that I know a lot about – just ask.

Programming talks

These are illustrated with code examples, and can be made suitable for a range of events including local programming meet-ups, conferences, keynotes, schools and more.

Writing Python to process millions of row of mobile data – in a weekend

In April 2105 there was a devastating earthquake in Nepal, killing thousands and displacing hundreds of thousands more. Robin Wilson was working for the Flowminder Foundation at the time, and was given the task of processing millions of rows of mobile phone call records to try and extract useful information on population displacement due to the disaster. The aid agencies wanted this information as quickly as possible – so he was given the unenviable task of trying to produce preliminary outputs in one bank-holiday weekend… This talk is the story of how he wrote code in Python to do this, and what can be learnt from his experience. Along the way he’ll show how Python enables rapid development, introduce some lesser-used built-in data structures, explain how strings and dictionaries work, and show a slightly different approach to data processing.

xarray: the power of pandas for multidimensional arrays

“I wish there was a way to easily manipulate this huge multi-dimensional array in Python…”, I thought, as I stared at a huge chunk of satellite data on my laptop. The data was from a satellite measuring air quality – and I wanted to slice and dice the data in some supposedly simple ways. Using pure numpy was just such a pain. What I wished for was something like pandas – with datetime indexes, fancy ways of selecting subsets, group-by operations and so on – but something that would work with my huge multi-dimensional array.

The solution: xarray – a wonderful library which provides the power of pandas for multi-dimensional data. In this talk I will introduce the xarray library by showing how just a few lines of code can answer questions about my data that would take a lot of complex code to answer with pure numpy – questions like ‘What is the average air quality in March?’, ‘What is the time series of air quality in Southampton?’ and ‘What is the seasonal average air quality for each census output area?’.

After demonstrating how these questions can be answered easily with xarray, I will introduce the fundamental xarray data types, and show how indexes can be added to raw arrays to fully utilise the power of xarray. I will discuss how to get data in and out of xarray, and how xarray can use dask for high-performance data processing on multiple cores, or distributed across multiple machines. Finally I will leave you with a taster of some of the advanced features of xarray – including seamless access to data via the internet using OpenDAP, complex apply functions, and xarray extension libraries.

recipy: effortless provenance in Python

Imagine the situation: You’ve written some wonderful Python code which produces a beautiful output: a graph, some wonderful data, a lovely musical composition, or whatever. You save that output, naturally enough, as awesome_output.png. You run the code a couple of times, each time making minor modifications. You come back to it the next week/month/year. Do you know how you created that output? What input data? What version of your code? If you’re anything like me then the answer will often, frustratingly, be “no”.

This talk will introduce recipy, a Python module that will save you from this situation! With the addition of a single line of code to the top of your Python files, recipy will log each run of your code to a database, keeping track of all of your input files, output files and the code that was used – as well as a lot of other useful information. You can then query this easily and find out exactly how that output was created.

In this talk you will hear how to install and use recipy and how it will help you, how it hooks into Python and how you can help with further development.

 

School talks/lessons

Decentralised systems, complexity theory, self-organisation and more

This talk/lesson is very similar to my complex systems talk described above, but is altered to make it more suitable for use in schools. So far I have run this as a lesson in the International Baccalaureate Theory of Knowledge (TOK) course, but it would also be suitable for A-Level students studying a wide range of subjects.

GIS/Remote sensing for geographers

I’ve run a number of lessons for sixth form geographers introducing them to the basics of GIS and remote sensing. These topics are often included in the curriculum for A-Level or equivalent qualifications, but it’s often difficult to teach them without help from outside experts. In this lesson I provide an easily-understood introduction to GIS and remote sensing, taking the students from no knowledge at all to a basic understanding of the methods involved, and then run a discussion session looking at potential uses of GIS/RS in topics they have recently covered. This discussion session really helps the content stick in their minds and relates it to the rest of their course.

Computing

As an experienced programmer, and someone with formal computer science education, I have provided input to a range of computing lessons at sixth-form level. This has included short talks and part-lessons covering various programming topics, including examples of ‘programming in the real world’ and discussions on structuring code for larger projects. Recently I have provided one-on-one support to A-Level students on their coursework projects, including guidance on code structure, object-oriented design, documentation and GUI/backend interfaces.


"Morphex's Blogologue": Adding (mandatory) SMTP authentication to my surveil app

$
0
0
So, I was thinking a bit lately, about adding a small feature to my surveillance app, so that it would send a mail whenever it was started.

Went about to add that this evening, when I discovered that there were large gaps in time between mailed videos in my Surveillance folder/label.

After some searching and debugging, I found that the SMTP server (GMail's in this case), was rejecting emails from my IP. I guess that's just an automated response, when over time, I sent emails to myself, morphex@gmail.com, from myself and at some point that gets flagged as spam, because I wasn't logged in before sending emails.

Anyway, I guess it was naive to try to run something without logging into an SMTP with spam being what it is, so I added (mandatory) support for logging into the outgoing SMTP server today.

In addition to this, I guess it is nice to have the ability to reboot the host regularly, as a host system might for some reason become bogged down. So I added a config option to specify the amount of time between each reboot, in config.py:

https://github.com/morphex/surveil/blob/d1ff83091d9f33533ce9...

I think the config file is quite neat, because Python is neat; one can add config options, and even some logic to deal with config options in one file, and it seems natural.

At the end of the config file as it was on that commit, there is a statement that adds 5-60 minutes of delay to the reboot interval specified above - so that it is a bit harder to predict when a surveillance camera is rebooted.

"Morphex's Blogologue": Taking a look at my Python surveil(lance) app

$
0
0
So, I created this surveillance app in Python, to surveil (https://github.com/morphex/surveil) the room where I spend most of my time, just to make sure that nobody else visits it, without my approval.

Before I wrote this app, I saw there were different applications out there, that could do some sort of surveillance, but I guess I recognized early on that I could easily mail images to myself, and that this was a good approach as it kind of disconnects the surveil app from outside dependencies, at least it doesn't have to have an internet connection up absolutely all the time, to function.

Another feature of mailing myself images compiled into videos, is that as soon as it comes into (in my case) GMail's system, there is a record of the video, and it is because of that, difficult to manipulate data, when a mail has been delivered.

Python was the language of choice, because I wanted to make things easy for myself, and Python is the language I've worked with the most, and it is easy to read and write things in Python.

Before this, I had dabbled a bit with ffmpeg, playing around with videos, adding effects to them and so on.

I'd say fortunately I dabbled with ffmpeg, because it is quite a powerful video processing package. The command line is not intuitive and user friendly, but once the command line is right, ffmpeg seems to be pretty stable.

I read about Python and the GIL (Global Interpreter Lock) today, and I remember this from years back. I guess over 90% of the programming work I've done to date, is in Python and Zope 2. Zope 2 had a way around the GIL and exploiting all CPU cores, and that was running a main database server, and then having several database clients/applications on each of their on process, which effectively went around the GIL, as each application was one process to the operating system. This worked well as the system was built for few writes to the database and many reads.

So, fortunately I dabbled with ffmpeg before this project, because I soon realized that threading could be a bit of a headache, so I opted for running ffmpeg as a subprocess of the surveil app, and using shell scripting and files to pass messages; so when ffmpeg is done processing a set of images into a video, it creates a file, and a thread with a loop in the surveil app monitors for these files, then mails the video and deletes the files related to that video afterwards.

Python is simple and intuitive, and that's the main reason I think, that it became my programming language of choice. It was easy to get started, interesting to explore Python via the interpreter, and there was an advanced web system which used Python as the main programming language.

Years back, I was pretty much obsessed with cross-platform portability, but with this surveil app, I'm creating a temporary (RAM-based) file system which is Linux-specific, and I'd be happy if it runs on *nix/POSIX systems. It's all in keeping things simple.

So I guess the point of this post was to underline that, yes, Python has a GIL, but in most instances it is a non-issue, and if there are CPU-intensive things, these can be forked out to a command-line tool, or you could even have an extension in C which does the heavy lifting. If I hadn't learnt ffmpeg, I could easily have spent a lot more time writing this app.

Another point that is worth mentioning, is the config.py file for this project, is easy to read, and goes with the Python style of simplicity, and goes one step beyond being a config file, it also has some (easy to understand) logic.

This project is a mix of different systems; Python to glue it all together, *nix file systems and scripting in a middle layer, and the ffmpeg application for the heavy-duty work. As far as I can tell, this is all good and stable, although the surveil app reboots about every 24 hours, so it is hard to know how well it runs with a 100% uptime.

Mike Driscoll: Creating Jupyter Notebook Widgets with interact

$
0
0

The Jupyter Notebook has a feature known as widgets. If you have ever created a desktop user interface, you may already know and understand the concept of widgets. They are basically the controls that make up the user interface. In your Jupyter Notebook you can create sliders, buttons, text boxes and much more.

We will learn the basics of creating widgets in this chapter. If you would like to see some pre-made widgets, you can go to the following URL:

These widgets are Notebook extensions that can be installed in the same way that we learned about in my Jupyter extensions article. They are really interesting and well worth your time if you’d like to study how more complex widgets work by looking at their source code.


Getting Started

To create your own widgets, you will need to install the ipywidgets extension.

Installing with pip

Here is how you would install the widget extension with pip:

pip install ipywidgets
jupyter nbextension enable --py widgetsnbextension

If you are using virtualenv, you may need to use the --sys-prefix option to keep your environment isolated.

Installing with conda

Here is how you would install the widgets extension with conda:

conda install -c conda-forge ipywidgets

Note that when installing with conda, the extension will be automatically enabled.


Learning How to Interact

There are a number of methods for creating widgets in Jupyter Notebook. The first and easiest method is by using the interact function from ipywidgets.interact which will automatically generate user interface controls (or widgets) that you can then use to explore your code and interact with data.

Let’s start out by creating a simple slider. Start up a new Jupyter Notebook and enter the following code into the first cell:

from ipywidgets import interact
 
def my_function(x):
    return x
 
# create a slider
interact(my_function, x=20)

Here we import the interact class from ipywidgets. Then we create a simple function called my_function that accepts a single argument and then returns it. Finally we instantiate interact by passing it a function along with the value that we want interact to pass to it. Since we passed in an integer (i.e. 20), the interact class will automatically create a slider.

Try running the cell that contains the code above and you should end up with something that looks like this:

That was pretty neat! Try moving the slider around with your mouse. If you do, you will see that the slider updates interactively and the output from the function is also automatically updated.

You can also create a FloatSlider by passing in floating point numbers instead of integers. Give that a try to see how it changes the slider.

Checkboxes

Once you are done playing with the slider, let’s find out what else we can do with interact. Add a new cell in the same Jupyter Notebook with the following code:

interact(my_function, x=True)

When you run this code you will discover that interact has created a checkbox for you. Since you set “x” to **True**, the checkbox is checked. This is what it looked like on my machine:

You can play around with this widget as well by just checking and un-checking the checkbox. You will see its state change and the output from the function call will also get printed on-screen.

Textboxes

Let’s change things up a bit and try passing a string to our function. Create a new cell and enter the following code:

interact(my_function, x='Jupyter Notebook!')

When you run this code, you will find that interact generates a textbox with the string we passed in as its value:

Try editing the textbox’s value. When I tried doing that, I saw that the output text also changed.

Comboboxes / Drop-downs

You can also create a combobox or drop-down widget by passing a list or a dictionary to your function in interact. Let’s try passing in a list of tuples and see how that behaves. Go back to your Notebook and enter the following code into a new cell:

languages = [('Python', 'Rocks!'), ('C++', 'is hard!')]
interact(my_function, x=languages)

When you run this code, you should see “Python” and “C++” as items in the combobox. If you select one, the Notebook will display the second element of the tuple to the screen. Here is how my mine rendered when I ran this example:

If you’d like to try out a dictionary instead of a list, here is an example:

languages = {'Python': 'Rocks!', 'C++': 'is hard!'}
interact(my_function, x=languages)

The output of running this cell is very similar to the previous example.


More About Sliders

Let’s back up a minute so we can talk a bit more about sliders. We can actually do a bit more with them than I had originally let on. When you first created a slider, all you needed to do was pass our function an integer. Here’s the code again for a refresher:

from ipywidgets import interact
 
def my_function(x):
    return x
 
# create a slider
interact(my_function, x=20)

The value, 20, here is technically an abbreviation for creating an integer-valued slider. The code:

interact(my_function, x=20)

is actually the equivalent of the following:

interact(my_function, x=widgets.IntSlider(value=20))

Technically, Bools are an abbreviation for Checkboxes, lists / dicts are abbreviations for Comboboxes, etc.

Anyway, back to sliders again. There are actually two other ways to create integer-valued sliders. You can also pass in a tuple of two or three items:

  • (min, max)
  • (min, max, step)

This allows us to make the slider more useful as now we get to control the min and max values of the slider as well as set the step. The step is the amount of change to the slider when we change it. If you want to set an initial value, then you need to change your code to be like this:

def my_function(x=5):
    return x
 
interact(my_function, x=(0, 20, 5))

The x=5 in the function is what sets the initial value. I personally found that a little counter-intuitive as the IntSlider itself appears to be defined to work like this:

IntSlider(min, max, step, value)

The interact class does not instantiate IntSlider the same way that you would if you were creating one yourself.

Note that if you want to create a FloatSlider, all you need to do is pass in a float to any of the three arguments: min, max or step. Setting the function’s argument to a float will not change the slider to a FloatSlider.


Using interact as a decorator

The interact class can also be used as a Python decorator. For this example, we will also add a second argument to our function. Go back to your running Notebook and add a new cell with the following code:

from ipywidgets import interact
 
@interact(x=5, y='Python')def my_function(x, y):
    return(x, y)

You will note that in this example, we do not need to pass in the function name explicitly. In fact, if you did so, you would see an error raised. Decorators call functions implicitly. The other item of note here is that we are passing in two arguments instead of one: an integer and a string. As you might have guessed, this will create a slider and a text box respectively:

As with all the previous examples, you can interact with these widgets in the browser and see their outputs.


Fixed Arguments

There are many times where you will want to set one of the arguments to a set or fixed value rather than allowing it to be manipulated through a widget. The Jupyter Notebook ipywidgets package supports this via the fixed function. Let’s take a look at how you can use it:

from ipywidgets import interact, fixed
 
@interact(x=5, y=fixed('Python'))def my_function(x, y):
    return(x, y)

Here we import the fixed function from ipywidgets. Then in our interact decorator, we set the second argument as “fixed”. When you run this code, you will find that it only creates a single widget: a slider. That is because we don’t want or need a widget to manipulate the second argument.

In this screenshot, you can see that we have just the one slider and the output is a tuple. If you change the slider’s value, you will see just the first value in the tuple change.


The interactive function

There is also a second function that is worth covering in this chapter that is called interactive. This function is useful during those times when you want to reuse widgets or access the data that is bound to said widgets. The biggest difference between interactive and interact is that with interactive, the widgets are not displayed on-screen automatically. If you want the widget to be shown, then you need to do so explicitly.

Let’s take a look at a simple example. Open up a new cell in your Jupyter Notebook and enter the following code:

from ipywidgets import interactive
 
def my_function(x):
    return x
 
widget = interactive(my_function, x=5)type(widget)

When you run this code, you should see the following output:

ipywidgets.widgets.interaction.interactive

But you won’t see a slider widget like you did had you used the interact function. Just to demonstrate, here is a screenshot of what I got when I ran the cell:

If you’d like the widget to be shown, you need to import the display function. Let’s update the code in the cell to be the following:

from ipywidgets import interactive
from IPython.displayimport display
 
def my_function(x):
    return x
 
widget = interactive(my_function, x=5)
display(widget)

Here we import the display function from IPython.display and then we call it at the end of the code. When I ran this cell, I got the slider widget:

Why is this helpful? Why wouldn’t you just use interact instead of jumping through extra hoops? Well the answer is that the interactive function gives you additional information that interact does not. You can access the widget’s keyword arguments and its result. Add the following two lines to the end of the cell that you just edited:

print(widget.kwargs)print(widget.result)

Now when you run the cell, it will print out the arguments that were passed to the function and the return value (i.e. result) of calling the function.


Wrapping Up

We learned a lot about Jupyter Notebook widgets in this chapter. We covered the basics of using the `interact` function as well as the `interactive` function. However there is more that you can about these functions by checking out the documentation.

Even this is just scratching the surface of what you can do with widgets in Jupyter. In the next chapter we will dig into creating widgets by hand outside of using the interact / interactive functions we learned about this in this chapter. We will learn much more about how widgets work and how you can use them to make your Notebooks much more interesting and potentially much more powerful.


Related Reading

Wallaroo Labs: Introducing Connectors: Wallaroo’s Window to the World

$
0
0
Introduction We’re excited today to introduce you to a preview release of a new Wallaroo feature: Connectors. Connectors make inputting and receiving data from Wallaroo even easier. In this post, we’ll briefly go over what Wallaroo is, the role connectors now play as Sources and Sinks for Wallaroo, how to get started with connectors, and talk about what is coming next. If you’re familiar with what Wallaroo is feel free to skip the next section.

Codementor: 5 Steps to Prepare for a Data Science Job

$
0
0
What are the key steps in preparing for a data science job? The basic things you absolutely must do? We answer all the these question in this article.

Codementor: Skills Series: A Beginner’s Guide to Python

$
0
0
Start wPython is the ?1 most prevalent programming dialect utilized by information examiners, information researchers, and programming designers to mechanize forms, assemble the usefulness of...

Catalin George Festila: Python Qt5 - toolbar example.

$
0
0
This is a simple example with PyQt5 python module and python 3.6.4 version.
The example is about how to create a toolbar with PyQt5.
The base of this source code is the create a default window application.
I create a toolbar and I add an action to this toolbar.
The name of the toolbar is my_toolbar.
The action is named one_action.
This action is linked to a python function named action_one.
I add to my source code another function named alert.
This is good for debugging part to handle with errors and displaying alerts.
Let's see the source code:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys

class MainWindow(QMainWindow):

def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.status = QStatusBar()
self.setStatusBar(self.status)
my_toolbar = QToolBar("toolbar")
my_toolbar.setIconSize(QSize(48, 48))
self.addToolBar(my_toolbar)

one_action = QAction(QIcon(), "Action one", self)
one_action.setStatusTip("Action one on toolbar")
one_action.triggered.connect(self.action_one)
my_toolbar.addAction(one_action)

self.setWindowTitle("Window PyQt5 - 001")
self.show()

def action_one(self):
print("Action one")

def alert(self, s):
"""
This handle errors and displaying alerts.
"""
err = QErrorMessage(self)
err.showMessage(s)

if __name__ == '__main__':

app = QApplication(sys.argv)
app.setApplicationName("Window PyQt5 - 001")

window = MainWindow()
app.exec_()

Continuum Analytics Blog: Patching Source Code to Conda Build Recipes

$
0
0

By Casey Clements and Michael Sarahan If you are a developer who relies upon conda, we hope to encourage you to begin building your own packages so that your projects can be used just like all of the other packages you rely upon. The success of Anaconda rests upon the ease to search for, install, …
Read more →

The post Patching Source Code to Conda Build Recipes appeared first on Anaconda.

Mike Driscoll: Working with Jupyter Notebook Widgets

$
0
0

What are Jupyter widgets? A widget is an “eventful python object” that in the case of Jupyter Notebook, resides in the browser and is a user interface element, such as a slider or textbox. Jupyter supports a fairly wide array of widgets including the following:

  • Numeric
  • Boolean
  • Selection
  • String
  • Image
  • Button
  • Output
  • Animation
  • Date picker
  • Color picker
  • Controller (i.e. game controller)
  • Layout

We won’t cover every type of widget in this article, but we will get to see a fairly wide range of them in action. For a full list you can check out the documentation. Or you can run the following code in your Notebook:

import ipywidgets as widgets
 
print(dir(widgets))

If you have ever created a user interface in the past, then you probably already understand what a widget is. If you haven’t, then let’s just take a second to define what they are for.

A widget is used to create an interactive graphical user interface for your user. The widgets synchronize stateful and stateless information between Python and Javascript.

We looked briefly at how to create widgets using the interact and interactive functions in the previous article. In this tutorial we will create the widgets directly and learn more about how they work.


Creating a Widget

A widget can be created really easily. Let’s create a simple slider for demonstration purposes. Create a new Jupyter Notebook and put the following code into a cell:

import ipywidgets as widgets
 
widgets.IntSlider()

When you run the cell, you should see something like this:

As you can see, the default value is zero for the slider. If you want to save the widget to a variable, then you will need to tell Jupyter to show the widget using the display function:

import ipywidgets as widgets
from IPython.displayimport display
 
btn = widgets.Button(description='Press Me')
display(btn)

Here we add the extra import that we need to display a widget and we create a simple Button. Note that we had to specify its description so that the button would have a label. Finally we called the display function and passed it the widget object. The result looks like this:

Now let’s back up a second and create that slider again, but this time around we will use display to show it twice:

import ipywidgets as widgets
from IPython.displayimport display
 
btn = widgets.IntSlider()
display(btn)
display(btn)

If you put the code above into a cell in your Notebook and run it, you should see two sliders:

Try moving around one of the sliders. You will quickly discover that when you move one slider, the other slider also moves. The reason that this happens is that we only create ONE slider object. The actual visible widgets are both pointing to the same object underneath, so when you move one you are effectively moving the other. You can think of these two sliders are two views to the same object. Of course, if you had created two IntSliders and assigned them to unique variable names, then you could move them independently of each other.


Closing a Widget

You can close a widget by calling its close() method. If you want to remove the widget, just clear the cell.


Properties and Keys of a Widget

Jupyter widgets follow a set of rules for their properties. If you want to get a full listing of widgets properties and methods, you can use Python’s dir() function to introspect the widget for you:

dir(btn)

Let’s say you put the code above in a new cell just below the cell that contains the code for your `IntSlider` widget. If you ran this new cell, you would get output like this:

Try adjusting the slider to something above zero. Then create a new cell and enter the following code:

btn.value

When you run this, it will print out the current value of the slider. Widgets also have keys, which are such things as `description`, `min`, and `max` and `disabled`. For the full list of a widget’s keys, you can consult the documentation for the widget, read the source or just run this:

btn.keys

Also worth mentioning is that you can also set a property to a new value. So if you wanted to set the slider to a new value and add a description, you could do the following:

btn.value = 50
btn.description = 'Hello slider'

If you run this code, you will see the slider update its value and description.

Widgets also support setting properties when you instantiate the widget. For example, if we wanted to, we could set a few of our Slider’s properties when we create it:

import ipywidgets as widgets
from IPython.displayimport display
 
btn = widgets.IntSlider(value=10, description='My Slider',
                        orientation='vertical')
display(btn)

When you run this code, you will see that the widget looks a bit different:


Linking Two Widgets

Some Jupyter widgets can be linked together. For example, you can link a FloatText widget to a FloatSlider widget. What this means is that when one widget is updated, the other will be too. This is known as “synchronization of attributes”. Let’s take a look at a simple example:

text = widgets.FloatText()
slider = widgets.FloatSlider()
display(text,slider) 
mylink = widgets.jslink((text, 'value'), (slider, 'value'))

Here we create two widgets and display them both. Then we call widgets.jslink and tell it to link the value of the text and slider together. When you use the jslink method, you are linking the widgets from the client side. This makes the widgets sync up more robustly because they will be using the local machine’s hardware. You can also use jsdlink, which will create a unidirectional link between two widgets on the client side. What that means is that you can make one of the two widgets affect the second widget whereas the second widget will NOT affect the first.

Try changing the example above to use jsdlink like we do below:

text = widgets.FloatText()
slider = widgets.FloatSlider()
display(text,slider) 
mylink = widgets.jsdlink((text, 'value'), (slider, 'value'))

Now try editing each widget’s value and see how they interact. It’s hard to demonstrate in the book but basically when you change the text widget it will change the slider to match. However if you change the slider’s value, the text control won’t change.

There are also two other methods you can use to link two widgets together:

  • link
  • dlink

These methods work in the same way as jslink and jsdlink respectively, except that they will talk to the server so you might experience some latency when using them. Frankly on your local machine, you probably won’t see much difference between the two.

Finally I want to point out that you can also unlink the widgets by calling the link’s unlink method:

mylink.unlink()

You can try this out by adding this code to the end of the cell where you created the linked widgets. If you do add it and re-run the cell, then you will find that the two widgets are no longer linked together.


Events

Linking widgets is closely related to widget events. An event occurs when you interact with a widget. For example, when you push a button, that is known as a click event. Let’s look at an example of how this might work using a button widget.

import ipywidgets as widgets
from IPython.displayimport display
 
btn = widgets.Button(description='Test')
display(btn) 
 
def my_event_handler(btn_object):
    print('You pressed the {} button!'.format(btn_object.description)) 
btn.on_click(my_event_handler)

Here we import the various bits and pieces we need to use widgets and display them. Then we create a simple button and a function that is named my_event_handler. This is the event handler which is what you would bind your widget’s event to. To actually bind your button’s click event to this handler, you must call on_click and pass it the function that you want it bound to. You will note that the event handler accepts an argument implicitly, which is the button object itself.

This allows you to access the button’s properties, so in this case I went ahead and told it to print out the label of the button. The following is a screenshot of what my code and button looked like when I ran it after pressing the button:

What this also allows you to do is to bind multiple buttons to the same event handler. Here’s how you might do that:

import ipywidgets as widgets
from IPython.displayimport display
 
btn = widgets.Button(description='Test')
other_btn = widgets.Button(description='Other')
display(btn)
display(other_btn) 
def my_event_handler(btn_object):
    print('You pressed {}'.format(btn_object.description)) 
btn.on_click(my_event_handler)
other_btn.on_click(my_event_handler)

In this code, we create a second button called other_btn with a different label and bind it to the same event handler as the other button. Now you can try pressing the each button a few times and see how they behave. Here’s an example session:

You can do this type of event binding with other types of widget events. You will need to look at the documentation for the widget or the source of the widget to figure out what events it supports.

Speaking of the documentation, it also mentions something that Jupyter calls Traitlet events. These are IPython traitlets which basically give you a different method of binding an event to a function using the observe method. To get an idea of how you might use a traitlet, let’s run the following code in our Jupyter Notebook:

print(btn.observe.__doc__)

When I ran this code, I got the following output:

Setup a handler to be called when a trait changes.
 
        Thisis used to setup dynamic notifications of trait changes.
 
        Parameters
        ----------
        handler : callable
            A callable that is called when a trait changes. Its
            signature should be ``handler(change)``, where ``change``is a
            dictionary. The change dictionary at least holds a 'type' key.
            *``type``: the type of notification.
            Other keys may be passed depending on the value of 'type'. In the
            case where typeis'change', we also have the following keys:
            *``owner`` : the HasTraits instance
            *``old`` : the old value of the modified trait attribute
            *``new`` : the new value of the modified trait attribute
            *``name`` : the name of the modified trait attribute.
        names : list, str, All
            If names is All, the handler will apply to all traits.  If a list
            of str, handler will apply to all names in the list.  If a
            str, the handler will apply just to that name.
        type : str, All (default: 'change')
            The type of notification to filter by. If equal to All, then all
            notifications are passed to the observe handler.

So how does this work? Basically you call observe with the name of the function that you want to bind to as well as which traits you want to “observe”. You can pass in a list of strings, set it to “All” or pass in a singular string. The documentation has a good example, but I am going to take it and expand it slightly to become the following:

int_range = widgets.IntSlider()
display(int_range) 
def on_value_change(change):
    print(change)print(change['new']) 
int_range.observe(on_value_change, names='value')

The difference between this and the one you will find in the documentation is that I am printing out the change object too. This will tell us what we are dealing with. In this case, the output when you change the slider looks like this:

{'owner': IntSlider(value=2), 'new': 2, 'old': 0, 'name': 'value', 'type': 'change'}2

That tells us that the change argument is a dictionary of values and that the key new will give us the new value of the slider. So when we tell Jupyter to observe the value string, it will send in a dictionary of values. Interestingly this dictionary also contains the old value of the slider in addition to the new value. This can be useful to know in other widgets if you need to implement something like undo / redo, for example.


Layout

Jupyter widgets also have a layout attribute that they expose which allows you to set a number of CSS properties that you may use to control how widgets are laid out. These properties include size, display, box model, positioning and more. You can use the layout feature to make your widgets change sizes based on the available space.

Let’s look at a simple example:

from ipywidgets import Button, Layout
from IPython.displayimport display
 
layout = Layout(width='50%', height='100px') 
btn = Button(description='(50% width, 100px height) button',
             layout=layout)
display(btn)

Here we import our Button and a Layout. We set up the Layout such that it will make the widget take up 50% of the available width and 100 pixels high. Then we set the button’s layout argument to the layout that we just created. This is what the result looks like:

You can apply layouts by creating a layout instance like we did in the previous example or you can have a widget use another widget’s layout directly. To demonstrate, let’s add the following code to the bottom of the cell that we just created in the previous example:

btn2 = Button(description='Another button', layout=btn.layout)
display(btn2)

Here we set this new button’s layout to btn.layout, which basically makes our new button use the same layout object that the original button is using. You should end up seeing the button’s like this:


Styling

Interestingly, some widgets allow you to put in a description that is too long to be shown.

from ipywidgets import IntSlider
 
IntSlider(description='A really long description')

When you run this code, you will find that Jupyter cuts off some of the text:

You can get around this issue by applying a style. The style to use in this case would look like this:

style = {'description_width': 'initial'}

Oddly enough, the documentation on Jupyter styles is pretty much non-existent. Looking at the source code in widget_int.py, it appears that the value, initial, refers to the initial label width. So when you create a style that has description_width as the key and set it to initial, you are, in effect, telling Jupyter to use the widget label’s string length as its width. To apply the style, you can just do the following:

from ipywidgets import IntSlider
 
style = {'description_width': 'initial'}
IntSlider(description='A really long description', style=style)

And now your widget will look like this:

Of course, the problem with this solution is that now your slider looks too small. We can solve that issue by using a helper function!


Arranging Widgets

There are some helper functions like HBox and VBox that you can use for combining widget and laying them out visually. The HBox will add widgets to it one at a time horizontally. You can think of it as lining up individual pieces in a horizontal line from left-to-right. Let’s use an HBox and a Label widget to solve our problem:

from ipywidgets import HBox, Label, IntSlider
 
label = Label('A really long description')
my_slider = IntSlider() 
HBox([label, my_slider])

Here we import the pieces we need, create a couple of widgets and then add them to our HBox. The result is as follows:

As you might expect, you can use a combination of HBox and VBox layouts to make complex layouts of your widgets. For example, you could add a couple of widgets to one HBox and a couple more to a second HBox and then add both HBoxes to a VBox to create a grid. Let’s go ahead and do that so you can see how that might work.

from ipywidgets import Button, HBox, VBox
 
btn_one = Button(description='One') 
btn_two = Button(description='Two')
btn_three = Button(description='Three')
btn_four = Button(description='Four') 
first_line = HBox([btn_one, btn_two])
second_line = HBox([btn_three, btn_four]) 
VBox([first_line, second_line])

Here we create four buttons and two HBoxes. The HBoxes each hold two buttons apiece. Finally we add the HBoxes to the VBox and this is what we end up with:

in the documentation, so I will leave that one for you to read as an exercise. I also want to mention that there is one other widget for creating layouts called the GridBox.

In my experience using wxPython, a cross-platform desktop user interface toolkit, I have found that using VBox and HBox type containers is more than enough for creating complex layouts of my widgets and the more advanced layout tools tend to make things more difficult unless you are already planning to do a grid-like interface. You are more than welcome to give these other types of layouts a try as they definitely have their place.


Wrapping Up

In this chapter we learned a lot about how to create and use Jupyter Widgets. We also learned how we can display, style and arrange them. There are lots of other things you can learn about widgets on by checking out the documentation on the topic or reading a few tutorials on the web. For example, Jupyter supports the ability for you to create your own custom widgets. It also supports the use of asynchronous widgets. I highly recommend using widgets in your Notebooks to give them some pizazz and to make them more useful too!

Python Anywhere: Today's Upgrades: Always-On Tasks

$
0
0

Always-On Tasks

We are officially live with our always-on tasks! All paying customers will get one always-on task, and you can add more by customizing your plan on our accounts page. Our infrastructure will try to keep your script always running (ie. we will restart it if your script errors and stops etc). We'd love to know what you think- Just drop us a line using the "Feedback" link, or email us at support@pythonanywhere.com!

Logging Improvements

We have also improved user experience working with log files. You can now access our API to delete log files (or wipe the file if your particular log file is currently in use), and we have better formatting in place when logging certain web app errors.

Other Stuff

We also optimized the editor that you can access from the 'Files' tab to make consoles within it start faster and to avoid scripts rerunning unintentionally.

Hynek Schlawack: Testing & Packaging

$
0
0

How to ensure that your tests run code that you think they are running, and how to measure your coverage over multiple tox runs (in parallel!).

Catalin George Festila: Python Qt5 - webcam example.

$
0
0
Today I come with another source code.
This example use QtMultimedia to create use the webcam.
The source code follows the steps from finding, set and use webcam.
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtMultimedia import *
from PyQt5.QtMultimediaWidgets import *

import os
import sys

class MainWindow(QMainWindow):

def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)

self.online_webcams = QCameraInfo.availableCameras()
if not self.online_webcams:
pass #quit
self.exist = QCameraViewfinder()
self.exist.show()
self.setCentralWidget(self.exist)

# set the default webcam.
self.get_webcam(0)
self.setWindowTitle("WebCam")
self.show()

def get_webcam(self, i):
self.my_webcam = QCamera(self.online_webcams[i])
self.my_webcam.setViewfinder(self.exist)
self.my_webcam.setCaptureMode(QCamera.CaptureStillImage)
self.my_webcam.error.connect(lambda: self.alert(self.my_webcam.errorString()))
self.my_webcam.start()

def alert(self, s):
"""
This handle errors and displaying alerts.
"""
err = QErrorMessage(self)
err.showMessage(s)


if __name__ == '__main__':

app = QApplication(sys.argv)
app.setApplicationName("WebCam")

window = MainWindow()
app.exec_()

Stack Abuse: Object Oriented Programming in Python

$
0
0

Introduction

Object-Oriented Programming (OOP) is a programming paradigm where different components of a computer program are modeled after real-world objects. An object is anything that has some characteristics and can perform a function.

Consider a scenario where you have to develop a Formula 1 car racing game using the object-oriented programming approach. The first thing you need to do is to identify real-world objects in the actual Formula 1 race. What are the entities in a Formula 1 race that have some characteristics and can perform any function? One of the obvious answers to this question is the car. A car can have characteristics like engine capacity, make, model, manufacturer, and so on. Similarly, a car can be started, stopped, accelerated and so on. A driver can be another object in a Formula 1 race. A driver has a nationality, age, gender, and so on, and he can perform functionalities like driving the car, moving the steering or changing the transmission.

Just like in this example, in object-oriented programming we will create objects for the corresponding real-world entity.

It is important to mention here that object-oriented programming is not a language-dependent concept. It is a general programming concept and most of the modern languages, such as Java, C#, C++, and Python, support object-oriented programming. In this article, we will see a detailed introduction to Object-Oriented Programming in Python, but before that, we will see some of the advantages and disadvantages of object-oriented programming.

Pros and Cons of OOP

Following are some of the advantages of object-oriented programming:

  • Object-oriented programming fosters reusability. A computer program is written in the form of objects and classes, which can be reused in other projects as well.
  • The modular approach used in object-oriented programming results in highly maintainable code.
  • In object-oriented programming, every class has a specific task. If an error occurs in one part of the code, you can rectify it locally without having to affect other parts of the code.
  • Data encapsulation (which we will study later in the article) adds an extra layer of security to the program developed using the object-oriented approach.

Though object-oriented programming has several advantages as discussed, it has some downsides as well, some of which have been enlisted below:

  • Detailed domain knowledge of the software being developed is needed in order to create objects. Not every entity in software is a candidate for being implemented as an object. It can be hard for newbies to identify this fine line.
  • As you add more and more classes to the code, the size and complexity of the program grows exponentially.

In the next section, we will see some of the most important concepts of object-oriented programming.

As the name suggests, object-oriented programming is all about objects. However, before an object can be created we need to define the class for the object.

Class

A class in object-oriented programming serves as a blueprint for the object. A class can be considered as a map for the house. You can get an idea of what the house looks like by simply seeing the map. However, a class itself is nothing. For instance, a map is not a house, it only explains how the actual house will look.

The relationship between a class and object can be understood by looking at the relationship between a car and an Audi. An Audi is actually a car. However, there is no such thing as a car only. A car is an abstract concept, it is actually implemented in the form of Toyota, Ferrari, Honda, etc.

The keyword class is used in order to create a class in Python. The name of the class follows the class keyword, followed by the colon character. The body of the class starts on a new line, indented one tab from the left.

Let's see how we can create a very basic class in Python. Take a look at the following code:

# Creates class Car
class Car:

    # create class attributes
    name = "c200"
    make = "mercedez"
    model = 2008

    # create class methods
    def start(self):
        print ("Engine started")

    def stop(self):
        print ("Engine switched off")

In the example above, we create a class named Car with three attributes: name, make, and model. The car class also contains two methods: start() and stop().

Objects

Earlier, we said that a class provides a blueprint. However, to actually use the objects and methods of a class, you need to create an object out of that class. There are few class methods and attributes that can be used without an object, which we will see in the later section. For now, just keep in mind that by default, we need to create an object of a class before we can use its methods and attributes.

An object is also called an instance; therefore, the process of creating an object of a class is called instantiation. In Python, to create an object of a class we simply need to write the class name followed by opening and closing parenthesis.

Let's create an object of the Car class that we created in the last section.

# Creates car_a object of Car class
car_a = Car()

# Creates car_b object of car class
car_b = Car()  

In the script above, we created two objects of the car class: car_a and car_b. To check the type of the objects we created, we can use the type method and pass it the name of our object. Execute the following script:

print(type(car_b))  

In the output, you will see:

<class '__main__.Car'>  

Which says that the type of car_b object is a class Car.

At this point we've created our class and the corresponding objects. Now is the time to access class attributes and call class method using the class object. To do so, you simply have to write the object name, followed by dot operator and the name of the attribute or the method that you want to access or call, respectively. Take a look at the following example:

car_b.start()  

In the script above, we call the start() method via the car_b object. The output will be as follows:

Engine started  

Similarly, you can access an attribute using the following syntax:

print(car_b.model)  

In the output, you will see the value of the model attribute, as shown below:

2008  

Attributes

In the previous section, we saw how we can create objects of a class and can use those objects to access the attributes of a class.

In Python, every object has some default attributes and methods in addition to user-defined attributes. To see all the attributes and methods of an object, the built-in dir() function can be used. Let's try to see all the attributes of the car_b object that we created in the last section. Execute the following script:

dir(car_b)  

In the output, you will see the following attributes:

['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'make',
 'model',
 'name',
 'start',
 'stop']

This built-in function is useful for inspecting all of the attributes and functions of an object, especially when used via Python's REPL.

Class vs Instance Attributes

Attributes can be broadly categorized into two types: Class attributes and Instance attributes. Class attributes are shared by all the objects of a class while instance attributes are the exclusive property of the instance.

Remember, an instance is just another name for the object. Instance attributes are declared inside any method while class attributes are declared outside of any method. The following example clarifies the difference:

class Car:

    # create class attributes
    car_count = 0

    # create class methods
    def start(self, name, make, model):
        print ("Engine started")
        self.name = name
        self.make = make
        self.model = model
        Car.car_count += 1

In the script above, we create a class Car with one class attribute car_count and three instance attributes name, make and mode. The class contains one method start() which contains the three instance attributes. The values for the instance attributes are passed as arguments to the start() method. Inside the start method, the car_count attribute is incremented by one.

It is important to mention that inside the method, the instance attributes are referred using the self keyword, while class attributes are referred by the class name.

Let's create an object of the Car class and call the start() method.

car_a = Car()  
car_a.start("Corrola", "Toyota", 2015)  
print(car_a.name)  
print(car_a.car_count)  

In the above script we print the instance attribute name and class attribute car_count. You will see in the output that the car_count attribute will have a value of 1, as shown below:

Engine started  
Corrola  
1  

Now, let's create another object of the car class and call the start() method.

car_b = Car()  
car_b.start("City", "Honda", 2013)  
print(car_b.name)  
print(car_b.car_count)  

Now if you print the value of the car_count attribute, you will see 2 in the output. This is because the car_count attribute is a class attribute and hence it is shared between the instances. The car_a object incremented its value to 1, while car_b object incremented it again, hence the final value became 2. The output looks like this:

Engine started  
City  
2  

Methods

As we described earlier, in object-oriented programming, the methods are used to implement the functionalities of an object. In the previous section, we created start() and stop() methods for the Car class. Till now, we have been using the objects of a class in order to call the methods. However, there is a type of method that can be called directly using the class name. Such a method is called a static method.

Static Methods

To declare a static method, you have to specify the @staticmethod descriptor before the name of the method as shown below:

class Car:

    @staticmethod
    def get_class_details():
        print ("This is a car class")

Car.get_class_details()  

In the above script, we create a class Car with one static method get_class_details(). Let's call this method using the class name.

Car.get_class_details()  

You can see that we did not need to create an instance of the Car class in order to call the get_class_details() method, rather we simply used the class name. It is important to mention that static methods can only access class attributes in Python.

Returning Multiple Values from a Method

One of the best features of the Python language is the ability of class methods to return multiple values. Take a look at the following example:

class Square:

    @staticmethod
    def get_squares(a, b):
        return a*a, b*b

print(Square.get_squares(3, 5))  

In the above script, we created a class named Square with one static method get_squares(). The method takes two parameters; multiply each parameter with itself and returns both the results using return statement. In the output of the script above, you will see the squares of 3 and 5.

The str Method

Till now we have been printing attributes using the print() method. Let's see what happens if we print the object of a class.

To do so we'll create a simple Car class with one method and try to print the object of the class to the console. Execute the following script:

class Car:

    # create class methods
    def start(self):
        print ("Engine started")

car_a = Car()  
print(car_a)  

In the script above we create car_a object of the Car class and print its value on the screen. Basically here we are treating car_a object as a string. The output looks likes this:

<__main__.Car object at 0x000001CCCF4335C0>  

The output shows the memory location where our object is stored. Every Python object has a __str__ method by default. When you use the object as a string, the __str__ method is called, which by default prints the memory location of the object. However, you can provide your own definition for the __str__ method as well. For instance, look at the following example:

# Creates class Car
class Car:

    # create class methods

    def __str__(self):
        return "Car class Object"

    def start(self):
        print ("Engine started")

car_a = Car()  
print(car_a)  

In the script above, we override the __str__ method by providing our own custom definition for the method. Now, if you print the car_a object, you will see the message "Car class Object" on the console. This is the message that we printed inside our custom the __str__ method.

Using this method you can create custom and more meaningful descriptions for when an object is printed. You could even display some of the data within the class, like the name of a Person class.

Constructors

A constructor is a special method that is called by default whenever you create an object of a class.

To create a constructor, you have to create a method with keyword __init__. Take a look at the following example:

class Car:

    # create class attributes
    car_count = 0

    # create class methods
    def __init__(self):
        Car.car_count +=1
        print(Car.car_count)

In the script above, we create a Car class with one class attribute car_count. The class contains a constructor which increments the value of car_count and prints the resultant value on screen.

Now, whenever an object of the Car class will be created the constructor will be called, the value of the car_count will be incremented and displayed on the screen. Let's create a simple object and see what happens:

car_a = Car()  
car_b = Car()  
car_c = Car()  

In the output, you will see a value of 1, 2, and 3 printed since with every object the value of car_count variable is incremented and displayed on the screen.

Except for the name, the constructor can be used as an ordinary method. You can pass and receive values from a constructor. It is usually used in this way when you want to initialize attribute values upon instantiating the class.

Local vs Global Variables

We know that there are two types of Python attributes, instance attributes, and class attributes. The attributes of a class are also referred to as variables. Depending on the scope, variables can also be categorized into two types: Local variables and Global variables.

Local Variables

A local variable in a class is a variable that can only be accessed inside the code block where it is defined. For instance, if you define a variable inside a method, it cannot be accessed anywhere outside that method. Look at the following script:

# Creates class Car
class Car:  
    def start(self):
        message = "Engine started"
        return message

In the script above we create a local variable message inside the start() method of the Car class. Now let's create an object of the Car class and try to access the local variable message as shown below:

car_a = Car()  
print(car_a.message)  

The above script will return the following error:

AttributeError: 'Car' object has no attribute 'message'  

This is because we cannot access the local variable outside the block in which the local variable is defined.

Global Variable

A global variable is defined outside of any code block e.g method, if-statements, etc. A global variable can be accessed anywhere in the class. Take a look at the following example.

# Creates class Car
class Car:  
    message1 = "Engine started"

    def start(self):
        message2 = "Car started"
        return message2

car_a = Car()  
print(car_a.message1)  

In the script above, we created a global variable message1 and printed its value on the screen. In the output, you will see the value of the message1 variable, printed without an error.

It is important to mention that there is a difference between class and instance attributes, and local vs global variables. The class and instance attributes differ in the way they are accessed i.e. using class name and using instance name. On the other hand, local vs global variables differ in their scope, or in other words the place where they can be accessed. A local variable can only be accessed inside the method. Though in this article, both the local variable and instance attributes are defined inside the method, local attribute is defined with the self-keyword.

Access Modifiers

The access modifiers in Python are used to modify the default scope of variables. There are three types of access modifiers in Python: public, private, and protected.

Variables with the public access modifiers can be accessed anywhere inside or outside the class, the private variables can only be accessed inside the class, while protected variables can be accessed within the same package.

To create a private variable, you need to prefix double underscores with the name of the variable. To create a protected variable, you need to prefix a single underscore with the variable name. For public variables, you do not have to add any prefixes at all.

Let's see public, private, and protected variables in action. Execute the following script:

class Car:  
    def __init__(self):
        print ("Engine started")
        self.name = "corolla"
        self.__make = "toyota"
        self._model = 1999

In the script above, we create a simple Car class with a constructor and three variables name, make, and model. The name variable is public while the make and model variables have been declared private and protected, respectively.

Let's create an object of the Car class and try to access the name variable. Execute the following script:

car_a = Car()  
print(car_a.name)  

Since name is a public variable, therefore we can access it outside the class. In the output, you will see the value for the name printed on the console.

Now let's try to print the value of the make variable. Execute the following script:

print(car_a.make)  

In the output, you will see the following error message:

AttributeError: 'Car' object has no attribute 'make'  

We have covered most of the basic object-oriented programming concepts in the last few sections. Now, let's talk about the pillars of the object-oriented programming: Polymorphism, Inheritance, and Encapsulation, collectively referred to as PIE.

Inheritance

Inheritance in object-oriented programming is pretty similar to real-world inheritance where a child inherits some of the characteristics from his parents, in addition to his/her own unique characteristics.

In object-oriented programming, inheritance signifies an IS-A relation. For instance, a car is a vehicle. Inheritance is one of the most amazing concepts of object-oriented programming as it fosters code re-usability.

The basic idea of inheritance in object-oriented programming is that a class can inherit the characteristics of another class. The class which inherits another class is called the child class or derived class, and the class which is inherited by another class is called parent or base class.

Let's take a look at a very simple example of inheritance. Execute the following script:

# Create Class Vehicle
class Vehicle:  
    def vehicle_method(self):
        print("This is parent Vehicle class method")

# Create Class Car that inherits Vehicle
class Car(Vehicle):  
    def car_method(self):
        print("This is child Car class method")

In the script above, we create two classes Vehicle class, and the Car class which inherits the Vehicle class. To inherit a class, you simply have to write the parent class name inside the parenthesis that follows the child class name. The Vehicle class contains a method vehicle_method() and the child class contains a method car_method(). However, since the Car class inherits the Vehicle class, it will also inherit the vehicle_method().

Let's see this in action. Execute the following script:

car_a = Car()  
car_a.vehicle_method() # Calling parent class method  

In the script above, we create an object of the Car class and call the vehicle_method() using that Car class object. You can see that the Car class doesn't have any vehicle_method() but since it has inherited the Vehicle class that contains the vehicle_method(), the car class can also use it. The output looks likes this:

This is parent Vehicle class method  

In Python, a parent class can have multiple children and similarly, a child class can have multiple parent classes. Let's take a look at the first scenario. Execute the following script:

# Create Class Vehicle
class Vehicle:  
    def vehicle_method(self):
        print("This is parent Vehicle class method")

# Create Class Car that inherits Vehicle
class Car(Vehicle):  
    def car_method(self):
        print("This is child Car class method")

# Create Class Cycle that inherits Vehicle
class Cycle(Vehicle):  
    def cycleMethod(self):
        print("This is child Cycle class method")

In the script above the parent Vehicle class is inherited by two child classes Car and Cycle. Both the child classes will have access to the vehicle_method() of the parent class. Execute the following script to see that:

car_a = Car()  
car_a.vehicle_method() # Calling parent class method  
car_b = Cycle()  
car_b.vehicle_method() # Calling parent class method  

In the output, you will see the output of the vehicle_method() method twice as shown below:

This is parent Vehicle class method  
This is parent Vehicle class method  

You can see how a parent class can be inherited by two child classes. In the same way, a child can have multiple parents. Let's take a look at the example:

class Camera:  
    def camera_method(self):
        print("This is parent Camera class method")

class Radio:  
    def radio_method(self):
        print("This is parent Radio class method")

class CellPhone(Camera, Radio):  
     def cell_phone_method(self):
        print("This is child CellPhone class method")

In the script above, we create three classes: Camera, Radio, and CellPhone. The Camera class and the Radio classes are inherited by the CellPhoneclass which means that the CellPhone class will have access to the methods of both Camera and Radio classes. The following script verifies this:

cell_phone_a = CellPhone()  
cell_phone_a.camera_method()  
cell_phone_a.radio_method()  

The output looks likes this:

This is parent Camera class method  
This is parent Radio class method  

Polymorphism

The term polymorphism literally means having multiple forms. In the context of object-oriented programming, polymorphism refers to the ability of an object to behave in multiple ways.

Polymorphism in programming is implemented via method-overloading and method overriding.

Method Overloading

Method overloading refers to the property of a method to behave in different ways depending upon the number or types of the parameters. Take a look at a very simple example of method overloading. Execute the following script:

# Creates class Car
class Car:  
   def start(self, a, b=None):
        if b is not None:
            print (a + b)
        else:
            print (a)

In the script above, if the start() method is called by passing a single argument, the parameter will be printed on the screen. However, if we pass 2 arguments to the start() method, it will add both the arguments and will print the result of the sum.

Let's try with single argument first:

car_a = Car()  
car_a.start(10)  

In the output, you will see 10. Now let's try to pass 2 arguments:

car_a.start(10,20)  

In the output, you will see 30.

Method Overriding

Method overriding refers to having a method with the same name in the child class as in the parent class. The definition of the method differs in parent and child classes but the name remains the same. Let's take a simple example method overriding in Python.

# Create Class Vehicle
class Vehicle:  
    def print_details(self):
        print("This is parent Vehicle class method")

# Create Class Car that inherits Vehicle
class Car(Vehicle):  
    def print_details(self):
        print("This is child Car class method")

# Create Class Cycle that inherits Vehicle
class Cycle(Vehicle):  
    def print_details(self):
        print("This is child Cycle class method")

In the script above the Car and Cycle classes inherit the Vehicle class. The vehicle class has print_details() method, which is overridden by the child classes. Now if you call the print_details() method, the output will depend upon the object through which the method is being called. Execute the following script to see this concept in action:

car_a = Vehicle()  
car_a. print_details()

car_b = Car()  
car_b.print_details()

car_c = Cycle()  
car_c.print_details()  

The output will look like this:

This is parent Vehicle class method  
This is child Car class method  
This is child Cycle class method  

You can see that the output is different, although the print_details() method is being called through derived classes of the same base class. However, since the child classes have overridden the parent class method, the methods behave differently.

Encapsulation

Encapsulation is the third pillar of object-oriented programming. Encapsulation simply refers to data hiding. As a general principle, in object-oriented programming, one class should not have direct access to the data of the other class. Rather, the access should be controlled via class methods.

To provide controlled access to class data in Python, the access modifiers and properties are used. We have already seen access modifiers, in this section, we will see properties in action.

Suppose we want to ensure that the car model should always be between 2000 and 2018. If a user tries to enter a value less than 2000 for the car model, the value is automatically set to 2000 and if the entered value is greater than 2018, it should be set to 2018. If the value is between 2000 and 2018, it should not be changed. We can create a property for the model attribute which implements this logic as follows:

# Creates class Car
class Car:

    # Creates Car class constructor
    def __init__(self, model):
        # initialize instance variables
        self.model = model

    # Creates model property
    @property
    def model(self):
        return self.__model

    # Create property setter
    @model.setter
    def model(self, model):
        if model < 2000:
            self.__model = 2000
        elif model > 2018:
            self.__model = 2018
        else:
            self.__model = model

    def getCarModel(self):
        return "The car model is " + str(self.model)

carA = Car(2088)  
print(carA.getCarModel())  

A property has three parts. You have to define the attribute, which is model in the above script. Next, you have to define the property for the attribute using the @property decorator. Finally, you have to create property setter which is @model.setter descriptor in the above script.

Now, if you try to enter a value greater than 2018 for the model attribute, you will see that the value is set to 2018. Let's test this. Execute the following script:

car_a = Car(2088)  
print(car_a.get_car_model())  

Here we are passing 2088 as the value for model, however if you print the value for the model attribute via get_car_model() function, you will see 2018 in the output.

Conclusion

In this article, we studied some of the most important object-oriented programming concepts. Object-oriented programming is one of the most famous and commonly used programming paradigms. The importance of object-oriented programming is reflected by the fact that most of the modern programming languages are either fully object-oriented or support object-oriented programming.

Real Python: Practical Text Classification With Python and Keras

$
0
0

Imagine you could know the mood of the people on the Internet. Maybe you are not interested in its entirety, but only if people are today happy on your favorite social media platform. After this tutorial, you’ll be equipped to do this. While doing this, you will get a grasp of current advancements of (deep) neural networks and how they can be applied to text.

Reading the mood from text with machine learning is called sentiment analysis, and it is one of the prominent use cases in text classification. This falls into the very active research field of natural language processing (NLP). Other common use cases of text classification include detection of spam, auto tagging of customer queries, and categorization of text into defined topics. So how can you do this?

Free Bonus:5 Thoughts On Python Mastery, a free course for Python developers that shows you the roadmap and the mindset you'll need to take your Python skills to the next level.

Choosing a Data Set

Before we start, let’s take a look at what data we have. Go ahead and download the data set from the Sentiment Labelled Sentences Data Set from the UCI Machine Learning Repository.

By the way, this repository is a wonderful source for machine learning data sets when you want to try out some algorithms. This data set includes labeled reviews from IMDb, Amazon, and Yelp. Each review is marked with a score of 0 for a negative sentiment or 1 for a positive sentiment.

Extract the folder into a data folder and go ahead and load the data with Pandas:

importpandasaspdfilepath_dict={'yelp':'data/sentiment_analysis/yelp_labelled.txt','amazon':'data/sentiment_analysis/amazon_cells_labelled.txt','imdb':'data/sentiment_analysis/imdb_labelled.txt'}df_list=[]forsource,filepathinfilepath_dict.items():df=pd.read_csv(filepath,names=['sentence','label'],sep='\t')df['source']=source# Add another column filled with the source namedf_list.append(df)df=pd.concat(df_list)print(df.iloc[0])

The result will be as follows:

sentence    Wow... Loved this place.label                              1source                          yelpName: 0, dtype: object

This looks about right. With this data set, you are able to train a model to predict the sentiment of a sentence. Take a quick moment to think about how you would go about predicting the data.

One way you could do this is to count the frequency of each word in each sentence and tie this count back to the entire set of words in the data set. You would start by taking the data and creating a vocabulary from all the words in all sentences. The collection of texts is also called a corpus in NLP.

The vocabulary in this case is a list of words that occurred in our text where each word has its own index. This enables you to create a vector for a sentence. You would then take the sentence you want to vectorize, and you count each occurrence in the vocabulary. The resulting vector will be with the length of the vocabulary and a count for each word in the vocabulary.

The resulting vector is also called a feature vector. In a feature vector, each dimension can be a numeric or categorical feature, like for example the height of a building, the price of a stock, or, in our case, the count of a word in a vocabulary. These feature vectors are a crucial piece in data science and machine learning, as the model you want to train depends on them.

Let’s quickly illustrate this. Imagine you have the following two sentences:

>>>
>>> sentences=['John likes ice cream','John hates chocolate.']

Next, you can use the CountVectorizer provided by the scikit-learn library to vectorize sentences. It takes the words of each sentence and creates a vocabulary of all the unique words in the sentences. This vocabulary can then be used to create a feature vector of the count of the words:

>>>
>>> fromsklearn.feature_extraction.textimportCountVectorizer>>> vectorizer=CountVectorizer(min_df=0,lowercase=False)>>> vectorizer.fit(sentences)>>> vectorizer.vocabulary_{'John': 0, 'chocolate': 1, 'cream': 2, 'hates': 3, 'ice': 4, 'likes': 5}

This vocabulary serves also as an index of each word. Now, you can take each sentence and get the word occurrences of the words based on the previous vocabulary. The vocabulary consists of all five words in our sentences, each representing one word in the vocabulary. When you take the previous two sentences and transform them with the CountVectorizer you will get a vector representing the count of each word of the sentence:

>>>
>>> vectorizer.transform(sentences).toarray()array([[1, 0, 1, 0, 1, 1],    [1, 1, 0, 1, 0, 0]])

Now, you can see the resulting feature vectors for each sentence based on the previous vocabulary. For example, if you take a look at the first item, you can see that both vectors have a 1 there. This means that both sentences have one occurrence of John, which is in the first place in the vocabulary.

This is considered a Bag-of-words (BOW) model, which is a common way in NLP to create vectors out of text. Each document is represented as a vector. You can use these vectors now as feature vectors for a machine learning model. This leads us to our next part, defining a baseline model.

Defining a Baseline Model

When you work with machine learning, one important step is to define a baseline model. This usually involves a simple model, which is then used as a comparison with the more advanced models that you want to test. In this case, you’ll use the baseline model to compare it to the more advanced methods involving (deep) neural networks, the meat and potatoes of this tutorial.

First, you are going to split the data into a training and testing set which will allow you to evaluate the accuracy and see if your model generalizes well. This means whether the model is able to perform well on data it has not seen before. This is a way to see if the model is overfitting.

Overfitting is when a model is trained too well on the training data. You want to avoid overfitting, as this would mean that the model mostly just memorized the training data. This would account for a large accuracy with the training data but a low accuracy in the testing data.

We start by taking the Yelp data set which we extract from our concatenated data set. From there, we take the sentences and labels. The .values returns a NumPy array instead of a Pandas Series object which is in this context easier to work with:

>>>
>>> fromsklearn.model_selectionimporttrain_test_split>>> df_yelp=df[df['source']=='yelp']>>> sentences=df_yelp['sentence'].values>>> y=df_yelp['label'].values>>> sentences_train,sentences_test,y_train,y_test=train_test_split(... sentences,y,test_size=0.25,random_state=1000)

Here we will use again on the previous BOW model to vectorize the sentences. You can use again the CountVectorizer for this task. Since you might not have the testing data available during training, you can create the vocabulary using only the training data. Using this vocabulary, you can create the feature vectors for each sentence of the training and testing set:

>>>
>>> fromsklearn.feature_extraction.textimportCountVectorizer>>> vectorizer=CountVectorizer()>>> vectorizer.fit(sentences_train)>>> X_train=vectorizer.transform(sentences_train)>>> X_test=vectorizer.transform(sentences_test)>>> X_train<750x1714 sparse matrix of type '<class 'numpy.int64'>'    with 7368 stored elements in Compressed Sparse Row format>

You can see that the resulting feature vectors have 750 samples which are the number of training samples we have after the train-test split. Each sample has 1714 dimensions which is the size of the vocabulary. Also, you can see that we get a sparse matrix. This is a data type that is optimized for matrices with only a few non-zero elements, which only keeps track of the non-zero elements reducing the memory load.

CountVectorizer performs tokenization which separates the sentences into a set of tokens as you saw previously in the vocabulary. It additionally removes punctuation and special characters and can apply other preprocessing to each word. If you want, you can use a custom tokenizer from the NLTK library with the CountVectorizer or use any number of the customizations which you can explore to improve the performance of your model.

Note: There are a lot of additional parameters to CountVectorizer() that we forgo using here, such as adding ngrams, beacuse the goal at first is to build a simple baseline model. The token pattern itself defaults to token_pattern=’(?u)\b\w\w+\b’, which is a regex pattern that says, “a word is 2 or more Unicode word characters surrounded by word boundaries.”.

The classification model we are going to use is the logistic regression which is a simple yet powerful linear model that is mathematically speaking in fact a form of regression between 0 and 1 based on the input feature vector. By specifying a cutoff value (by default 0.5), the regression model is used for classification. You can use again scikit-learn library which provides the LogisticRegression classifier:

>>>
>>> fromsklearn.linear_modelimportLogisticRegression>>> classifier=LogisticRegression()>>> classifier.fit(X_train,y_train)>>> score=classifier.score(X_test,y_test)>>> print("Accuracy:",score)Accuracy: 0.796

You can see that the logistic regression reached an impressive 79.6%, but let’s have a look how this model performs on the other data sets that we have. In this script, we perform and evaluate the whole process for each data set that we have:

forsourceindf['source'].unique():df_source=df[df['source']==source]sentences=df_source['sentence'].valuesy=df_source['label'].valuessentences_train,sentences_test,y_train,y_test=train_test_split(sentences,y,test_size=0.25,random_state=1000)vectorizer=CountVectorizer()vectorizer.fit(sentences_train)X_train=vectorizer.transform(sentences_train)X_test=vectorizer.transform(sentences_test)classifier=LogisticRegression()classifier.fit(X_train,y_train)score=classifier.score(X_test,y_test)print('Accuracy for {} data: {:.4f}'.format(source,score))

Here’s the result:

Accuracy for yelp data: 0.7960Accuracy for amazon data: 0.7960Accuracy for imdb data: 0.7487

Great! You can see that this fairly simple model achieves a fairly good accuracy. It would be interesting to see whether we are able to outperform this model. In the next part, we will get familiar with (deep) neural networks and how to apply them to text classification.

A Primer on (Deep) Neural Networks

You might have experienced some of the excitement and fear related to artificial intelligence and deep learning. You might have stumbled across some confusing article or concerned TED talk about the approaching singularity or maybe you saw the backflipping robots and you wonder whether a life in the woods seems reasonable after all.

On a lighter note, AI researchers all agreed that they did not agree with each other when AI will exceed Human-level performance. According to this paper we should still have some time left.

So you might already be curious how neural networks work. If you already are familiar with neural networks, feel free to skip to the parts involving Keras. Also, there is the wonderful Deep Learning book by Ian Goodfellow which I highly recommend if you want to dig deeper into the math. You can read the whole book online for free. In this section you will get an overview of neural networks and their inner workings, and you will later see how to use neural networks with the outstanding Keras library.

In this article, you don’t have to worry about the singularity, but (deep) neural networks play a crucial role in the latest developments in AI. It all started with a famous paper in 2012 by Geoffrey Hinton and his team, which outperformed all previous models in the famous ImageNet Challenge.

The challenge could be considered the World Cup in computer vision which involves classifying a large set of images based on given labels. Geoffrey Hinton and his team managed to beat the previous models by using a convolutional neural network (CNN), which we will cover in this tutorial as well.

Since then, neural networks have moved into several fields involving classification, regression and even generative models. The most prevalent fields include computer vision, voice recognition and natural language processing (NLP).

Neural networks, or sometimes called artificial neural network (ANN) or feedforward neural network, are computational networks which were vaguely inspired by the neural networks in the human brain. They consist of neurons (also called nodes) which are connected like in the graph below.

You start by having a layer of input neurons where you feed in your feature vectors and the values are then feeded forward to a hidden layer. At each connection, you are feeding the value forward, while the value is multiplied by a weight and a bias is added to the value. This happens at every connection and at the end you reach an output layer with one or more output nodes.

If you want to have a binary classification you can use one node, but if you have multiple categories you should use multiple nodes for each category:

neural network structureNeural network model

You can have as many hidden layers as you wish. In fact, a neural network with more than one hidden layer is considered a deep neural network. Don’t worry: I won’t get here into the mathematical depths concerning neural networks. But if you want to get an intuitive visual understanding of the math involved, you can check out the YouTube Playlist by Grant Sanderson. The formula from one layer to the next is this short equation:

neural network formulaNeural network formula

Let’s slowly unpack what is happening here. You see, we are dealing here with only two layers. The layer with nodes a serves as input for the layer with nodes o. In order to calculate the values for each output node, we have to multiply each input node by a weight w and add a bias b.

All of those have to be then summed and passed to a function f. This function is considered the activation function and there are various different functions that can be used depending on the layer or the problem. It is generally common to use a rectified linear unit (ReLU) for hidden layers, a sigmoid function for the output layer in a binary classification problem, or a softmax function for the output layer of multi-class classification problems.

You might already wonder how the weights are calculated, and this is obviously the most important part of neural networks, but also the most difficult part. The algorithm starts by initializing the weights with random values and they are then trained with a method called backpropagation.

This is done by using optimization methods (also called optimizer) like the gradient descent in order to reduce the error between the computed and the desired output (also called target output). The error is determined by a loss function whose loss we want to minimize with the optimizer. The whole process is too extensive to cover here, but I’ll refer again to the Grant Sanderson playlist and the Deep Learning book by Ian Goodfellow I mentioned before.

What you have to know is that there are various optimization methods that you can use, but the most common optimizer currently used is called Adam which has a good performance in various problems.

You can also use different loss functions, but in this tutorial you will only need the cross entropy loss function or more specifically binary cross entropy which is used for binary classification problems. Be sure to experiment with the various available methods and tools. Some researchers even claim in a recent article that the choice for the best performing methods borders on alchemy. The reason being that many methods are not well explained and consist of a lot of tweaking and testing.

Introducing Keras

Keras is a deep learning and neural networks API by François Chollet which is capable of running on top of Tensorflow (Google), Theano or CNTK (Microsoft). To quote the wonderful book by François Chollet, Deep Learning with Python:

Keras is a model-level library, providing high-level building blocks for developing deep-learning models. It doesn’t handle low-level operations such as tensor manipulation and differentiation. Instead, it relies on a specialized, well-optimized tensor library to do so, serving as the backend engine of Keras (Source)

It is a great way to start experimenting with neural networks without having to implement every layer and piece on your own. For example Tensorflow is a great machine learning library, but you have to implement a lot of boilerplate code to have a model running.

Installing Keras

Before installing Keras, you’ll need either Tensorflow, Theano, or CNTK. In this tutorial we will be using Tensorflow so check out their installation guide here, but feel free to use any of the frameworks that works best for you. Keras can be installed using PyPI with the following command:

$ pip install keras

You can choose the backend you want to have by opening the Keras configuration file which you can find here:

$HOME/.keras/keras.json

If you are a Windows user, you have to replace $HOME with %USERPROFILE%. The configuration file should look as follows:

{"image_data_format":"channels_last","epsilon":1e-07,"floatx":"float32","backend":"tensorflow"}

You can change the backend field there to "theano", "tensorflow" or "cntk", given that you have installed the backend on your machine. For more details check out the Keras backends documentation.

You might notice that we use float32 data in the configuration file. The reason for this is that neural networks are frequently used in GPUs, and the computational bottleneck is memory. By using 32 bit, we are able reduce the memory load and we do not lose too much information in the process.

Your First Keras Model

Now you are finally ready to experiment with Keras. Keras supports two main types of models. You have the Sequential model API which you are going to see in use in this tutorial and the functional API which can do everything of the Sequential model but it can be also used for advanced models with complex network architectures.

The Sequential model is a linear stack of layers, where you can use the large variety of available layers in Keras. The most common layer is the Dense layer which is your regular densely connected neural network layer with all the weights and biases that you are already familiar with.

Let’s see if we can achieve some improvement to our previous logistic regression model. You can use the X_train and X_test arrays that you built in our earlier example.

Before we build our model, we need to know the input dimension of our feature vectors. This happens only in the first layer since the following layers can do automatic shape inference. In order to build the Sequential model, you can add layers one by one in order as follows:

>>>
>>> fromkeras.modelsimportSequential>>> fromkerasimportlayers>>> input_dim=X_train.shape[1]# Number of features>>> model=Sequential()>>> model.add(layers.Dense(10,input_dim=input_dim,activation='relu'))>>> model.add(layers.Dense(1,activation='sigmoid'))Using TensorFlow backend.

Before you can start with the training of the model, you need to configure the learning process. This is done with the .compile() method. This method specifies the optimizer and the loss function.

Additionally, you can add a list of metrics which can be later used for evaluation, but they do not influence the training. In this case, we want to use the binary cross entropy and the Adam optimizer you saw in the primer mentioned before. Keras also includes a handy .summary() function to give an overview of the model and the number of parameters available for training:

>>>
>>> model.compile(loss='binary_crossentropy',... optimizer='adam',... metrics=['accuracy'])>>> model.summary()_________________________________________________________________Layer (type)                 Output Shape          Param #   =================================================================dense_1 (Dense)              (None, 10)            17150     _________________________________________________________________dense_2 (Dense)              (None, 1)             11        =================================================================Total params: 17,161Trainable params: 17,161Non-trainable params: 0_________________________________________________________________

You might notice that we have 8575 parameters for the first layer and another 6 in the next one. Where did those come from?

See, we have 1714 dimensions for each feature vector, and then we have 5 nodes. We need weights for each feature dimension and each node which accounts for 1714 * 5 = 8570 parameters, and then we have another 5 times an added bias for each node, which gets us the 8575 parameters. In the final node, we have another 5 weights and one bias, which gets us to 6 parameters.

Neat! You are almost there. Now it is time to start your training with the .fit() function.

Since the training in neural networks is an iterative process, the training won’t just stop after it is done. You have to specify the number of iterations you want the model to be training. Those completed iterations are commonly called epochs. We want to run it for 100 epochs to be able to see how the training loss and accuracy are changing after each epoch.

Another parameter you have to your selection is the batch size. The batch size is responsible for how many samples we want to use in one epoch, which means how many samples are used in one forward/backward pass. This increases the speed of the computation as it need fewer epochs to run, but it also needs more memory, and the model may degrade with larger batch sizes. Since we have a small training set, we can leave this to a low batch size:

>>>
>>> history=model.fit(X_train,y_train,... epochs=100,... verbose=False,... validation_data=(X_test,y_test)... batch_size=10)

Now you can use the .evaluate() method to measure the accuracy of the model. You can do this both for the training data and testing data. We expect that the training data has a higher accuracy then for the testing data. Tee longer you would train a neural network, the more likely it is that it starts overfitting.

Note that if you rerun the .fit() method, you’ll start off with the computed weights from the previous training. Make sure to compile the model again before you start training the model again. Now let’s evaluate the accuracy model:

>>>
>>> loss,accuracy=model.evaluate(X_train,y_train,verbose=False)>>> print("Training Accuracy: {:.4f}".format(accuracy))>>> loss,accuracy=model.evaluate(X_test,y_test,verbose=False)>>> print("Testing Accuracy:  {:.4f}".format(accuracy))Training Accuracy: 1.0000Testing Accuracy:  0.7960

You can already see that the model was overfitting since it reached 100% accuracy for the training set. But this was expected since the number of epochs was fairly large for this model. However, the accuracy of the testing set has already surpassed our previous logistic Regression with BOW model, which is a great step further in terms of our progress.

To make your life easier, you can use this little helper function to visualize the loss and accuracy for the training and testing data based on the History callback. This callback, which is automatically applied to each Keras model, records the loss and additional metrics that can be added in the .fit() method. In this case, we are only interested in the accuracy. This helper function employs the matplotlib plotting library:

importmatplotlib.pyplotaspltplt.style.use('ggplot')defplot_history(history):acc=history.history['acc']val_acc=history.history['val_acc']loss=history.history['loss']val_loss=history.history['val_loss']x=range(1,len(acc)+1)plt.figure(figsize=(12,5))plt.subplot(1,2,1)plt.plot(x,acc,'b',label='Training acc')plt.plot(x,val_acc,'r',label='Validation acc')plt.title('Training and validation accuracy')plt.legend()plt.subplot(1,2,2)plt.plot(x,loss,'b',label='Training loss')plt.plot(x,val_loss,'r',label='Validation loss')plt.title('Training and validation loss')plt.legend()

To use this function, simply call plot_history() with the collected accuracy and loss inside the history dictionary:

>>>
>>> plot_history(history)
loss accuracy baseline modelAccuracy and loss for baseline model

You can see that we have trained our model for too long since the training set reached 100% accuracy. A good way to see when the model starts overfitting is when the loss of the validation data starts rising again. This tends to be a good point to stop the model. You can see this around 20-40 epochs in this training.

Note: When training neural networks, you should use a separate testing and validation set. What you would usually do is take the model with the highest validation accuracy and then test the model with the testing set.

This makes sure that you don’t overfit the model. Using the validation set to choose the best model is a form of data leakage (or “cheating”) to get to pick the result that produced the best test score out of hundreds of them. Data leakage happens when information outside the training data set is used in the model.

In this case, our testing and validation set are the same, since we have a smaller sample size. As we have covered before, (deep) neural networks perform best when you have a very large number of samples. In the next part, you’ll see a different way to represent words as vectors. This is a very exciting and powerful way to work with words where you’ll see how to represent words as dense vectors.

What Is a Word Embedding?

Text is considered a form of sequence data similar to time series data that you would have in weather data or financial data. In the previous BOW model, you have seen how to represent a whole sequence of words as a single feature vector. Now you will see how to represent each word as vectors. There are various ways to vectorize text, such as:

  • Words represented by each word as a vector
  • Characters represented by each character as a vector
  • N-grams of words/characters represented as a vector (N-grams are overlapping groups of multiple succeeding words/characters in the text)

In this tutorial, you’ll see how to deal with representing words as vectors which is the common way to use text in neural networks. Two possible ways to represent a word as a vector are one-hot encoding and word embeddings.

One-Hot Encoding

The first way to represent a word as a vector is by creating a so-called one-hot encoding, which is simply done by taking a vector of the length of the vocabulary with an entry for each word in the corpus.

In this way, you have for each word, given it has a spot in the vocabulary, a vector with zeros everywhere except for the corresponding spot for the word which is set to one. As you might imagine, this can become a fairly large vector for each word and it does not give any additional information like the relationship between words.

Let’s say you have a list of cities as in the following example:

>>>
>>> cities=['London','Berlin','Berlin','New York','London']>>> cities['London', 'Berlin', 'Berlin', 'New York', 'London']

You can use scikit-learn and the LabelEncoder to encode the list of cities into categorical integer values like here:

>>>
>>> fromsklearn.preprocessingimportLabelEncoder>>> encoder=LabelEncoder()>>> city_labels=encoder.fit_transform(cities)>>> city_labelsarray([1, 0, 0, 2, 1])

Using this representation, you can use the OneHotEncoder provided by scikit-learn to encode the categorical values we got before into a one-hot encoded numeric array. OneHotEncoder expects each categorical value to be in a separate row, so you’ll need to reshape the array, then you can apply the encoder:

>>>
>>> fromsklearn.preprocessingimportOneHotEncoder>>> encoder=OneHotEncoder(sparse=False)>>> city_labels=city_labels.reshape((5,1))>>> encoder.fit_transform(city_labels)array([[0., 1., 0.],       [1., 0., 0.],       [1., 0., 0.],       [0., 0., 1.],       [0., 1., 0.]])

You can see that categorical integer value represents the position of the array which is 1 and the rest is 0. This is often used when you have a categorical feature which you cannot represent as a numeric value but you still want to be able to use it in machine learning. One use case for this encoding is of course words in a text but it is most prominently used for categories. Such categories can be for example city, department, or other categories.

Word Embeddings

This method represents words as dense word vectors (also called word embeddings) which are trained unlike the one-hot encoding which are hardcoded. This means that the word embeddings collect more information into fewer dimensions.

Note that the word embeddings do not understand the text as a human would, but they rather map the statistical structure of the language used in the corpus. Their aim is to map semantic meaning into a geometric space. This geometric space is then called the embedding space.

This would map semantically similar words close on the embedding space like numbers or colors. If the embedding captures the relationship between words well, things like vector arithmetic should become possible. A famous example in this field of study is the ability to map King - Man + Woman = Queen.

How can you get such a word embedding? You have two options for this. One way is to train your word embeddings during the training of your neural network. The other way is by using pretrained word embeddings which you can directly use in your model. There you have the option to either leave these word embeddings unchanged during training or you train them also.

Now you need to tokenize the data into a format that can be used by the word embeddings. Keras offers a couple of convenience methods for text preprocessing and sequence preprocessing which you can employ to prepare your text.

You can start by using the Tokenizer utility class which can vectorize a text corpus into a list of integers. Each integer maps to a value in a dictionary that encodes the entire corpus, with the keys in the dictionary being the vocabulary terms themselves. You can add the parameter num_words, which is responsible for setting the size of the vocabulary. The most common num_words words will be then kept. I have the testing and training data prepared from the previous example:

>>>
>>> fromkeras.preprocessing.textimportTokenizer>>> tokenizer=Tokenizer(num_words=5000)>>> tokenizer.fit_on_texts(sentences_train)>>> X_train=tokenizer.texts_to_sequences(sentences_train)>>> X_test=tokenizer.texts_to_sequences(sentences_test)>>> vocab_size=len(tokenizer.word_index)+1# Adding 1 because of reserved 0 index>>> print(sentences_train[2])>>> print(X_train[2])Of all the dishes, the salmon was the best, but all were great.[11, 43, 1, 171, 1, 283, 3, 1, 47, 26, 43, 24, 22]

The indexing is ordered after the most common words in the text, which you can see by the word the having the index 1. It is important to note that the index 0 is reserved and is not assigned to any word. This zero index is used for padding, which I’ll introduce in a moment.

Unknown words (words that are not in the vocabulary) are denoted in Keras with word_count + 1 since they can also hold some information. You can see the index of each word by taking a look at the word_index dictionary of the Tokenizer object:

>>>
>>> forwordin['the','all','happy','sad']:... print('{}: {}'.format(word,tokenizer.word_index[word]))the: 1all: 43happy: 320sad: 450

Note: Pay close attention to the difference between this technique and the X_train that was produced by scikit-learn’s CountVectorizer.

With CountVectorizer, we had stacked vectors of word counts, and each vector was the same length (the size of the total corpus vocabulary). With Tokenizer, the resulting vectors equal the length of each text, and the numbers don’t denote counts, but rather correspond to the word values from the dictionary tokenizer.word_index.

One problem that we have is that each text sequence has in most cases different length of words. To counter this, you can use pad_sequence() which simply pads the sequence of words with zeros. By default, it prepends zeros but we want to append them. Typically it does not matter whether you prepend or append zeros.

Additionally you would want to add a maxlen parameter to specify how long the sequences should be. This cuts sequences that exceed that number. In the following code, you can see how to pad sequences with Keras:

>>>
>>> fromkeras.preprocessing.sequenceimportpad_sequences>>> maxlen=100>>> X_train=pad_sequences(X_train,padding='post',maxlen=maxlen)>>> X_test=pad_sequences(X_test,padding='post',maxlen=maxlen)>>> print(X_train[0,:])[  1  10   3 282 739  25   8 208  30  64 459 230  13   1 124   5 231   8  58   5  67   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]

The first values represent the index in the vocabulary as you have learned from the previous examples. You can also see that the resulting feature vector contains mostly zeros, since you have a fairly short sentence. In the next part you will see how to work with word embeddings in Keras.

Keras Embedding Layer

Notice that, at this point, our data is still hardcoded. We have not told Keras to learn a new embedding space through successive tasks. Now you can use the Embedding Layer of Keras which takes the previously calculated integers and maps them to a dense vector of the embedding. You will need the following parameters:

  • input_dim: the size of the vocabulary
  • output_dim: the size of the dense vector
  • input_length: the length of the sequence

With the Embedding layer we have now a couple of options. One way would be to take the output of the embedding layer and plug it into a Dense layer. In order to do this you have to add a Flatten layer in between that prepares the sequential input for the Dense layer:

fromkeras.modelsimportSequentialfromkerasimportlayersembedding_dim=50model=Sequential()model.add(layers.Embedding(input_dim=vocab_size,output_dim=embedding_dim,input_length=maxlen))model.add(layers.Flatten())model.add(layers.Dense(10,activation='relu'))model.add(layers.Dense(1,activation='sigmoid'))model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy'])model.summary()

The result will be as follows:

_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================embedding_8 (Embedding)      (None, 100, 50)           87350     _________________________________________________________________flatten_3 (Flatten)          (None, 5000)              0         _________________________________________________________________dense_13 (Dense)             (None, 10)                50010     _________________________________________________________________dense_14 (Dense)             (None, 1)                 11        =================================================================Total params: 137,371Trainable params: 137,371Non-trainable params: 0_________________________________________________________________

You can now see that we have 87350 new parameters to train. This number comes from vocab_size times the embedding_dim. These weights of the embedding layer are initialized with random weights and are then adjusted through backpropagation during training. This model takes the words as they come in the order of the sentences as input vectors. You can train it with the following:

history=model.fit(X_train,y_train,epochs=20,verbose=False,validation_data=(X_test,y_test),batch_size=10)loss,accuracy=model.evaluate(X_train,y_train,verbose=False)print("Training Accuracy: {:.4f}".format(accuracy))loss,accuracy=model.evaluate(X_test,y_test,verbose=False)print("Testing Accuracy:  {:.4f}".format(accuracy))plot_history(history)

The result will be as follows:

Training Accuracy: 0.5100Testing Accuracy:  0.4600
loss accuracy first modelAccuracy and loss for first model

This is typically a not very reliable way to work with sequential data as you can see in the performance. When working with sequential data you want to focus on methods that look at local and sequential information instead of absolute positional information.

Another way to work with embeddings is by using a MaxPooling1D/AveragePooling1D or a GlobalMaxPooling1D/GlobalAveragePooling1D layer after the embedding. You can think of the pooling layers as a way to downsample (a way to reduce the size of) the incoming feature vectors.

In the case of max pooling you take the maximum value of all features in the pool for each feature dimension. In the case of average pooling you take the average, but max pooling seems to be more commonly used as it highlights large values.

Global max/average pooling takes the maximum/average of all features whereas in the other case you have to define the pool size. Keras has again its own layer that you can add in the sequential model:

fromkeras.modelsimportSequentialfromkerasimportlayersembedding_dim=50model=Sequential()model.add(layers.Embedding(input_dim=vocab_size,output_dim=embedding_dim,input_length=maxlen))model.add(layers.GlobalMaxPool1D())model.add(layers.Dense(10,activation='relu'))model.add(layers.Dense(1,activation='sigmoid'))model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy'])model.summary()

The result will be as follows:

_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================embedding_9 (Embedding)      (None, 100, 50)           87350     _________________________________________________________________global_max_pooling1d_5 (Glob (None, 50)                0         _________________________________________________________________dense_15 (Dense)             (None, 10)                510       _________________________________________________________________dense_16 (Dense)             (None, 1)                 11        =================================================================Total params: 87,871Trainable params: 87,871Non-trainable params: 0_________________________________________________________________

The procedure for training does not change:

history=model.fit(X_train,y_train,epochs=50,verbose=False,validation_data=(X_test,y_test),batch_size=10)loss,accuracy=model.evaluate(X_train,y_train,verbose=False)print("Training Accuracy: {:.4f}".format(accuracy))loss,accuracy=model.evaluate(X_test,y_test,verbose=False)print("Testing Accuracy:  {:.4f}".format(accuracy))plot_history(history)

The result will be as follows:

Training Accuracy: 1.0000
Testing Accuracy:  0.8050
loss accurcay max poolingAccuracy and loss for max pooling model

You can already see some improvements in our models. Next you’ll see how we can employ pretrained word embeddings and if they help us with our model.

Using Pretrained Word Embeddings

We just saw an example of jointly learning word embeddings incorporated into the larger model that we want to solve.

An alternative is to use a precomputed embedding space that utilizes a much larger corpus. It is possible to precompute word embeddings by simply training them on a large corpus of text. Among the most popular methods are Word2Vec developed by Google and GloVe (Global Vectors for Word Representation) developed by the Stanford NLP Group.

Note that those are different approaches with the same goal. Word2Vec achieves this by employing neural networks and GloVe achieves this with a co-occurrence matrix and by using matrix factorization. In both cases you are dealing with dimensionality reduction, but Word2Vec is more accurate and GloVe is faster to compute.

In this tutorial, you’ll see how to work with the GloVe word embeddings from the Stanford NLP Group as their size is more manageable than the Word2Vec word embeddings provided by Google. Go ahead and download the 6B (trained on 6 billion words) word embeddings from here (822 MB).

You can find other word embeddings also on the main GloVe page. You can find the pretrained Word2Vec embeddings by Google here. If you want to train your own word embeddings, you can do so efficiently with the gensim Python package which uses Word2Vec for calculation. More details on how to do this here.

Now that we got you covered, you can start using the word embeddings in your models. You can see in the next example how you can load the embedding matrix. Each line in the file starts with the word and is followed by the embedding vector for the particular word.

This is a large file with 400000 lines, with each line representing a word followed by its vector as a stream of floats. For example, here are the first 50 characters of the first line:

$ head -n 1 data/glove_word_embeddings/glove.6B.50d.txt | cut -c-50
    the 0.418 0.24968 -0.41242 0.1217 0.34527 -0.04445

Since you don’t need all words, you can focus on only the words that we have in our vocabulary. Since we have only a limited number of words in our vocabulary, we can skip most of the 40000 words in the pretrained word embeddings:

importnumpyasnpdefcreate_embedding_matrix(filepath,word_index,embedding_dim):vocab_size=len(word_index)+1# Adding again 1 because of reserved 0 indexembedding_matrix=np.zeros((vocab_size,embedding_dim))withopen(filepath)asf:forlineinf:word,*vector=line.split()ifwordinword_index:idx=word_index[word]embedding_matrix[idx]=np.array(vector,dtype=np.float32)[:embedding_dim]returnembedding_matrix

You can use this function now to retrieve the embedding matrix:

>>>
>>> embedding_dim=50>>> embedding_matrix=create_embedding_matrix(... 'data/glove_word_embeddings/glove.6B.50d.txt',... tokenizer.word_index,embedding_dim)

Wonderful! Now you are ready to use the embedding matrix in training. Let’s go ahead and use the previous network with global max pooling and see if we can improve this model. When you use pretrained word embeddings you have the choice to either allow the embedding to be updated during training or only use the resulting embedding vectors as they are.

First, let’s have a quick look how many of the embedding vectors are nonzero:

>>>
>>> nonzero_elements=np.count_nonzero(np.count_nonzero(embedding_matrix,axis=1))>>> nonzero_elements/vocab_size0.9507727532913566

This means 95.1% of the vocabulary is covered by the pretrained model, which is a good coverage of our vocabulary. Let’s have a look at the performance when using the GlobalMaxPool1D layer:

model=Sequential()model.add(layers.Embedding(vocab_size,embedding_dim,weights=[embedding_matrix],input_length=maxlen,trainable=False))model.add(layers.GlobalMaxPool1D())model.add(layers.Dense(10,activation='relu'))model.add(layers.Dense(1,activation='sigmoid'))model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy'])model.summary()

The result will be as follows:

_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================embedding_10 (Embedding)     (None, 100, 50)           87350     _________________________________________________________________global_max_pooling1d_6 (Glob (None, 50)                0         _________________________________________________________________dense_17 (Dense)             (None, 10)                510       _________________________________________________________________dense_18 (Dense)             (None, 1)                 11        =================================================================Total params: 87,871Trainable params: 521Non-trainable params: 87,350_________________________________________________________________
history=model.fit(X_train,y_train,epochs=50,verbose=False,validation_data=(X_test,y_test),batch_size=10)loss,accuracy=model.evaluate(X_train,y_train,verbose=False)print("Training Accuracy: {:.4f}".format(accuracy))loss,accuracy=model.evaluate(X_test,y_test,verbose=False)print("Testing Accuracy:  {:.4f}".format(accuracy))plot_history(history)

The result will be as follows:

Training Accuracy: 0.7500Testing Accuracy:  0.6950
loss accuracy embedding untrainedAccuracy and loss for untrained word embeddings

Since the word embeddings are not additionally trained, it is expected to be lower. But let’s now see how this performs if we allow the embedding to be trained by using trainable=True:

model=Sequential()model.add(layers.Embedding(vocab_size,embedding_dim,weights=[embedding_matrix],input_length=maxlen,trainable=True))model.add(layers.GlobalMaxPool1D())model.add(layers.Dense(10,activation='relu'))model.add(layers.Dense(1,activation='sigmoid'))model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy'])model.summary()

The result will be as follows:

_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================embedding_11 (Embedding)     (None, 100, 50)           87350     _________________________________________________________________global_max_pooling1d_7 (Glob (None, 50)                0         _________________________________________________________________dense_19 (Dense)             (None, 10)                510       _________________________________________________________________dense_20 (Dense)             (None, 1)                 11        =================================================================Total params: 87,871Trainable params: 87,871Non-trainable params: 0_________________________________________________________________
history=model.fit(X_train,y_train,epochs=50,verbose=False,validation_data=(X_test,y_test),batch_size=10)loss,accuracy=model.evaluate(X_train,y_train,verbose=False)print("Training Accuracy: {:.4f}".format(accuracy))loss,accuracy=model.evaluate(X_test,y_test,verbose=False)print("Testing Accuracy:  {:.4f}".format(accuracy))plot_history(history)

The result will be as follows:

Training Accuracy: 1.0000Testing Accuracy:  0.8250
loss accuracy embedding trainedAccuracy and Loss for pretrained word embeddings

You can see that it is most effective to allow the embeddings to be trained. When dealing with large training sets it can boost the training process to be much faster than without. In our case it seemed to help but not by much. This does not have to be because of pretrained word embeddings.

Now it is time to focus on a more advanced neural network model to see if it is possible to boost the model and give it the leading edge over the previous models.

Convolutional Neural Networks (CNN)

Convolutional neural networks or also called convnets are one of the most exciting developments in machine learning in recent years.

They have revolutionized image classification and computer vision by being able to extract features from images and using them in neural networks. The properties that made them useful in image processing makes them also handy for sequence processing. You can imagine a CNN as a specialized neural network that is able to detect specific patterns.

If it is just another neural network, what differentiates it from what you have previously learned?

A CNN has hidden layers which are called convolutional layers. When you think of images, a computer has to deal with a two dimensional matrix of numbers and therefore you need some way to detect features in this matrix. These convolutional layers are able to detect edges, corners and other kinds of textures which makes them such a special tool. The convolutional layer consists of multiple filters which are slid across the image and are able to detect specific features.

This is the very core of the technique, the mathematical process of convolution. With each convolutional layer the network is able to detect more complex patterns. In the Feature Visualization by Chris Olah you can get a good intuition what these features can look like.

When you are working with sequential data, like text, you work with one dimensional convolutions, but the idea and the application stays the same. You still want to pick up on patterns in the sequence which become more complex with each added convolutional layer.

In the next figure you can see how such a convolution works. It starts by taking a patch of input features with the size of the filter kernel. With this patch you take the dot product of the multiplied weights of the filter. The one dimensional convnet is invariant to translations, which means that certain sequences can be recognized at a different position. This can be helpful for certain patterns in the text:

one dimensional convolution1D Convolution (Image source)

Now let’s have a look how you can use this network in Keras. Keras offers again various Convolutional layers which you can use for this task. The layer you’ll need is the Conv1D layer. This layer has again various parameters to choose from. The ones you are interested in for now are the number of filters, the kernel size, and the activation function. You can add this layer in between the Embedding layer and the GlobalMaxPool1D layer:

embedding_dim=100model=Sequential()model.add(layers.Embedding(vocab_size,embedding_dim,input_length=maxlen))model.add(layers.Conv1D(128,5,activation='relu'))model.add(layers.GlobalMaxPooling1D())model.add(layers.Dense(10,activation='relu'))model.add(layers.Dense(1,activation='sigmoid'))model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy'])model.summary()

The result will be as follows:

_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================embedding_13 (Embedding)     (None, 100, 100)          174700    _________________________________________________________________conv1d_2 (Conv1D)            (None, 96, 128)           64128     _________________________________________________________________global_max_pooling1d_9 (Glob (None, 128)               0         _________________________________________________________________dense_23 (Dense)             (None, 10)                1290      _________________________________________________________________dense_24 (Dense)             (None, 1)                 11        =================================================================Total params: 240,129Trainable params: 240,129Non-trainable params: 0_________________________________________________________________
history=model.fit(X_train,y_train,epochs=10,verbose=False,validation_data=(X_test,y_test),batch_size=10)loss,accuracy=model.evaluate(X_train,y_train,verbose=False)print("Training Accuracy: {:.4f}".format(accuracy))loss,accuracy=model.evaluate(X_test,y_test,verbose=False)print("Testing Accuracy:  {:.4f}".format(accuracy))plot_history(history)

The result will be as follows:

Training Accuracy: 1.0000Testing Accuracy:  0.7700
loss accuracy convolution modelAccuracy and loss for convolutional neural network

You can see that 80% accuracy seems to be tough hurdle to overcome with this data set and a CNN might not be well equipped. The reason for such a plateau might be that:

  • There are not enough training samples
  • The data you have does not generalize well
  • Missing focus on tweaking the hyperparameters

CNNs work best with large training sets where they are able to find generalizations where a simple model like logistic regression won’t be able.

Hyperparameters Optimization

One crucial steps of deep learning and working with neural networks is hyperparameter optimization.

As you saw in the models that we have used so far, even with simpler ones, you had a large number of parameters to tweak and choose from. Those parameters are called hyperparameters. This is the most time consuming part of machine learning and sadly there are no one-fits-all solutions ready.

When you have a look at the competitions on Kaggle, one of the largest places to compete against other fellow data scientists, you can see that many of the winning teams and models have gone through a lot of tweaking and experimenting until they reached their prime. So don’t get discouraged when it gets tough and you reach a plateau, but rather think about the ways you could optimize the model or the data.

One popular method for hyperparameter optimization is grid search. What this method does is it takes lists of parameters and it runs the model with each parameter combination that it can find. It is the most thorough way but also the most computationally heavy way to do this. Another common way, random search, which you’ll see in action here, simply takes random combinations of parameters.

In order to apply random search with Keras, you will need to use the KerasClassifier which serves as a wrapper for the scikit-learn API. With this wrapper you are able to use the various tools available with scikit-learn like cross-validation. The class that you need is RandomizedSearchCV which implements random search with cross-validation. Cross-validation is a way to validate the model and take the whole data set and separate it into multiple testing and training data sets.

There are various types of cross-validation. One type is the k-fold cross-validation which you’ll see in this example. In this type the data set is partitioned into k equal sized sets where one set is used for testing and the rest of the partitions are used for training. This enables you to run k different runs, where each partition is once used as a testing set. So, the higher k is the more accurate the model evaluation is, but the smaller each testing set is.

First step for KerasClassifier is to have a function that creates a Keras model. We will use the previous model, but we will allow various parameters to be set for the hyperparameter optimization:

defcreate_model(num_filters,kernel_size,vocab_size,embedding_dim,maxlen):model=Sequential()model.add(layers.Embedding(vocab_size,embedding_dim,input_length=maxlen))model.add(layers.Conv1D(num_filters,kernel_size,activation='relu'))model.add(layers.GlobalMaxPooling1D())model.add(layers.Dense(10,activation='relu'))model.add(layers.Dense(1,activation='sigmoid'))model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy'])returnmodel

Next, you want to define the parameter grid that you want to use in training. This consists of a dictionary with each parameters named as in the previous function. The number of spaces on the grid is 3 * 3 * 1 * 1 * 1, where each of those numbers is the number of different choices for a given parameter.

You can see how this could get computationally expensive very quickly, but luckily both grid search and random search are embarrassingly parallel, and the classes come with an n_jobs parameter that lets you test grid spaces in parallel. The parameter grid is initialized with the following dictionary:

param_grid=dict(num_filters=[32,64,128],kernel_size=[3,5,7],vocab_size=[5000],embedding_dim=[50],maxlen=[100])

Now you are already ready to start running the random search. In this example we iterate over each data set and then you want to preprocess the data in the same way as previously. Afterwards you take the previous function and add it to the KerasClassifier wrapper class including the number of epochs.

The resulting instance and the parameter grid are then used as the estimator in the RandomSearchCV class. Additionally, you can choose the number of folds in the k-folds cross-validation, which is in this case 4. You have seen most of the code in this snippet before in our previous examples. Besides the RandomSearchCV and KerasClassifier, I have added a little block of code handling the evaluation:

fromkeras.wrappers.scikit_learnimportKerasClassifierfromsklearn.model_selectionimportRandomizedSearchCV# Main settingsepochs=20embedding_dim=50maxlen=100output_file='data/output.txt'# Run grid search for each source (yelp, amazon, imdb)forsource,frameindf.groupby('source'):print('Running grid search for data set :',source)sentences=df['sentence'].valuesy=df['label'].values# Train-test splitsentences_train,sentences_test,y_train,y_test=train_test_split(sentences,y,test_size=0.25,random_state=1000)# Tokenize wordstokenizer=Tokenizer(num_words=5000)tokenizer.fit_on_texts(sentences_train)X_train=tokenizer.texts_to_sequences(sentences_train)X_test=tokenizer.texts_to_sequences(sentences_test)# Adding 1 because of reserved 0 indexvocab_size=len(tokenizer.word_index)+1# Pad sequences with zerosX_train=pad_sequences(X_train,padding='post',maxlen=maxlen)X_test=pad_sequences(X_test,padding='post',maxlen=maxlen)# Parameter grid for grid searchparam_grid=dict(num_filters=[32,64,128],kernel_size=[3,5,7],vocab_size=[vocab_size],embedding_dim=[embedding_dim],maxlen=[maxlen])model=KerasClassifier(build_fn=create_model,epochs=epochs,batch_size=10,verbose=False)grid=RandomizedSearchCV(estimator=model,param_distributions=param_grid,cv=4,verbose=1,n_iter=5)grid_result=grid.fit(X_train,y_train)# Evaluate testing settest_accuracy=grid.score(X_test,y_test)# Save and evaluate resultsprompt=input(f'finished {source}; write to file and proceed? [y/n]')ifprompt.lower()notin{'y','true','yes'}:breakwithopen(output_file,'a')asf:s=('Running {} data set\nBest Accuracy : ''{:.4f}\n{}\nTest Accuracy : {:.4f}\n\n')output_string=s.format(source,grid_result.best_score_,grid_result.best_params_,test_accuracy)print(output_string)f.write(output_string)

This takes a while which is a perfect chance to go outside to get some fresh air or even go on a hike, depending on how many models you want to run. Let’s take a look what we have got:

Running amazon data setBest Accuracy : 0.8122{'vocab_size': 4603, 'num_filters': 64, 'maxlen': 100, 'kernel_size': 5, 'embedding_dim': 50}Test Accuracy : 0.8457Running imdb data setBest Accuracy : 0.8161{'vocab_size': 4603, 'num_filters': 128, 'maxlen': 100, 'kernel_size': 5, 'embedding_dim': 50}Test Accuracy : 0.8210Running yelp data setBest Accuracy : 0.8127{'vocab_size': 4603, 'num_filters': 64, 'maxlen': 100, 'kernel_size': 7, 'embedding_dim': 50}Test Accuracy : 0.8384

Interesting! For some reason the testing accuracy is higher than the training accuracy which might be because there is a large variance in the scores during cross-validation. We can see that we were still not able to break much through the dreaded 80%, which seems to be a natural limit for this data with its given size. Remember that we have a small data set and convolutional neural networks tend to perform the best with large data sets.

Another method for CV is the nested cross-validation (shown here) which is used when the hyperparameters also need to be optimized. This is used because the resulting non-nested CV model has a bias toward the data set which can lead to an overly optimistic score. You see, when doing hyperparameter optimization as we did in the previous example, we are picking the best hyperparameters for that specific training set but this does not mean that these hyperparameters generalize the best.

Conclusion

There you have it: you have learned how to work with text classification with Keras, and we have gone from a bag-of-words model with logistic regression to increasingly more advanced methods leading to convolutional neural networks.

You should be now familiar with word embeddings, why they are useful, and also how to use pretrained word embeddings for your training. You have also learned how to work with neural networks and how to use hyperparameter optimization to squeeze more performance out of your model.

One big topic which we have not covered here left for another time was recurrent neural networks, more specifically LSTM and GRU. Those are other powerful and popular tools to work with sequential data like text or time series. Other interesting developments are currently in neural networks that employ attention which are under active research and seem to be a promising next step since LSTM tend to be heavy on the computation.

You have now an understanding of a crucial cornerstone in natural language processing which you can use for text classification of all sorts. Sentiment analysis is the most prominent example for this, but this includes many other applications such as:

  • Spam detection in emails
  • Automatic tagging of texts
  • Categorization of news articles with predefined topics

You can use this knowledge and the models that you have trained on an advanced project as in this tutorial to employ sentiment analysis on a continuous stream of twitter data with Kibana and Elasticsearch. You could also combine sentiment analysis or text classification with speech recognition like in this handy tutorial using the SpeechRecognition library in Python.

Further Reading

If you want to delve deeper into the various topics from this article you can take a look at these links:


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]


Stories in My Pocket: Pathlib: my new favorite module

$
0
0

Though `pathlib` was introduced in python 3.4 to some praise, I didn't "get" it. Like many things in python, I needed some time to come around and tinker with it before I realized the power within. I can't remember when `pathlib` started "clicking" for me, but I'm sure it was an accidental rediscovering of it via the Dash documentation application. [dsh]{More on Dash in an upcoming post}

I've found it incredibly helpful in a number of ways, and I hope to pass along some helpful tips if you haven't started using it yet.

You don't have to think as hard

Pretty much every time I needed to configure media or static folders for Django or read some config files, I would have a mental block, frequently asking myself, "What are the `os.path` commands for getting the absolute path of a folder?"

I had to look it up every time, and it just wouldn't stick, but this is usually how I would do it:

local_project_dir = os.path.abspath(os.path.dirname(__file__))

Now, `pathlib` allows me to find a folder in a similar way that I would through the command line:

local_project_path = Path(__file__, '..').resolve()

I also appreciate the flexibility `pathlib` gives you to do the same thing in a couple of ways, to give you freedom to find what works better for you.

All these return a path to the same place:


Path(__file__ , '..').resolve()
(Path(__file__) / '..').resolve()
Path(__file__).parent.absolute()

I find I don't have to think as hard to process this syntax, as compared to the `os.path` syntax above.

Built-in conveniences

I also love how `pathlib` bundles actions into a `Path`. In particular, one doesn't need to create a indented block of code to read its contents. I'm thankful to not have to think about whether I want to iterate over a file's lines, or just read it in.

with open('readfile.txt') as f:
    data = f.read()

Now, you can read all the data in one line [read_text]{documentation}:

data = Path('readfile.txt').read_text()

Don't get me wrong, I think the `open` block is still very much needed, especially when you want to have fine control over when a file is open, or if you need to iterate over a large file. But when you just need to get the decoded contents,[bytes]{Or if you need the bytes, you can do that too.} it sure is nice to have a quick method to do so.

A real-world example

Today, I found out that some of the images in one of my projects were not aligning with the content. I realized I could create a contact sheet of images to help us find the problem images. However, the images were named after the id of the content they were relating to, and there's no easy way for a human to connect those ids to the product.

But we have an API that takes an id and returns the details of the product, including its name. So I was confident I could quickly whip up a little python script to convert the names of each photo to its product name.

from pathlib import Path
import requests


IMG_DIR = Path('C:/Users/chmay/Desktop/pdfcovers')


def id_to_name(id: str):
    url = f'https://stage.projectloc.net/api/v1/products/{id}'
    headers = {
        'accept': 'application/json'
    }
    r = requests.get(url, headers=headers)
    r.raise_for_status()
    return r.json()['name']


def main(path:Path):
    for item in path.glob('*.jpg'):
        id = Path(item).stem
        name = id_to_name(id)
        item.rename(Path(f'{IMG_DIR}/{name}.jpg'))


if __name__ == '__main__':
    main(IMG_DIR)

For those learning python, I'll explain what's going on.

The top imports the dependencies, in this case the `Path` object from `pathlib`, and the `requests` module that I installed into my project's virtual environment.[venv]{Reach out to me if this is hard to understand. Virtual environments are hard to understand when starting out... and many times after that.}

Next, I'm setting a variable (`IMG_DIR`) that points to the folder the images are in. This script uses this variable as a default setting that I could overwrite it if I wanted to use it again somewhere else.

Following that is the function called `id_to_name`. It takes in the id of the product, uses `requests` to get the information, and returns the product's name.

Next up is `main`, where `pathlib` shines.

  • I set up a loop to iterate over all the `jpg` files in the folder[glob]{glob docs}
  • Then get the id from the file name[stem]{stem docs}.
  • Pass that id in to the `id_to_name` function
  • And then rename the photo[rename]{rename docs}.

Finally the last two lines of code are what kicks off the process, but only when I run this file from the command line like this:

python rename_images.py
It's your turn!

I have shown this module to a number of people at work, and like me, none of them realized the greatness that lies within. So I encourage you to open up the documentation,[again]{Or if you're on a Mac purchase Dash.} a REPL or your favorite IDE, and mess around with it.

Thanks!

Thanks go to Trey Hunner, who sent out an email in praise of `pathlib` the other day, and it was a reminder that I hadn’t finished this entry.

PyCharm: PyCharm 2018.3 EAP 8

$
0
0

Our Early Access Preview (EAP) program continues, and the eighth preview version of PyCharm 2018.3 is now available. Download now from our website

New in This Version

Customizable SQL Aliases

Join Aliases

You may have seen that PyCharm Professional Edition automatically completes SQL join statements for you, and chooses aliases for your tables. You can now customize what aliases PyCharm uses for your tables in the settings.

If you haven’t tried using the database integration in PyCharm Professional Edition, be sure to check it out.

Further Improvements

  • We had a bug where the Python console would stop working when importing matplotlib 3.0.0, this has been resolved.
  • Copying a line into a JSON file will now automatically add a comma to the preceding line, if necessary. Editing HTML, JavaScript, and JSON are included in PyCharm Professional Edition only. PyCharm Professional Edition bundles all functionality from WebStorm, our JavaScript IDE.
  • An issue that would cause Docker Compose-based Python interpreters to stop working when updating PyCharm has been resolved.
  • Did you read about the new copyright feature last week, and are you interested in reading more about it? We’ve updated the documentation
  • And much more, check out our release notes

Interested?

Download this EAP from our website. Alternatively, you can use the JetBrains Toolbox App to stay up to date throughout the entire EAP.

If you’re on Ubuntu 16.04 or later, you can use snap to get PyCharm EAP, and stay up to date. You can find the installation instructions on our website.

PyCharm 2018.3 is in development during the EAP phase, therefore not all new features are already available. More features will be added in the coming weeks. As PyCharm 2018.3 is pre-release software, it is not as stable as the release versions. Furthermore, we may decide to change and/or drop certain features as the EAP progresses.

All EAP versions will ship with a built-in EAP license, which means that these versions are free to use for 30 days after the day that they are built. As EAPs are released weekly, you’ll be able to use PyCharm Professional Edition EAP for free for the duration of the EAP program, as long as you upgrade at least once every 30 days.

Python Bytes: #101 Nobel Prize awarded to a Python convert

Talk Python to Me: #183 Qt for Python

$
0
0
Python is taking over much of the development world as it quickly is becoming one of the, or simply the most widely used programming languages. But that does not mean that Python is without its weaknesses. In my mind, there are three such weaknesses: #1 GUIs applications, #2 Native, general purpose mobile apps (iOS and Android), #3 deployment as a single binary or set of binary and resource files.

Mike Driscoll: Python 101: Episode #30 – The configobj Package

Viewing all 22645 articles
Browse latest View live