保留正文与补采附件。源页面当前未重新核验;具体限制见交付清单。
We value your privacy
We use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking "Accept All", you consent to our use of cookies.
Customise Reject All Accept All
Customise Consent Preferences
We use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.
The cookies that are categorised as "Necessary" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... Show more
NecessaryAlways Active
Necessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.
Cookie BCTempID
Duration 10 minutes
Description No description available.
Cookie __cf_bm
Duration 1 hour
Description This cookie, set by Cloudflare, is used to support Cloudflare Bot Management.
Cookie AWSALBCORS
Duration 7 days
Description Amazon Web Services set this cookie for load balancing.
Cookie _cfuvid
Duration session
Description Cloudflare sets this cookie to track users across sessions to optimize user experience by maintaining session consistency and providing personalized services
Cookie li_gc
Duration 6 months
Description Linkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.
Cookie __hssrc
Duration session
Description This cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.
Cookie __hssc
Duration 1 hour
Description HubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.
Cookie wpEmojiSettingsSupports
Duration session
Description WordPress sets this cookie when a user interacts with emojis on a WordPress site. It helps determine if the user's browser can display emojis properly.
Cookie BCSessionID
Duration 1 year 1 month 4 days
Description Blueconic sets this cookie as a unique identifier for the BlueConic profile.
Cookie _octo
Duration 1 year
Description No description available.
Cookie logged_in
Duration 1 year
Description No description available.
Cookie __Secure-YEC
Duration past
Description YouTube sets this cookie to stores the user's video player preferences using embedded YouTube video
Cookie __eoi
Duration 6 months
Description Description is currently not available.
Cookie AWSALBTGCORS
Duration 7 days
Description No description available.
Cookie login-status-p
Duration past
Description Description is currently not available.
Cookie AWSALBTG
Duration 7 days
Description No description available.
Cookie csrf_token
Duration session
Description No description available.
Cookie token_v2
Duration 1 day
Description Description is currently not available.
Cookie D
Duration 1 year
Description Description is currently not available.
Cookie PHPSESSID
Duration session
Description This cookie is native to PHP applications. The cookie stores and identifies a user's unique session ID to manage user sessions on the website. The cookie is a session cookie and will be deleted when all the browser windows are closed.
Cookie VISITOR_PRIVACY_METADATA
Duration 6 months
Description YouTube sets this cookie to store the user's cookie consent state for the current domain.
Cookie cookietest
Duration session
Description The cookietest cookie is typically used to determine whether the user's browser accepts cookies, essential for website functionality and user experience.
Cookie __Host-airtable-session
Duration 1 year
Description This cookie is used to enable us to integrate the services of Airtable.
Cookie __Host-airtable-session.sig
Duration 1 year
Description This cookie is used to enable us to integrate the services of Airtable.
Cookie m
Duration 1 year 1 month 4 days
Description Stripe sets this cookie for fraud prevention purposes. It identifies the device used to access the website, allowing the website to be formatted accordingly.
Cookie BIGipServer*
Duration session
Description Marketo sets this cookie to collect information about the user's online activity and build a profile about their interests to provide advertisements relevant to the user.
Cookie __cfruid
Duration session
Description Cloudflare sets this cookie to identify trusted web traffic.
Cookie _GRECAPTCHA
Duration 6 months
Description Google Recaptcha service sets this cookie to identify bots to protect the website against malicious spam attacks.
Cookie __Secure-YNID
Duration 6 months
Description Google cookie used to protect user security and prevent fraud, especially during the login process.
Cookie cookieyes-consent
Duration 1 year
Description CookieYes sets this cookie to remember users' consent preferences so that their preferences are respected on subsequent visits to this site. It does not collect or store any personal information about the site visitors.
Functional Functional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.
Cookie lidc
Duration 1 day
Description LinkedIn sets the lidc cookie to facilitate data center selection.
Cookie brw
Duration 1 year
Description No description available.
Cookie brwConsent
Duration 5 minutes
Description Description is currently not available.
Cookie WMF-Uniq
Duration 1 year
Description Description is currently not available.
Cookie loom_anon_comment
Duration 1 year
Description No description available.
Cookie loom_referral_video
Duration session
Description Description is currently not available.
Cookie VISITOR_INFO1_LIVE
Duration 6 months
Description A cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.
Cookie yt-remote-connected-devices
Duration Never Expires
Description YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.
Cookie ytidb::LAST_RESULT_ENTRY_KEY
Duration Never Expires
Description The cookie ytidb::LAST_RESULT_ENTRY_KEY is used by YouTube to store the last search result entry that was clicked by the user. This information is used to improve the user experience by providing more relevant search results in the future.
Cookie yt-remote-device-id
Duration Never Expires
Description YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.
Cookie yt-remote-session-name
Duration session
Description The yt-remote-session-name cookie is used by YouTube to store the user's video player preferences using embedded YouTube video.
Cookie yt-remote-fast-check-period
Duration session
Description The yt-remote-fast-check-period cookie is used by YouTube to store the user's video player preferences for embedded YouTube videos.
Cookie yt-remote-session-app
Duration session
Description The yt-remote-session-app cookie is used by YouTube to store user preferences and information about the interface of the embedded YouTube video player.
Cookie yt-remote-cast-available
Duration session
Description The yt-remote-cast-available cookie is used to store the user's preferences regarding whether casting is available on their YouTube video player.
Cookie yt-remote-cast-installed
Duration session
Description The yt-remote-cast-installed cookie is used to store the user's video player preferences using embedded YouTube video.
Cookie cp_session
Duration 3 months
Description Codepen sets this cookie for Help systems found in the website.
Cookie loid
Duration 1 year 1 month 4 days
Description This cookie is set by the Reddit. The cookie enables the sharing of content from the website onto the social media platform.
Analytics Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.
Cookie __hstc
Duration 6 months
Description Hubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).
Cookie hubspotutk
Duration 6 months
Description HubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.
Cookie _ga
Duration 1 year 1 month 4 days
Description Google Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.
Cookie ga*
Duration 1 year 1 month 4 days
Description Google Analytics sets this cookie to store and count page views.
Cookie __Host-psifi.analyticsTrace
Duration 6 hours
Description Description is currently not available.
Cookie __Host-psifi.analyticsTraceV2
Duration 6 hours
Description Description is currently not available.
Cookie _gh_sess
Duration session
Description GitHub sets this cookie for temporary application and framework state between pages like what step the user is on in a multiple step form.
Cookie YSC
Duration session
Description YSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.
Cookie ajs_anonymous_id
Duration 1 year
Description This cookie is set by Segment to count the number of people who visit a certain site by tracking if they have visited before.
Cookie vuid
Duration 1 year 1 month 4 days
Description Vimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website.
Performance Performance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Cookie AWSALB
Duration 7 days
Description AWSALB is an application load balancer cookie set by Amazon Web Services to map the session to the target.
Cookie acq
Duration past
Description Description is currently not available.
Cookie acq.sig
Duration past
Description Description is currently not available.
Cookie ptc
Duration 2 years
Description No description available.
Advertisement Advertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.
Cookie muc_ads
Duration 1 year 1 month 4 days
Description Twitter sets this cookie to collect user behaviour and interaction data to optimize the website.
Cookie guest_id_marketing
Duration 1 year 1 month 4 days
Description Twitter sets this cookie to identify and track the website visitor.
Cookie guest_id_ads
Duration 1 year 1 month 4 days
Description Twitter sets this cookie to identify and track the website visitor.
Cookie personalization_id
Duration 1 year 1 month 4 days
Description Twitter sets this cookie to integrate and share features for social media and also store information about how the user uses the website, for tracking and targeting.
Cookie guest_id
Duration 1 year 1 month 4 days
Description Twitter sets this cookie to identify and track the website visitor. It registers if a user is signed in to the Twitter platform and collects information about ad preferences.
Cookie bcookie
Duration 1 year
Description LinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.
Cookie __Secure-ROLLOUT_TOKEN
Duration 6 months
Description YouTube sets this cookie to manage feature rollout and experimentation. It helps Google control which new features or interface changes are shown to users as part of testing and staged rollouts, ensuring consistent experience for a given user during an experiment.
Cookie yt.innertube::nextId
Duration Never Expires
Description YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.
Cookie yt.innertube::requests
Duration Never Expires
Description YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.
Cookie session_tracker
Duration session
Description This cookie is set by the Reddit. This cookie is used to identify trusted web traffic. It also helps in adverstising on the website.
Cookie edgebucket
Duration session
Description Reddit sets this cookie to save the information about a log-on Reddit user, for the purpose of advertisement recommendations and updating the content.
Cookie did
Duration 1 year
Description Arbor sets this cookie to show targeted ads to site visitors.This cookie expires after 2 months or 1 year.
Uncategorised Other uncategorised cookies are those that are being analysed and have not been classified into a category as yet. No cookies to display. Reject All Save My Preferences Accept All Toggle SearchToggle Mobile Navigation Publish AI, ML & data-science insights to a global community of data professionals. Sign inSubmit an Article LatestEditor's PicksNewsletterShipAI
Write For TDS LinkedInXToggle Search Deep Learning
Understanding and implementing MobileNetV1 from scratch with PyTorch
Muhammad Ardi Putra
September 4, 202526 min read
Photo by Andreas Weilguny on Unsplash
Deep learning engineers used to focus on improving accuracy. They kept pushing the limit higher and higher until they eventually realized that the computational complexity of their models became more and more expensive. This was definitely a problem researchers needed to address because we want deep learning models to be able to work not only on high-end computers but also on small devices. To overcome this issue, Howard et al. back in 2017 proposed an extremely lightweight neural network model referred to as MobileNet which they introduced in a paper titled MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications [1]. In fact, the model proposed in the paper is the first version of MobileNet, which is commonly known as MobileNetV1. Currently we already have four MobileNet versions: MobileNetV1 all the way to MobileNetV4. However, in this article we are only going to focus on MobileNetV1, covering the idea behind the architecture and how to implement it from scratch with PyTorch — I’ll save the later MobileNet versions for my upcoming articles. ··· Learn this step by step with the interactive Machine Learning roadmap.
In order to achieve a lightweight model, MobileNet leverages the idea of depthwise separable convolution , which is used nearly throughout the entire network. Figure 1 below displays the structural difference between this layer (right) and a standard convolution layer (left). You can see in the figure that depthwise separable convolution basically comprises two types of convolution layers: depthwise convolution and pointwise convolution. In addition to that, we typically follow the conv-BN-ReLU structure when it comes to constructing CNN-based models. This is essentially the reason that in the illustration we have batch normalization and ReLU right after each conv layer. We are going to discuss depthwise and pointwise convolutions more deeply in the subsequent sections.
Figure 1. The structure of a standard convolution layer (left) and a depthwise separable convolution layer (right) [1].
A standard convolution layer is basically a convolution with the group parameter set to 1. It is important to remember that in this case using a 3×3 kernel actually means applying a kernel of shape C ×3×3 to the input tensor, where C is the number of input channels. The use of this kernel shape allows us to aggregate information from all channels within each 3×3 patch at once. This is the reason why the standard convolution operation is computationally expensive, yet in return the output tensor contains a lot of information. If you take a closer look at Figure 2 below, a standard convolution layer corresponds to the one in the leftmost part of the tradeoff line.
Figure 2. The tradeoff between fewer and more convolution groups [2].
If you’re already familiar with group convolution , depthwise convolution should be easy for you to understand. Group convolution is a method where we divide the channels of the input tensor according to the number of groups used and apply convolution independently within each group. For instance, suppose we have an input tensor of 64 channels and want to process it with 128 kernels grouped into 2. In such a case, the first 64 kernels are responsible for processing the first 32 channels of the input tensor, while the remaining 64 kernels process the last 32 channels of the input tensor. This mechanism results in 64 output channels for each group. The final output tensor is obtained by concatenating the resulting tensors from all groups along the channel dimension, resulting in a total of 128 channels in this example.
As we continue increasing the number of groups, we eventually reach the extreme case known as depthwise convolution, which is a special case of group convolution where the number of groups is set equal to the number of input channels. With this configuration, we basically have each channel processed independently of each other, causing every channel in the input to produce only a single output channel. By concatenating all the resulting 1-channel tensors, the final number of output channels remains exactly the same as that of the input. This mechanism requires us to use a kernel of size 1×3×3 instead of C ×3×3, preventing us to perform information aggregation along channel axis. This allows us to have extremely lightweight computation, yet in return causing the output tensor to contain less information due to the absence of channel-wise information aggregation.
Since the objective of MobileNet is to make the computation as fast as possible, we need to position ourselves at the rightmost part of the above tradeoff line despite capturing the least amount of information. This is definitely a problem that needs to be addressed, which is the reason why we employ pointwise convolution in the subsequent step.
Pointwise convolution is basically just a standard convolution, except that it uses kernels of size 1×1 — or to be more precise, it is actually C ×1×1. This kernel shape allows us to aggregate information along the channel axis without being influenced by spatial information, effectively compensating for the limitation of depthwise convolution. Furthermore, remember that depthwise convolution alone can only output a tensor of the same number of channels as its input, which limits our flexibility in designing the model architecture. By applying pointwise convolution in the next step, we can set it to return as many channels as we want, allowing us to adapt the layer to the subsequent one as needed. We can think of depthwise convolution and pointwise convolution as two complementary processes, where the former focuses on capturing spatial relationships while the latter captures channel relationships. These two processes might seem a bit inefficient at first glance since we can basically do the two processes at once using a standard convolution layer. However, if we take a closer look at the computational complexity, depthwise separable convolution is a lot more lightweight compared to the traditional convolution layer counterpart. In the next section I’ll discuss in more detail how we can calculate the number of parameters in these two models which definitely also affects the computational complexity.
Suppose we have an image of size 3× H × W , where H and W are the height and width of an image, respectively. For the sake of this example, let’s assume that we are about to process the image with 16 kernels of size 5×5, where the stride is set to 1 and the padding is set to 2 (which in this case is equivalent to padding = same). With this configuration, the size of the output tensor is going to be 16× H × W. If we use a standard convolution layer, the number of parameters will be 5×5×3×16 = 1200 (without bias), in which this number is obtained based on the equation in Figure 3. The use of bias term is not strictly necessary in this case, but if we do the total number of parameters is going to be (5×5×3+1) × 16 = 1216.
Figure 3. Equation to calculate the number of parameters of a convolution layer [2].
Now let’s calculate the parameter count of the depthwise separable convolution counterpart to produce the exact same tensor dimension. Following the same formula, we will have 5×5×1×3 = 75 for the depthwise convolution part (without bias). Or if we also account for the biases, then we will have (5×5×1+1) × 3 = 78 trainable params. In the case of depthwise convolution like this, the number of input channels is considered 1 since each kernel is responsible for processing a single channel only. To the pointwise convolution part, the number of parameters will be 1×1×3×16 = 48 (without bias) or (1×1×3+1) × 16 = 64 (with bias). Now to obtain the total number of parameters in the entire depthwise separable convolution process, we can simply calculate 75+48 = 123 (without bias) or 78+64 = 142 (with bias) — that’s nearly 90% reduction in parameter count if we compare it with the standard convolution! In theory, such an extreme drop in parameter count causes the model to have much lower capacity. But that’s just the theory. Later I will show you how MobileNet manages to keep up with other models in terms of accuracy.
···
Figure 4 below displays the entire MobileNetV1 architecture in detail. The depthwise convolution layers are the rows marked with dw , while the pointwise convolutions are the ones having 1×1 filter shape. Notice that each dw layer is always followed by a 1×1 convolution, indicating that the entire architecture mainly consists of depthwise separable convolutions. Furthermore, if you take a closer look at the architecture, you will see that spatial downsampling is done by the depthwise convolutions that have a stride of 2 (notice the rows with s2 in the table). Here you can see that every time we reduce the spatial dimension by half, the number of channels doubles to compensate for the loss of spatial information.
Figure 4. The entire MobileNetV1 architecture [1].
The authors of MobileNet proposed a new parameter tuning mechanism by introducing the so-called width and resolution multiplier , which are formally denoted as α and ρ , respectively. The α parameter can technically be adjusted freely, but authors suggest using either 1.0, 0.75, 0.5, or 0.25. This parameter works by reducing the number of channels produced by all convolution layers. For instance, if we set α to 0.5, the first convolution layer in the network will turn 3-channel input into 16 instead of 32. On the other hand, ρ is used to adjust the spatial dimension of the input tensor. It is important to note that even though ideally we should assign a floating-point number to this parameter, yet in practice it is more preferable to directly determine the actual resolution for the input image. In this case, authors recommend using either 224, 192, 160 and 128, in which the input size of 224×224 corresponds to ρ = 1. The architecture displayed in Figure 4 above follows the default configuration where both α and ρ are set to 1. ···
Authors conducted plenty of experiments to prove the robustness of MobileNet. The first result to discuss is the one displayed in Figure 5 below, where in this experiment they tried to find out how the use of depthwise separable convolution layers affects performance. The second row of the table shows the result obtained by the architecture I showed you earlier in Figure 4, whereas the first row is the result when the layers are replaced with traditional convolutions. Here we can see that the accuracy of MobileNet with traditional CNN is indeed higher than that of the one using depthwise separable convolutions. However, if we take into account the number of multiplications and additions (mult-adds) as well as the parameter count, we can clearly see that the one with traditional convolution layers requires much more computational cost and memory usage just to make a slight improvement in accuracy. Thus, with depthwise separable convolutions, even though the model complexity of MobileNet significantly gets reduced, authors proved that the model capacity remains high.
Figure 5. Performance comparison between MobileNet with depthwise separable convolution layers (second row) and its full-convolution counterpart (first row) [1].
The α and ρ parameters I explained earlier are mainly used to provide flexibility, considering that not all tasks require the highest MobileNet capability. Authors originally conducted experiments on 1000-class ImageNet dataset, but in practice, we might probably only need the model to perform classification on a dataset with fewer number of classes. In such a case, selecting lower values for the two parameters might be preferable as it can speed up the inference process while at the same time the model still has enough capacity to accommodate the classification task. Talking more specifically about α , using smaller value for this parameter causes MobileNet to have lower accuracy. But that’s the result on 1000-class dataset. If our dataset is simpler and has fewer classes, using smaller α might still be fine. In Figure 6 below the values 1.0, 0.75, 0.5, and 0.25 written next to each model correspond to the α used.
Figure 6. How width multiplier affects model accuracy, number of operations, and parameter count [1].
The same thing also applies to the ρ parameter, which is responsible for changing the resolution of the input image. Figure 7 below displays what the experimental results look like when we use different input resolutions. The results are somewhat similar to the ones in the previous figure, where the accuracy score decreases as we make the input image smaller. It is important to keep in mind that reducing input resolution like this also reduces the number of operations but does not affect the parameter count. This is essentially because the ones counted as parameters are the weights and biases, where in the case of CNN they correspond to the values inside the kernel. So, the parameter count will remain the same as long as we don’t change the configuration of the convolution layers. The number of operations, on the other hand, gets reduced in accordance with the decrease in input resolution since the number of pixels to be processed in smaller images is fewer than in larger images.
Figure 7. How input resolution affects model accuracy, number of operations, and parameter count [1].
Instead of just comparing different values for α and ρ , authors also compared MobileNet with other popular models. We can see in Figure 8 that the largest MobileNet variant (the one using maximum α and ρ) achieved comparable accuracy with GoogLeNet (InceptionV1) and VGG16 while maintaining the lowest computational complexity. This is basically the reason that I named this article The Tiny Giant — lightweight yet powerful.
Figure 8. MobileNet achieves comparable accuracy to popular models while maintaining a much lower computational complexity and parameter count [1].
Additionally, authors also compared the smaller MobileNet variant with other small models. What’s to me interesting in Figure 9 is that even though the parameter count of SqueezeNet is lower than MobileNet, the number of operations in MobileNet is over 22 times smaller than SqueezeNet while still maintaining higher accuracy.
Figure 9. The performance of the smaller MobileNet variant compared to popular models [1].
···
As we have understood the idea behind MobileNetV1, we can now jump into the code. The architecture I am about to implement is based on the table in Figure 4. As always, the first thing we need to do is to import the required modules. python
# Codeblock 1
import torch
import torch.nn as nn
from torchinfo import summary
Next, we initialize several configurable parameters so that we can adjust the model size according to our needs. In Codeblock 2 below, I denote α as ALPHA, which the value can be changed to 0.75, 0.5 or 0.25 if we want the model to be smaller. We don’t specify any variable for ρ since we can directly change IMAGE_SIZE to 192, 160 or 128 as we discussed earlier.
python
# Codeblock 2
BATCH_SIZE = 1
IMAGE_SIZE = 224
IN_CHANNELS = 3
NUM_CLASSES = 1000
ALPHA = 1
···
If we go back to Figure 4, we can see that MobileNet essentially only consists of repeating patterns, i.e., a depthwise separable convolution followed by pointwise convolution. However, notice that the first row in the figure does not follow this pattern as it is actually just a standard convolution layer. Due to this reason, we need to create a separate class for this, which I refer to as FirstConv in Codeblock 3 below.
python
# Codeblock 3
class FirstConv(nn.Module):
def __init__(self):
super().__init__()
self.conv = nn.Conv2d(in_channels=3,
out_channels=int(32*ALPHA), #(1)
kernel_size=3, #(2)
stride=2, #(3)
padding=1, #(4)
bias=False) #(5)
self.bn = nn.BatchNorm2d(num_features=int(32*ALPHA))
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.bn(self.conv(x)))
return x
Remember that MobileNet follows the conv-BN-ReLU structure. Thus, we need to initialize these three layers within the __init__() method of this class. The convolution layer itself is set to accept 3 input channels and output 32 channels. Since we want this number of output channels to be adjustable, we need to multiply it with ALPHA at the line marked with #(1). Keep in mind that we need to change the datatype to integer after the multiplication since having a floating-point number for channel count is just nonsense. Next, at line #(2) and #(3) we set the kernel size to 3 and the stride to 2. With this configuration, the spatial dimension of the resulting tensor is going to be half that of the input. Additionally, using kernel of size 3×3 like this implicitly requires us to set the padding to 1 to implement padding = same (#(4)). In this case we are not going to utilize the bias term, which is the reason that we set the bias parameter to False (#(5)). This is actually a standard practice when we use the conv-BN-ReLU structure, since at the end of the day the value distribution of the convolution kernels will be centered around 0 again by the batch normalization layer, cancelling out the biases applied within the convolution kernel.
In order to find out whether the FirstConv class works properly, we are going to test it with the Codeblock 4 below. Here we initialize the layer and pass a tensor simulating a single RGB image of size 224×224. You can see in the resulting output that our convolution layer successfully downsampled the spatial dimension to 112×112 while at the same time expanding the number of channels to 32.
python
# Codeblock 4
first_conv = FirstConv()
x = torch.randn((1, 3, 224, 224))
out = first_conv(x)
out.shape
text
# Codeblock 4 Output
torch.Size([1, 32, 112, 112])
···
As the first convolution is done, we can now work on the repeating depthwise-pointwise layers. Since this pattern is the core idea of depthwise separable convolution, in the following code I wrap the two types of conv layers in a class called DepthwiseSeparableConv.
python
# Codeblock 5
class DepthwiseSeparableConv(nn.Module):
def __init__(self, in_channels, out_channels, downsample=False): #(1)
super().__init__()
in_channels = int(in_channels*ALPHA) #(2)
out_channels = int(out_channels*ALPHA) #(3)
if downsample: #(4)
stride = 2
else:
stride = 1
self.dwconv = nn.Conv2d(in_channels=in_channels,
out_channels=in_channels, #(5)
kernel_size=3, #(6)
stride=stride, #(7)
padding=1,
groups=in_channels, #(8)
bias=False)
self.bn0 = nn.BatchNorm2d(num_features=in_channels) #(9)
self.pwconv = nn.Conv2d(in_channels=in_channels,
out_channels=out_channels, #(10)
kernel_size=1, #(11)
stride=1, #(12)
padding=0, #(13)
groups=1, #(14)
bias=False)
self.bn1 = nn.BatchNorm2d(num_features=out_channels) #(15)
self.relu = nn.ReLU() #(16)
def forward(self, x):
print(f'original\t: {x.size()}')
x = self.relu(self.bn0(self.dwconv(x)))
print(f'after dw conv\t: {x.size()}')
x = self.relu(self.bn1(self.pwconv(x)))
print(f'after pw conv\t: {x.size()}')
return x
Different from FirstConv which does not take any input argument in the initialization phase, here we set the DepthwiseSeparableConv class to take several inputs as shown at line #(1) in Codeblock 5 above. I do this because we want the class to be reusable across all depthwise separable convolution layers throughout the entire network, in which each of them has slightly different behaviors from one another.
We can see in Figure 4 that after the 3-channel image is expanded to 32 by the first layer, this channel count increases to 64, 128, and so on all the way to 1024 in the subsequent processes. This is basically the reason that I set this class to accept the number of input and output channels (in_channels and out_channels) so that we can initialize the layer with flexible channel configurations. It is also important to keep in mind that we need to adjust this channel counts based on ALPHA. This can simply be done using the code at line #(2) and #(3). Furthermore, here I also create a flag called downsample as the input parameter which by default is set to False. This flag is responsible to determine whether the layer will reduce the spatial dimension. Again, if you go back to Figure 4, you will notice that there are conditions where we reduce the spatial dimension by half and there are also some other conditions where the dimension is preserved. Every time we want to perform downsampling, we need to set the stride to 2, but if we don’t, we will set this parameter to 1 instead (#(4)).
Still with the Codeblock 5 above, the next thing we need to do is to initialize the layers themselves. As we have discussed earlier, the depthwise convolution is responsible to capture spatial relationships between pixels, which is actually the reason that the kernel size is set to 3×3 (#(6)). In order for the input channels to be processed independently of each other, we can simply set the groups and out_channels parameters to be the same as the number of input channels itself (#(8) and #(5)). It is worth noting that if we set out_channels to be more than the number of input channels — say, twice as large — then we will have each channel processed by 2 kernels. Lastly for the depthwise convolution layer, the stride parameter at line #(7) can either be 1 or 2 which is determined according to the downsampling flag we discussed earlier.
Meanwhile, the pointwise convolution uses 1×1 kernel (#(11)) since it is not intended to capture spatial information. This is actually the reason that we set the padding to 0 (#(13)) because there is no way this kernel size can reduce spatial dimension on its own. The groups parameter, on the other hand, is set to 1 (#(14)) because we want this layer to capture information from all channels at once. Unlike the depthwise convolution layer, here we can employ as many kernels as needed which corresponds to the number of channels in the resulting output tensor (#(10)). Meanwhile, the stride is set fixed to 1 (#(12)) since we will never perform downsampling with this layer.
Here we need to initialize two separate batch normalization layers to be placed after the depthwise and pointwise convs (#(9) and #(15)). As for the ReLU activation function, we only need to initialize it once (#(16)) since it is just a mapping function without any trainable parameters. Thanks to this, we can reuse the same ReLU instance multiple times within the network.
Now let’s see if our DepthwiseSeparableConv class works properly by passing a dummy tensor through it. Here I have prepared two test cases for this class. The first one is when we don’t perform downsampling and the second one is when we do. In Figure 10 below, the two tests I want to perform involve the use of the layers highlighted in green and blue, respectively.
Figure 10. The layers highlighted in green and blue are the ones we are going to simulate to test the DepthwiseSeparableConv class [1][2].
To create the green part, we can simply use the DepthwiseSeparableConv class and set the number of input and output channels to 32 and 64 as seen in Codeblock 6 below (#(1–2)). Passing downsample = False is not quite necessary since we already set it as the default configuration (#(3)) — but I do this anyway just for the sake of clarity. The shape of the dummy tensor x is also configured to have the size of 32×112×112, in which it matches exactly with the input shape of the layer (#(4)).
python
# Codeblock 6
depthwise_sep_conv = DepthwiseSeparableConv(in_channels=32, #(1)
out_channels=64, #(2)
downsample=False) #(3)
x = torch.randn((1, int(32*ALPHA), 112, 112)) #(4)
x = depthwise_sep_conv(x)
If you run the above code, the following output should appear on your screen. Here you can see that the depthwise convolution layer returns a tensor of the exact same shape as the input (#(1)). The number of channels then doubles from 32 to 64 after the tensor being processed by the pointwise convolution (#(2)). This result proves that our DepthwiseSeparableConv class works properly for non-downsampling process. We will use this output tensor in the subsequent test as the input for the blue layer.
text
# Codeblock 6 Output
original : torch.Size([1, 32, 112, 112])
after dw conv : torch.Size([1, 32, 112, 112]) #(1)
after pw conv : torch.Size([1, 64, 112, 112]) #(2)
The second test is quite similar to the first one, except that here we need to configure the model based on the number of input and output channels of the blue layer. Not only that, the downsample parameter also needs to be set to True since we want the layer to reduce the spatial dimension by half. See Codeblock 7 below for the details.
python
# Codeblock 7
depthwise_sep_conv = DepthwiseSeparableConv(in_channels=64,
out_channels=128,
downsample=True)
x = depthwise_sep_conv(x)
text
# Codeblock 7 Output
original : torch.Size([1, 64, 112, 112])
after dw conv : torch.Size([1, 64, 56, 56]) #(1)
after pw conv : torch.Size([1, 128, 56, 56]) #(2)
We can see in the above output that the spatial downsampling works properly as the depthwise convolution layer successfully converted the 112×112 image to 56×56 (#(1)). The channel axis is finally expanded to 128 with the help of the pointwise convolution layer (#(2)), making it ready to be fed into the subsequent layer.
Based on the two tests I demonstrated above, it is proven that our DepthwiseSeparableConv class is correct and thus ready to be used to construct the entire MobileNetV1 architecture.
···
I wrap everything within a class which I refer to as MobileNetV1. Since this class is quite long, I break it down into Codeblock 8a and 8b. If you want to run this code yourself, just ensure that these two codeblocks are written within the same notebook cell.
Now let’s start from the __init__() method of this class. The first thing to do here is to initialize the FirstConv layer we created earlier (#(1)). The next layers we need to initialize are the core idea of MobileNet, i.e., the depthwise separable convolutions, in which every single of those layers consists of depthwise and pointwise convs. In this implementation I decided to name these pairs starting from depthwise_sep_conv0 all the way to depthwise_sep_conv8. If you go back to Figure 4, you will notice that the downsampling process is done alternately with the non-downsampling layers. This can simply be implemented by setting the downsample flag to True for the layers number 1, 3, 5 and 7. The depthwise_sep_conv6 is a bit special since it is actually not a standalone layer. Rather, it is a bunch of depthwise separable convolutions of the exact same specification repeated 5 times.
python
# Codeblock 8a
class MobileNetV1(nn.Module):
def __init__(self):
super().__init__()
self.first_conv = FirstConv() #(1)
self.depthwise_sep_conv0 = DepthwiseSeparableConv(in_channels=32,
out_channels=64)
self.depthwise_sep_conv1 = DepthwiseSeparableConv(in_channels=64,
out_channels=128,
downsample=True)
self.depthwise_sep_conv2 = DepthwiseSeparableConv(in_channels=128,
out_channels=128)
self.depthwise_sep_conv3 = DepthwiseSeparableConv(in_channels=128,
out_channels=256,
downsample=True)
self.depthwise_sep_conv4 = DepthwiseSeparableConv(in_channels=256,
out_channels=256)
self.depthwise_sep_conv5 = DepthwiseSeparableConv(in_channels=256,
out_channels=512,
downsample=True)
self.depthwise_sep_conv6 = nn.ModuleList(
[DepthwiseSeparableConv(in_channels=512, out_channels=512) for _ in range(5)]
)
self.depthwise_sep_conv7 = DepthwiseSeparableConv(in_channels=512,
out_channels=1024,
downsample=True)
self.depthwise_sep_conv8 = DepthwiseSeparableConv(in_channels=1024, #(2)
out_channels=1024)
num_out_channels = self.depthwise_sep_conv8.pwconv.out_channels #(3)
self.avgpool = nn.AdaptiveAvgPool2d(output_size=(1,1)) #(4)
self.fc = nn.Linear(in_features=num_out_channels, #(5)
out_features=NUM_CLASSES)
self.softmax = nn.Softmax(dim=1) #(6)
As we have reached the very last DepthwiseSeparableConv layer (#(2)), what we need to do next is to initialize three more layers: an average pooling layer (#(4)), a fully-connected layer (#(5)), and a softmax activation function layer (#(6)). One thing that you need to keep in mind is that the number of output channels produced by the depthwise_sep_conv8 is not always 1024 even though it appears to be fixed to that number. In fact, this output channel count will be different if we change the ALPHA. In order to make our implementation adaptive to such changes, we need to take the actual number of output channels generated using the code at line #(3), which will then be used as the input size of the fully-connected layer (#(5)).
Regarding the forward() method in Codeblock 8b, I think there is nothing I need to explain since what we basically do here is just to pass a tensor from one layer to the subsequent ones.
python
# Codeblock 8b
def forward(self, x):
x = self.first_conv(x)
print(f"after first_conv\t\t: {x.shape}")
x = self.depthwise_sep_conv0(x)
print(f"after depthwise_sep_conv0\t: {x.shape}")
x = self.depthwise_sep_conv1(x)
print(f"after depthwise_sep_conv1\t: {x.shape}")
x = self.depthwise_sep_conv2(x)
print(f"after depthwise_sep_conv2\t: {x.shape}")
x = self.depthwise_sep_conv3(x)
print(f"after depthwise_sep_conv3\t: {x.shape}")
x = self.depthwise_sep_conv4(x)
print(f"after depthwise_sep_conv4\t: {x.shape}")
x = self.depthwise_sep_conv5(x)
print(f"after depthwise_sep_conv5\t: {x.shape}")
for i, layer in enumerate(self.depthwise_sep_conv6):
x = layer(x)
print(f"after depthwise_sep_conv6 #{i}\t: {x.shape}")
x = self.depthwise_sep_conv7(x)
print(f"after depthwise_sep_conv7\t: {x.shape}")
x = self.depthwise_sep_conv8(x)
print(f"after depthwise_sep_conv8\t: {x.shape}")
x = self.avgpool(x)
print(f"after avgpool\t\t\t: {x.shape}")
x = torch.flatten(x, start_dim=1)
print(f"after flatten\t\t\t: {x.shape}")
x = self.fc(x)
print(f"after fc\t\t\t: {x.shape}")
x = self.softmax(x)
print(f"after softmax\t\t\t: {x.shape}")
return x
Now let’s see if our MobileNetV1 works properly by running the following test code. python
# Codeblock 9
mobilenetv1 = MobileNetV1()
x = torch.randn((BATCH_SIZE, IN_CHANNELS, IMAGE_SIZE, IMAGE_SIZE))
out = mobilenetv1(x)
And below is what the output looks like. Here we can see that our dummy image tensor successfully went through the first_conv layer all the way to the final output layer. During the convolution phase, we can see that the spatial dimension decreased as we get into the deeper layers while at the same time the number of channels increased. Afterwards, we apply an average pooling layer which works by taking the average value from each channel. We can say that at this point every single channel of size 7×7 is now represented as a single value, which is actually the reason that the spatial dimension dropped to 1×1 (#(1)). This tensor is then flattened (#(2)) so that we can process it further with the fully-connected layer (#(3)).
text
# Codeblock 9 Output
after first_conv : torch.Size([1, 32, 112, 112])
after depthwise_sep_conv0 : torch.Size([1, 64, 112, 112])
after depthwise_sep_conv1 : torch.Size([1, 128, 56, 56])
after depthwise_sep_conv2 : torch.Size([1, 128, 56, 56])
after depthwise_sep_conv3 : torch.Size([1, 256, 28, 28])
after depthwise_sep_conv4 : torch.Size([1, 256, 28, 28])
after depthwise_sep_conv5 : torch.Size([1, 512, 14, 14])
after depthwise_sep_conv6 #0 : torch.Size([1, 512, 14, 14])
after depthwise_sep_conv6 #1 : torch.Size([1, 512, 14, 14])
after depthwise_sep_conv6 #2 : torch.Size([1, 512, 14, 14])
after depthwise_sep_conv6 #3 : torch.Size([1, 512, 14, 14])
after depthwise_sep_conv6 #4 : torch.Size([1, 512, 14, 14])
after depthwise_sep_conv7 : torch.Size([1, 1024, 7, 7])
after depthwise_sep_conv8 : torch.Size([1, 1024, 7, 7])
after avgpool : torch.Size([1, 1024, 1, 1]) #(1)
after flatten : torch.Size([1, 1024]) #(2)
after fc : torch.Size([1, 1000]) #(3)
after softmax : torch.Size([1, 1000])
If you want an even more detailed architecture, we can use the summary() function from torchinfo we imported earlier. If you scroll down the resulting output below, we can see that this model contains approximately 4.2 million trainable parameters, in which this number matches with the one written in Figure 5, 6, 7 and 8. I also tried to initialize the same model with different ALPHA, and I found that the numbers match with the table in Figure 6. Thanks to this reason, I think our MobileNetV1 implementation is correct.
python
# Codeblock 10
mobilenetv1 = MobileNetV1()
summary(mobilenetv1, input_size=(BATCH_SIZE, IN_CHANNELS, IMAGE_SIZE, IMAGE_SIZE))
text
# Codeblock 10 Output
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
MobileNetV1 [1, 1000] --
├─FirstConv: 1-1 [1, 32, 112, 112] --
│ └─Conv2d: 2-1 [1, 32, 112, 112] 864
│ └─BatchNorm2d: 2-2 [1, 32, 112, 112] 64
│ └─ReLU: 2-3 [1, 32, 112, 112] --
├─DepthwiseSeparableConv: 1-2 [1, 64, 112, 112] --
│ └─Conv2d: 2-4 [1, 32, 112, 112] 288
│ └─BatchNorm2d: 2-5 [1, 32, 112, 112] 64
│ └─ReLU: 2-6 [1, 32, 112, 112] --
│ └─Conv2d: 2-7 [1, 64, 112, 112] 2,048
│ └─BatchNorm2d: 2-8 [1, 64, 112, 112] 128
│ └─ReLU: 2-9 [1, 64, 112, 112] --
├─DepthwiseSeparableConv: 1-3 [1, 128, 56, 56] --
│ └─Conv2d: 2-10 [1, 64, 56, 56] 576
│ └─BatchNorm2d: 2-11 [1, 64, 56, 56] 128
│ └─ReLU: 2-12 [1, 64, 56, 56] --
│ └─Conv2d: 2-13 [1, 128, 56, 56] 8,192
│ └─BatchNorm2d: 2-14 [1, 128, 56, 56] 256
│ └─ReLU: 2-15 [1, 128, 56, 56] --
├─DepthwiseSeparableConv: 1-4 [1, 128, 56, 56] --
│ └─Conv2d: 2-16 [1, 128, 56, 56] 1,152
│ └─BatchNorm2d: 2-17 [1, 128, 56, 56] 256
│ └─ReLU: 2-18 [1, 128, 56, 56] --
│ └─Conv2d: 2-19 [1, 128, 56, 56] 16,384
│ └─BatchNorm2d: 2-20 [1, 128, 56, 56] 256
│ └─ReLU: 2-21 [1, 128, 56, 56] --
├─DepthwiseSeparableConv: 1-5 [1, 256, 28, 28] --
│ └─Conv2d: 2-22 [1, 128, 28, 28] 1,152
│ └─BatchNorm2d: 2-23 [1, 128, 28, 28] 256
│ └─ReLU: 2-24 [1, 128, 28, 28] --
│ └─Conv2d: 2-25 [1, 256, 28, 28] 32,768
│ └─BatchNorm2d: 2-26 [1, 256, 28, 28] 512
│ └─ReLU: 2-27 [1, 256, 28, 28] --
├─DepthwiseSeparableConv: 1-6 [1, 256, 28, 28] --
│ └─Conv2d: 2-28 [1, 256, 28, 28] 2,304
│ └─BatchNorm2d: 2-29 [1, 256, 28, 28] 512
│ └─ReLU: 2-30 [1, 256, 28, 28] --
│ └─Conv2d: 2-31 [1, 256, 28, 28] 65,536
│ └─BatchNorm2d: 2-32 [1, 256, 28, 28] 512
│ └─ReLU: 2-33 [1, 256, 28, 28] --
├─DepthwiseSeparableConv: 1-7 [1, 512, 14, 14] --
│ └─Conv2d: 2-34 [1, 256, 14, 14] 2,304
│ └─BatchNorm2d: 2-35 [1, 256, 14, 14] 512
│ └─ReLU: 2-36 [1, 256, 14, 14] --
│ └─Conv2d: 2-37 [1, 512, 14, 14] 131,072
│ └─BatchNorm2d: 2-38 [1, 512, 14, 14] 1,024
│ └─ReLU: 2-39 [1, 512, 14, 14] --
├─ModuleList: 1-8 -- --
│ └─DepthwiseSeparableConv: 2-40 [1, 512, 14, 14] --
│ │ └─Conv2d: 3-1 [1, 512, 14, 14] 4,608
│ │ └─BatchNorm2d: 3-2 [1, 512, 14, 14] 1,024
│ │ └─ReLU: 3-3 [1, 512, 14, 14] --
│ │ └─Conv2d: 3-4 [1, 512, 14, 14] 262,144
│ │ └─BatchNorm2d: 3-5 [1, 512, 14, 14] 1,024
│ │ └─ReLU: 3-6 [1, 512, 14, 14] --
│ └─DepthwiseSeparableConv: 2-41 [1, 512, 14, 14] --
│ │ └─Conv2d: 3-7 [1, 512, 14, 14] 4,608
│ │ └─BatchNorm2d: 3-8 [1, 512, 14, 14] 1,024
│ │ └─ReLU: 3-9 [1, 512, 14, 14] --
│ │ └─Conv2d: 3-10 [1, 512, 14, 14] 262,144
│ │ └─BatchNorm2d: 3-11 [1, 512, 14, 14] 1,024
│ │ └─ReLU: 3-12 [1, 512, 14, 14] --
│ └─DepthwiseSeparableConv: 2-42 [1, 512, 14, 14] --
│ │ └─Conv2d: 3-13 [1, 512, 14, 14] 4,608
│ │ └─BatchNorm2d: 3-14 [1, 512, 14, 14] 1,024
│ │ └─ReLU: 3-15 [1, 512, 14, 14] --
│ │ └─Conv2d: 3-16 [1, 512, 14, 14] 262,144
│ │ └─BatchNorm2d: 3-17 [1, 512, 14, 14] 1,024
│ │ └─ReLU: 3-18 [1, 512, 14, 14] --
│ └─DepthwiseSeparableConv: 2-43 [1, 512, 14, 14] --
│ │ └─Conv2d: 3-19 [1, 512, 14, 14] 4,608
│ │ └─BatchNorm2d: 3-20 [1, 512, 14, 14] 1,024
│ │ └─ReLU: 3-21 [1, 512, 14, 14] --
│ │ └─Conv2d: 3-22 [1, 512, 14, 14] 262,144
│ │ └─BatchNorm2d: 3-23 [1, 512, 14, 14] 1,024
│ │ └─ReLU: 3-24 [1, 512, 14, 14] --
│ └─DepthwiseSeparableConv: 2-44 [1, 512, 14, 14] --
│ │ └─Conv2d: 3-25 [1, 512, 14, 14] 4,608
│ │ └─BatchNorm2d: 3-26 [1, 512, 14, 14] 1,024
│ │ └─ReLU: 3-27 [1, 512, 14, 14] --
│ │ └─Conv2d: 3-28 [1, 512, 14, 14] 262,144
│ │ └─BatchNorm2d: 3-29 [1, 512, 14, 14] 1,024
│ │ └─ReLU: 3-30 [1, 512, 14, 14] --
├─DepthwiseSeparableConv: 1-9 [1, 1024, 7, 7] --
│ └─Conv2d: 2-45 [1, 512, 7, 7] 4,608
│ └─BatchNorm2d: 2-46 [1, 512, 7, 7] 1,024
│ └─ReLU: 2-47 [1, 512, 7, 7] --
│ └─Conv2d: 2-48 [1, 1024, 7, 7] 524,288
│ └─BatchNorm2d: 2-49 [1, 1024, 7, 7] 2,048
│ └─ReLU: 2-50 [1, 1024, 7, 7] --
├─DepthwiseSeparableConv: 1-10 [1, 1024, 7, 7] --
│ └─Conv2d: 2-51 [1, 1024, 7, 7] 9,216
│ └─BatchNorm2d: 2-52 [1, 1024, 7, 7] 2,048
│ └─ReLU: 2-53 [1, 1024, 7, 7] --
│ └─Conv2d: 2-54 [1, 1024, 7, 7] 1,048,576
│ └─BatchNorm2d: 2-55 [1, 1024, 7, 7] 2,048
│ └─ReLU: 2-56 [1, 1024, 7, 7] --
├─AdaptiveAvgPool2d: 1-11 [1, 1024, 1, 1] --
├─Linear: 1-12 [1, 1000] 1,025,000
├─Softmax: 1-13 [1, 1000] --
==========================================================================================
Total params: 4,231,976
Trainable params: 4,231,976
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 568.76
==========================================================================================
Input size (MB): 0.60
Forward/backward pass size (MB): 80.69
Params size (MB): 16.93
Estimated Total Size (MB): 98.22
==========================================================================================
···
That was pretty much everything about MobileNetV1. I do encourage you to play around with the above model. If you want to train it for image classification, you can adjust the number of neurons in the output layer according to the number of classes available in your dataset. You can also try to explore different α and ρ to find the values that suit best for your case in terms of accuracy and efficiency. Furthermore, since this implementation is literally done from scratch, it is also possible to change other things that are not explicitly mentioned in the paper, such as the number of repeats of the depthwise_sep_conv6 layer, or even using α and ρ greater than 1. And well, there are basically lots of things to explore from our MobileNetV1 implementation! You can also access the code used in this article in my GitHub repository [3].
Feel free to comment if you spot any mistake in my explanation or the code. Thanks for reading!
···
[1] Andrew G. Howard et al. MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications. Arxiv. https://arxiv.org/abs/1704.04861 [Accessed April 7, 2025]. [2] Image created originally by author. [3] MuhammadArdiPutra. The Tiny Giant — MobileNetV1. GitHub. https://github.com/MuhammadArdiPutra/medium_articles/blob/main/The Tiny Giant - MobileNetV1.ipynb [Accessed April 7, 2025].
All articles on Towards Data Science are available to registered users. Create a free account today to access this article and thousands more. Register for freeSign in to your account
Written By
Deep learning storyteller See all from Muhammad Ardi Putra Editor's PicksArtificial IntelligenceMachine LearningPythonPytorch Share This Article
Towards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program. Write for TDS
Deep Learning Our weekly selection of must-read Editors' Picks and original features TDS Editors July 14, 20223 min read
Programming We will explore the definitions behind Truth values, how to operate on them and avoid nasty bugs, and how to tune the Truth of our classes. Pere Miquel Brull April 4, 20224 min read
Artificial Intelligence A step-by-step explanation of the multi-headed self-attention block Saketh Kotamraju October 7, 202010 min read
Artificial Intelligence Beginner's Guide to Working with AI in Medicine Ishtiak Mahmud September 12, 20205 min read
Machine Learning Learn the intuition behind PCA in Python and Sklearn by transforming a multidimensional dataset into an arbitrary number of dimensions and... Andrea D'Agostino September 6, 202313 min read
Deep Learning A powerful method for object detection and debugging Patrick Brus July 23, 20205 min read
Data Science Quick tips to write better python code Pranjal Saxena September 24, 20215 min read
Deep Learning Over the last decade, neural networks have been showing superb performance across a large variety of datasets and problems. While metrics... Lukas Huber August 2, 20225 min read
Data Science It's all fun and games until your time-series is not Normal anymore Sarem Seitz June 17, 20229 min read
Your home for data science and AI. The world's leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals. Subscribe to Our NewsletterWrite For TDS•About•Advertise•Privacy Policy•Terms of Use © Insight Media Group, LLC 2026•The New Stack•roadmap.sh•Creator Network
Close