<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Glose Engineering - Medium]]></title>
        <description><![CDATA[Stories from the Glose team - Medium]]></description>
        <link>https://medium.com/glose-team?source=rss----9af43e5ffc08---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Glose Engineering - Medium</title>
            <link>https://medium.com/glose-team?source=rss----9af43e5ffc08---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 15 May 2026 15:45:32 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/glose-team" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[A glowing progress ring with rounded ends for Android]]></title>
            <link>https://medium.com/glose-team/a-glowing-progress-ring-with-rounded-ends-for-android-865eb0161cc1?source=rss----9af43e5ffc08---4</link>
            <guid isPermaLink="false">https://medium.com/p/865eb0161cc1</guid>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Andréas Saudemont]]></dc:creator>
            <pubDate>Mon, 10 Aug 2020 12:53:12 GMT</pubDate>
            <atom:updated>2020-08-10T12:53:12.419Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O7-5QM-eUlX49BZl1Q8PXg.png" /></figure><p><em>How we created a custom Android view which draws a glowing progress ring with rounded ends.</em></p><p>With <a href="https://play.google.com/store/apps/details?id=com.glose.android">Glose</a> you can set a goal of reading a given number of pages each day. You can then follow your daily progress with the daily goal widget on the home screen:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*gN2fHgW4ZvlypBaaliIwbA.png" /><figcaption>When you haven’t reached your daily goal yet</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*SnErZXro11qrzz_LnnTvSw.png" /><figcaption>When you’ve reached or exceeded your daily goal</figcaption></figure><p>The first implementation of the widget used a circular <a href="https://developer.android.com/reference/android/widget/ProgressBar">ProgressBar</a> to represent the progress ring. This did the job but missed a few features we wanted:</p><ol><li>The ends of the progress ring should be rounded. With the circular ProgressBar the ends are flat and do not match the overall style of the app.</li><li>We should be able to use any drawable for the progress ring, like a gradient for instance, and not be limited to a solid color.</li><li>The progress ring should project a glow when you’ve exceeded your goal. The more the goal has been exceeded, the bigger the glow effect.</li></ol><p>With these features in mind, we set out to update our implementation of the daily goal widget.</p><h3>Rounding the ends</h3><p>We couldn’t find a way to create a drawable for the ProgressBar that would draw rounded ends on the progress ring. So we replaced the ProgressBar with a custom view inspired by <a href="https://stackoverflow.com/a/53830379/276074">this StackOverflow answer</a>.</p><p>This custom view sets up the two Paint objects that will be used to draw the ring:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0b5c9453fa48388606f4ace8ff01778c/href">https://medium.com/media/0b5c9453fa48388606f4ace8ff01778c/href</a></iframe><p>ringPaint is configured with the <a href="https://developer.android.com/reference/android/graphics/Paint.Cap#ROUND">ROUND</a> stroke cap, which is how the progress ring will get its ends rounded.</p><p>The drawing of the ring is then performed by the onDraw() override using simple <a href="https://developer.android.com/reference/android/graphics/Canvas#drawArc(android.graphics.RectF,%20float,%20float,%20boolean,%20android.graphics.Paint)">drawArc()</a> primitives:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4e904039d2741dc94e9d9475eabf13ee/href">https://medium.com/media/4e904039d2741dc94e9d9475eabf13ee/href</a></iframe><p>arcRect is a RectF that caches the bounds of the arc used to draw the ring. It is updated each time onSizeChanged() is called:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/73f22a5a8224e4d0d42331557095c277/href">https://medium.com/media/73f22a5a8224e4d0d42331557095c277/href</a></iframe><p>And that’s all it takes to get nice rounded ends:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*BeYTDheIvWGTQakThFsaIw.png" /><figcaption>Look ma, rounded ends!</figcaption></figure><h3>Using a drawable for the progress ring</h3><p>The progress ring is also used in a popup panel showing historical data. In this popup the ring must be drawn using a gradient that is lighter at the top and darker at the bottom.</p><p>To achieve this, we’ve updated the onDraw() method so that the arc it draws acts as a mask on top of the ring drawable:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7e1ae7bac727573850593321fca4ba5c/href">https://medium.com/media/7e1ae7bac727573850593321fca4ba5c/href</a></iframe><p>The key here is ringMaskPaint, which is used as the paint for the second <a href="https://developer.android.com/reference/android/graphics/Canvas#saveLayer(android.graphics.RectF,%20android.graphics.Paint)">Canvas.saveLayer()</a> call. It is configured with the <a href="https://developer.android.com/reference/android/graphics/PorterDuff.Mode#MULTIPLY">PorterDuff.Mode.MULTIPLY</a> <a href="https://developer.android.com/reference/android/graphics/Xfermode">transfer mode</a>, and as a result anything that is drawn in this layer (the progress ring) will act as a mask over what is drawn in the layer below (the ring drawable). See <a href="http://ssp.impulsetrain.com/porterduff.html">Porter/Duff Compositing and Blend Modes</a> by Søren Sandmann Pedersen for an illustrated explanation of these concepts.</p><p>The onSizeChanged() method is also modified to adjust the bounds of ringDrawable appropriately:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8097eabc5212d488ab289de4351a6a10/href">https://medium.com/media/8097eabc5212d488ab289de4351a6a10/href</a></iframe><p>And here is the result:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/451/1*GcUX_3t65rLwJvsdBUPf9Q.png" /><figcaption>Using a gradient to draw the ring</figcaption></figure><h3>Adding the glow effect</h3><p>The progress ring is expected to glow when you’ve exceeded your reading goal, and the more you’ve exceeded your goal, the more the ring glows. We achieve this in the onDraw() override by drawing an arc with a Paint object configured to draw a <a href="https://developer.android.com/reference/android/graphics/Paint#setShadowLayer(float,%20float,%20float,%20int)">shadow layer</a> below the arc. We’ve also added the following attributes on the custom view:</p><ul><li>inset defines the dimension by which the progress ring is inset inside the view bounds to leave space for the glow effect.</li><li>glowColor is the color used to draw the glow effect.</li><li>glowPeak is the ratio beyond maxProgress where the glow effect reaches its peak.</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/99d3f188accab97afa339311d5699198/href">https://medium.com/media/99d3f188accab97afa339311d5699198/href</a></iframe><p>And that is what it looks like in the daily goal widget:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*BS1_60cJi43iypCUoF15hw.png" /><figcaption>The ✨ glow ✨</figcaption></figure><h3>Animating the ring and putting it all together</h3><p>Lastly, we added a setProgressAnimated() method on the custom view that we use to animate the progress ring from zero to its current value. This uses a <a href="https://developer.android.com/reference/android/animation/ValueAnimator">ValueAnimator</a> that updates the progress value from zero to the desired value. The duration of the animation is adjusted so that the progress ring grows approximately at the same speed across the progress value spectrum.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dc8fe9f60522c20962c2b9d4f414ba2d/href">https://medium.com/media/dc8fe9f60522c20962c2b9d4f414ba2d/href</a></iframe><p>This is how the progress ring is animated in the popup panel:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*5k8daZ0waBmrdZ_F7jtecQ.gif" /></figure><p>And that is how it is animated in the daily goal widget:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*MN07dVh2FgpKHajsEEr8uQ.gif" /></figure><p>Here’s the same animation in slow-motion:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*NRt2uHzDNomNanEvRtzSEA.gif" /></figure><p>Curious about what it looks like in real life? Go ahead and install <a href="http://play.google.com/store/apps/details?id=com.glose.android">Glose for Android</a>, and let us know what you think!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=865eb0161cc1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/glose-team/a-glowing-progress-ring-with-rounded-ends-for-android-865eb0161cc1">A glowing progress ring with rounded ends for Android</a> was originally published in <a href="https://medium.com/glose-team">Glose Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Getting offline with WorkManager in a Redux world]]></title>
            <link>https://medium.com/glose-team/getting-offline-with-workmanager-in-a-redux-world-327f39f96e2b?source=rss----9af43e5ffc08---4</link>
            <guid isPermaLink="false">https://medium.com/p/327f39f96e2b</guid>
            <category><![CDATA[redux]]></category>
            <category><![CDATA[workmanager]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Alexandre Bruneau]]></dc:creator>
            <pubDate>Wed, 13 May 2020 08:34:56 GMT</pubDate>
            <atom:updated>2020-05-13T08:34:56.605Z</atom:updated>
            <content:encoded><![CDATA[<p>Hey friends,</p><p>We (the Glose’s Android team) wanted to share with you some of the latest fun we had developing. As an e-book reader getting offline is an important part of our app, we were already able to bring our books off the grid, but we wanted to improve how our users can add annotations, reactions and highlights to their favorite books even without internet connection. After some thinking we decided to go with the new <strong>WorkManager</strong> library and to see where it leads us. We ended up with something we found interesting and wanted to share it with you and have your thoughts on it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/284/1*7OuJGmqt7LwPEwhaJ6ijWQ.png" /><figcaption>an offline annotation (the shaded one) and a synchronized one</figcaption></figure><h3>Intro to Redux</h3><p>First of all a quick recap for those who aren’t familiar with the <strong>Redux</strong> Architecture which is the architecture we are using for our Android app and it will have its importance. It’s an architecture coming from JavaScript and React world.</p><p>It is mainly based on 6 kinds of components:</p><ol><li><strong>Store</strong>: is holding an immutable instance of the app state</li><li><strong>State</strong>: an immutable instance describing the state of the application</li><li><strong>Actions</strong>: are representing how we want to change State</li><li><strong>Reducer</strong>: a pure function that creates a new state given a previous state and a dispatched action.</li><li><strong>Middleware</strong>: are components that also intercept actions like Reducers but their goal is to perform other actions than changing state like deleting files, dispatching others actions, logging etc. For example most of our job on WorkManager will happen in a middleware</li><li><strong>UI</strong>: is all your views, components that are listening for state modification and rendering accordingly to those changes. They also trigger actions on user interactions.</li></ol><p>Main benefits of this are the single source of truth coming from Store and States, we have a simple <strong>unidirectional data flow</strong>, and all our components are easy to test.</p><p>I won’t be covering any details on how to implement Redux on your Android app here but if you want to learn more about it I recommend some nicely written articles about it at the end of this article.<br>However here it’s important because the abstraction given by Reducers will make the offline switch way easier.</p><h3>Intro to WorkManager</h3><p>WorkManager is a library part of Android Jetpack responsible for covering background’s work. There are many reasons that make WorkManager a strong tool, I will try to give you a short list of them:</p><ul><li>It offers a unified interface and is backwards compatible up to <strong>API 14</strong>, that’s been said you won’t have to choose between JobScheduler and AlarmManager.</li><li>It’s highly configurable and really easy to use: you can both use one-time tasks or periodic tasks, you can constrain your task to only be executed on some conditions depending on battery status and in our case network status. You can specify backoff criteria, set delay and more.</li><li>It deals with power-saving features so we won’t have to think about it.</li><li>As the documentation states: <strong>“WorkManager ensures task execution”</strong> you are sure your job will be done one day, that’s the complicated part you can’t really know when it happens.</li><li>It also supports task chaining (we won’t be using here but can be very convenient)</li></ul><p>WorkManager is a very powerful toolbox for background processing but considering the fact you can’t know when it will run it might not be the best choice to run network requests initiated by UI actions. That’s where the Redux magic does the trick, and some thinking, the reason we could choose WorkManager for our problem was the fact we won’t apply it to all our requests but only for some user’s inputs like posting quotes, highlights and such.</p><p>Let’s jump into some code now that basic logic has been set.</p><h3>What we had before</h3><p>To understand how we achieve our actions independently of the current network connectivity let’s see what we had before using the Redux pattern. To keep this short I will only use the annotation’s case because it’s pretty much the similar process for other parts.</p><p>What we call an Annotation in Glose is adding a comment on part of a quote from a book.</p><p>So somewhere in our UI in a click listener we had this line:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d07d406d2d9d6d3f6e1097dc15074295/href">https://medium.com/media/d07d406d2d9d6d3f6e1097dc15074295/href</a></iframe><p>This a typical Redux call where we dispatch an Action (here the Post action), this will produce two things:</p><ol><li>Middlewares that are listening for this kind of action will do their job. Here as Post is a particular kind of action of our own (a RequestAction) it will perform the corresponding HTTP call (I’m simplifying a little bit but those are the large lines).</li><li>The action will be reduced, that means if one or more reducers listen for this kind of action the associated states will change accordingly to the reducer effect. At this time there was no reducer for this action.</li></ol><p>Let’s have a look at Post implementation:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3a4ff2d34b3ca475d958bf8ad7219f43/href">https://medium.com/media/3a4ff2d34b3ca475d958bf8ad7219f43/href</a></iframe><p>Basically Post described the Retrofit call to perform and what to do on success and on error. Note here that we also dispatch results via a Redux action so there will have a reducer changing the app state according to the newly posted annotation like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f8499f164da2cc91cec6ee1f3871796b/href">https://medium.com/media/f8499f164da2cc91cec6ee1f3871796b/href</a></iframe><p>This what a reducer looks like, it just creates a new state depending on the action being dispatched. And then the UI listening to those changes could render them (not really interesting here).</p><p>Here is what happened:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/624/1*X-zcvse671Q3hwuHWzVBLg.png" /></figure><h3>What we have now</h3><p>So the goal is to add some magic somewhere with the WorkManager to free ourselves from network connectivity status. To do so we are going to wrap the HTTP call inside a job for the WorkManager and have an optimistic approach and proceed immediately as if it was a success.</p><p>Let’s see what changed from the last chapter.</p><p>First of all we now have:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8d2b6656e797c7b461b0780d7a36115f/href">https://medium.com/media/8d2b6656e797c7b461b0780d7a36115f/href</a></iframe><p>So here instead of directly dispatching the Post action we are dispatching an Enqueue action that will enqueue an offline action with some dedicated “ActionParams” as described below:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3d515f3a3ffa07f35c4eed0d8f2986ee/href">https://medium.com/media/3d515f3a3ffa07f35c4eed0d8f2986ee/href</a></iframe><p>The local id is used to identify offline content (not synchronized yet), it’s also used if we need to identify a job to cancel (for opposite operations like post and then delete post etc. which will be defined in each implementation). And the buildAsyncAction() function will be used to create the actual request action to perform using the params in each implementation. So let’s add those action params to our previously described Post:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c53f685c8dee5214b808ab5ff537a85d/href">https://medium.com/media/c53f685c8dee5214b808ab5ff537a85d/href</a></iframe><p>So we moved all parameters from the request action class to the <strong>Params</strong> and this last one is able to build the source action when buildAsyncAction() will be called (in the worker, it’s coming soon I swear).</p><p>But what really happens when we dispatch the Enqueue action with those params? This is where the Redux magic happens :) This is operated by two kind of components:</p><h4>Reducer</h4><p>A reducer watching for the Enqueue action in the optimistic way to simulate this Post. Previously no reducer were watching for the post action, but now we have one watching for the Enqueue with some Post parameter:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/292dab6d9974dd6fe30f81cdd7b75285/href">https://medium.com/media/292dab6d9974dd6fe30f81cdd7b75285/href</a></iframe><p>You guess the logic is pretty much the same and mostly shared between “boring internal logic” I will skip the detail but we had to add a cleaning offline shreds in the post annotation to keep our states clean and the fully new one was really the same than what was done before except we faked items coming from the api with some local data (mostly local id we were talking about before).</p><p>If we only kept to this part everything would have happened locally and so far our users wouldn’t be able to tell the difference between before and now on their device locally.</p><h4>Middleware</h4><p>As described before a middleware is in charge of different kinds of behaviors that are not changing states (that’s the reducer’s responsibility) so here it will be in charge of managing our WorkManager.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3ee33d4612949b7ee054f53343508b06/href">https://medium.com/media/3ee33d4612949b7ee054f53343508b06/href</a></iframe><p>So when it catches an offline action we create a one-time work request (no need for a periodic one here :D ) send the parameters data through the input data logic so that the worker can have access to them and use them (in next code sample), we specify a backoff criteria to retry if something goes wrong, and finally we add the network constraint. We can enqueue this work request as a unique work with the local id generated before. Last lines are here if we want to cancel a job in some cases (changing highlight colors etc.). I won’t talk too much about this but you can cancel work this way :)</p><p>This will enqueue a work to be done somehow when constraint will be satisfied and here is the worker:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bf1d16ed4d69f10210c370cbee1b2098/href">https://medium.com/media/bf1d16ed4d69f10210c370cbee1b2098/href</a></iframe><p>So our worker first grabs the previously serialized params, those two lines aren’t perfect but they seriously do the job. As we are supposed to have network connectivity now we can dispatch our request through a request action just like before so here in our sample:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b009a15f80e29be396ca356a4a06097c/href">https://medium.com/media/b009a15f80e29be396ca356a4a06097c/href</a></iframe><p>Is the equivalent of:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d07d406d2d9d6d3f6e1097dc15074295/href">https://medium.com/media/d07d406d2d9d6d3f6e1097dc15074295/href</a></iframe><p>And we wait for the the result (asyncAction.awaitCompletion() is a wrapper for a deferred.await()) thanks to deferred job and coroutines (might develop this part in another article if you are interested in), but notice that your work can have three kind of results:</p><ul><li>Success obviously means that job has been completed successfully, in case of chained work, those whose depend on this can be executed.</li><li>Retry mean this one failed but should be retried when possible (because of a transient network failure or an 5xx HTTP error from the backend, for instance).</li><li>Failure means this is a permanent failure, so every work depending on this one will be a failure also.</li></ul><p>As we dispatched the same action than before the migration to WorkManager we didn’t have to change what happened next on success though Redux logic ;)</p><p>To give you an overview of what happened here is a schema, a little bit more complex than before maybe:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/623/1*6x4WmTJ-Uk-Oi68Ol33l7g.png" /></figure><h3>Conclusion</h3><p><strong>Combining</strong> brand new <strong>WorkManager</strong> and <strong>Redux</strong> we ended up in this which gives us an improvement on how we are behaving without network connectivity. From what we have seen when you enqueue your work request, most of the time, it’s done immediately but when you get in a state where you match your constraints it’s more random this is the bad part of this approach on the problem. Knowing this point it matches our needs so far and works pretty well on that kind of feature. In this article we only take the annotation process as an example but it worked the very same way for Highlights (quoting text with highlight color) and Reactions.</p><p>If you have any questions, or feedback on this let us know in the comments. Thanks for reading have a nice day.</p><h3>Articles about Redux:</h3><p><a href="https://hackernoon.com/lessons-learned-implementing-redux-on-android-cba1bed40c41">https://hackernoon.com/lessons-learned-implementing-redux-on-android-cba1bed40c41</a></p><p><a href="https://medium.com/@trikita/writing-a-todo-app-with-redux-on-android-5de31cfbdb4f">https://medium.com/@trikita/writing-a-todo-app-with-redux-on-android-5de31cfbdb4f</a></p><p><a href="https://jayrambhia.com/blog/android-redux-intro">https://jayrambhia.com/blog/android-redux-intro</a></p><p><a href="https://netflixtechblog.com/making-our-android-studio-apps-reactive-with-ui-components-redux-5e37aac3b244">https://netflixtechblog.com/making-our-android-studio-apps-reactive-with-ui-components-redux-5e37aac3b244</a></p><h3>Articles about WorkManager:</h3><p><a href="https://androidwave.com/android-workmanager-tutorial/">https://androidwave.com/android-workmanager-tutorial/</a></p><p><a href="https://medium.com/androiddevelopers/introducing-workmanager-2083bcfc4712">https://medium.com/androiddevelopers/introducing-workmanager-2083bcfc4712</a></p><p><a href="https://developer.android.com/topic/libraries/architecture/workmanager">https://developer.android.com/topic/libraries/architecture/workmanager</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=327f39f96e2b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/glose-team/getting-offline-with-workmanager-in-a-redux-world-327f39f96e2b">Getting offline with WorkManager in a Redux world</a> was originally published in <a href="https://medium.com/glose-team">Glose Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to evaluate readers text comprehension?]]></title>
            <link>https://medium.com/glose-team/how-to-evaluate-readers-text-comprehension-7c8892cf2c13?source=rss----9af43e5ffc08---4</link>
            <guid isPermaLink="false">https://medium.com/p/7c8892cf2c13</guid>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Lucas Willems]]></dc:creator>
            <pubDate>Fri, 19 Jul 2019 12:09:28 GMT</pubDate>
            <atom:updated>2019-10-22T11:11:29.934Z</atom:updated>
            <content:encoded><![CDATA[<p>Our mission at <a href="https://glose.com">Glose</a> is to <strong>make reading better</strong>, which starts by ensuring that readers understand what they read. In this post, I will be presenting a system that automatically generates a comprehension test for any input text. The automatic part is particularly important given the size of our corpus (<a href="https://glose.com/bookstore">1M+ books</a>), which does not allow manual test writing.</p><h3>Text comprehension tests</h3><p>A <em>text comprehension test</em> is a set of questions regarding a text, that allow to evaluate reader’s text comprehension. For example, for this paragraph of <a href="https://glose.com/book/the-little-prince-2">The Little Prince</a>, which does not lack irony:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*bcOORnht2ACYk3tl" /></figure><p>a text comprehension test could be:</p><ol><li>Which job did the author originally choose?</li><li>What did the grown-ups advise him to do?</li><li>Which job did he finally choose?</li><li>Was what he had studied useful for this job?</li></ol><p>However, generating such questions and correcting them is very challenging and still an open research problem (<a href="https://software.intel.com/en-us/articles/using-natural-language-processing-for-smart-question-generation">[1]</a>, <a href="https://www.aclweb.org/anthology/W11-1407">[2]</a>, <a href="https://arxiv.org/pdf/1809.02393v2.pdf">[3]</a>, <a href="https://arxiv.org/pdf/1808.04961.pdf">[4]</a> or <a href="https://arxiv.org/pdf/1905.08949.pdf">[5]</a>). For a start, we decided to only generate a specific kind of tests, called <em>cloze tests</em>.</p><p><strong>Cloze tests.</strong> A <em>cloze test</em> is a text where some words are hidden. Usually, 4 answer propositions (1 true, 3 wrong) are given for every hidden word. Here is an example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*WuTnKN72rB74HbnI" /></figure><p>Generating and correcting cloze tests is easier than generating and correcting other types of tests where questions may be open, while still giving a good evaluation of text comprehension. However, there still are some difficulties:</p><ol><li>Which words to hide? Ideally, we would like to hide a set of words that is small and contains <strong>the most important words</strong> for text comprehension.</li><li>Which <em>distractors</em> (i.e. relevant incorrect words) to propose? We can neither propose words that are obviously wrong, nor words that could be correct in the context (e.g. synonyms).</li></ol><p>Now that we have explained the objective and the main difficulties, let us dive into the technical solution that we have developed.</p><h3>Step 1: Which words to hide?</h3><p>The first step in making a cloze test consists in selecting which words to hide. Here is the process we follow:</p><p><strong>Step 1.a.</strong> We have decided to <strong>only hide common nouns</strong> because, along with verbs, they contain most of the meaning of a text.</p><p><strong>Step 1.b.</strong> We <strong>give an importance score </strong><strong>i to every common noun</strong>, based on the assumption that:</p><blockquote>The more difficult it is to guess a hidden word, the more important it is.</blockquote><p>or to put it differently:</p><blockquote>The easier it is to guess a word after hiding it, the less important it is.</blockquote><p>In practice, we use BERT (<a href="https://arxiv.org/pdf/1810.04805.pdf">[6]</a>), a deep neural network developed by Google, to predict the nouns after hiding them (more precisely, we use <a href="https://github.com/huggingface/pytorch-transformers">this Pytorch implementation</a>). BERT was in part trained to infill cloze tests. This makes it an algorithm of choice for the task we are describing here. Let us see how we can use it to give an importance score to every common noun in this sentence:</p><pre>s = “I love playing tennis with my cat.”</pre><p>The first common noun is tennis. If we hide it (or mask it), the sentence becomes:</p><pre>s_tennis = “I love playing [MASK] with my cat.”</pre><p>Then, when we feed s_tennis to BERT which tries to predict the masked word. For this, it outputs a prediction score p_w for every word w in its vocabulary:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1d3554c708afe007cf7d150b608eb0e6/href">https://medium.com/media/1d3554c708afe007cf7d150b608eb0e6/href</a></iframe><p>The higher the score of a word, the more BERT thinks it is the correct one. From these prediction scores, BERT misprediction of tennis can be evaluated with the following formula: (max p_w) — p_tennis. Hence, following our <a href="#ff46">previous assumption</a>, it gives the definition of the importance of tennis in the sentence:</p><pre>i_tennis = (max p_w) - p_tennis</pre><p>Numerically, i_tennis = 3.205.</p><p>The same process can be done for cat, the second common noun of the sentence, i.e. :</p><ol><li>Hiding (or masking) cat in s giving s_cat.</li><li>Feeding s_cat to BERT and retrieving its prediction scores for every word in the vocabulary.</li><li>Computing i_cat. Numerically, i_cat = 8.327.</li></ol><p>In this example, a higher importance score had been given to cat than to tennis because it is a much more unexpected word.</p><p><strong>Step 1.c.</strong> We <strong>remove common nouns that are not important enough</strong>, i.e. common nouns with an importance score lower than a fixed threshold i_min. Indeed being able to guess them is not a good indicator of text understanding. We experimented with different values of i_min and ended up taking i_min = 2.5 because it gave us the best filtration.</p><p><strong>Step 1.d.</strong> We only <strong>keep the </strong><strong>X best common nouns</strong>, and finally hide them. Because we do not want too many words of the text to be hidden, we limit the ratio by taking X = number of words x r_max. Again, we experimented with different values of r_max = 0.05 because it gave us the best results.</p><h3>Step 2: Which distractors to propose?</h3><p>The second step consists in proposing distractors for every hidden word. We want them to be neither trivially wrong, nor correct in the context (e.g. synonyms). Let us use again the sentence s_tennis = &quot;I love playing [MASK] with my cat.&quot; to present how we propose distractors for the hidden word tennis:</p><p><strong>Step 2.a. </strong>We only <strong>keep predictions that are not the correct word or its singular / plural</strong>. If the correct word is among the distractors, then twice the same word will appear in the propositions, making it obvious it is the answer. If the singular or plural of the correct word is among the distractors, the answer also becomes obvious.</p><p><strong>Step 2.b.</strong> We only <strong>keep predictions with the same <em>casing</em> as the correct word</strong>. The <em>casing</em> of a word is either <em>lowercase</em> when all the letters of the word are lowercased, or <em>title</em> when the first letter is uppercased and the others lowercased, or <em>other</em>. We do this not to get distractors that are trivially wrong (e.g. if the hidden word is the first one of a sentence and the distractors are not in the <em>title</em> casing).</p><p>In our example, tennis is lowercased, so we will only keep distractors in this casing.</p><p><strong>Step 2.c.</strong> We <strong>order the remaining predictions by their prediction scores in decreasing order</strong>.</p><p>In our example, the best remaining predictions when hiding tennis are:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5037bdcf189763e9dd7f49ed9e8f4ec0/href">https://medium.com/media/5037bdcf189763e9dd7f49ed9e8f4ec0/href</a></iframe><p><strong>Step 2.d.</strong> We <strong>keep the 3 predictions with the best scores that are constantly spaced</strong>, i.e. we take the prediction with the best score, p_1, then the prediction with the best prediction score, p_2, such that p_2 &lt; p_1 — p_gap, then the prediction with the best score, p_3, such that p_3 &lt; p_2 — p_gap. Spacing predictions prevents having distractors that are synonyms because synonyms usually get the same prediction score. It also helps to have distractors with a higher diversity of meaning. We experimented with different values of p_gap and ended up taking p_gap = 2 because it gave the best results.</p><p>In our example, the 3 predictions with the best scores spaced by 2 are:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1cfba8eb5fa553ec9de8d410e7f05ae7/href">https://medium.com/media/1cfba8eb5fa553ec9de8d410e7f05ae7/href</a></iframe><h3>Conclusion</h3><p>To sum up, the process we follow to generate cloze tests is two-fold:</p><ol><li><strong>Selecting which words to hide</strong>. The main idea is to assume that <em>the more difficult it is to guess a word after hiding it, the more important it is</em>.</li><li><strong>Proposing distractors for every word to hide</strong>. The main idea is to take the predictions with the best scores that are constantly spaced.</li></ol><p>This process leads to cloze tests that are satisfactory, although not optimal. Here are possible improvements:</p><ol><li>A new deep neural network, XLNet (<a href="https://arxiv.org/pdf/1906.08237.pdf">[7]</a>), developed by researchers from CMU and Google Brain, has been released one month ago. It outperforms BERT on numerous tasks including cloze tests infilling. Replacing BERT by XLNet could lead to more relevant predictions. However, XLNet is only available for english language in contrary to BERT that has a multilingual version.</li><li>A new paper (<a href="https://arxiv.org/pdf/1906.04980.pdf">[8]</a>), from Facebook researchers, presents a way to turn a cloze test into a series of questions. Adding it to our process could lead to an improved version of cloze tests where hidden words are replaced by questions.</li></ol><h3>References</h3><p><a href="https://software.intel.com/en-us/articles/using-natural-language-processing-for-smart-question-generation">[1] Smart Question Generation</a></p><p><a href="https://www.aclweb.org/anthology/W11-1407">[2] Automatic Gap-fill Question Generation from Text Books</a></p><p><a href="https://arxiv.org/pdf/1809.02393v2.pdf">[3] Improving Neural Question Generation using Answer Separation</a></p><p><a href="https://arxiv.org/pdf/1808.04961.pdf">[4] A Framework for Automatic Question Generation from Text using Deep Reinforcement Learning</a></p><p><a href="https://arxiv.org/pdf/1905.08949.pdf">[5] Recent Advances in Neural Question Generation</a></p><p><a href="https://arxiv.org/pdf/1810.04805.pdf">[6] BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding</a></p><p><a href="https://arxiv.org/pdf/1906.08237.pdf">[7] XLNet: Generalized Autoregressive Pretraining for Language Understanding</a></p><p><a href="https://arxiv.org/pdf/1906.04980.pdf">[8] Unsupervised Question Answering by Cloze Translation</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7c8892cf2c13" width="1" height="1" alt=""><hr><p><a href="https://medium.com/glose-team/how-to-evaluate-readers-text-comprehension-7c8892cf2c13">How to evaluate readers text comprehension?</a> was originally published in <a href="https://medium.com/glose-team">Glose Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What powers Glose: Hundreds of pods, dozens of technologies]]></title>
            <link>https://medium.com/glose-team/what-powers-glose-hundreds-of-pods-dozens-of-technologies-fd738f48c9f5?source=rss----9af43e5ffc08---4</link>
            <guid isPermaLink="false">https://medium.com/p/fd738f48c9f5</guid>
            <category><![CDATA[infrastructure]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[redux]]></category>
            <category><![CDATA[asyncio]]></category>
            <dc:creator><![CDATA[Arthur Darcet]]></dc:creator>
            <pubDate>Thu, 27 Jun 2019 08:09:31 GMT</pubDate>
            <atom:updated>2019-06-27T08:09:31.486Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DJhvSMK3lD5ij1P3AbWUtQ.png" /></figure><p>One of the most common questions we get asked is “what is your stack?” We are of course always glad to discuss our technology choices in person, but we thought an article was the occasion to have a more comprehensive description of what we use. Each section here will also be a way for us to give an abstract of the more in-depth descriptions that we will continue to publish.</p><p>To give credit where it’s due: the format of this article is freely inspired by the great article from the Instagram team named “What powers Instagram.”</p><h3>OS / Hosting</h3><p>We are hosting almost everything on Google Cloud Platform, and we orchestrate our services with Kubernetes. We migrated to this solution from dedicated servers on OVH as soon as Kubernetes became a viable option, and the k8s “magic” has allowed us to grow beyond anything we could have managed on raw servers without having to dedicate too many resources to sys-ops roles.</p><p>The cost of hosting everything on GCP is of course way higher than the equivalent fire-power on a more classic hosting provider such as OVH, but the time saved building and maintaining servers has been well worth it until now. We also had the chance to be part of the “Google for startups” program, with the Surge package, which helped a lot with the bill while growing.</p><p>We might revisit this decision in the future, especially now that OVH is starting to launch k8s packages with very aggressive price points.</p><h3>Backend</h3><h4>Architecture</h4><p>Our backend is split into dozens of microservices (around 57 at the time of writing). Without going into too many details here on the pros and cons of a “microservices” architecture, this allows us to be very <em>agile</em> when deploying updates, and it lets us distribute responsibilities and ownership of part of the stack to each member of our engineering team.</p><p>Most of those services communicate with very simple HTTP calls, authenticated when it makes sense. Some “many to many” messages are sent using RabbitMQ exchanges, to avoid creating dependency loops between services; and some services publish jobs to RPC queues in RabbitMQ for workflows where HTTP calls would not be appropriate.</p><p>And to have some kind of consistency between all these services, they are all named after <em>A Song of Ice and Fire</em> castles.</p><h4>Frameworks</h4><p>Most of our services are run by Python 3.7. Some are still using 3.6, and some are taking advantage of the more advanced garbage collection done by Pypy (Python does not release memory, even once the objects are garbage collected. This isn’t an issue for most use cases, and even allows some performance gains, but for services that might need huge amounts of RAM for short periods of time, not being able to release it once the job is done is a serious issue).</p><p>We also decided early on to use asynchronous programming, with <a href="https://docs.python.org/3/library/asyncio.html">asyncio</a> in Python. This choice was first motivated by our ebooks ingestion pipeline that received files on an FTP server, and we would then need to monitor those files on the disk to trigger updates whenever there was a change. Doing this with <a href="http://man7.org/linux/man-pages/man7/inotify.7.html">inotify</a> in Python can be easily achieved with threads, but that solution does not scale to the millions of files we had to watch (millions of threads is a no-go…), so we instead switched our HTTP server to Tornado (by Facebook), and started using coroutines.</p><p>Since then, our ingestion pipeline was re-written entirely to expose a custom FTP server that simply directly sends the files for processing instead of writing them on a disk and hoping another process picks up the change. The Tornado framework was also dropped for the more mainline <a href="https://aiohttp.readthedocs.io/en/stable/">aiohttp</a> library and the asyncio implementation in the Python stdlib.</p><p>We found that this asynchronous approach is very compatible with both our database (Mongo is made for tons of small concurrent requests) and our microservices architecture (which relies on aggregating data from many different small HTTP requests) and we applied this basic principle and to (almost) all our backend services.</p><h4>Data storage</h4><p>Most of our data lives in MongoDB; lots of pros and cons here too, but all-in-all this has worked well for us until now, mainly by allowing us to iterate very quickly and very easily without having to write too many migrations.</p><p>We are hosting those mongo servers inside our k8s cluster, backing them with gcloud SSD drives which give us sufficient performance for now.</p><p>Some of our data points don’t fit well in the Mongo space, or the performance was too poor for our use case: for instance, the detailed reading progress metrics we are aggregating to provide our user with insightful data dashboards are more adapted to a time-based database (we use <a href="https://www.influxdata.com/">InfluxDB</a> for this). Some of our data also live in PostgreSQL, and we use Elasticsearch to power our search engine and store the related data.</p><p>We are also using two distinct Redis clusters: one is used to cache some data in an LRU cache (“least recently used”, a cache that only keeps the last items it can store, and drops old items when it reaches its limit) ; and we use the other system as a “cluster-wide” locking mechanism, to share locks between processes that might run on different servers.</p><p>And of course, since we are in the GCP ecosystem, our files are hosted on Google Cloud Storage.</p><h4>Task Queue</h4><p>Some of our backend tasks are CPU-bound (ie their execution time is constrained by how much CPU we can allocate to the task). This includes most of the NLU tasks (Natural Language Understanding) done by our Research team, and some expensive statistic computations for the data reports we expose to our users.</p><p>Locking a whole CPU does not fit well in our asynchronous approach to the backend, and they need to be dispatched to special workers configured for this. We use RabbitMQ queues to connect the dots here.</p><p>We also use RabbitMQ exchanges when a service needs to broadcast updates that might concern other services, such as metadata changes for a book. This lets us publish from service A, and subscribe from service B without directly having a dependency loop between A and B which would make updating both tricky.</p><p>And finally, our logs ingestion stack also uses RabbitMQ to connect the log producers to our ingestion project. More on that in the logging section.</p><h4>Logging</h4><p>All those running parts produce around 500 lines of logs per second, that we need to store somewhere accessible and usable to build our internal reporting dashboards and to investigate issues.</p><p>While benchmarking different solutions, we found that building our <a href="https://www.elastic.co/elk-stack">ELK (Elasticsearch Logstash Kibana)</a> pipeline ourselves was both manageable and cheaper than relying on an external dedicated solution. So we built an in-house Elasticsearch cluster dedicated to our logs with six instances of Elasticsearch 7 — this gives us around 1800 logs/second when ingesting full speed — and we collect the logs with <a href="https://www.fluentd.org/">fluentd</a>. The collectors push the logs to a RabbitMQ queue, where they are picked up by some dedicated workers, which will push them to Elasticsearch after some transformations (geolocalisation for the IP addresses of our access logs for instance).</p><p>We are not using <a href="https://www.elastic.co/products/logstash">Logstash</a> for this: we found that to process our 500 logs per second, the cluster of Logstash services used around 20 cores of our CPUs; and the in-house service doing the same “pick up logs from RabbitMQ, process, push to Elasticsearch” job uses between 0.5 and 1 cores using Pypy. We initially used Go for this service, to use as little CPU as possible, but we found that the performance gain (around a factor 2) was not worth maintaining a project in another language (and nobody in the team had any experience maintaining a Go project beyond a PoC).</p><h3>Mobile</h3><p><strong>Redux</strong></p><p>Mobile apps for iOS and Android are structured around a Redux architecture and its three fundamental principles:</p><ol><li>The state of the whole application is described by an object tree within a store providing a single source of truth.</li><li>The application state is read-only and the only way to change it is to emit an action, which will result in a different state being produced, thus ensuring unidirectional data flow.</li><li>New states are produced by pure functions (a function that does not have any side effect), called reducers, which take the previous state and an action as its input and return the next state as its output.</li></ol><p>Components in the apps that depend on the state connect to the store via an in-house implementation of the connect() functionality from React Redux. A connected component provides two lambdas to the connector: a pure function that derives the component properties from a state object tree and another function which renders the components from a given properties object. The connector then takes care of listening to store state changes, deriving the component’s properties, and requesting the component to render when its properties have changed. This provides a simple and efficient way to propagate app state changes to the UI layer.</p><p>Asynchronous operations such as HTTP requests are implemented with Redux: an action is dispatched to initiate the operation (more details <a href="https://medium.com/glose-team/efficient-asynchronous-flow-with-react-and-redux-311e96d27b2">here</a>), and another action is later dispatched to handle its result. The initial action is handled by a middleware, a function that is called before reducers and which is allowed to have side effects (but not to modify the state).</p><p><strong>iOS</strong></p><p>Glose for iOS is in Swift. The Redux part is implemented using <a href="https://github.com/ReSwift/ReSwift">ReSwift</a>. Every object stored in the state is a struct conforming to Equatable. As Swift does not provide automatic copy function (like in Kotlin), reducers actually return a mutated copy of the state and connect functions use our Equatable implementation to compare and dispatch changes.</p><p>The whole flux parts (action initialization, middlewares, and reducers) are executed in a serial OperationQueue.</p><p>This was a necessary implementation because it can be quite costly to compare a complex object graph, even if our connectors are small and very targeted. So the networking, JSON encode/decode, mutation and state changes all happen asynchronously and are then dispatched to the connected component on the UI thread. One of the drawbacks is that your UI has to handle asynchronous data almost everywhere. While this is easy in a “static” screen, it’s more complex for layout pass in UITableView and UICollectionView. As cells layout (height) is not computed dynamically, you have to notify your UITableView that you need to update the height of specific cells once your dynamic content is loaded.</p><p>With Redux we can make highly re-usable and self-contained <a href="https://medium.com/glose-team/building-reusable-user-interfaces-in-swift-599cc56e4208">component</a>s. Glose for iOS use extensively UICollectionView &amp; UITableView and provide various “Provider” so you can easily connect whole data source logic and delegate actions in any screen of the app.</p><p>Our reader uses a custom WKWebView, into which we inject a custom content controller, so we can scope and intercept javascript messages. We use this bridge system to communicate between Swift and Javascript code. The custom WKWebView render the book as a pure HTML content, while we inject annotations, reaction, etc. as native UIKit views.</p><p>We also make extensive use of TextKit to display custom emojis and now even inject custom views in order for our upcoming animated reactions. It’s a powerful tool, and you can quite easily display anything in your UITextView with custom NSTextAttachment and LayoutManager.</p><p><strong>Android</strong></p><p>Glose for Android is written 100% in Kotlin. The Redux architecture is built upon <a href="https://github.com/ReKotlin/ReKotlin">ReKotlin</a> on top of which we’ve built an in-house connect() functionality (see above). We rely on the ease and power of Kotlin coroutines for asynchronous operations where appropriate. We use various Android Jetpack components that make our job easier, including Android KTX for idiomatic Kotlin code, lifecycle-aware components that reduce coupling, and paged lists for on-demand loading of data sets.</p><p>We also rely heavily on the awesome <a href="https://github.com/airbnb/epoxy">Epoxy</a> library by Airbnb, which makes it easy and painless to build complex screens declaratively on top of recycler views.</p><p>Also involved: <a href="https://developers.chrome.com/multidevice/webview/overview">Chromium</a> for the rendering of book contents; Retrofit and OkHttp for HTTP requests; <a href="https://insert-koin.io">Koin</a> for lightweight dependency injection; Picasso for image loading; Firebase for push notifications; Stripe for payment processing; Mockito and Espresso for unit tests and UI tests; <a href="https://github.com/Triple-T/gradle-play-publisher">Gradle Play Publisher</a> for automated releases from our CI.</p><h3>Web</h3><p><em>Coming soon</em></p><h3>Monitoring</h3><p>Our monitoring stack is pretty standard:</p><p>We monitor exception in production with Sentry for all our Python and Javascript code, and through Crashlytics (formerly fabric.io) for our mobile applications.</p><p>Server status metrics and basic application metrics are collected using Prometheus.</p><p>And we use Grafana dashboards that aggregate both the Prometheus data and data extracted from our logs Elasticsearch cluster to display relevant graphs and trigger alerts when necessary.</p><h3>You?</h3><p>We are a (still) small but growing fast team and we are always looking for new talents.</p><p>If this description of our platform interests you, or if you are dying to tell us all that we did wrong here, we would love to <a href="mailto:jobs@glose.com">hear from you!</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fd738f48c9f5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/glose-team/what-powers-glose-hundreds-of-pods-dozens-of-technologies-fd738f48c9f5">What powers Glose: Hundreds of pods, dozens of technologies</a> was originally published in <a href="https://medium.com/glose-team">Glose Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Evaluate Text Readability with NLP]]></title>
            <link>https://medium.com/glose-team/how-to-evaluate-text-readability-with-nlp-9c04bd3f46a2?source=rss----9af43e5ffc08---4</link>
            <guid isPermaLink="false">https://medium.com/p/9c04bd3f46a2</guid>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Marc Benzahra]]></dc:creator>
            <pubDate>Thu, 20 Jun 2019 12:26:11 GMT</pubDate>
            <atom:updated>2019-06-20T12:54:14.371Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ngx7FkcxwJZHdAeQLTQwng.jpeg" /><figcaption>Library shelves without readability level indexation — <a href="https://pixabay.com/photos/bookshelf-old-library-old-books-1082309/">Pixabay</a></figcaption></figure><p>Reader engagement is a recurrent problem among all types of readers: adults/children and teachers/students. They all face the same issue: finding books close to their current readability ability, either for casual reading (easy level) or to improve and learn (hard level) without being flooded by too much difficulty which usually results in a harsh experience for most of us.</p><p>At Glose, our goal is to enable readers to access books that are gradually more difficult and recommend books that fit their current reading ability.</p><p>In this article, we will show how we developed a machine learning system that objectively evaluates <strong>text readability</strong>.</p><h3><strong>Text Complexity: Facets and Usage</strong></h3><p>Text complexity measurement estimates how difficult it is to understand a document. It can be defined by two concepts: <strong>legibility</strong> and <strong>readability</strong>.</p><p><strong>Legibility</strong> ranges from character perception to all the nuances of <strong>formatting</strong> such as <strong>bold</strong>, font style, font size, <em>italic</em>, word spacing …</p><p><strong>Readability</strong>, on the other hand,<strong> </strong>focuses on textual <strong>content</strong> such as lexical, semantical, syntactical and discourse cohesion analysis. It is usually computed in a very approximate manner, using average sentence length (in characters or words) and average word length (in characters or syllables) in sentences.</p><p>A few other text complexity features do not depend on the text itself, but rather on the reader’s intent (homework, learning, leisure, …) and cognitive focus (which can be influenced by ambient noise, stress level, or any other type of distraction).</p><blockquote>Why is it crucial to be able to measure <strong>text readability</strong> ?</blockquote><p>In the context of conveying important information to most readers (drugs leaflets, news, administrative and legal documents), an evaluation of readability helps text writers to adjust their content to their target audience’s level.</p><p>Another use case is the field of automatic text simplification, where a robust readability metric can replace standard <a href="https://www.aclweb.org/anthology/Q16-1029">objective functions</a> (such as a mixture of <a href="https://en.wikipedia.org/wiki/BLEU">BLEU</a> and <a href="https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests#Flesch%E2%80%93Kincaid_grade_level">Flesch-Kincaid</a>) used to train text simplification systems.</p><p>In this article, we will focus solely on estimating <strong>text readability </strong>using annotated datasets and machine learning algorithms. We implemented them using the <a href="https://scikit-learn.org/stable/">scikit-learn</a> framework.</p><h3><strong>Data</strong></h3><p>The starting point of any machine learning task is to collect data. In our case, we extract it from two sources:</p><ol><li>Our database at Glose contains more than 1 million books which include around 800,000 english books.</li><li>A dataset of 330,000 book identifiers, graded on the <a href="https://fab.lexile.com/">Lexile</a> text complexity scale ∈ [-200, 2200].</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D9o_OGs38sN7Kj9fxwCSKg.png" /><figcaption>Most common genres (5%, 20 out of 393) distribution over 17027 books</figcaption></figure><p>This dataset is biased in two ways:</p><ol><li>The distribution of book genres in our merged dataset is unbalanced (figure above).</li><li>It assumes that the Lexile score is close to the true readability perception of the average human, which might not be, due to their <a href="https://files.eric.ed.gov/fulltext/ED435977.pdf">usage of mainly two features: sentence length and words frequency</a>.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*ZB8iW4T61xYlIAoP.png" /><figcaption>ISBN semantic (<a href="https://selfpublishingadvice.org/isbns-for-self-published-books/">Source</a>)</figcaption></figure><p>Book identifiers (namely ISBN), are unique to a book’s edition. Each book can have multiple ISBNs due to the large number of editors distributing the same content. In short, each identifier in our dataset maps to multiple identifiers of similar content.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eac5fd9990a3b94c1c58551b01087dc0/href">https://medium.com/media/eac5fd9990a3b94c1c58551b01087dc0/href</a></iframe><p>In order to have a unique mapping between ISBN, book content, and Lexile score, we select an intersection subset (where we have both a book’s content in our database and a Lexile annotation) of 17,000 english books.</p><h3>Book representation</h3><p>In the first step of our natural language processing pipeline, we clean and tokenize the text into sentences and words. Then we have to represent text as an array of numbers (a.k.a. feature vector): here we choose to represent text by hand-crafted variables in order to embed higher level meaning than a sequence of raw characters.</p><p>Each book is represented by a vector of 50 float numbers, each of them being a text feature such as:</p><ul><li>Mean number of <a href="https://pypi.org/project/PyHyphen/">syllables</a> per word,</li><li>Mean number of <a href="http://www.nltk.org/api/nltk.tokenize.html#nltk.tokenize.punkt.PunktLanguageVars.word_tokenize">words</a> per <a href="https://spacy.io/usage/linguistic-features#sbd">sentence</a>,</li><li>Mean number of words considered “difficult” in a sentence (a word is “difficult” if it is not part of an <a href="http://countwordsworth.com/download/DaleChallEasyWordList.txt">“easy” words reference list</a>),</li><li><a href="https://spacy.io/api/tagger">Part-of-Speech </a>(POS) tags count per book,</li><li><a href="https://gist.github.com/1475963/50dd029659180f35ec2743843093bc07">Readability formulas</a> such as Flesch-Kincaid and</li><li>Number of polysyllables (more than 3 syllables).</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/852/1*xfgqaTcF-5vmde6JXgwiHA.png" /><figcaption>Spacial representation of 3 features (out of 50) for 1000 data points. The Dune book position is indicated by the arrow.</figcaption></figure><p>These features are all on different scales (c.f. figure above), however we would like to have a similar scale from -1 to 1 because some of the algorithms we use during modelling (Support Vector Regression with a <strong>Linear</strong> kernel and <strong>Linear</strong> regression) assume that the data given as input follows a <a href="https://en.wikipedia.org/wiki/Normal_distribution">Gaussian distribution</a>. This process, namely <strong>standardisation</strong>, is about removing the mean and dividing by the standard deviation of a dataset.</p><h3>Feature selection</h3><p>Now that we built a set of features representing a text, we would like to truncate that vector to the most salient features ; the ones that discriminate the most our annotations. Using features that do not carry information related to the target variable (the readability score) is a computation time burden to the model, because the inference is done with more features than necessary.</p><p>To perform this <strong>feature selection</strong> step, we use the <a href="https://beta.vu.nl/nl/Images/werkstuk-fonti_tcm235-836234.pdf"><strong>LASSO</strong></a> method (<a href="https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LassoCV.html#sklearn.linear_model.LassoCV">scikit-learn implementation</a>) with cross-validation (CV is the process of training and testing models with different data splits to avoid a bias from a specific dataset order) because the difference between execution time with and without 10-fold CV is negligible. Moreover, it guarantees to have a model that is less subject to variance when confronted to real data.</p><figure><img alt="Image result for cross validation illustration" src="https://cdn-images-1.medium.com/proxy/1*me-aJdjnt3ivwAurYkB7PA.png" /><figcaption>10-fold cross-validation with 10 models performances as a result (<a href="http://karlrosaen.com/ml/learning-log/2016-06-20/">Source</a>)</figcaption></figure><p>The LASSO method is performed by creating multiple subsets of our feature set. For each feature set a regression function is fitted using our training data. Then a correlation is computed (using a metric such as <a href="https://en.wikipedia.org/wiki/Pearson_correlation_coefficient">Person</a>, <a href="https://www.r-tutor.com/gpu-computing/correlation/kendall-tau-b">Kendall-Tau</a> or <a href="https://en.wikipedia.org/wiki/Chi-squared_test">Chi-Square</a>) between each set’s regression function and the readability score. Feature sets are ranked by correlation performance and the best one is selected.</p><h3><strong>Choosing the right model</strong></h3><p>Our output variable is numerical and continuous which narrows the spectrum of machine learning models applicable to our dataset (regression task). To <strong>select an appropriate model</strong>, there is several indicators that may guide one’s choice, such as the number of features or the number of samples available.</p><p>In the case of <strong>constrained bayesian algorithms</strong> such as Naive Bayes variants (simple or tree augmented), performances are likely to decrease with large number of features. This is due to their inability to build large variable dependencies between an output variable and an explanatory variable. Naive Bayes is built under the assumption that variables are independent, which is less likely the case with longer feature vectors. Tree Augmented Naive Bayes (<a href="http://www.cs.technion.ac.il/~dang/journal_papers/friedman1997Bayesian.pdf">TAN</a>) allows only one explanatory variable as a dependency of another to predict an output variable. This lack of feature intrication makes these algorithms bad candidates for our feature vector length (50), <strong>we will not use them in this article</strong>.</p><p>However, <strong>Decision Tree (DT) based algorithms</strong> cope very well with high dimensional data (more features) but need lots of data samples (varies as a function of algorithm hyperparameters). DTs build rules (for example: average number of words per sentence <em>&gt; 5</em>) and these rules are split when a given amount of data samples fit them. For example: 10 samples fit the previous rule, we consider that there is too much samples in this rule, so we build two other rules <em>&gt; 5 AND &lt; 10</em> and <em>&gt; 10</em> where we fit respectively 4 and 6 samples instead of 10 in one rule. In decision tree algorithms, the number of data samples is a function of model granularity, by handling overfitting correctly, the more data and features there is, the better a DT based model is.</p><p>Another approach to <strong>model selection</strong> that we choose to use is <a href="https://towardsdatascience.com/grid-search-for-model-tuning-3319b259367e"><strong>Grid Search</strong></a>, this technique is a training and testing brute force over a set of models and a set of hyper parameters for each model.</p><p><strong>Pros</strong>: Easy to setup, less preliminary analysis of dataset, specific model knowledge isn’t much needed, empirical evidence <em>(you won’t know unless you try)</em>.</p><p><strong>Cons</strong>: Hyper parameter sets definition needs specific model knowledge and literature review to reduce computation time, time-consuming search (e.g. next figure), no global optimum guarantee.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/587/1*gj-WTXLmHXWDU2ui6-U_6A.png" /><figcaption>Number of complete training and testing iterations during grid search CV</figcaption></figure><p>In our <a href="https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html">Grid Search</a>, three algorithms compete: a <a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html">Random Forest Regressor</a> (4 hyper parameters), a <a href="https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html">Linear Regression</a> and a <a href="https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVR.html">Support Vector Regressor</a> (2 hyper parameters), the best model is generated through <strong>Random Forest regression</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/944/1*y7H2k3FSDBKEFB7kW8xOrg.png" /><figcaption>Sketch overview of our system evaluating text readability</figcaption></figure><h3>Interpreting readability scores</h3><p>We now have a production grade model that takes a book’s feature vector (obtained through pre-processing) as input and gives a readability score as output. In order to display a comprehensible metric to users (especially pre-college students), we would like to have a more meaningful representation of this score by converting it to grade level bins, we use the following formula to define those bins.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/433/1*j2x4CeYn-na0pwNgR23jSQ.png" /><figcaption>Conversion formula from readability score to grade level (<a href="http://languageartsreading.dadeschools.net/pdf/FAIR/LexileConversionChart.pdf">Source</a>)</figcaption></figure><p>On the following figure we can see the most interesting sections of the readability scale for the students that will read their books on Glose. A teacher can follow a student’s progression on this scale by monitoring the mean grade level of the books he reads.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*jY38PR7lRDalkdUT" /><figcaption>K-12 grade level scale</figcaption></figure><h3><strong>Performance</strong></h3><p>Overall our best model achieves around 0.88 for the metric R² which explains 88% of our test set variance. R², also known as <a href="https://en.wikipedia.org/wiki/Coefficient_of_determination">coefficient of determination</a>, is the metric we use to test our regression algorithm. The resulting value we get from it ranges from 0 to 1 and Random Forest is optimised to converge to 1. This value is the explained variance accounted by our model: the higher it is, the less test data samples we find outside of our model’s prediction error range.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G2rIYD_c5Re3x9FMW7UsPQ.png" /><figcaption>Absolute residuals across all reading levels</figcaption></figure><p>On the figure above we see that most of our predictions (60%) fall in the right grade level, whereas nearly 35% in only one grade level above or below ground truth. Adjacent precision is equal to 95%, this metric is more relaxed than precision as it allows up to one grade level error.</p><p>However, when we inspect the residuals per grade level and the distribution of grade levels over our test set, we realise that most of our errors (yellow, orange and red bars) happen on grade levels with fewer samples (levels 7 to 12 included).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/642/1*Uh_kVIinlmt2Ilyr4asHbA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2GXJzEozBsn9Wli42DSv2g.gif" /><figcaption>(left) Books grade level distribution (right) Residuals broken down per grade level (ground truth grade level — predicted grade level)</figcaption></figure><p>Statistically, our results seem satisfactory. However we have room for improvement with this approach and we are going to evaluate the robustness of our model with human experts giving their feedback in the loop.</p><h3><strong>Conclusion and outlook</strong></h3><p>As a <strong>TL;DR</strong> and a takeaway of this post, you should have learned:</p><ul><li>What is text complexity, and why is it meaningful.</li><li>How a machine learning pipeline is designed to create a production model.</li><li>A few specifics about parts of this pipeline such as features and models selection.</li><li>That this post’s readability score is 878 which is lower compared to <a href="https://arxiv.org/pdf/1905.10752.pdf">TIGS: An Inference Algorithm for Text Infilling with Gradient Search</a> that reaches the score 992 on our scale, whereas <a href="https://glose.com/book/in-search-of-lost-time-6">In Search of Lost Time by Marcel Proust</a> stands at 1441.</li></ul><p>As a premise of our next article, we are currently working on another approach to evaluate text readability using neural language models as comprehension systems to infill <a href="https://onlinelibrary.wiley.com/doi/abs/10.1111/j.1467-1770.1973.tb00100.x">Cloze tests</a> (text chunks with blank words). The training phase of this other approach is unsupervised and has the advantage of being language agnostic.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9c04bd3f46a2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/glose-team/how-to-evaluate-text-readability-with-nlp-9c04bd3f46a2">How to Evaluate Text Readability with NLP</a> was originally published in <a href="https://medium.com/glose-team">Glose Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[️ Fast bag-of-words using spaCy and cython]]></title>
            <link>https://medium.com/glose-team/%EF%B8%8F-fast-bag-of-words-using-spacy-and-cython-574c308a9ff3?source=rss----9af43e5ffc08---4</link>
            <guid isPermaLink="false">https://medium.com/p/574c308a9ff3</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[cython]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Mehdi Hamoumi]]></dc:creator>
            <pubDate>Tue, 14 May 2019 15:47:09 GMT</pubDate>
            <atom:updated>2019-05-16T11:23:46.929Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4VteVvjpkEbbFuElv4G6Dw.jpeg" /><figcaption><a href="https://pixabay.com/photos/formula-ford-car-race-fast-speed-930921/">Source</a></figcaption></figure><p>When processing large amounts of text (more than 1 million books) like we do at Glose, it is crucial to optimize every step of the pipeline.</p><p>In this post, we will be focusing on a very common Natural Language Processing (NLP) task that involves counting the number of occurrences of every word in a text: building a bag-of-words (BoW).</p><h3><strong>🎒 BoW applications and a simple example</strong></h3><p>NLP pipelines usually start by converting a text to an array (or several arrays) of numbers (vectors). This vectorial representation is crucial because it is much easier to manipulate vectors than raw strings in machine learning algorithms (such as document classification, sentiment analysis, Part-of-Speech tagging, Named Entities Recognition…), or in recommendation systems that compute similarities between items based on their vectors.</p><p>Building the BoW representation is often the first step in obtaining the vectorial representation. For instance, in <a href="https://radimrehurek.com/gensim/">topic modeling</a>, where every number in the vector corresponds to the contribution of a topic to the text, we start by the BoW, then compute the words’ frequencies (<a href="https://en.wikipedia.org/wiki/Tf%E2%80%93idf">Tf-Idf</a>), and finally compute the topics (using methods like <a href="https://en.wikipedia.org/wiki/Latent_semantic_analysis">LSA</a> or <a href="https://en.wikipedia.org/wiki/Latent_Dirichlet_allocation">LDA</a>).</p><p>Let us start with a simple example, using this post’s introduction:</p><pre>When processing large amounts of text, we optimize every step. In this post, we will be focusing on counting the number of occurrences of every word in a text.</pre><p>Before building the BoW representation, we convert the text to lowercase, remove all the <a href="https://en.wikipedia.org/wiki/Stop_words">stopwords</a> (meaningless words) and punctuation, and replace all words by their <a href="https://en.wikipedia.org/wiki/Lemmatisation">lemmas</a>, which gives:</p><pre>process large amount text optimize step post focus count number occurrence word text</pre><p>Then we count the occurrences of each word, which gives:</p><pre>{<br>    &#39;process&#39;: 1,<br>    &#39;large&#39;: 1,<br>    &#39;amount&#39;: 1,<br>    &#39;text&#39;: 2,<br>    &#39;optimize&#39;: 1,<br>    &#39;step&#39;: 1,<br>    &#39;post&#39;: 1,<br>    &#39;focus&#39;: 1,<br>    &#39;count&#39;: 1,<br>    &#39;number&#39;: 1,<br>    &#39;occurrence&#39;: 1,<br>    &#39;word&#39;: 1,<br>}</pre><p>If we associate an integer identifier to every word (‘process’ ↔ 0, ‘large’ ↔ 1 …) — a.k.a build a dictionary — we can write the BoW representation in a more compact manner [(id, count), …]:</p><pre>[(0,1), (1,1), (2,1), (3,2), … , (11, 1)]</pre><p>We can then use this BoW representation in further processing steps, as mentioned above.</p><h3>🐍 A naive implementation</h3><p>Let us first write a simple python program that transforms a preprocessed text into a compact BoW representation:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f78e577e5765c4b723ebaa024a5f9f41/href">https://medium.com/media/f78e577e5765c4b723ebaa024a5f9f41/href</a></iframe><p>We use python’s built-in <strong>collections.defaultdict</strong> to count the number of occurrences of words, and build the dictionary by iterating on all the words, and adding the missing ones with their integer identifier.</p><p>We can now try our BoW implementation on the previous preprocessed example:</p><pre>sample_text = &#39;process large amount text optimize step post focus count number occurrence word text&#39;</pre><pre>dictionary = {}</pre><pre>print(&#39;BOW representation:&#39;, text2bow(sample_text.split(), dictionary))</pre><pre>print(&#39;Dictionary:&#39;, dictionary)</pre><p>We get:</p><pre>BOW representation: [(0, 1), (1, 1), (2, 1), (3, 2), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1), (10, 1), (11, 1)]</pre><pre>Dictionary: {&#39;process&#39;: 0, &#39;large&#39;: 1, &#39;amount&#39;: 2, &#39;text&#39;: 3, &#39;optimize&#39;: 4, &#39;step&#39;: 5, &#39;post&#39;: 6, &#39;focus&#39;: 7, &#39;count&#39;: 8, &#39;number&#39;: 9, &#39;occurrence&#39;: 10, &#39;word&#39;: 11}</pre><p>which is exactly what we got in the previous section, by manually counting the occurrences.</p><p>Finally let us time the text2bow function on a preprocessed text of 26893 words, corresponding to a single book:</p><pre>4.36 ms ± 29.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)</pre><p>While this seems like a reasonable processing time for a single book, it is important to remember that it is only the first step of our NLP pipeline, and that it will be applied on ~1M books, which takes us to ~ 1 hour 12 minutes of processing time just to create BoW representations of all our books.</p><p>We will see in the following sections how to use Cython and spaCy’s Cython API to speed up this code.</p><h3>⚙️ About cython and spaCy</h3><p>Working with pure Python code is great for fast iteration and experimentation. However there are some use cases where one can benefit from a statically typed and compiled language. Mainly:</p><ol><li>When developing a production module, that needs to work at full speed.</li><li>When a bottleneck is identified in Python code (<a href="https://docs.python.org/3/library/profile.html">profiling</a> is <a href="https://jiffyclub.github.io/snakeviz/">key</a>!). This is often related to portions of Python code with plain or nested for-loops.</li><li>When native parallelism is needed (that means releasing Python’s <a href="https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock">Global Interpreter Lock</a>).</li></ol><p>This brings us to <a href="https://cython.org/">Cython</a>, which is defined as follows in its <a href="https://cython.org/">homepage</a>:</p><blockquote><strong>Cython</strong> is an <strong>optimising static compiler</strong> for both the<a href="http://www.python.org/about/"> <strong>Python</strong></a> programming language and the extended Cython programming language (based on <strong>Pyrex</strong>). It makes writing C extensions for Python as easy as Python itself. […]</blockquote><blockquote>The Cython language is a superset of the <strong>Python</strong> language that additionally supports calling <strong>C functions</strong> and declaring <strong>C types</strong> on variables and class attributes. This allows the compiler to generate very <strong>efficient C code</strong> from Cython code. The C code is <strong>generated once</strong> and then compiles with all major C/C++ compilers […].</blockquote><p>Cython thus solves (1) and (3) by generating pure C/C++ code, and (2) by being a superset of Python, which means that Python code is valid Cython, and allows to only optimize the bottlenecks.</p><p>Leveraging all of Cython’s benefits for NLP can however be tricky. It is mentioned in the <a href="http://cython.readthedocs.io/en/latest/src/tutorial/strings.html">documentation</a> that one should not use C strings because they require manual memory management and are more cumbersome than Python strings.</p><p>This is where <a href="https://spacy.io/">spaCy</a> comes of help. It is a fast NLP library written in Python/Cython, which uses a clever method to manage strings, that manipulates 64-bit hashes internally instead of Python strings, making the code much faster while keeping the flexibility of Python strings from the user’s perspective.</p><h4>spaCy’s StringStore</h4><p>Most of spaCy’s strings management is taken care of in the file <a href="https://github.com/explosion/spaCy/blob/f95ecedd83ce75b7062af6afaf47f9ed6fe59550/spacy/strings.pyx">strings.pyx</a>, and its <a href="https://spacy.io/api/stringstore">StringStore</a>. As mentioned in the documentation’s header, the StringStore’s purpose is to:</p><blockquote>Look up strings by 64-bit hashes. As of v2.0, spaCy uses hash values instead of integer IDs. This ensures that strings always map to the same ID, even from different StringStores.</blockquote><p>This means that instead of manipulating strings internally, spaCy computes and manipulates their 64-bit hashes only. They are converted back to their Python string equivalent only when necessary (for instance when a user wants to print the output of the spaCy pipeline).</p><p>Hence, the StringStore is the data structure where the mapping between the 64-bit hashes and their Python unicode string equivalent is stored. It is in fact a Cython <a href="https://cython.readthedocs.io/en/latest/src/userguide/extension_types.html">extension type</a>, which means that from a Python module’s perspective it behaves like a Python class, but internally it can have cdef statements (either <a href="https://cython.readthedocs.io/en/latest/src/userguide/extension_types.html#static-attributes">static attributes</a>, or <a href="https://cython.readthedocs.io/en/latest/src/userguide/extension_types.html#c-methods">C methods</a>), and is built as a C struct.</p><p>In order to leverage the speed of spaCy’s low level C structures for our fast BoW program, we need to understand what happens when a Python unicode string is added to the StringStore. Most of this behavior is located in the StringStore’s methods add, intern_unicode, and _intern_utf8.</p><p>Here are the main steps of this process:</p><ol><li>The unicode string is encoded in utf-8 (<a href="https://github.com/explosion/spaCy/blob/f95ecedd83ce75b7062af6afaf47f9ed6fe59550/spacy/strings.pyx#L300">code</a>)</li><li>The encoded string is hashed to a 64-bit integer by using <a href="https://github.com/explosion/murmurhash">MurmurHash2</a> (<a href="https://github.com/explosion/spaCy/blob/f95ecedd83ce75b7062af6afaf47f9ed6fe59550/spacy/strings.pyx#L307">code</a>)</li><li>The encoded string is transformed into a C char array (or pointer). This is done in a way that optimizes memory usage <em>(by either using a char array of 8 elements if the string is small enough, or using a pointer array otherwise) </em>(<a href="https://github.com/explosion/spaCy/blob/f95ecedd83ce75b7062af6afaf47f9ed6fe59550/spacy/strings.pyx#L68">code for the _allocate function</a>, that converts the encoded string to a C array/pointer, <a href="https://github.com/explosion/spaCy/blob/f95ecedd83ce75b7062af6afaf47f9ed6fe59550/spacy/strings.pyx#L311">line</a> where the _allocate function is called)</li><li>The (64-bit hash, C char array/pointer) couple is stored as key-value in a <a href="https://github.com/explosion/preshed">Cython Hash Table for Pre-Hashed Keys</a>, which is an attribute of the StringStore (<a href="https://github.com/explosion/spaCy/blob/f95ecedd83ce75b7062af6afaf47f9ed6fe59550/spacy/strings.pyx#L312">code</a>)</li><li>The 64-bit hash is stored in a list attribute of the StringStore (<a href="https://github.com/explosion/spaCy/blob/f95ecedd83ce75b7062af6afaf47f9ed6fe59550/spacy/strings.pyx#L313-L314">code</a>)</li></ol><p>💡 The main lessons from this analysis are that in order to be able to use a fast C level hash table the unicode strings need to be converted to a C char array (or pointer)<a href="#e810">¹</a>, and that all fast computations must be performed on the 64-bit hashes, not on the strings.</p><h3>🏎️ Cythonized version of BoW</h3><p>Now that we know how spaCy manages strings internally, we can start implementing our own Cython version of the BoW.</p><p>First, we will import the necessary C types and classes (or extension types):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/64cc2d3ef00996bf4db0070e5e90078d/href">https://medium.com/media/64cc2d3ef00996bf4db0070e5e90078d/href</a></iframe><p>Among all these imports let us comment on a few:</p><ul><li>The cymem package is used to tie memory to a Python object, so that the memory is freed when the object is garbage collected.</li><li>The preshed package contains both the Hash table where we store the (64-bit hash, C char array/pointer) couples, and a fast counter extension type (PreshCounter) that we will use to perform the BoW counting.</li><li>We use cimport instead of import to access the extensions types’ C methods and attributes.</li></ul><p>Let us now write the counting function:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cbd8d066d498f01bd467d7f8a56f4fcc/href">https://medium.com/media/cbd8d066d498f01bd467d7f8a56f4fcc/href</a></iframe><p>The _insert_in_hashmap function (used in fast_count) is defined below, as well as other utility functions, directly inspired from spaCy’s <a href="https://github.com/explosion/spaCy/blob/f95ecedd83ce75b7062af6afaf47f9ed6fe59550/spacy/strings.pyx">strings.pyx</a>:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1f0bb245d504ed7793346ebd3efd3ed5/href">https://medium.com/media/1f0bb245d504ed7793346ebd3efd3ed5/href</a></iframe><p>Finally, we compile the Cython file with the following simple setup.py, by executing <em>python setup.py — build-ext -if</em> (more details on Cython compilation <a href="https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html#compilation">here</a>):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4b4dd0703be42dbd58cb16188491ac90/href">https://medium.com/media/4b4dd0703be42dbd58cb16188491ac90/href</a></iframe><p>This time, by executing text2bow on the example text we get:</p><pre>BOW representation: [(2751841902330220293, 1), (6601561424492272668, 1), (11916616154811659322, 1), (4645701992108298564, 1), (15099781594404091470, 2), (9470301821735104089, 1), (9556349622722280057, 1), (6437996555066804658, 1), (1020421249059553464, 1), (12460925685579008443, 1), (18223104521466393082, 1), (8530854408006191868, 1)]</pre><pre>Representation of the hash table using unicode strings: {<br>    6601561424492272668 :  post,<br>    11916616154811659322 :  word,<br>    1020421249059553464 :  process,<br>    4645701992108298564 :  step,<br>    6437996555066804658 :  amount,<br>    12460925685579008443 :  focus,<br>    18223104521466393082 :  number,<br>    15099781594404091470 :  text,<br>    9470301821735104089 :  optimize,<br>    9556349622722280057 :  occurrence,<br>    8530854408006191868 :  count,<br>    2751841902330220293 :  large,<br>}</pre><p>We can see the 64-bit hashes in the BoW, and a representation of the hash table using unicode strings.</p><p>Processing the same 26893-words book is now around 4 times faster:</p><pre>1.1 ms ± 6.68 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)</pre><p>Computing the<strong> BoW of 1M books now only takes ~18 minutes 🚀</strong> compared to the previous 1 hour 12 minutes!</p><h3>Conclusion</h3><p>In this post we have presented both a naive implementation of Bag-of-Words, as well as an optimized Cython one, directly inspired by spaCy’s internal way of managing strings.</p><p>Exploring Cython and spaCy is a good way to write more optimized NLP pipelines, and we definitely advise you to read more about these two. In particular some great introductory Cython material can be found in Kurt Smith’s <a href="https://www.youtube.com/watch?v=gMvkiQ-gOW8">video</a>, and his book, while more advanced topics can be studied in Cython’s <a href="https://cython.readthedocs.io/en/latest/">documentation</a>. For spaCy, same thing goes with the <a href="https://spacy.io/usage">great documentation</a>, and don’t hesitate to delve into the <a href="https://github.com/explosion/spaCy">code</a>!</p><p><a href="#e76d">¹</a> Which is exactly what Cython’s documentation was warning us about! The tricky manual memory management of the C strings is in fact taken care of in the _allocate function.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=574c308a9ff3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/glose-team/%EF%B8%8F-fast-bag-of-words-using-spacy-and-cython-574c308a9ff3">🏎️ Fast bag-of-words using spaCy and cython</a> was originally published in <a href="https://medium.com/glose-team">Glose Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building reusable user interfaces in Swift]]></title>
            <link>https://medium.com/glose-team/building-reusable-user-interfaces-in-swift-599cc56e4208?source=rss----9af43e5ffc08---4</link>
            <guid isPermaLink="false">https://medium.com/p/599cc56e4208</guid>
            <category><![CDATA[ui]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[reusable]]></category>
            <dc:creator><![CDATA[Thomas Ricouard]]></dc:creator>
            <pubDate>Fri, 17 Nov 2017 15:26:43 GMT</pubDate>
            <atom:updated>2017-11-17T15:26:42.599Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xht3cuZH5cUQCZwRyUtR9Q.png" /><figcaption>iOS 11 unified some of the things iOS 10 started</figcaption></figure><p>Note: This post is a continuation, but also a better approach of my previous <a href="https://medium.com/glose-team/reuse-complex-uitableview-with-swift-a3a37cd55d40">post about reusing UITableView</a></p><p>I had some fun writing some of our user interface code for the next version of our iOS application at <a href="https://medium.com/u/61e079e0d760">Glose books</a>. Writing clean UI code is essential, writing <strong>independent</strong>, clearly defined and confined <strong>UIViewController</strong> and interface flow <strong>IS</strong> fundamental, if you want to pack your applications with a lot of features, while maintaining a clean, understandable, and reusable codebase.</p><p>By the past, I’ve made the mistake to not refactor my code early on, always postponing some stuff to “When I’ll need to use it a third time”, and in some old legacy codebase, it was clearly a mistake. I never want to write spaghetti code ever again. You know, that little specificity you want to reuse and re-wire without rewriting the whole interface logic behind… anyway those sorts of things are old stories now.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cvojIHEfbH7KWnx8mAd4JQ.jpeg" /></figure><p>In the following example, I’ll try to teach you how to have a clean, less than 100 lines <strong>UIViewController</strong>, which display a <strong>UICollectionView</strong> with a complicated user interface and business logic. A <strong>UICollectionView</strong> configuration that you can literally connect in any <strong>UIViewController</strong> in a few lines of code.</p><p>Let’s say you want to to display some books in a UICollectionView, so you want to display them as a grid, but also as list, so you’ll need a least two different <strong>UICollectionViewCell</strong> . But you also want to provide a custom title for your <strong>UIViewController</strong> navigation. Your cells have some delegate for forwarding action to your business logic, and you also want to provide a few customisation options. On top of that you also want to support 3D touch previewing. Oh and I forgot, it’ll also support pagination. As you can understand, you’ll not want to write the code to handle all that more than once.</p><p>And I’ll need to use this UICollectionView in a view controller which contain only that, a simple grid and list of books, but also in a more complexe one, where I’ll have a more complicated user interface as the header of the said collection view.</p><h3>And now the code</h3><h4>The Inteface Provider</h4><p>Let’s write the code and wire all that together. First, here is the class which will register as the delegate and datasource of your <strong>UICollectionView</strong>, it’ll provide its own delegate, so you can customize it wherever you’ll need it. It’ll be much shorter to implement in your final UIViewController than the full <strong>UICollectionView</strong> delegate and datasource. I call them the InterfaceProvider:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9a009771347fde257c3dda0d856ec2f6/href">https://medium.com/media/9a009771347fde257c3dda0d856ec2f6/href</a></iframe><p>As you can see in the code above, the delegate provide method where you can return a customized header, you can also listen to the UIScrollView did scroll events. Useful in some case.</p><p>This class will manage the integrity of your UICollectionView from the moment you instanciate it from your controller. You pass your view controller and collection view as weak, because you view controller is responsible to keep, well… itself and his UICollectionView alive.</p><p>In my case the datasource is just some id of objects I load from a shared store. Very easy and lightweight to pass around.</p><h4>The Data Provider</h4><p>Now, let’s see how you actually configure and build your datasource, alongisde some other data for your UIViewController, it’ll be the provider for the business logic, I call them DataProvider:</p><p>It’s defined by a very simple protocol, that you later use in concrete classes:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7e954f6ca53a98d6c37eb69ec4b5b461/href">https://medium.com/media/7e954f6ca53a98d6c37eb69ec4b5b461/href</a></iframe><p>It also provide a base implementation, because in my case, it need a user from where to feed its datasource, it’s very handy, you can provide your own initialisers, properties, and default implementation if needed.</p><p>In your implementation, you’ll have to return the title, which is used as the navigation bar title, your datasource, and also implement the method to actually load your data and follow page navigation.</p><p>Now, here is one of the concrete implementation of this protocol, for a collection view which will basically show all the books of the user:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e011fc94d74a75d53a3d7c47b9773af9/href">https://medium.com/media/e011fc94d74a75d53a3d7c47b9773af9/href</a></iframe><h4>The View Controller</h4><p>And now, the part you were all waiting for, the UIViewController wiring all that together, which is only 75 lines long:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9caf1dbefa19144c442c9e87dda19d94/href">https://medium.com/media/9caf1dbefa19144c442c9e87dda19d94/href</a></iframe><p>Note: I’m using <a href="https://github.com/ReSwift/ReSwift">ReSwift</a>, a Swift implementation of <a href="https://redux.js.org">Redux</a>, it fits very well with this interface pattern because you don’t have to wire any of your data update and changes. Everything is live.</p><h4>Using it</h4><p>And to instantiate this UIViewController, you simply inject your DataProvider as a dependency like this, if you don’t, your app will crash, because the data provider is declared as a var! in your view controller. And believe it or not, that is totally fine! You won’t forget a dependency, would you?</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fce6edb1ad569e157d9dcdb36002cfd0/href">https://medium.com/media/fce6edb1ad569e157d9dcdb36002cfd0/href</a></iframe><p>Here you go, now you can create various concrete implementation of your DataProvider, and inject it in your BooksCollectionViewController without writing all new controllers.</p><p>It’s also very decoupled, for example I’m using the InterfaceProvider but not the DataProvider in another controller, because I needed the interface, but the business logic was too complicated and is handled inside the controller itself.</p><p>You can also push it one step further and use a system of contained and container view controllers, that you can assemble like legos. More on that later!</p><p>Feel free to comment or ping me at <a href="https://twitter.com/dimillian">@Dimillian</a> if you have any question!</p><p>Happy coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=599cc56e4208" width="1" height="1" alt=""><hr><p><a href="https://medium.com/glose-team/building-reusable-user-interfaces-in-swift-599cc56e4208">Building reusable user interfaces in Swift</a> was originally published in <a href="https://medium.com/glose-team">Glose Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Efficient asynchronous flow with React and Redux]]></title>
            <link>https://medium.com/glose-team/efficient-asynchronous-flow-with-react-and-redux-311e96d27b2?source=rss----9af43e5ffc08---4</link>
            <guid isPermaLink="false">https://medium.com/p/311e96d27b2</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[redux]]></category>
            <dc:creator><![CDATA[Mathieu Savy]]></dc:creator>
            <pubDate>Tue, 24 Oct 2017 09:02:23 GMT</pubDate>
            <atom:updated>2017-10-24T11:58:34.411Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*85v7D8S-bl_XIiauFSUTig.jpeg" /></figure><p>At <a href="https://glose.com">Glose</a>, we are building the reading platform of the 21st century, serving millions of eBooks to millions of readers across the world, giving them access to an incredible amount of knowledge right under their finger.</p><p>Our interfaces can be complex as we offer a lot of interactions for the readers, and that imply a certain amount of HTTP requests between client and server. That’s why we need to have an efficient flow to handle it.</p><p>React and Redux allow us to have a great and efficient control over our state and user interface. When it comes to asynchronous actions that interact with an API (or whatever, API is just the most common example), things can became redundant and laborious. We will see a way to have a great async flow without writing tons of identical code.</p><p>This article will only deal with thunks, which are actions that return functions. If you are not familiar with this concept, I invite you to have a look at <a href="https://github.com/gaearon/redux-thunk">redux-thunk</a> middleware.</p><h3>The naive way</h3><p>The most common way to deal with asynchronous actions is with thunks. Thunks are actions that are not objects but functions that can dispatch other actions — and eventually do other things, but let’s keep it simple for now.</p><p>Taking a simple action we want to perform, getting some info about an organization on GitHub. We want to know two things:</p><ul><li>when the request starts</li><li>when the request ends (and if it succeeded or failed)</li></ul><p>With a thunk, it will look like the following:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/073a0d77d69f78c70fda210d69b4de99/href">https://medium.com/media/073a0d77d69f78c70fda210d69b4de99/href</a></iframe><p>First we are dispatching aFETCH_ORG_REQUEST action, so that the user interface can handle the loading state.</p><p>Then, the fetch action is performed. It’s the actual HTTP request. If you don’t know about fetch, <a href="https://github.com/github/fetch">check it out</a>, it’s now native on modern browsers and there is a good polyfill for older ones.</p><p>When the fetch is over, we have two possibilities:</p><ul><li>either it has failed, then an error is raised an the FETCH_ORG_FAILED action is dispatched</li><li>or everything went well (yay!) and the FETCH_ORG_SUCCESS action is dispatched with the result of the request</li></ul><p>Pretty simple isn’t it? Only 26 lines of code (yes it could be less, but I like to be able to read my code later without having a headache). Now imagine a second action to fetch the repositories of an organization. Yup, same stuff, probably around 25–30 lines of code too.</p><p>Now think about a large scale project with dozens or hundreds of this kind of HTTP requests. Three actions: ‘request’, ‘success, ‘failed’. Same code, hundreds of times. Pretty boring, and massive. It will represent a lots of code, for absolutely no interest, it’s only mechanic.</p><p>We have to factorize that.</p><h3>Factorize all the things!</h3><p>Okay, what can be factorized here? The three kinds of actions for a single one. Let’s start with that.</p><p>We should make a function that returns our action creator (that itself return a function — a thunk). Our function, let’s say asyncAction will take the name of actions to dispatch and the URL, the suffix will be added automatically.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a9a4acd9411be7542cafeb514f32312f/href">https://medium.com/media/a9a4acd9411be7542cafeb514f32312f/href</a></iframe><p>That’s not so bad. But, there are a lot of issues with that:</p><ul><li>We don’t handle arguments. This action would have been a lot better if we could give facebook as an argument to it, and simply call fetchOrganization(&#39;facebook&#39;).</li><li>Event if we took all arguments with an ...args at the function returned on line 2, we would lose the information of the argument name, and this is a really important information. We don’t want to refer to our arguments with their position on args array.</li></ul><p>Function is not so bad, but it didn’t work. Do you know what has been a cool addition to ES6? Classes.</p><h3>Do it classy</h3><p>Even if React and Redux world put a lot of accent on functional programming, I find OOP very elegant in this kind of situation.</p><p>What do we want here? A way to express easily all of our asynchronous actions that talks to our API without repeating ourselves.</p><p>We can make a class per action, its constructor handling the arguments (if any), a method to perform the call. And that’s it. The parent class takes care of all the stuff discussed before.</p><p>This kind of action will look like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c80daca65629c7a9fbe96df52ff2fa0b/href">https://medium.com/media/c80daca65629c7a9fbe96df52ff2fa0b/href</a></iframe><p>Pretty neat isn’t it?</p><p>Now, here is the magic behind AsyncAction parent class.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f6e1b3881e82f4a309fff975a016211d/href">https://medium.com/media/f6e1b3881e82f4a309fff975a016211d/href</a></iframe><p>The parent class only defines a toThunk method, that we will use in just a minute. The logic is the same as before, dispatch an action, make the request, dispatch according to the result.</p><p>Before we can make this work, we have to make a small transformation, because Redux doesn’t know what to do with a class. That’s why we defined a toThunk method, because Redux understands thunks with redux-thunk middleware.</p><p>We just have to intercept all actions that are AsyncAction instances, and continue working with the thunk of it. To do that, let’s create a small middleware:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3c8eae5f92e1ae0ae8b79e122435eee9/href">https://medium.com/media/3c8eae5f92e1ae0ae8b79e122435eee9/href</a></iframe><p>Don’t forget to add this middleware <strong>before</strong> redux-thunk middleware when creating your redux store (or else transformation to thunk will occur after the action is treaded by redux-thunk middleware), and we’re done.</p><p>If you are not familiar with Redux middlewares, I invite you to read <a href="http://redux.js.org/docs/advanced/Middleware.html">the documentation about middlewares</a>.</p><p>Now, you can simply dispatch your action like dispatch(new GetOrganization(&#39;facebook&#39;)). It seems unusual to have a class instanciation with a dispatch, but it’s nothing and you know what is working under, so it’s not a big deal.</p><h3>Going further</h3><p>This way to handle asynchronous actions is one among a lot of another. There are great libraries with different approaches like <a href="https://github.com/redux-saga/redux-saga">Redux-Saga</a> for example. It’s up to you to learn different ways to deal with your asynchronous flow and to choose the one that better fits your needs.</p><p>What I like about the approach I am presenting here is that it’s lightweight and very extensible. At <a href="https://glose.com">Glose</a>, we are using it, with some additions for error handling, before request hooks, etc.</p><p>The code is simple and you can easily add some logic to answer different issues about your asynchronous flow.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=311e96d27b2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/glose-team/efficient-asynchronous-flow-with-react-and-redux-311e96d27b2">Efficient asynchronous flow with React and Redux</a> was originally published in <a href="https://medium.com/glose-team">Glose Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Créer de belles couvertures de livres… numériques]]></title>
            <link>https://medium.com/glose-team/cr%C3%A9er-de-belles-couvertures-de-livres-num%C3%A9riques-c3ce2e307586?source=rss----9af43e5ffc08---4</link>
            <guid isPermaLink="false">https://medium.com/p/c3ce2e307586</guid>
            <category><![CDATA[design]]></category>
            <category><![CDATA[book-covers]]></category>
            <category><![CDATA[books]]></category>
            <category><![CDATA[graphic-design]]></category>
            <dc:creator><![CDATA[Anne Catel]]></dc:creator>
            <pubDate>Wed, 27 Sep 2017 13:52:59 GMT</pubDate>
            <atom:updated>2017-09-27T16:16:33.516Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SBWeZUMtrqq0j9_BFWcWIQ.jpeg" /></figure><p>Depuis mon plus jeune âge, j’aime les livres.</p><p>J’aime d’abord ce qu’il y a dedans. Je ne compte pas le nombre de journées passées à dévorer roman après roman, mes parents me fustigeant d’aller jouer dehors durant les vacances d’été, alors même que suivre les péripéties de tous ces personnages constituait pour moi la plus belle des aventures estivales. Je me revois aussi dans mon lit, me cachant sous la couverture avec une lampe de poche pour pouvoir lire en cachette jusqu’à une heure avancée de la nuit –voire du matin, tout en sachant pertinemment que le réveil serait difficile. Peu importe, c’était le prix de l’évasion, et ça en valait largement la peine. C’est toujours le cas d’ailleurs.</p><p>J’aime aussi l’objet. Le livre que l’on aime regarder, explorer, toucher, exposer, conserver dans sa bibliothèque. Le livre que l’on est heureux d’avoir, que l’on a envie d’ouvrir, dont on veut parcourir toutes les pages, les unes après les autres, ou que l’on souhaite simplement posséder. Le livre que l’on va ajouter à sa collection et exposer comme un trophée. Le livre que l’on dévore. Le livre qui nous dévore. Ce livre qui finalement, devient une part de nous-même.</p><h3>Le livre d’hier à aujourd’hui</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/498/1*bJoH5f5cvGel9m6D9R28ZA.jpeg" /><figcaption>Une reliure du XIVe siècle.</figcaption></figure><p>Aujourd’hui, c’est un objet des plus communs, mais il n’en a pas toujours été ainsi. Jusqu’au XIXe siècle, le livre était un objet incroyablement précieux. Ouvragé à l’extrême, avec des matériaux nobles, parfois serti d’or et/ou de pierres précieuses, il contenait et protégeait des textes sacrés, réservés à une population restreinte d’érudits qui savaient lire. Entre le XVIe et le XIXe siècle, c’était l’acheteur qui faisait façonner la couverture par un relieur, le livre étant vendu nu, sans rien pour le protéger. Dès 1820, la production de livres s’industrialise, les nouvelles presses permettent de produire plus à moindre coût. On imite le style du cuir et de l’or et par le même temps, la littérature devient profane, les auteurs s’accordent plus de liberté de création, les illustrateurs de couvertures font de même. Le livre devient un objet tendance et la couverture sert à décrire et mettre en valeur son contenu. Fin XIXe-début XXe, les <em>penny dreadfuls</em> (des histoires effrayantes qui ont donné leur nom à la série TV du même nom) et <em>yellowbacks</em> côtoient les beaux-livres pour lesquels les artistes-illustrateurs s’en donnent à cœur joie.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/736/1*l6xJeSywiJEyiKX7ULTGGA.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/380/1*TCPnDEHbML4Dbluii3Q7Og.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/373/1*cyFmwiR6sTf3kNec9nnzRA.png" /><figcaption>Quelques penny dreadfuls et yellow-backs, les livres cheap du XIXe siècle.</figcaption></figure><p>Certains, comme Alfons Mucha, ou George Wharton Edwards, deviennent de véritables stars. La couverture prend une signification de plus en plus culturelle et à visée communicative. Plusieurs courants comme l’Art Nouveau et le Dadaïsme se succèdent. Après-guerre, la concurrence entre éditeurs fait rage, et la couverture devient un outil marketing, une sorte de teaser du livre, qui tend à attirer l’attention du lecteur.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/450/1*k_fiHt2Df1ans14mVo1Rzg.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/672/1*Xf1FNU0RS4k2Y3KYQG3AvA.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/1*2chZVjMIe4GXFDoiWquVJw.jpeg" /><figcaption>De gauche à droite : une illustration de Alfons Muchas, une illlustration de George Wharton Edwards, une couverture de la collection Hetzel.</figcaption></figure><p>Pour une histoire du livre plus détaillée, je ne peux que conseiller <a href="https://www.grapheine.com/histoire-du-graphisme/histoire-graphisme-couvertures-livres-1">l’excellent article</a> de Graphéine. D’autres sources sont disponibles en bas de page.</p><p><em>Don’t judge a book by its cover.</em> Bien que l’expression se révèle d’une grande sagesse, je ne peux pas m’empêcher de ne pas être d’accord si je la prends au sens littéral. Imaginons des clients qui déambulent aux hasard parmi les rayons d’une librairie, cherchant sans l’aide de leur libraire ni idée préconçue leur prochaine lecture. C’est bien la couverture qui la première, attirera leur attention vers tel ou tel ouvrage, qui saura donner une idée du style du livre, de son sujet, du ton employé. C’est elle qui attirera leur regard par des couleurs vives et chatoyantes ou leur indiquera un certain sérieux par sa sobriété. Un essai politique ne ressemblera pas à un polar scandinave, une romance Harlequin non plus.</p><h3>Beau livre et livre numérique</h3><p>Quand je suis arrivée chez <a href="http://www.glose.com">Glose</a>, qui est une librairie numérique, j’étais –et je suis toujours– animée par une passion des beaux livres et des belles couvertures. Alors quand nous avons décidé de mettre à disposition gratuitement plusieurs dizaines d’ouvrages libres de droits, j’y ai vu l’occasion rêvée de partager ma passion pour la littérature en sublimant tous ces livres par leur couverture.</p><p>Pourtant, au vu de ce qui se faisait chez la concurrence, la création de couvertures pour les livres numériques libres de droits semblait reléguée au second plan. Dans le meilleur des cas, l’éditeur ou le distributeur reprenait la version imprimée de la couverture, la réadaptait parfois. Mais si l’édition avait le malheur de n’être que numérique, l’effort était moindre, la couverture, pauvre. Lorsque l’on produit plusieurs centaines de livres numériques dans le but de les distribuer gratuitement, il est compréhensible que le budget alloué soit moins conséquent que pour des productions payantes (et oui, le design graphique a un prix !). Pour autant, l’automatisation et la standardisation ne sont pas forcément synonymes de médiocrité pour peu que l’on y mette un peu du nôtre. En l’occurence, le fait que ces livres ne soient pas imprimés, qu’ils ne se transforment pas en objets physiques, semblait être un prétexte pour les reléguer au statut de sous-produit, alors même que leur contenu était identique.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/525/1*S5P3KnPGfZvSicEJYLOErw.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/333/1*lB25F77OXSDd58K9jxlUTA.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*qedOURWesRaVMa5_tikOng.jpeg" /><figcaption>Quelques exemples de livres numériques libres de droits trouvés sur les librairies de iBooks, Amazon Kindle et Google Play Livres.</figcaption></figure><p>Mon (notre) envie est de rendre la lecture plus facile, plus agréable, plus fun. La première étape n’est-elle pas de la rendre plus désirable ? À ce titre, quoi de mieux qu’une belle couverture, pour donner envie de découvrir ce qui se cache derrière ?</p><h3>Rendre la lecture désirable</h3><p>J’ai donc commencé à créer une poignée de couvertures pour la collection de livres libres de droits de Glose. Ça a été l’occasion pour moi de découvrir (ou re-découvrir) certains grands classiques de la littérature que je ne connaissais parfois que de nom. En effet, pour concevoir la couverture d’un livre, il est nécessaire de s’imprégner pleinement de son contenu, afin d’en saisir les subtilités, le ton et l’ambiance générale. Pour <em>Le Cid</em> de Corneille par exemple, outre les rideaux qui évoquent le fait qu’il s’agit d’une pièce de théâtre, j’ai mis en corrélation une épée et une rose, symboles du dilemme de Rodrigue lorsqu’il doit choisir entre son amour pour Chimène et son devoir en battant en duel le père de cette dernière. L’ombre portée sous le titre souligne la dimension dramatique de l’œuvre.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*YlPo4QlD18uheqY6CZLhGA.png" /><figcaption><em>Le Cid</em>, de Pierre Corneille • <a href="https://dribbble.com/shots/2950244-Le-Cid-Book-Cover">Dribbble</a></figcaption></figure><p>Pour <em>Le Dernier Jour d’un condamné</em> de Victor Hugo, plaidoyer contre la peine de mort, j’ai assimilé le titre et le nom de l’auteur à des barreaux devant la silhouette déshumanisée et anonyme du prisonnier. J’ai repris les mêmes codes graphiques pour <em>Quatrevingt-treize</em> et <em>L’Homme qui rit</em>, deux autres romans à forte portée politique. Dans le premier, qui a pour trame de fond la Révolution Française et la <a href="https://fr.wikipedia.org/wiki/Terreur_(R%C3%A9volution_fran%C3%A7aise)">Terreur</a>, j’ai situé le contexte en montrant le chateau des Lantenac (d’après un dessin de Victor Hugo), famille au sein de laquelle vont se confronter deux visions politiques opposées : celle de la tradition monarchique et celle de la révolution républicaine. Dans le second, j’ai représenté les deux éléments qui mettent l’intrigue en place : le naufrage du bateau d’où le protagoniste réchappe et le rictus qui le défigure. Les couleurs sont volontairement ternes et tristes, en accord avec la dimension dramatique et politique de ces trois œuvres.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*WgBR0nTeFSu4_yXdwLc53A.png" /><figcaption>Le Dernier Jour d’un condamné, Quatrevingt-treize et L’Homme qui rit, de Victor Hugo • <a href="https://dribbble.com/shots/2947487-Victor-Hugo-Book-Covers">Dribbble</a></figcaption></figure><p>La couverture des <em>Malheurs de Sophie</em> de la Comtesse de Ségur a raisonné en moi comme un souvenir d’enfance. Outre le livre que j’ai lu lorsque j’étais plus jeune, j’ai également été bercée par la série d’animation que je regardais chaque soir après l’école (et non, je ne faisais pas que lire 😄). L’image qui m’est donc immédiatement venue en tête en pensant à Sophie a été le tissu rose et la dentelle de sa robe, son ruban vert, ainsi que l’épisode des sourcils coupés, qui est de loin celui qui m’a le plus marquée.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QZz7MgoAxy4usCG3w7tm7w.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*lhAqZdZJu9SCFaKOs0wzNA.jpeg" /><figcaption>Les Malheurs de Sophie, de la Comtesse de Ségur • Série d’animation de <a href="https://fr.wikipedia.org/wiki/Bernard_Deyri%C3%A8s">Bernard Deyriès</a> • <a href="https://dribbble.com/shots/2951048-Les-Malheurs-de-Sophie-Book-Cover">Dribbble</a></figcaption></figure><p>D’autres couvertures ont suivi, chacune tendant à retranscrire l’univers de chaque livre : la vie malheureuse d’épouse et de mère de Jeanne dans <em>Une Vie</em> de Guy de Maupassant, les batailles Napoléoniennes de <em>La Chartreuse de Parme</em> de Stendhal, <em>Le Rouge et le Noir</em> du même auteur, etc.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*LCehFD0cMjWKNuueISYYrQ.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*T5VbR1zcTRcRFfz7jGG5rA.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*mOHXBML0P6WJmAS0DBhNEg.jpeg" /><figcaption>Une Vie, de Guy de Maupassant • La Chartreuse de Parme, de Stendhal • Le Rouge et le Noir, de Stendhal</figcaption></figure><h3><strong>La création d’une collection</strong></h3><p>Glose étant régulièrement utilisée en milieu scolaire, nous avons décidé de mettre en plus à disposition un grand panel de livres libres de droits à étudier en classe. S’ensuivait donc un grand nombre de couvertures à réaliser, mais nous ne voulions pas que cela se fasse au détriment de la qualité.</p><p>Contrairement aux livres précédents, j’ai donc choisi de rester dans la tradition éditoriale française, <a href="http://www.slate.fr/story/69737/pourquoi-france-couvertures-livres-sobres">réputée pour la sobriété de ses couvertures</a>. Afin que chacune d’elle soit unique, j’ai défini un ensemble de constantes et de variables, dans l’optique de créer la collection des Classiques Glose.</p><h4>La structure</h4><p>Elle est identique pour tous les livres, avec la présence des informations principales sur le tiers haut de la couverture, et l’image évocatrice de l’œuvre occupant les deux tiers restants.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*hS0NftdKyB1vCHu8ghw2hw.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*FNSbanU8fTBdQd1GToxqEw.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*m6XO4kIxOOuFF5NPoJFKyg.jpeg" /></figure><h4>L’image et la couleur</h4><p>Le choix des images s’est révélé d’une importance capitale, le but étant que ces visuels transmettent l’univers et l’ambiance inhérents à chaque œuvre : la bourgeoisie parisienne de <em>Pot-Bouille</em>, l’ambiance exotique du <em>Supplément au Voyage de Bouguainville</em>, la passion adultérine de Thérèse et Laurent dans <em>Thérèse Raquin</em>, etc. De là est ressortie une ambiance colorée que j’ai appliquée à la marge et au nom de l’auteur, le but étant de donner de la singularité à chacune des couvertures.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*RxDEb1B4DU-j7MH42XFnMw.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*EunQQ55BfMRIZXbR1_Z2pg.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*yZyhaZ1P0bzcmjsLu4JClQ.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/1*kE9yJqNvrqDqEUsntc_Yjw.jpeg" /></figure><h4>La typographie</h4><p>J’ai choisi de tirer parti de la grande variété de longueurs de titres en m’octroyant une certaine liberté dans leur composition, ce qui a permis de donner des rythmes et impacts différents aux ouvrages.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/440/1*NzIyGf_q4FAzAyi1NBtfOg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/440/1*SN6EOEPT6JdHFwxsB0kUkw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/440/1*0CpYcP8v7EyGSei_3WQFKw.png" /></figure><p>Certains de ces livres sont d’ores et déjà disponibles sur <a href="https://glose.com/bookstore/livres-gratuits">Glose</a>.</p><p>Qu’elle soit un exercice entièrement créatif ou plus contraint, la création de toutes ces couvertures de livres consiste vraiment à dévoiler une part de moi-même : j’y retranscris ce qui m’a marqué dans le livre, ce qu’il m’évoque, ce qu’il me fait ressentir, dans le but de transmettre tout cela au futur lecteur et de le guider, lui donner envie de découvrir l’œuvre. C’est aussi une l’occasion d’appréhender l’œuvre d’une autre manière, de la comprendre et de s’en imprégner pour mieux la représenter visuellement, sans la dénaturer pour autant. L’expérience se révèle donc extrêmement enrichissante à titre personnel.</p><p>Et vous, qu’est-ce que vous attendez d’une bonne couverture de livre ? Quelles sont celles qui vous ont le plus marqué ?</p><p>Sources : <a href="https://www.grapheine.com/histoire-du-graphisme/histoire-graphisme-couvertures-livres-1">https://www.grapheine.com/histoire-du-graphisme/histoire-graphisme-couvertures-livres-1</a><br><a href="http://histoirevisuelle.fr/cv/icones/1818">http://histoirevisuelle.fr/cv/icones/1818</a><br><a href="https://www.lamaisondubourg.net/single-post/2016/03/04/Explorons-en-profondeur-Les-couvertures-de-livre">https://www.lamaisondubourg.net/single-post/2016/03/04/Explorons-en-profondeur-Les-couvertures-de-livre</a><br><a href="http://www.lib.msu.edu/exhibits/historyofbinding/20thcentury/">http://www.lib.msu.edu/exhibits/historyofbinding/20thcentury/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c3ce2e307586" width="1" height="1" alt=""><hr><p><a href="https://medium.com/glose-team/cr%C3%A9er-de-belles-couvertures-de-livres-num%C3%A9riques-c3ce2e307586">Créer de belles couvertures de livres… numériques</a> was originally published in <a href="https://medium.com/glose-team">Glose Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Custom scheme handling and WKWebview in iOS 11]]></title>
            <link>https://medium.com/glose-team/custom-scheme-handling-and-wkwebview-in-ios-11-72bc5113e344?source=rss----9af43e5ffc08---4</link>
            <guid isPermaLink="false">https://medium.com/p/72bc5113e344</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[webview]]></category>
            <category><![CDATA[webkit]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Thomas Ricouard]]></dc:creator>
            <pubDate>Thu, 24 Aug 2017 15:09:53 GMT</pubDate>
            <atom:updated>2017-08-24T16:30:29.294Z</atom:updated>
            <content:encoded><![CDATA[<p>Apple wanted everyone to migrate from the perfectly fine <a href="https://developer.apple.com/documentation/uikit/uiwebview"><strong>UIWebview</strong></a> to the new <a href="https://developer.apple.com/documentation/webkit/wkwebview"><strong>WKWebView</strong></a> when they released it as a part of the iOS 8 SDK.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ltsbr0AAG6kXP_jkPC2pyQ.png" /></figure><p>There is even a “beautiful” warning message, and it’s been there for 4 years now… Well, you know what? Most apps still use <strong>UIWebView</strong>, because it’s simple to use and do the job. But, you should really migrate to the <a href="https://developer.apple.com/documentation/webkit/wkwebview"><strong>WKWebView</strong></a>, because it’s backed directly by the <strong>WebKit</strong> framework, it have more features, and it use a faster javascript engine.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ws8sg2hM36cZoEde7XvIVA.png" /></figure><p>At <a href="https://medium.com/u/61e079e0d760">Glose books</a>, as part of our reader, we extensively use <strong>UIWebView</strong>, even if it’s extremely custom and fuse native and web interactions and UI. Yes, the new Javascript bridge (called <a href="http://WKScriptMessageHandler"><strong>WKScriptMessageHandler</strong></a>) is one of the most wonderful thing of the <strong>WKWebView</strong>. The core content of a book is still in <strong>HTML</strong> format. So it makes sense to use a browser view(be it a UIWebView or anything else) to display it. In the current version of our application, our reader still run on <strong>UIWebview </strong>(shame, shame, shame), because at the time, it was the only solution available.</p><p>In our in development version, we’ve made a totally new mobile reader, and this time it’s based on <strong>WKWebView</strong>. Why did it took us so long? Because until the <strong>iOS 11 SDK</strong> there was no way to handle custom url scheme loading with a <strong>WKWebView</strong>. There were still so many advantages of using <strong>WKWebView</strong>, but the custom scheme loading was a total blocker.</p><p><strong>UIWebView</strong> support custom <strong>NSURLProtocol</strong>, it means that in order to provide a custom way to load a custom url scheme, you simply had to create a subclass of <strong>NSURLProtocol</strong> and then register your class with NSURLProtocol. Then anything calling your custom scheme (like helloworld://) would invoke your custom NSURLProtocol class. Then you’re responsible for loading and forwarding your content. This is very powerful, and in our case we use it to load various assets within a book, such as images, videos etc… So a total no go if we can’t do that. There is a lot of workaround we could have used, but they were mostly hacks, and not really ok for a production application.</p><p><strong>WKWebview</strong> doesn’t support custom <strong>NSURLProtocol</strong>, so you can register any classes you want, it’ll never be called because of a <strong>WKWebView</strong> requests. Well <strong>REJOICE</strong> ! In iOS 11 Apple added <a href="https://developer.apple.com/documentation/webkit/wkurlschemehandler"><strong>WKURLSchemeHandler</strong></a>. It works almost the same as NSURLProtocol!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1d3kxpUMTl2-1xfdfmoHyQ.png" /></figure><p>Here is some code example for you to understand, because let’s be honest, you don’t really care about all the bullshit above :)</p><p>So first you create your <strong>WKURLSchemeHandler</strong> subclass, you have to implement the two methods, start and stop. Only the start method is relevant, because this is where you are going to do the work.</p><p>The stop method can be left empty, or used for you to do any necessary cleanup.</p><p>You have access to the full request, so you can check, extract anything from the url, load your necessary data and forward them to the <strong>WKWebView</strong>. Your content should then load.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e96ee821bfbe575d15b41799e0cdc74e/href">https://medium.com/media/e96ee821bfbe575d15b41799e0cdc74e/href</a></iframe><p>And after your class is created, you have to register it with the WKWebView, and you have to do that when you create its configuration:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/29eb6857b83e9ccd7f5677b65677f0a7/href">https://medium.com/media/29eb6857b83e9ccd7f5677b65677f0a7/href</a></iframe><p>You’re all set, now you’re ready to load any custom content in your <strong>WKWebView</strong>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=72bc5113e344" width="1" height="1" alt=""><hr><p><a href="https://medium.com/glose-team/custom-scheme-handling-and-wkwebview-in-ios-11-72bc5113e344">Custom scheme handling and WKWebview in iOS 11</a> was originally published in <a href="https://medium.com/glose-team">Glose Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>