理解卷积神经网络中的局部感受野
技术和解释
想过为什么卷积神经网络中的所有神经元都是相连的吗?
这篇文章面向所有水平的练习机器学习或更具体地说深度学习的个人。
C 旋转神经网络(CNN)具有对通过网络输入的图像的仿射变换保持不变的特性。这提供了识别图像中偏移、倾斜或轻微扭曲的图案的能力。
由于 CNN 架构的三个主要属性,引入了仿射不变性的这些特征。
- 局部感受野
- 共享权重
- 空间子采样
在这篇文章中,我们将探索局部感受野,理解它们的目的和它们在 CNN 架构中的优势。
介绍
在 CNN 架构中,有几个层的组合,其中有一组单元或神经元。
这些单元接收来自前一层中类似子部分的相应单元的输入。在传统的全连接前馈神经网络中,层内的单元/神经元接收来自前一层的所有单元的输入。
曾经想知道为什么卷积神经网络中的所有神经元没有连接起来吗?
将前一层的所有单元连接到当前层的单元是不切实际的。由于连接的增加,训练这种网络的计算资源将是巨大的。此外,这样的网络将需要一组更广泛的训练数据来利用网络的全部容量。
但更重要的是,CNN 中的每个神经元负责输入数据的一个定义区域,这使神经元能够学习构成图像的线条、边缘和小细节等模式。
神经元或单元在输入数据中暴露的这一限定的空间区域被称为局部感受野。
感受野
感受野是空间或空间结构的定义部分,包含向相应层内的一组单元提供输入的单元。
感受野由卷积神经网络中一层的滤波器大小来定义。感受野也是一个层内的神经元或单元可以接触到的输入数据范围的指示*(见下图)。*
例子
下图显示了输入体积为 32x32x3 的输入数据(红色)。
输入体积基本上告诉我们,输入数据中的图像具有 32×32(高/宽)的尺寸,沿着三个颜色通道:红色、绿色和蓝色。
图像中的第二个对象(蓝色)代表一个卷积层。conv 层的滤波器大小为 5×5,对应于该层中每个神经元对输入数据的局部感受野面积。
感受野不仅作用于输入体积的面积,而且也作用于深度,在本例中是 3。
对于下图中的示例,我们可以根据输入量得出每个神经元具有的可训练参数的数量。这是感受野乘以输入体积的深度(5x5x3 = 75 个可训练参数)。
假设我们有(32,32,3)的输入量,卷积层的感受野是 5×5,那么卷积层中的每个神经元将具有 5×5×3 区域的权重,这是神经元内的 75 个权重。
卷积图层的输出是特征地图,图层中特征地图的数量是一个定义的超参数,通过将特征地图的维度乘以可训练参数的数量,可以推导出特征地图中的连接数。
局部感受野是由卷积层中的神经元在卷积过程中暴露的输入数据内容所占据的定义的分段区域。
LeNet 论文介绍了利用卷积神经网络进行字符识别的第一个用例。它还介绍了 CNN 中局部感受野的概念和实现。
照片由 Cole Wyland 在 Unsplash 上拍摄
但是,局部感受域或者更确切地说是仅暴露于一段输入数据的后续单位——局部连接——的概念早在 20 世纪 60 年代就在一项研究中引入了,该研究由探索猫的视觉皮层。
优势
局部感受野在识别视觉模式方面的优势在于,层内的单元或神经元直接负责从一小块输入数据区域中学习视觉特征——这不是全连接神经网络的情况,在全连接神经网络中,单元接收来自前一层内单元的输入。
在 CNN 内的较低层中,单元/神经元学习图像内的低级特征,例如线条、边缘、轮廓等。较高层学习图像的更多抽象特征,例如形状,因为较高层内的单元暴露的图像区域较大,这是先前较低层的感受野累积的结果。
下面的代码片段展示了如何使用 TensorFlow 深度学习 python 库定义卷积层。
Conv2D 类构造函数接受参数“过滤器”,该参数对应于过滤器产生的输出数量,也是特征映射的数量。参数“kernel_size”采用一个表示内核/筛选器的高度和宽度的整数;在这种情况下,整数 5 对应于尺寸 5×5。
simple_conv_layer = tf.keras.layers.Conv2D(filters=6, kernel_size=5, activation='relu', input_shape=(28,28,1))
我希望这篇文章对你有用。
要联系我或找到更多类似本文的内容,请执行以下操作:
- 订阅我的 YouTube 频道 即将发布的视频内容 这里
- 跟着我上 中
- 通过 LinkedIn 联系我
理解深度神经网络中使用的一种常见转换技术
towardsdatascience.com](/batch-normalization-explained-algorithm-breakdown-23d2794511c) [## 作为机器学习工程师你需要的 5 个软技能(以及为什么)
包括成为任何劳动力的有用组成部分的提示
towardsdatascience.com](/5-soft-skills-you-need-as-a-machine-learning-engineer-and-why-41ef6854cef6)
从零开始理解逻辑回归— Kaggle 笔记本
通过自己实现来学习算法。
由作者创建
目录
1.目标
2.加载数据
3.从文本中提取特征
4.实施逻辑回归
- 4.1 概述
- 4.2 乙状结肠
- 4.3 成本函数
- 4.4 梯度下降
- 4.5 正规化
5.火车模型
6.测试我们的逻辑回归
7.用 Scikit 学习逻辑回归测试
让我们用 Python 导入所有必要的模块。
# regular expression operations
import re
# string operation
import string
# shuffle the list
from random import shuffle
# linear algebra
import numpy as np
# data processing
import pandas as pd
# NLP library
import nltk
# download twitter dataset
from nltk.corpus import twitter_samples
# module for stop words that come with NLTK
from nltk.corpus import stopwords
# module for stemming
from nltk.stem import PorterStemmer
# module for tokenizing strings
from nltk.tokenize import TweetTokenizer
# scikit model selection
from sklearn.model_selection import train_test_split
# smart progressor meter
from tqdm import tqdm
1.目标
这个内核的目标是使用 twitter 数据集从零开始实现用于情感分析的逻辑回归。我们将主要关注逻辑回归的构建模块。这个内核可以提供对 内部如何进行逻辑回归 的深入理解。使用 JupytertoMedium python 库将笔记本转换成中型文章。Kaggle 笔记本可从这里获得。
给定一条推文,如果它有正面情绪,它将被分类👍或者消极情绪👎。这对初学者和其他人都很有用。
2.加载数据
# Download the twitter sample data from NLTK repository
nltk.download('twitter_samples')
twitter_samples
包含 5000 条正面推文和 5000 条负面推文。总共有 10,000 条推文。- 我们每个班都有相同数量的数据样本。
- 这是一个平衡的数据集。
# read the positive and negative tweets
pos_tweets = twitter_samples.strings('positive_tweets.json')
neg_tweets = twitter_samples.strings('negative_tweets.json')
print(f"positive sentiment 👍 total samples {len(pos_tweets)} \nnegative sentiment 👎 total samples {len(neg_tweets)}")positive sentiment 👍 total samples 5000
negative sentiment 👎 total samples 5000# Let's have a look at the data
no_of_tweets = 3
print(f"Let's take a look at first {no_of_tweets} sample tweets:\n")
print("Example of Positive tweets:")
print('\n'.join(pos_tweets[:no_of_tweets]))
print("\nExample of Negative tweets:")
print('\n'.join(neg_tweets[:no_of_tweets]))Let's take a look at first 3 sample tweets:
输出:
Example of Positive tweets:
#FollowFriday @France_Inte @PKuchly57 @Milipol_Paris for being top engaged members in my community this week :)
@Lamb2ja Hey James! How odd :/ Please call our Contact Centre on 02392441234 and we will be able to assist you :) Many thanks!
@DespiteOfficial we had a listen last night :) As You Bleed is an amazing track. When are you in Scotland?!
Example of Negative tweets:
hopeless for tmr :(
Everything in the kids section of IKEA is so cute. Shame I'm nearly 19 in 2 months :(
@Hegelbon That heart sliding into the waste basket. :(
- 推文可能包含 URL、数字和特殊字符。因此,我们需要对文本进行预处理。
预处理文本
预处理是流水线中的重要步骤之一。它包括在建立机器学习模型之前清理和删除不必要的数据。
预处理步骤:
- 对字符串进行标记
- 将 tweet 转换成小写,并将 tweet 拆分成令牌(单词)
- 删除停用字词和标点符号
- 删除 twitter 平台上的常用词,如标签、转发标记、超链接、数字和电子邮件地址
- 堵塞物
- 这是把一个单词转换成它最普通形式的过程。它有助于减少我们的词汇量。例如,engage 这个词有不同的词干,
- 订婚
- 订婚的
- 订婚
让我们看看如何实现这一点。
# helper class for doing preprocessing
class Twitter_Preprocess():
def __init__(self):
# instantiate tokenizer class
self.tokenizer = TweetTokenizer(preserve_case=False, strip_handles=True,
reduce_len=True)
# get the english stopwords
self.stopwords_en = stopwords.words('english')
# get the english punctuation
self.punctuation_en = string.punctuation
# Instantiate stemmer object
self.stemmer = PorterStemmer()
def __remove_unwanted_characters__(self, tweet):
# remove retweet style text "RT"
tweet = re.sub(r'^RT[\s]+', '', tweet)
# remove hyperlinks
tweet = re.sub(r'https?:\/\/.*[\r\n]*', '', tweet)
# remove hashtags
tweet = re.sub(r'#', '', tweet)
#remove email address
tweet = re.sub('\S+@\S+', '', tweet)
# remove numbers
tweet = re.sub(r'\d+', '', tweet)
## return removed text
return tweet
def __tokenize_tweet__(self, tweet):
# tokenize tweets
return self.tokenizer.tokenize(tweet)
def __remove_stopwords__(self, tweet_tokens):
# remove stopwords
tweets_clean = []
for word in tweet_tokens:
if (word not in self.stopwords_en and # remove stopwords
word not in self.punctuation_en): # remove punctuation
tweets_clean.append(word)
return tweets_clean
def __text_stemming__(self,tweet_tokens):
# store the stemmed word
tweets_stem = []
for word in tweet_tokens:
# stemming word
stem_word = self.stemmer.stem(word)
tweets_stem.append(stem_word)
return tweets_stem
def preprocess(self, tweets):
tweets_processed = []
for _, tweet in tqdm(enumerate(tweets)):
# apply removing unwated characters and remove style of retweet, URL
tweet = self.__remove_unwanted_characters__(tweet)
# apply nltk tokenizer
/ tweet_tokens = self.__tokenize_tweet__(tweet)
# apply stop words removal
tweet_clean = self.__remove_stopwords__(tweet_tokens)
# apply stemmer
tweet_stems = self.__text_stemming__(tweet_clean)
tweets_processed.extend([tweet_stems])
return tweets_processed# initilize the text preprocessor class object
twitter_text_processor = Twitter_Preprocess()
# process the positive and negative tweets
processed_pos_tweets = twitter_text_processor.preprocess(pos_tweets)
processed_neg_tweets = twitter_text_processor.preprocess(neg_tweets)5000it [00:02, 2276.81it/s]
5000it [00:02, 2409.93it/s]
让我们看看预处理 tweets 后得到了什么输出。我们能够成功处理推文,这很好。
pos_tweets[:no_of_tweets], processed_pos_tweets[:no_of_tweets](['#FollowFriday @France_Inte @PKuchly57 @Milipol_Paris for being top engaged members in my community this week :)',
'@Lamb2ja Hey James! How odd :/ Please call our Contact Centre on 02392441234 and we will be able to assist you :) Many thanks!',
'@DespiteOfficial we had a listen last night :) As You Bleed is an amazing track. When are you in Scotland?!'],
[['followfriday', 'top', 'engag', 'member', 'commun', 'week', ':)'],
['hey',
'jame',
'odd',
':/',
'pleas',
'call',
'contact',
'centr',
'abl',
'assist',
':)',
'mani',
'thank'],
['listen', 'last', 'night', ':)', 'bleed', 'amaz', 'track', 'scotland']])
3.从文本中提取特征
- 给定文本,以这样一种方式表示
features (numeric values)
是非常重要的,这样我们就可以输入到模型中。
3.1 创建一个单词包(BOW)表示法
BOW 代表单词及其在每个类中的出现频率。我们将创建一个dict
来存储每个单词的positive
和negative
类的频率。让我们指出一条positive
推文是1
,而negative
推文是0
。dict
键是一个包含(word, y)
对的元组。word
是处理过的字,y
表示类的标签。dict 值代表类y
的frequency of the word
。
示例:#单词 bad 在 0(负)类中出现 45 次{(“bad”,0) : 32}
# word bad occurs 45 time in the 0 (negative) class
{("bad", 0) : 45}# BOW frequency represent the (word, y) and frequency of y class
def build_bow_dict(tweets, labels):
freq = {}
## create zip of tweets and labels
for tweet, label in list(zip(tweets, labels)):
for word in tweet:
freq[(word, label)] = freq.get((word, label), 0) + 1
return freq# create labels of the tweets
# 1 for positive labels and 0 for negative labels
labels = [1 for i in range(len(processed_pos_tweets))]
labels.extend([0 for i in range(len(processed_neg_tweets))])
# combine the positive and negative tweets
twitter_processed_corpus = processed_pos_tweets + processed_neg_tweets
# build Bog of words frequency
bow_word_frequency = build_bow_dict(twitter_processed_corpus, labels)
现在,我们有各种方法来表示 twitter 语料库的特征。一些基本而强大的技术是,
- 计数矢量器
- TF-IDF 功能
1.计数矢量器
计数矢量器指示稀疏矩阵,并且该值可以是单词的频率**。在我们的语料库中,每一列都是唯一的标记。**
稀疏矩阵的维数将是
*no of unique tokens in the corpus * no of sample tweets*
。
示例:corpus = [ 'This is the first document.', 'This document is the second document.', 'And this is the third one.', 'Is this the first document?', ]
并且 CountVectorizer 表示为
[[0 1 1 1 0 0 1 0 1] [0 2 0 1 0 1 1 0 1] [1 0 0 1 1 0 1 1 1] [0 1 1 1 0 0 1 0 1]]
2.TF-IDF(术语频率-逆文档频率)
TF-IDF 统计度量,用于评估单词与文档集合中的文档的相关程度。TF-IDF 的计算如下:
TF-IDF 方程
词频:词频 tf(t,d) ,最简单的选择就是使用一个词(词)在文档中的出现频率。逆文档频率: idf(t,D) 衡量单词提供多少信息,即它在所有文档中是常见还是罕见。它是包含该单词的文档的逆分数的对数标度。定义见维基。
3.2.为我们的模型提取简单特征
- 给定一个推文列表,我们将提取两个特征。
- 第一个特征是一条推文中正面词的数量。
- 第二个特征是推文中负面词的数量。
这看似简单,不是吗?也许是的。我们没有向稀疏矩阵表示我们的特征。将使用最简单的特征进行分析。
# extract feature for tweet
def extract_features(processed_tweet, bow_word_frequency):
# feature array
features = np.zeros((1,3))
# bias term added in the 0th index
features[0,0] = 1
# iterate processed_tweet
for word in processed_tweet:
# get the positive frequency of the word
features[0,1] = bow_word_frequency.get((word, 1), 0)
# get the negative frequency of the word
features[0,2] = bow_word_frequency.get((word, 0), 0)
return features
打乱语料库,将训练集和测试集分开。
# shuffle the positive and negative tweets
shuffle(processed_pos_tweets)
shuffle(processed_neg_tweets)
# create positive and negative labels
positive_tweet_label = [1 for i in processed_pos_tweets]
negative_tweet_label = [0 for i in processed_neg_tweets]
# create dataframe
tweet_df = pd.DataFrame(list(zip(twitter_processed_corpus, positive_tweet_label+negative_tweet_label)), columns=["processed_tweet", "label"])
3.3 训练和测试分割
让我们保留 80%的数据用于训练,20%的数据样本用于测试。
# train and test split
train_X_tweet, test_X_tweet, train_Y, test_Y = train_test_split(tweet_df["processed_tweet"], tweet_df["label"], test_size = 0.20, stratify=tweet_df["label"])
print(f"train_X_tweet {train_X_tweet.shape}, test_X_tweet {test_X_tweet.shape}, train_Y {train_Y.shape}, test_Y {test_Y.shape}")train_X_tweet (8000,), test_X_tweet (2000,), train_Y (8000,), test_Y (2000,)# train X feature dimension
train_X = np.zeros((len(train_X_tweet), 3))
for index, tweet in enumerate(train_X_tweet):
train_X[index, :] = extract_features(tweet, bow_word_frequency)
# test X feature dimension
test_X = np.zeros((len(test_X_tweet), 3))
for index, tweet in enumerate(test_X_tweet):
test_X[index, :] = extract_features(tweet, bow_word_frequency)
print(f"train_X {train_X.shape}, test_X {test_X.shape}")train_X (8000, 3), test_X (2000, 3)
输出:
train_X[0:5]array([[1.000e+00, 6.300e+02, 0.000e+00],
[1.000e+00, 6.930e+02, 0.000e+00],
[1.000e+00, 1.000e+00, 4.570e+03],
[1.000e+00, 1.000e+00, 4.570e+03],
[1.000e+00, 3.561e+03, 2.000e+00]])
看一看样本训练特征。
- 第 0 个索引是添加的偏差项。
- 第一个指标代表正词频
- 第二个指数代表负词频
4.实施逻辑回归
4.1 概述
现在,让我们看看逻辑回归是如何工作和实现的。
很多时候,当你听到逻辑回归时,你可能会想,这是一个回归问题。不,不是, Logistic 回归是一个分类问题,是一个非线性模型。
由作者创建
如上图所示,大多数最大似然算法有 4 个阶段,
第一步。初始化权重
- 随机权重已初始化
第二步。应用功能
- 计算乙状结肠
第三步。计算成本(算法的目标)
- 计算二元分类的对数损失
第四步。梯度下降
- 迭代更新权重,直到找到最小成本
逻辑回归采用线性回归,并将 sigmoid 应用于线性回归的输出。因此,它产生了每一类的概率,其总和为 1。
**回归:**一元线性回归方程如下:
单变量线性回归公式
- 注意,θ值是 权重
- x_0,x_1,x_2,… x_N 是输入特征
你可能会想到这个方程有多复杂。我们需要将在ith
位置的每个特征的所有权重相乘,然后求和。
好在线性代数带来了这个易操作的方程。没错,就是矩阵
*dot*
产品。您可以应用特征和权重的点积来找到 z 。
4.2 乙状结肠
- sigmoid 函数定义为:
它将输入“z”映射到一个介于 0 和 1 之间的值,因此它可以被视为一个概率。
def sigmoid(z):
# calculate the sigmoid of z
h = 1 / (1+ np.exp(-z))
return h
4.3 成本函数
逻辑回归中使用的成本函数是:
这就是二进制分类的**测井损失。**在逻辑回归中计算所有训练样本的对数损失的平均值,对所有训练样本的等式 3 修改如下:
- m 是训练样本的数量
- 是 与 训练实例的实际标签。
- ****【h(z(\theta)^{(i)}】**为 与 训练样本的模型预测。
单个训练示例的损失函数是,
损失函数
- 所有的 h 的值都在 0 到 1 之间,所以日志会是负数。这就是将系数-1 应用于两个损失项之和的原因。
- 当模型预测 1,(h(z(θ))= 1)且标签 y 也为 1 时,该训练示例的损失为 0。
- 同样,当模型预测为 0,(h(z(θ))= 0,而实际标签也为 0 时,该训练示例的损失为 0。
- 但当模型预测接近 1(h(z(θ))= 0.9999)且标号为 0 时,对数损失的第二项变成一个很大的负数,再乘以-1 的总因子,转换成正的损失值。1×(1 0)×log*(1 0.9999)≈9.2 模型预测越接近 1,损耗越大。*
4.4 梯度下降
梯度下降是一种用于**迭代更新权重θ以最小化目标函数(成本)的算法。我们需要迭代地更新权重,因为,**
在初始随机权重下,模型不会学到太多东西。为了改进预测,我们需要通过多次迭代从数据中学习,并相应地调整随机权重。
对于权重之一θ_ J的成本函数 J 的梯度是:
梯度函数
4.5 正规化
正则化是一种通过惩罚成本函数来解决机器学习算法中过拟合问题的技术。在成本函数中会有一个附加的惩罚项。有两种类型的正则化技术:
- 拉索(L1 范数)正则化
- 岭(L2 范数)正则化
*拉索回归(L1)**L1-范数损失函数也被称为最小绝对误差(LAE)。$λ∑ |w| 是一个正则项。它是 是一个正则项。它是 是一个正则项。它是λ$正则化项与权的绝对和的乘积。较小的值表示较强的正则化。
*岭回归(L2)**L2-范数损失函数也称为最小二乘误差(LSE)。$λ∑ (w) 是一个正则项。它是 是一个正则项。它是 是一个正则项。它是λ$正则化项与权的平方和的乘积。较小的值表示较强的正则化。
你会注意到,这有很大的不同。是的,它做得很好。主要的区别在于你在成本函数中加入了什么类型的正则项来最小化误差。
L2(岭)缩小所有系数相同的比例,但它不消除任何特征,而 L1(拉索)可以缩小一些系数为零,也执行特征选择。
在下面的代码中将添加 L2 正则化
*# implementation of gradient descent algorithm def gradientDescent(x, y, theta, alpha, num_iters, c): # get the number of samples in the training
m = x.shape[0]
for i in range(0, num_iters):
# find linear regression equation value, X and theta
z = np.dot(x, theta)
# get the sigmoid of z
h = sigmoid(z)
# calculate the cost function, log loss
#J = (-1/m) * (np.dot(y.T, np.log(h)) + np.dot((1 - y).T, np.log(1-h)))
# let's add L2 regularization
# c is L2 regularizer term
J = (-1/m) * ((np.dot(y.T, np.log(h)) + np.dot((1 - y).T, np.log(1-h))) + (c * np.sum(theta)))
# update the weights theta
theta = theta - (alpha / m) * np.dot((x.T), (h - y))
J = float(J)
return J, theta*
5.火车模型
让我们训练梯度下降函数来优化随机初始化的权重。在第 4 节中已经给出了简要的解释。
*# set the seed in numpy
np.random.seed(1)
# Apply gradient descent of logistic regression
# 0.1 as added L2 regularization term
J, theta = gradientDescent(train_X, np.array(train_Y).reshape(-1,1), np.zeros((3, 1)), 1e-7, 1000, 0.1)
print(f"The cost after training is {J:.8f}.")
print(f"The resulting vector of weights is {[round(t, 8) for t in np.squeeze(theta)]}")The cost after training is 0.22154867.
The resulting vector of weights is [2.18e-06, 0.00270863, -0.00177371]*
6.测试我们的逻辑回归
是时候在模型之前没有见过的测试数据上测试我们的逻辑回归函数了。
预测一条推文是正面的还是负面的。
- 将 sigmoid 应用于 logits 以获得预测值(介于 0 和 1 之间的值)。
新推文预测
*# predict for the features from learned theata values
def predict_tweet(x, theta):
# make the prediction for x with learned theta values
y_pred = sigmoid(np.dot(x, theta))
return y_pred# predict for the test sample with the learned weights for logistics regression
predicted_probs = predict_tweet(test_X, theta)
# assign the probability threshold to class
predicted_labels = np.where(predicted_probs > 0.5, 1, 0)
# calculate the accuracy
print(f"Own implementation of logistic regression accuracy is {len(predicted_labels[predicted_labels == np.array(test_Y).reshape(-1,1)]) / len(test_Y)*100:.2f}")Own implementation of logistic regression accuracy is 93.45*
*到目前为止,我们已经看到了如何自己实现逻辑回归。得到了 **94.45 的精度。*让我们看看来自流行的机器学习(ML) Python 库的结果。
7.用 Scikit 学习逻辑回归测试
这里,我们将训练内置 Python 库中的逻辑回归来检查结果。
*# scikit learn logiticsregression and accuracy score metric
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
clf = LogisticRegression(random_state=42, penalty='l2')
clf.fit(train_X, np.array(train_Y).reshape(-1,1))
y_pred = clf.predict(test_X)/opt/conda/lib/python3.7/site-packages/sklearn/utils/validation.py:73: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
return f(**kwargs)print(f"Scikit learn logistic regression accuracy is {accuracy_score(test_Y , y_pred)*100:.2f}")Scikit learn logistic regression accuracy is 94.45*
太好了!!!。结果非常接近。
最后,我们自己实现了逻辑回归,并尝试使用内置的 Scikit learn 逻辑回归来获得类似的准确性。但是,这种特征提取的方法非常简单和直观。
我是边做边学。 欢迎在评论中留下您的想法或任何建议。非常感谢你的反馈,它能增强我的信心。
🙏感谢阅读!你可以通过 LinkedIn 联系我。
用一篇文章理解 Python 中的循环
读完这篇文章后,Loops 再也不会把你扔进 Loop 了
计算机擅长一遍又一遍地执行重复性任务,因为它们永远不会“感到无聊”或出错。一个简单的计算请求计算机可以执行一次或一千次,第一个结果将与最后一个结果一样准确。这是人类无法保证的。
能够将日常任务的自动化委托给计算机是一项无价的技能,每个程序员都应该掌握这项技能,以提高他们的编码技能和代码准确性。
在循环的帮助下,可执行的任务数量从将文件复制到网络上的一组计算机、向某些用户发送个性化电子邮件或 验证进程是否仍在运行。
不管任务有多复杂,你说多少次,计算机就会执行多少次,更重要的是,这让你有更多的时间去从事更有趣的活动。
在 Python 中,循环可以通过三种方式实现:
- While循环
- 为循环
- 递归
在本文中,我将解释这些技术,考虑到每一种技术采用的方法略有不同。你将学习如何编写代码,以及如何理解何时使用一种技术而不是其他技术。
目录:
1.While 循环(4 分钟读取)
2.对于循环(4 分钟读取)
3.递归(3 分钟读取)
1.While 循环
该技术指示计算机根据条件的值连续执行代码。它以关键字 *while、*开头,后跟要计算的比较,然后是一个冒号。下一行是要执行的代码块,向右缩进。类似于一个 if 语句,只有当比较结果为真时,才会执行主体中的代码。
然而,当循环分开时,设置的原因是只要评估语句为真,代码块就会继续执行。一旦语句不再为真,循环将退出,下一行代码将被执行。
看看下面的例子:
让我们仔细检查这个循环中的每一行代码:
- 在第一行中,我们将值 0 赋给以单词“iteration”命名的变量**“I”**。为了给变量一个初始值,这个动作叫做“初始化”。
- 在那之后的行中,我们开始了而循环。我们为这个循环设置了一个条件,即“ i ”必须小于 5。现在,“ i ”是 0,因为它刚刚被初始化,所以这个条件当前为真。
- 在接下来的两行中,我们有一个向右缩进的块。在这里,我们可以使用 Python 函数共有的特性,这表明共享相同数量缩进空间的每一行代码都将是函数体的一部分,或者在本例中是循环。
- 循环体中有两行。在第一行中,我们打印一条消息,后跟当前迭代,由值“ i ”表示。在第二行中,“ i 的值递增。我们通过在它的当前值上加 1 并将其赋回“ i ”来实现这一点。所以在第一次执行循环体之后,“ i ”将会是 1 而不是 0。
因为这是一个循环,所以计算机不会继续执行脚本中的下一行。相反,它会循环回去,重新评估 while 循环的条件。
因为这里的 1 仍然小于 5,所以它再次执行循环体。计算机将继续这样做,直到条件不再为真。在本例中,当“I”不再小于 5 时,条件将为假。一旦条件为假,循环结束,执行下一行代码。
避免无限循环陷阱
照片由 Grooveland 设计在 Unsplash 上拍摄
如前所述, while 循环使用条件来检查是否从循环结构中退出。 while 循环的主体需要确保被检查的条件将会改变。如果不改变,循环可能永远不会结束,我们会得到所谓的无限循环,一个不断执行且永不停止的循环。
以下面的代码为例。和前面的例子一样,我们初始化了“ i 变量**,但是忘记在循环中添加一个索引来在每次迭代中刷新变量**。因此,循环将一直运行,直到我们用 CTRL+C 命令手动中断它:
为了避免这个问题,花点时间考虑一个变量可以取的不同值是个好主意。这有助于确保循环在迭代过程中不会被卡住。
你应该总是试图避免无限循环吗?
虽然您需要小心无限循环,但它们并不总是坏事。
有时你实际上希望你的程序持续执行,直到某些外部条件被满足。
如果您在 Linux 或 macOS 系统上使用过 ping 实用程序,或者在 Windows 系统上使用过 ping-t ,您会看到一个无限循环。这个工具会一直发送数据包,并将结果打印到终端,除非你给它发送中断信号,通常是按 Ctrl+C 。
在 Python 中,我们通常使用关键字 break *,*创建带有自动指示的循环来中断迭代,您可以在下面的代码中看到,这表示当前循环应该停止运行:
正如您所看到的,所指示的过程是为每次迭代将变量“I”加 1,直到它达到值 10,这时它应该中断该过程。代码的逻辑应该是这样的:
2.对于循环
一个 for 循环遍历一系列值。 for 循环的一个非常简单的例子是迭代一系列数字,例如从 0 到 4。
注意这个结构有点类似于循环时的结构。第一行表示关键字的区别*,以冒号结尾。循环体向右缩进,就像在 while 循环、 if 块和函数定义中一样。这种情况下不同的是我们在中有了关键词。*
此外,在关键字的和关键字的之间,我们有一个变量的名称,在本例中“I”代表“index”。该变量将接受循环遍历的序列中的每个值。所以在这个例子中,它将遍历使用 range() 函数生成的数字序列。
提醒一下,在 Python 和许多其他编程语言中,默认情况下,一系列数字将以值 0 开始。此外,生成的数字列表将比给定值小 1。在这个简单的例子中,“I”取值为 0、1、2、3 和 4。
在本文的这一点上,你可能会疑惑:为什么有两个看起来做同样事情的循环?
for 循环的强大之处在于,我们可以用它来迭代任意类型的值序列,而不仅仅是一系列数字。例如,我们可以迭代字符串或单词列表:
循环的迭代的序列可以包含任何类型的元素,而不仅仅是字符串。例如,我们可以迭代一系列数字来计算总和以及平均值。
在这个例子中,我们定义了一个值列表。之后,我们初始化两个变量,一些和长度,它们将在循环的主体中更新。在循环的中,我们遍历列表中的每个值,将当前值加到值的和中,然后将长度加 1,计算列表中有多少个元素。一旦我们完成了整个列表,我们就打印出总数和平均值。
每当我们想要迭代任何序列的元素并对它们进行操作时,我们将在示例中继续使用 for 循环。
如何确定使用哪个循环?
如果你想知道什么时候应该使用进行循环,什么时候应该使用而 l 哎呀,有一种方法可以告诉你:
- 当有一个元素序列需要迭代时,使用进行循环。
- 当想要重复一个动作直到条件改变时,使用 while 循环。
如果在循环时,无论你想做什么都可以用代替或来完成,就用你最喜欢的那个。
提升你的循环技能:嵌套循环
基本上,嵌套循环是另一个循环中一个或多个 for 循环。例如,假设你要准备一场网球锦标赛的时间表,我们最喜欢的四名选手将互相比赛。
为了准备我们的时间表,让我们用 Python 脚本对球员进行排序,该脚本将输出所有可能的比赛配对。为此,名字的顺序很重要,因为对于每场比赛,第一个名字将是一个玩家,第二个名字将是竞争者。当然,我们不想做的是有一个玩家和自己作对。为此,我们需要使用一个条件来确保我们只在名称不同时打印配对。
如您所见,嵌套循环对于解决某些问题非常有用,例如在锦标赛中对球员进行排序。
循环的常见错误
- 遍历非序列:正如我已经提到的, for 循环遍历序列。因此,Python 的解释器将拒绝迭代单个元素,比如整数或者不可迭代的对象。
- **初始化变量失败。**确保循环条件中使用的所有变量在循环前都已初始化。
- 意外的无限循环。确保循环体修改了条件中使用的变量,这样循环将最终针对变量的所有可能值结束。
- 忘记了不包括范围的上限()。
作为第一个场景的实际例子,让我们尝试迭代一个整数:
这个问题有两种解决方案,这取决于我们要做什么:
- 如果你想从 0 到 25,那么我们使用范围功能。
- 如果你试图迭代一个只有 25 个元素的列表,那么它必须是一个列表。
3.递归
递归是 Python 中除了循环遍历一系列值的第三种机制,而和用于循环。
虽然递归是软件工程中非常常见的技术,但它在自动化任务中并不常用。尽管如此,了解它还是很有价值的,因为您可能会在别人的代码中遇到它,或者更重要的是,您可能会面临递归是解决它的最佳方式的问题。
递归是对较小的问题重复应用相同的过程。
这些方法的一个很好的视觉例子是俄罗斯套娃:
正如你可能看到的,每个娃娃里面都有一个更小的娃娃。当你打开娃娃,发现里面有一个更小的娃娃时,你继续往前走,直到你碰到那个打不开的最小的娃娃。递归让我们通过将问题简化来解决复杂的问题。
假设我们想知道总共有多少个娃娃,我们需要循环遍历每个娃娃,直到找到最后一个,然后计算我们打开了多少个娃娃。这就是递归在起作用。
在编程中,递归是一种通过函数调用自身来完成重复任务的方法。递归函数通常用修改后的参数调用自己,直到达到特定的条件。这种情况称为基本情况。
在俄罗斯娃娃的例子中,最小的娃娃将是基础案例。让我们尝试一个现实生活中的问题,其中我们将尝试编写一个计算数字阶乘的函数。在数学中,正整数 n 的阶乘,用 n 表示!,是所有小于等于 n 的正整数的乘积:
正如您在上面的代码中所看到的,函数 factorial() 调用自身来求解大于 1 的数字的阶乘。每次执行该函数时,它都用一个较小的数字调用自己,直到它到达基例。一旦它到达基本情况,它就返回值 1。这个循环将继续下去,直到第一个 factorial() 函数返回期望的结果。
您可能想知道,如果我可以只使用 for 或 while 循环,为什么我们还需要递归函数?
因为在使用递归函数时,一些特定问题的解决方案更容易编写和理解。例如,对于试图自动化任务的 IT 专家来说,递归将是检查计算机目录的有用工具,因为每个目录都包含包含文件的子目录。
基本情况是一个没有子目录的目录。对于这种情况,该函数将只返回文件的数量,但是对于剩余的子目录,它将调用递归函数。当操作递归结构时,使用递归函数通常比 for 或 while 循环更容易。
重要的是要指出,在某些语言中,你可以使用最大数量的递归调用。在 Python 中,默认情况下,可以调用递归函数 1000 次,直到达到极限。这对于子目录或类似的递归结构来说很好。
结论
在这篇文章中,我解释了如何告诉计算机重复做一个动作。Python 给了我们三种不同的方法来执行重复的任务: while 循环,用于循环,递归。
For 当你想要迭代一个已知的元素序列,但是当你想要在某个条件为真的情况下操作时,循环是最好的选择。
如果你喜欢这篇文章中的信息,不要犹豫,联系我分享你的想法。它激励我继续分享!
深入了解 Python 编程的相关文章:
创建股票价格模拟器:
几何布朗运动过程的简单应用来模拟股票价格。
towardsdatascience.com](/create-a-stock-price-simulator-with-python-b08a184f197d)
了解如何用 Python 编写令人惊叹的可视化代码:
我准备了一份详尽的指南来展示漂亮的可视化效果,以增强度量、KPI、预测和其他…
towardsdatascience.com](/business-intelligence-visualizations-with-python-1d2d30ce8bd9)
用 Python 分析纽约的数据:
发现最方便的租赁方式,以便继续实施具有良好可视化效果的数据分析。
towardsdatascience.com](/airbnb-rental-analysis-of-new-york-using-python-a6e1b2ecd7dc)
感谢您花时间阅读我的文章!如果您有任何问题或想法要分享,请随时联系我的电子邮件,或者您可以在以下社交网络中找到我以了解更多相关内容:
用一篇文章理解机器学习
让我们深入了解市场上最令人兴奋、要求最高的学科之一。
摄影爱好在 Unsplash 上
我们所知道的事情中,很少有不能简化为数学推理的。当他们不能时,这表明我们对他们的了解非常少而且混乱。
《机会法则》,序言(1962)——约翰·阿巴斯诺特
与其他任何数学学科相比,统计学更大程度上是时间的产物。如果前几个世纪的科学家能够接触到真正的计算能力,甚至是计算机,他们的研究和当前的领域作为一个整体将会描绘出一幅完全不同的画面。
虽然不常讲,但统计学是今天已知的机器学习的基础。如今,很难找到一个用例,其中机器学习没有以某种形式应用,统计数据被放在一边。随着越来越多的人意识到 ML 应用程序的好处,并获得其部署的简单性,这种趋势可能会继续增长。如果你喜欢学习更多这方面的知识,或者你只是好奇它是如何工作的,这篇文章可能适合你。
目录:
- 什么是机器学习?我为什么要用它?(2 分钟读取)
- 流行的误解(1 分钟阅读)
- 机器学习和计量经济学有什么不同?(1 分钟读取)
- 必读的机器学习书籍(1 分钟阅读)
- 金融中的机器学习(1 分钟阅读)
- 用 Python 编程支持向量机和线性回归模型(4 分钟阅读)
1.什么是机器学习?
广义而言,机器学习是指在高维空间中学习复杂模式的一组算法。我们来澄清一下这个说法:
- ML 模型在没有接受任何特定指导的情况下学习模式,因为研究人员对数据施加很少的结构。相反,算法从数据中导出结构。
- 我们说它学习复杂的模式,因为由算法识别的结构可能无法表示为有限的方程组。
- 高维空间这个术语指的是在处理大量变量的同时寻找解,以及变量之间的相互作用。
例如,我们可以通过展示示例并让模型推断人脸的模式和结构,来训练 ML 算法识别人脸。我们没有定义面部,因此算法在没有我们指导的情况下学习。
ML 涉及三种类型的学习:
- 监督学习:学习方法,包括手动告诉模型用户想要为训练数据集预测什么标签。起点是程序员希望算法尊重的一组特征和结果标签。例如:回归,分类。
- 无监督学习:学习方法,包括让模型根据每个元素具有的更明显的特征来确定数据集的标签和分组元素。例如:聚类、表示。
- 强化学习:智能体通过与环境互动,从环境中学习并因执行动作而获得奖励的学习方法。类似于监督学习,它接收一种指南,但它不是来自程序员的输入。
为什么要用?
机器学习正在改变我们生活的方方面面。如今,算法可以完成直到最近只有专家才能完成的任务,即使没有这样的专业水平也逐渐可以实现。另一方面,传统的数据分析技术保持静态,尽管在定义了数据结构的情况下非常有用,例如:
发现最方便的租赁方式,以便继续实施具有良好可视化效果的数据分析。
towardsdatascience.com](/airbnb-rental-analysis-of-new-york-using-python-a6e1b2ecd7dc)
在快速变化的非结构化数据输入的情况下,传统数据分析的用途有限,而非结构化数据输入最适合监督和非监督学习等技术的应用。这时,能够分析数十种输入和变量的自动化流程成为宝贵的工具。
除此之外,ML 技术实现的解决过程****与传统的统计和数据分析有很大不同,因为它们专注于从用户那里接收确定目标的输入,并了解哪些因素对实现该目标很重要,而不是由用户来设置将决定目标变量结果的因素。
它不仅允许算法进行预测,还允许算法与预测进行比较,并调整结果的准确性。
2.流行的误解
ML 是圣杯还是没用?
围绕着 ML 的炒作和反炒作多得令人难以置信。第一个创造了在可预见的未来可能无法实现的期望。相反,反宣传试图让观众相信 ML 没有什么特别之处,经典统计学产生的结果与 ML 从业者和爱好者声称的一样。这两个极端都阻止了用户和爱好者认识到它今天所提供的真正的和不同的价值,因为它有助于克服经典技术的许多限制。
ML 是一个黑盒
这是流传最广的神话。显然,这并不像马科斯·洛佩斯·德·普拉多 在其著作《*资产管理者的机器学习》*中的论点那样,他指出,在某种程度上,ML 技术与科学方法是兼容的,因为它们已经被应用于世界上的每个实验室,用于药物开发、基因组研究和高能物理等实践。无论是否有人在黑盒中应用它,这都是个人的选择,当然,如果理论在最初被解释的模型后面,更好的使用应用将被设计出来。
3.ML 和计量经济学有什么区别?
计量经济学的目标是推断统计模型的参数,以便能够解释变量并根据这些参数得出结论,因为它们本身就有意义。例如,线性回归系数表示每个自变量和因变量之间是正相关还是负相关。计量经济学既不是专注于用模型预测变量的值,也不是试图让模型在这个意义上竞争来增强预测。
硬币的另一面是 ML 分析师,他们的分析纯粹是为了优化预测过程而设计的,与参数解释或模型解释无关,如上所述,这有时是不可能做到的。虽然它们的目标不同,但这两个学科是互补的,并且具有可相互应用的宝贵工具。
4.必读的机器学习书籍
机器学习潜在的读书人有两种:从业者和学院派。对于他们中的每一个人,都有一系列的文献要处理,考虑到第一组人本质上是在寻找更多的应用技术,而不是专注于模型背后的数学。另一方面,学者们可能会在他们将利用的算法背后寻找严格而有力的实证。
以下是对每组的简要建议:
学术界:
- 詹姆斯,g .等人(2013)——统计学习介绍——施普林格
- Bishop,c .(2011)——模式识别和机器学习——Springer
- 马斯兰德,s .(2014)——机器学习:算法视角——第二版。—查普曼&大厅
- 萨顿,r .和巴尔托,a .(2018)——强化学习:介绍——第二版——麻省理工学院出版社
- Hastie,t .等人(2009 年)——统计学习的要素——第二版——施普林格
- Aggarwal,C. (2018) — 神经网络和深度学习 — Springer
从业者:
- Geron,a .(2017)——用 Scikit-learn&tensor flow——O ’ Reilly 进行动手机器学习
- Chollet,F. (2018) — 用 Python 进行深度学习 — Manning
- 洛佩兹·德·普拉多(2018)——金融机器学习的进展——威利
5.金融中的机器学习
金融应用对统计学和 ML 提出了完全不同的挑战,因为经济系统表现出一定程度的复杂性,超出了经典统计工具的掌握范围。因此,随着大型数据集、更强大的计算能力和更高效的算法交付给学生、分析师和整个社区,ML 将越来越多地在该领域发挥重要作用,这一趋势不太可能改变。
金融应用程序的主要区别在于:
- 平稳性:在金融领域,我们使用非平稳数据,例如资产价格和其他时间序列数据集,而在其他科学领域,数据往往是平稳的,这意味着信息取决于时间段。此外,财务数据往往至少部分相关,这增加了分析的复杂性。
- 信噪比:基本上,这方面指的是根据当前输入,我们可以预测多少未来数据。在金融领域,今天的数据对未来结果的预测能力往往较低,而在药物开发或医疗应用等普通科学领域,情况并非如此。在这些领域中,实验程序在一致和不变的条件下进行,以消除波动性给等式带来的不确定性,例如市场每天必须处理的无数信息源。
这个问题并不意味着 ML 不能应用于金融,而是它必须以不同的方式应用。 - 结果的可解释性:由于金融法规和机构的合规义务,与其他科学领域面临的审查水平相比,在金融战略中论证 ML 模型的应用对项目经理和资产经理来说是一个挑战。
在最后一部分中,我将执行两个样本模型的 Python 应用程序,支持向量机和线性回归,以便比较它们,并在实践中展示 ML 脚本是如何实现的。如果你不想进入编码领域,也许你会发现这篇文章很有用:
我准备了一个简单的应用程序,向您展示如何借助一个有趣的工具来实现这一点,这个工具叫做…
towardsdatascience.com](/is-it-possible-to-make-machine-learning-algorithms-without-coding-cb1aadf72f5a)
6.用 Python 实现支持向量机和线性回归;
让我们深入编码吧!
我们将开始导入必要的库: Scikit-Learn 和 Numpy 。
Scikit-Learn 是一个开源的机器学习库,支持监督和非监督学习。它还提供了各种工具,用于模型拟合、数据预处理以及模型选择和评估等。在这种情况下,我们将利用:
- Model_selection 类 :包含用于在训练和测试样本中拆分数据集的工具、超参数优化器和模型验证器。
- Linear_model 类 :用于实现各种线性模型,如逻辑回归、线性回归等。
- svm 类 :用于实现用于分类、回归和离群点检测的监督学习方法。
另一方面, Numpy 是一个库,它提供了对大型多维数组和矩阵的支持,以及对这些数组进行操作的大量高级数学函数。这是机器学习堆栈基础的一部分。
# Imports
import numpy as np
import sklearn.model_selection as sk_ms
import sklearn.linear_model as sk_lm
import sklearn.svm as sk_sv
在那之后,我们继续创建我们将要使用的 Numpy 数组。考虑这些将作为“合成”数据集,因为这个过程也可以用. csv 文件或数据库来执行。
首先,我将生成 1000 行和 5 个要素的随机样本,作为模型的输入。除此之外,我还特意包含了一个带有预定数组的示例函数。这个函数执行的过程包括计算输入数组中每个元素与 X 数组中每个元素的乘积。
# Setting seed to lock random selection
np.random.seed(1234)# Generate random uniform-distributed sample
X = np.random.uniform(size=(1000,5))
y = 2\. + X @ np.array([1., 3., -2., 7., 5.]) + np.random.normal(size=1000)
为了澄清前面的代码,请参考下面的笔记本,查看所创建变量的输出:
这些是每个阵列的形状(长度和尺寸):
在我们创建了样本数据集之后,我们必须继续进行我在这篇文章中解释的训练和测试子集的划分。这个任务可以用已经引入的 scikit-learn 包的类来执行,称为 model_selection , train_test_split 方法:
在接下来的步骤中,我将实现两个模型,即线性回归和支持向量回归。作为每个模型的概述:
- 线性回归:这是一种统计模型,假设响应变量(y)是权重的线性组合,也称为参数,乘以一组预测变量(x)。该模型包括考虑随机采样噪声的误差。对于术语响应变量,我的意思是由输出值显示的行为取决于另一组因素,称为预测因素。
在下面的等式中,您将看到包含的β是权重,x 是预测变量的值,ε是误差项,表示随机采样噪声或模型中未包含的变量的影响。
传统线性回归方程
- **支持向量机:**这也是一个用于分类和回归问题的线性模型,它基本上由一个算法组成,该算法将数据作为输入,并输出一条线,该线将观察结果分成两类。
在下面的要点中,您将找到编写两个引入模型的脚本,在其中我们可以看到每个模型的结果平均值。为了比较这些模型,我运行了一个 交叉验证 过程,这是一种将我们的数据分割成一定数量的子集的方法,称为折叠(在本例中为五个),以避免过度拟合我们的模型:
最后,我们开始评估测试样本上的模型,并获得系数和截距的输出:
结论
我写这篇文章的目的是以一种简单的方式传递一些机器学习的基础知识,并稍微关注一下金融,因为这是我个人感兴趣的一个主题。我相信,投资者将逐步被引入算法交易和涉及人工智能和机器学习过程的智能资产管理实践,因此在某种程度上,本文试图诱导更多的人参与这场运动,以便成为变革的一部分。
我希望我已经实现了我的目标,并且对你有用。如果你喜欢这篇文章中的信息,不要犹豫,联系我分享你的想法。它激励我继续分享!
参考
- [1]Numpy 的基本指南 —西达尔特·迪克西特— 2018
- [2] 支持向量机 — Rushikesh Pupale — 2018
感谢您花时间阅读我的文章!如果您有任何问题或想法要分享,请随时通过我的电子邮件联系我,或者您可以在以下社交网络中找到我以了解更多相关内容:
理解 map()函数操纵熊猫系列
熊猫基础知识
了解使用 map()函数将数据转换为所需格式的基本原理
当我们处理真实世界的数据时,数据集很少或者很可能从来没有为我们的分析目的准备好确切的格式。因此,数据处理阶段的一个重要步骤是将数据从原始的不良格式转换成便于分析的格式。
除了处理缺失数据和异常值,另一个重要的数据转换过程是数据映射。最初作为一个数学概念,映射是从一组现有值创建一组新值的过程,通常是一对一的。作为数据科学研究中最受欢迎的 Python 库之一,pandas 库为我们提供了操作系列数据的map()
函数。一旦您很好地理解了map()
函数,我们就可以在以后的文章中继续研究另一个更强大的数据操作函数apply()
。
熊猫系列
在 pandas 中,系列是一个类似一维数组的对象,包含一系列值。这些值中的每一个都与一个标签相关联,该标签被称为指数。我们可以通过传入一个类似数组的对象(例如 list)或一个字典来创建一个序列。下面是一些常见的例子。
熊猫系列
在上图中,我们首先使用列表创建了一个系列。如果我们不设置可选的index
参数,序列将使用从 0 开始的默认整数索引。name
参数也是可选的。当它被指定时,它成为系列的名称。
我们还使用字典创建了一个系列,字典的值构成了系列的值。这些键自动成为系列的索引。
map()的基本语法
map()
函数的语法如下:Series.map(self, arg, na_action=None)
。如你所见,这个函数的调用者是一个熊猫系列,我们可以说map()
函数是一个系列对象的实例方法。想了解更多关于函数中self
参数的内容,可以参考我之前的文章。
在 Python 中使用 self
medium.com](https://medium.com/better-programming/unlock-the-4-mysteries-of-self-in-python-d1913fbb8e16)
na_action
论证
na_action
参数用于指定我们如何处理NaN
值。该参数的默认选项是None
,使用它将原始数据中的 NA 值传递给映射函数(即arg
参数)。该参数的另一个选项是‘ignore’
,使用它不会对原始数据中的 NA 值执行任何操作。
以下示例显示了这两个选项在处理序列中最后一个数据点(即安娜值)方面的差异。当na_action
为‘ignore’
时,映射函数不会尝试对 NA 值做任何事情,并将映射值设置为NaN
。相反,当na_action
为None
时,映射函数将使用 NA 值(即nan
)来创建映射值(即Number: nan
)。
“无”和“忽略”的区别
数据转换(arg 参数)
map()函数有趣的部分主要是关于我们如何使用arg
参数。具体来说,arg
参数向函数给出了如何将现有数据映射到新数据的指令。该参数可以设置为函数或字典。让我们来看看它们是如何工作的。
使用 Lambda 函数
实际上,在上面关于na_action
选项的例子中,您看到我们使用了 lambda 函数来映射数据。如果你不知道什么是 Python lambda 函数,请参考我之前关于这个主题的文章。
使用 Python lambdas 的最佳实践
medium.com](https://medium.com/better-programming/the-top-4-misuses-of-lambdas-in-python-e419f426b74f)
简言之,lambda 函数被称为匿名函数,它们的基本语法是lambda arguments: expression
。具体来说,声明由关键字lambda
表示,后面是参数列表(0 个或更多),以及指定对这些参数执行什么操作的表达式。
为了给你一个关于 lambda 函数的更实际的用例,让我们考虑下面的例子。在现实生活中的数据,我们可能有百分比数据表示为字符串。我们可以编写一个 lambda 函数,将字符串数据转换为数字数据,这是一种更便于分析的格式。
map()中的 Lambda 函数
在上面的例子中,我们获取原始数据的值,并使用切片技术获得一个子串。相关知识点是字符串中最后一个字符的索引为-1。所以表达式x[:-1]
将创建一个从原始字符串的开始到最后一个字符的字符串(不包含)。
使用常规函数
除了使用 lambda 函数,我们还可以使用内置或自定义的函数。这些函数需要能够映射系列中的每个数据点,否则将会出现错误。
map()中的内置函数和自定义函数
在上图中,我们首先创建了一个由姓名列表组成的序列。第一项任务是为这些人创建姓名首字母。为此,我们编写了一个名为initials()
的函数。这个函数接受序列的值。我们使用列表理解技术来创建每个名字的首字母列表,并连接这些字母来创建最终输出作为映射值。如您所见,映射后,新系列包含所有人的姓名首字母。
除了使用自定义函数,我们还可以使用一些内置函数来生成新的系列。例如,在上面的代码中,我们可以简单地使用len()
函数,它将计算新系列中名称的长度(包括空格)。
使用字典
在大多数情况下,我们应该能够为arg
参数设置一个如上的函数来满足我们的需求。然而,我们也可以使用字典作为映射函数,尽管我们不经常使用它。这是这种用法的一个简单例子。
地图中的字典()
如上所示,我们创建了一个从 0 到 4 的整数序列。然后我们创建了一个字典,它有从 1 到 4 的键,这个字典被用作map()
函数调用的arg
参数。
需要注意的一点是,如果在字典中没有找到这些值,那么映射的值将是NaN
。但是,我们可以使用defaultdict
数据类型来代替dict
数据类型(关于defaultdict
的更多信息,请参考我的上一篇文章)。在这种情况下,NaN
值将被从defaultdict
对象生成的默认值替换。有关此功能,请参见以下示例。
map()中的 Defaultdict
在你走之前
数据操作是预处理数据的重要步骤。Pandas 系列可以看作是更加灵活和强大的 DataFrame 对象的构建块。因此,了解map()
函数的使用可以方便你对 DataFrame 数据的操作,关于这一点,我们可以在后面进行更多的讨论。
感谢阅读。用于本教程的笔记本可以在 GitHub 上找到。
关于作者
我写关于 Python 和数据处理与分析的博客。以防你错过了我之前的一些博客。以下是与本文相关的几篇文章的链接。
更好的 Python
medium.com](https://medium.com/better-programming/30-simple-tricks-to-level-up-your-python-coding-5b625c15b79a) [## 掌握 Python 中列表理解的 9 件事
本教程将帮助你学习 Python 中列表理解的最常见用法
medium.com](https://medium.com/better-programming/9-things-to-know-to-master-list-comprehensions-in-python-8bc0411ec2ed) [## 为什么在做其他事情之前应该首先可视化数据的 3 个简单原因
我们大脑处理的 90%的信息是视觉信息,我们大脑处理视觉信息的速度比…
towardsdatascience.com](/3-simple-reasons-why-you-should-first-visualize-data-before-doing-anything-else-63ec05d86d9)
理解神经网络和模型泛化
深度神经网络的模型泛化、过拟合和正则化方法的挑战
在几个与神经网络相关的人工智能项目之后,我意识到模型的归纳能力是人工智能项目成功的核心。我想写这篇文章来帮助读者理解如何通过正则化方法来优化模型的性能,并更好地理解交付基于神经网络的可靠和可扩展的人工智能解决方案的复杂性。
泛化是一个用来描述模型对新数据做出反应的能力的术语。
泛化是你的模型经过训练消化新数据并做出准确预测后的能力。这可能是你的人工智能项目中最重要的元素。模型的概括能力是人工智能项目成功的关键。事实上,我们担心的是,一个模型在训练数据上训练得太好了,但却无法推广。
因为这个原因,我们经常达不到生产阶段…当给定新数据时,它会做出不准确的预测,使模型无用,即使它能够对训练数据做出准确的预测。这叫过度拟合。
相反的情况也可能发生。欠拟合是指模型在数据上没有得到足够的训练。在拟合不足的情况下,它使模型变得毫无用处,并且它不能做出准确的预测,即使使用训练数据也是如此。
在所有人工智能项目中,我们在现有数据上建立我们的模型,并希望它们能够完美地适应(推广)新数据。在监督学习中,我们拥有过去的数据,包括所有预测值和我们希望预测的真实值。尽管定义业务问题、收集相关数据、清理和准备数据以及构建模型都具有挑战性并且非常耗时……但另一个挑战仍然存在— 如何知道模型是否会很好地预测未来?
训练一个可以很好地推广到新数据的深度神经网络是一个具有挑战性的问题。
就神经网络而言,正则化是一种对学习算法进行轻微修改的技术,以便模型更好地推广。反过来,这也提高了模型在不可见数据上的性能。
模型复杂性
从商业角度来看,深度神经网络的主要优势是,随着数据集越来越大,它们的性能会不断提高。当公司试图创造数据网络效果时,这是非常有趣的。
然而,一个几乎有无穷多个例子的模型最终会达到网络学习能力的极限。适当的正则化是更好的泛化性能的关键原因,因为深度神经网络通常过参数化,并且可能遭受过拟合问题。
我们可以通过以下方式降低神经网络的复杂性,从而减少过拟合:
减小模型的容量可以降低模型过度拟合定型数据集的可能性,使其不再过度拟合。
通过保持较小的网络权重来减少过拟合的技术被称为正则化方法。
正则化:一类添加额外信息以将不适定问题转化为更稳定的适定问题的方法。
下面,我列出了几种我们经常使用的正则化方法(其他方法确实存在比如权重约束或者活动正则化)。然而,减少过度拟合的最简单的方法是从本质上限制你的模型的容量。
根据我的经验,这种方法是迄今为止最有效的正则化技术。在网络中的两个连续层之间应用 Dropout。在每次迭代中,关键思想是从神经网络中随机删除单元(以及它们的连接)。这导致后续层依赖于其与前一层的所有连接。这种方法通过防止训练数据上复杂的共同适应来帮助对抗过度适应。
全连接(FC)层最容易过度拟合,因为它们包含的参数最多。应该对这些层应用断开(影响它们与下一层的连接)。除了标准形式的辍学,还有几种辍学的变化,旨在进一步提高泛化性能。比如自适应退学,退学率由另一个神经网络动态决定…
然而,我注意到,如果你使用 CNN,dropout 现在不太常用了。反而看到越来越多的数据科学家使用**批量归一化。**当你拥有庞大的数据集时,批量规范化比丢弃更有效。
噪声 一种常见的正则化类型是在训练期间注入噪声:向神经网络的隐藏单元添加或乘以噪声。通过在训练深度神经网络时允许一些不准确性,不仅可以提高训练性能,而且可以提高模型的准确性。
根据 Jason Brownlee 的说法,训练中最常见的噪声类型是将高斯噪声添加到输入变量中。添加的噪声量(如扩散或标准偏差)是一个可配置的超参数。太少的噪声没有效果,而太多的噪声使得映射函数太难学习。请确保在评估模型期间,或者在使用模型对新数据进行预测时,没有添加任何噪声源。
提前停止
提前停止是一种交叉验证策略,我们保留训练集的一部分作为验证集。实际上,当我们看到验证集的性能越来越差时,我们会停止对模型的训练。
换句话说,这种方法试图在开始对噪声建模之前,在估计器已经学会从数据中提取所有有意义的关系时,尽早停止估计器的训练阶段。
这是通过监控验证损失(或另一个验证度量)并在该特定度量停止改善时结束训练阶段来实现的。通过这样做,我们给了估计器足够的时间来学习有用的信息,但是没有足够的时间从噪声中学习。
我对这种方法的问题是,不能保证在任何给定的时间点,模型不会再次开始改善。比提前停止更实际的方法是存储在验证集上实现最佳性能的模型的权重…
迁移学习
这种方法是通过将您的网络的权重初始化为另一个具有相同架构的网络的权重来完成的在大型通用数据集上进行预训练。我们经常在计算机视觉项目中使用这种方法。当我们的业务问题没有太多数据时,它对我们帮助很大,但我们可以找到另一个有数据的类似问题。在这种情况下,我们可以使用迁移学习来减少过拟合。
批量标准化 批量标准化(BN)是一种标准化网络输入的技术,适用于前一层的激活或直接输入。这种方法允许网络的每一层都能够独立于其他层进行学习。BN 用于通过调整和缩放激活来标准化输入层。
批量标准化(BN)是一种标准化深度神经网络中间层激活的技术。
除了具有正则化效果之外,批处理规范化还在其他方面帮助您的模型(允许使用更高的学习率等)。).我建议您验证各图层的权重和偏差分布看起来近似标准正态。
由于 BN 有一种正规化的效果,这也意味着你可以经常取消辍学(这是有帮助的,因为辍学通常会减慢训练)。
在训练期间,我们随着神经网络的权重和偏差更新批量标准化参数。对批处理规范化的一个更重要的观察是,批处理规范化作为一种规范化,因为使用小批处理显示出随机性。
批量 =一次正向/反向传递中训练样本的数量。批量越大,需要的内存空间就越大。
数据增强。处理过度拟合的另一种方法是提高数据质量。你可能会想到异常值/噪声去除,然而,实际上,它们的效率相当有限。另一种有趣的方式(特别是在图像相关的任务中)是数据增强**。目标是随机转换训练示例,以便在模型看来它们不同时,它们传达相同的语义信息。就我个人而言,当我看到我的模型在训练集上接近 0 损失时,我开始考虑使用数据增强。**
建议
我建议在考虑正则化方法之前先经历一些基本步骤。事实上,大多数时候,我们不能确定对于每个学习问题,都存在一个可学习的神经网络模型,它可以产生尽可能低的泛化误差。
正确的期望
首先要找到一个好的参考,它会告诉你,在你的数据集上,或者在你能找到参考的最相似的数据集上,存在一个可以达到你所寻找的泛化误差的架构。在您在自己的数据集上进行训练之前,尝试在这些参考数据集上再现这样的结果是一件有趣的事情,这是对您所有基础设施都已正确就位的测试。
培训程序验证
检查你的训练程序是否正确也很重要。这些检查包括以下内容:
超参数/架构搜索
最后,理解正则化本身并不一定意味着您的概化误差会变小,这一点很关键:模型必须具有足够大的容量才能实现良好的概化属性。这通常意味着,在你看到正规化的好处之前,你需要一个足够深的关系网。
如果没有其他帮助,您将不得不测试多个不同的超参数设置(贝叶斯优化可能会有所帮助)或多个不同的架构变化。
关于这个话题的更多信息,我推荐这些链接:
-https://machineellingmastery . com/train-neural-networks-with-noise-to-reduce-over fitting/
-https://towardsdatascience . com/batch-normalization-in-neural-networks-1ac 91516821 c
-https://papers . nips . cc/paper/5032-adaptive-dropout-for-training-deep-neural-networks
用一篇文章理解 Python 中的 O.O.P
深入面向对象编程以增长您的 Python 知识,重点关注真实世界的概念和类表示。
软件中的另一个技巧是通过使用已经编写好的部分来避免重写软件,这就是所谓的组件方法,这种方法的最新术语是所谓的面向对象编程。——比尔·盖茨。
比尔·盖茨的这句话说明了这篇文章的目的。理解面向对象编程允许你开发一种思考和实现代码的方式,我的目标是让它对你来说非常简单。毕竟,我们编码的主要原因之一,或者至少想学习如何编码,是为了自动化日常任务。正如微软的联合创始人所说,O.O.P .是实现这一目的的一种手段。
目录:
1.面向对象编程介绍(3 分钟阅读)
2.定义新类别(3 分钟阅读)
3.实例方法(1 分钟读取)
4.定义构造函数和其他特殊方法(2 分钟阅读)
5.代码重用(2 分钟读取)
1.面向对象编程简介
为了开始理解这种编程技术背后的直觉,让我们看一个初始的例子。想象一下,你必须向一个从未见过汽车的人描述一辆汽车,你会怎么做?
你可能想开始说它是一种用于运输的轮式机动车辆。你也可以说有几个品牌的汽车制造公司,它们生产不同类型的汽车来满足不同的需求。在一个更基本的层面上,你可能会说它有四个轮胎,在大多数情况下,它们可以承载五个人,并且它们主要用于运输人员,而不是货物。
当向计算机解释这是什么类型的物体时,用类似的方式接近它是一个好主意。计算机并不知道汽车是什么,为什么要制造汽车,或者谁在使用汽车。如果你想让你的计算机正确理解这个物体,在这个例子中是一辆汽车,你必须清楚地解释哪些是它的属性。
为了让计算机更容易理解这些新概念,Python 使用了一种叫做面向对象编程的编程模式,使用类和对象对概念进行建模。这是一个灵活、强大的范例,其中类表示和定义概念,而对象是 类的实例。在汽车示例中,我们可以创建一个名为 car 的类,向计算机定义其特征。我们将能够创建那个类 car 的数千个实例,它们是那个类的单个对象。
面向对象编程的想法可能听起来抽象而复杂,但是如果你已经编写了任何软件,你可能已经使用了对象,甚至没有意识到这一点。Python 中几乎所有的东西都是对象,所有的数字、字符串、列表和字典都包含在这种类型的元素中。面向对象编程的核心概念归结为与类型相关联的属性和方法:
- 属性是与类型相关的特征。
- 方法是与类型相关联的函数。
在汽车示例中,颜色和品牌是与程序创建的每个实例或汽车相关联的两个属性。另一方面,方法可以是由汽车执行的动作,例如驾驶。一个更面向计算机的例子是目录中的文件,因为每个文件都有名称、大小和创建日期。
如上图所示,当我们像刚才一样使用 type 函数时,Python 会告诉我们值或变量属于哪个类。由于整数和字符串是类,它们有一堆与之相关的属性和方法。您可以使用 Python 中的 dir 函数访问类的属性和方法,如下所示:
在我之前关于字符串的文章中,我探索了 string 类的许多方法和属性。看看它,进一步了解如何处理字符串对象,在这种情况下,它将是字符串类的另一个实例。这意味着它们都有相同的方法,尽管字符串中的内容不同。
为什么会有一堆以双下划线开头和结尾的方法?
这些被称为特殊方法,它们通常不会被冠以那些奇怪的名字。相反,它们由一些内部 Python 函数调用。例如, len 方法由 len 函数调用。
如果你想知道一个特定的方法做什么,你应该使用帮助功能:
当我们对任何变量或值使用 help 函数时,我们正在访问相应类的所有文档。在这种情况下,我们正在查看 str 和 int 类的文档。
尽管 Python 为我们提供了许多预定义的类,但是当我们用自己的属性和方法定义自己的类时,面向对象编程的威力就显现出来了。
2.定义新类别
如前所述,面向对象编程的要点是以计算机理解的方式帮助定义现实世界的概念。让我们动手以汽车为例构建一个新的类:
让我们来阐明准则的具体内容:
- 在 Python 中,我们使用 class reserved 关键字告诉计算机我们正在开始一个新的类。我们在后面加上类名和冒号。Python 风格指南建议类名应该以大写字母开头。在我这里,这个类叫做车。
- 在带有类定义的那一行之后是类的主体,按照循环或函数的模式向右缩进。
- 我们将在本文的第四部分讨论特殊方法 init 和 repr 。
我们如何扩展汽车类别的定义?它可能具有相同的属性,代表我们想要与汽车相关联的信息,如品牌和颜色。
现在,让我们继续创建 Car 类的一个实例,并将它赋给一个名为“my_car”的变量。为了创建任何类的新实例,我们调用类名,就像调用函数一样:
如您所见,我将 brand 和 color 作为参数传递,因为我已经配置了我的类,在创建该类的新对象时需要这两项。因此,我们可以调用创建的实例的属性,并接收先前分配的值:
用于访问属性的语法称为点符号,因为表达式中使用了点。点符号让你可以使用该物体可能拥有的任何能力,例如商标或颜色。
一些对象的属性和方法可以是其他对象,并且可以有自己的属性和方法。例如,我们可以使用 upper 或 capitalize 方法来修改我的实例的两个字符串属性:
到目前为止,我们已经创建了 Car 类的一个实例,并设置了它的属性。现在,我们可以创建具有不同属性的类的新实例:
3.实例方法
本质上,调用方法是为了让对象做一些事情。比如上和大写为弦。学习 O.O.P .中方法直觉的关键是理解方法是对类的特定实例的属性进行操作的函数。当我们在字符串上调用 lower 方法时,我们将特定字符串的内容变成小写。
让我们通过创建一个名为 Dog 的新类并定义我们自己的方法来更深入地了解一下。首先,我们需要定义这个类,并创建它的一个实例,就像我们之前对 Car 类所做的那样。虽然我的狗可能很棒,但只要我不为它们定义方法,它就不能执行任何动作。看一下这个例子:
如图所示,我们必须开始用 def 关键字定义一个方法,就像我们定义一个函数一样,并且将方法体向右缩进,就像我们定义一个函数一样。
该函数正在接收一个名为“self”的参数。此参数表示正在其上执行该方法的实例。
即使我的狗叫,它也总是用同样的方式。我们可以通过简单地修改代码来改变它的运行方式,以便在我们为类配置的属性和方法中获得灵活性:
4.定义构造函数和其他特殊方法
到目前为止创建的两个类都包含默认值作为属性和方法。这不是一个理想的场景,因为它为每个属性创建了冗余的代码,更重要的是,因为它很容易忘记设置一个重要的值。
因此,在编写代码时,最好在创建类时设置随实例变化的属性和方法,以确保每个实例包含相同的重要属性。为了做到这一点,我们需要使用一个叫做构造函数的特殊方法。
类的构造函数是当你调用类名时调用的方法。它总是被命名为 init 。
它包含关键字 self,它指的是正在创建的实例,以及在实例创建后将由程序员作为属性传递的参数,就像我们在下面的示例中所做的那样:
该类的第二个方法是 repr 方法,它告诉 Python 在每次调用该类的一个实例时打印一条预定的语句,如上图所示。
想知道哪个是具体方法的功能?参考之前介绍的帮助功能。由于内置的类包含了帮助用户理解每个方法或属性背后的直觉的指南,我们也可以在我们自己的类、方法和函数上这样做。我们可以通过添加一个文档字符串来实现。
Docstring 是解释某事的简短文本。
一旦将文档包含到类和对象中,您将获得更多关于所创建方法的信息,这将促进代码的可重用性并帮助其他用户理解它。请记住,docstring 必须始终在它所记录的块的同一级别缩进。
5.代码重用
面向对象编程的另一个重要方面是继承。就像人有父母、祖父母等等一样,对象也有祖先。继承的原则允许程序员在概念之间建立关系,并将它们组合在一起。特别是,这允许我们通过一般化我们的代码来减少代码重复。
例如,除了我们已经创建的汽车,或者除了我的狗之外的其他宠物,我们如何定义“**其他交通工具”**的表示?这种分组特性允许我们创建共享现有类的一些属性(但不是全部属性)的其他类,以便在不重写现有代码的情况下添加其他类似的实例。
在 Python 中,我们在类声明中使用括号来显示一个继承关系。在上面的运输示例中,我使用 Python 的语法告诉计算机汽车和火车都继承自 MyTransports 类。因此,它们自动具有相同的构造函数,用于设置颜色和品牌属性。
通过继承技术,我们可以使用 transportation 类来存储适用于所有可用传输方式的信息,并将汽车或火车的特定属性保存在它们自己的类中。您可以将 MyTransports 类视为父类,将 Car 和 Train 类视为兄弟类。
与您在 IT 部门执行的任务更接近的一个例子是处理您公司员工的系统。您可能有一个名为雇员的类,该类可能具有诸如人员的全名、公司系统中使用的用户名、雇员所属的组等属性。系统还可以有一个经理类,它也是一个雇员,但是有与之相关的附加信息,比如向特定经理报告的雇员。
结论
在 python 这样的面向对象语言中,现实世界的概念是由类来表示的。类的实例通常被称为对象。这样的对象有用来存储关于它们的信息的属性,我们可以通过调用它们的方法使对象工作。这篇文章的目的是让您清楚地了解 O.O.P .对程序员来说有多有用,因为它允许我们对现实世界的概念进行建模。
感谢您花时间阅读我的文章!如果您有任何问题或想法要分享,请随时通过我的电子邮件联系我,或者您可以在以下社交网络中找到我以了解更多相关内容:
如果您对更深入了解 Python 编程的更多相关文章感兴趣,请考虑以下内容:
理解 Python 中的循环:
读完这篇文章后,你将不再有机会被 Loops 抛出。
towardsdatascience.com](/understand-loops-in-python-with-one-article-bace2ddba789)
用 Python 创建惊人的可视化效果:
我准备了一份详尽的指南来展示漂亮的可视化效果,以增强度量、KPI、预测和其他…
towardsdatascience.com](/business-intelligence-visualizations-with-python-1d2d30ce8bd9)
创建股票价格模拟器:
几何布朗运动过程的简单应用来模拟股票价格。
towardsdatascience.com](/create-a-stock-price-simulator-with-python-b08a184f197d)
使用 Python 的电子商务数据科学:
我为电子商务行业准备了一份广泛的数据科学应用指南。
towardsdatascience.com](/data-science-for-e-commerce-with-python-a0a97dd7721d)
了解熊猫指数
为了有效地使用 Pandas,请忽略它的文档并了解索引的真相
Damian Patkowski 在 Unsplash 上拍摄的照片
Python Pandas 库是一个很好的数据操作工具。然而,只有当你理解熊猫索引时,它才是有效的。Pandas 索引是在几秒钟而不是几分钟或几小时内访问和连接行的关键。
指数
像 Python 字典(或关系数据库的索引)一样,Pandas 索引提供了一种将键转化为值的快速方法。例如,我们可以创建一个索引为alpha
的数据帧:
然后将键b
转到感兴趣的行。
但是熊猫指数是一个什么样的东西呢?文档说索引是一个
不可变 n 数组实现一个有序的、可切片的集合(增加了重点)
换句话说,一种数学集合。回想一下,数学集合有两个重要属性:
- 没有重复的元素
- 元素是无序的
但是现在,看第二个例子:
我们又把alpha
栏目变成了索引。然而,元素x
出现了两次,检索到的行遵循两个x
的顺序。
- 元素可以重复
- 元素是有序的
因此,与熊猫文献相反,熊猫指数不是一个数学集合。取而代之的是一种列表。具体来说,熊猫指数是
- 可散列元素的(一种)列表,其中
- 可以快速找到元素的位置。
有了这些知识,我们可以很容易地理解索引的基础,从它们的创建、删除和操作开始。
操纵索引
上面的例子展示了如何用.set_index()
将一个列转换成索引。我们可以用.reset_index()
将索引变回列:
让我们把索引放回去,然后看看索引中的所有元素。属性为.index.values
。正如所料,元素是一种列表,具体来说,是一个 NumPy 数组。
我们还希望能够快速找到对应于任何索引元素的行号。方法是.index.get_loc()
。结果将是一个整数或布尔数组,具体取决于行数。
行访问
访问带有索引元素的行的主要方法是.loc[…]
(注意方括号),其中的输入可以是:
- 单一元素
- 元素列表
- 元素切片
行将按照它们在输入中出现的顺序输出。这个例子展示了每一种输入。
注意,与 Python 的其他部分不同, start:stop 片段包含了 stop 值。
连接行
最后,让我们看看连接两个数据框架。规则是:
- 左边的数据帧不需要索引,但是右边的需要。
- 在连接的
on
输入中给出感兴趣的左列。
在这个例子中,我们将使用join
向 dataframe 添加一个“score”列。这是左边的数据框。它没有索引。
正确的数据帧需要一个索引,但它可以被命名为任何名称。在这里我们称之为alpha2
。
我们用左连接将两个数据帧结合起来。我们使用第一个数据帧中的列alpha
和第二个数据帧中的索引。结果是一个带有分数列的新数据帧。
结论
我们已经看到,与文献相反,熊猫指数不是一个数学集合。相反,它是一种可以快速找到任何元素位置的列表。
理解了这一点,就很容易理解如何创建、删除和操作索引。然后,我们可以使用索引来快速访问和连接行。
下一步是什么?有了这个基础,接下来您应该学习从多个列创建索引,对索引应用类似集合的操作符,以及有效地删除行。(令人惊讶的是,熊猫分组排序不需要也不使用索引。)
了解 Python 中的 slots。
改进 Python 代码的简单方法
当我们从一个类中创建一个对象时,该对象的属性将被存储在一个名为__dict__
的字典中。我们使用这个字典来获取和设置属性。它允许我们在创建对象后动态地创建新的属性。
让我们创建一个简单的类Article
,它最初有两个属性date
和writer
。如果我们打印出对象的__dict__
,我们将得到每个属性的键和值。同时,我们也打印出以后需要的类的__dict__
。之后,一个新的属性reviewer
被添加到对象中,我们可以在更新后的__dict__
中看到它。
够好吗?
在我们找到更好的解决方案之前,我们不能说这不好。Dictionary 在 Python 中非常强大,但是在创建成千上万的对象时,我们可能会面临一些问题:
- 字典需要记忆。数百万个对象肯定会耗尽内存。
- 字典实际上是一个哈希表。hash map 中 get/set 的时间复杂度最坏情况为 O(n)。
__ 插槽 _ _ 解决方案
来自 Python 文档 : slots 允许我们显式声明数据成员(如属性)和拒绝 dict 和 weakref(除非在 slots 中显式声明或在父代中可用。)
那么,这和我提到的问题有什么关系呢?
让我们创建一个类ArticleWithSlots
。两个类之间的唯一区别是额外的字段__slots__
。
__slots__
是在类级别上创建的,这意味着如果我们打印ArticleWithSlots.__dict__
,我们应该能够看到它。此外,我们还看到了类级别上的两个额外属性,date: <member 'date' ..>
和writer: <member 'writer' ..>
,它们属于类 member_descriptor 。
Python 中的描述符是什么?
在讨论描述符之前,我们应该理解 Python 中访问属性的默认行为。当你执行article.writer
时,Python 将调用方法__getattribute__()
,在__dict__
、self.__dict__["writer"]
中进行查找并返回值。
如果查找键是具有描述符方法之一的对象,那么默认行为将被描述符方法覆盖。
ArticleWithSlots 类的屏幕截图
描述符方法包括__get__()
、__set__()
和__delete__()
。描述符只是一个 Python 对象,它实现了至少一个描述符方法。
__slots__
通过描述符方法的实现,自动为每个属性创建一个描述符。你可以在截图中找到它们。意味着对象将使用__get__()
、__set__()
和__delete__()
与属性交互,而不是默认行为。
据吉多·范·罗苏姆,__get__()
,__set__()
的实现用数组代替字典,完全用 C 实现,效率高。
slots 具有更快的属性访问速度
在下面的代码中,我比较了Article
和ArticleWithSlots
的对象创建时间和属性访问时间。__slots__
快 10%左右。
__slots__
有稍微好一点的性能是因为的时间复杂度的 get/set 操作在最坏的情况下比一个链表中的字典要快。由于O(n)
只发生在最坏的情况下,我们大多数时候不会注意到差异,尤其是当你有少量数据的时候。
数据来源:wiki.python.org
slots 减少了 RAM 的使用
由于属性可以作为数据成员(比如属性)来访问,所以没有必要将它们存储在字典__dict__
中。其实,__slots__
根本就否定了__dict__
的创作。所以如果打印article_slots.__dict__
,会得到 AttributeError 异常。
这种行为减少了对象的 RAM 使用。我将使用 pympler 比较article
和article_slots
的大小。不使用sys.getsizeof()
的原因是getsizeof()
不包括被引用对象的大小。但是,__dict__
是一个被引用的对象,在getsizeof()
中将被忽略。
原来article_slots
比节省 50%以上的内存。哇,如此惊人的进步!
这么好的性能是因为article_slots
没有__dict__
属性,实际上节省了很多内存。
什么时候使用和不使用 slots?
到目前为止,看起来__slots__
是一个非常好的特性。能不能每节课都加?
答案是否定的!显然,有一些权衡。
固定属性
使用__dict__
的原因之一是它在创建对象后的灵活性,在这里你可以添加新的属性。然而,__slots__
将在您创建类时修复属性。因此,以后不可能添加新的属性。
但是…
在某些情况下,你想利用__slots__
,也有在运行时添加新属性的灵活性。你可以通过在__slots__
中添加__dict__
作为属性来实现。只有新添加的属性会出现在__dict__
中。当您的类有 10 个以上的固定属性,并且您希望以后有 1 个或 2 个动态属性时,这将非常有用。
继承
如果你想继承一个包含__slots__
的类,你不必在子类中重复这些属性。否则,子类会占用更多的空间。此外,重复的属性在父类中将是不可访问的。
当您继承一个命名的元组时,情况也是如此。你不需要在子类中重复属性。如果你想了解更多关于 NamedTuple 的信息,你可以阅读我的文章专门讨论这个话题。
你也可以在子类中添加__dict__
属性。或者,你不把__slots__
放在子类中,它默认会有__dict__
。
如果你继承了一个没有__slots__
的类,那么子类将包含__dict__
。
结论
希望你已经理解了什么是__slots__
以及实现的一些细节。在文章的最后,我想分享来自我自己的经验和互联网的利弊(链接在参考资料中)。
优点
当您对内存使用有压力时,__slots__
绝对有用。只需一行代码就可以非常容易地添加或删除。在__slots__
中将__dict__
作为一个属性的可能性给了开发人员更多的灵活性来管理属性,同时兼顾性能。
缺点
你需要清楚你在做什么,你想用__slots__
实现什么,特别是当用它继承一个类的时候。继承的顺序、属性名称会对性能产生巨大的影响。
不能用非空的__slots__
继承int
、bytes
、tuple
等内置类型。此外,你不能给__slots__
中的属性赋予默认值。这是因为这些属性应该是描述符。相反,您可以在__init__()
中指定默认值。
我希望你喜欢这篇文章!如果你有任何想法,请在下面留下你的评论。
参考
特殊属性 slots 允许您显式地声明您希望对象具有哪些实例属性…
stackoverflow.com](https://stackoverflow.com/questions/472000/usage-of-slots) [## 新型课堂的内幕
警告,这篇文章很长,而且非常专业。]从表面上看,新型的类看起来非常类似于…
python-history.blogspot.com](http://python-history.blogspot.com/2010/06/inside-story-on-new-style-classes.html) [## 时间复杂性- Python Wiki
本页记录了当前 CPython 中各种操作的时间复杂度(也称为“Big O”或“Big Oh”)。其他…
wiki.python.org](https://wiki.python.org/moin/TimeComplexity) [## 3.数据模型- Python 3.8.3 文档
对象是 Python 对数据的抽象。Python 程序中的所有数据都由对象或关系来表示…
docs.python.org](https://docs.python.org/3/reference/datamodel.html#slots)
理解火花,就好像是你设计的一样
从简单的功能到灵活的分布式框架。
为什么关心星火?
在当前数据领域可用的框架中,就采用和交付而言,只有少数几个达到了 Spark 的地位。框架已经成为明显的赢家之一,特别是在数据工程领域。
如果你正在看这篇文章,说明你已经明白了前一段背后的原因,所以我们直接跳到正题。
为什么关心 Spark 内部?
有人可能会说,我们不必为了驾驶汽车而去理解发动机是如何工作的,这是事实。然而,有人可能会说,了解发动机会让你成为一名更好的飞行员,因为你能够了解整辆车的能力、局限性和最终问题。
根据同样的原理,您不必理解 Spark 的内部机制就可以使用它的 API。但是,如果您这样做了,从低性能到隐藏的 bug 的许多痛苦将会减轻。此外,你将掌握贯穿整个分布式系统领域的概念。
进场
在我看来,学习有两种方式:知识和技术。前者与正规的知识获取有关,通过书本、结构化课程等。它更关注于什么。后者与工艺有关,而“边做边学”则更侧重于如何。这就是我们现在要走的路。
我们将从一个简单的问题开始,每个初学者都可以解决这个问题,并对其进行改进,以证明 Spark 的架构设计是正确的。在这个过程中,我们还将了解 HDFS (部分称为 Hadoop),因为它是一个与 Spark 配合得非常好的平台。
我们将是语言不可知的,所以所有的代码实际上都是伪代码。
问题
你被录用了,并被分配了一个简单的任务:数一数你的数组中有多少个偶数。
您将从本地文件系统中存储的 CSV 文件中读取该数组。不用想太多,你可能会得到下面的一大块:
新要求 1
你的客户对之前解决方案的巨大成功感到高兴,人们现在认为他们可以把每个问题都扔给你,所以他们要求你也计算这些偶数的平均值。
你肯定知道坚实的原则,特别是单一责任原则,它说一个类或方法应该只有一个改变的理由。然而,你决定打破规则,只是实现如下:
新要求 2
既然你这么快,人们又提出了另一个要求:还要返回所有偶数的和。
这时,你不仅开始考虑 T2 坚实的原则,也开始考虑事情的发展方式。你知道,通常,当一件事情发生一次并不意味着它会发生两次,但如果它发生两次,第三次就在眼前。所以你开始考虑实现一些更容易扩展的东西,你还记得面向对象编程中的封装概念。
此外,如果您捕获了适当的抽象,那么当另一个需求出现时,您甚至不必更改您的实现。
一组抽象来管理它们
你首先考虑到,如果他们让你数偶数,他们很可能会进一步问你奇数,或者低于或高于某个值,或者在某个范围内,等等。因此,即使你是应用 YAGNI ( 你不会需要它)的专家,你也决定实现一些支持所有这些情况的东西。
最后,您得出结论,所有这些操作都与从数组中过滤值有关,因此,您没有对每个可能的过滤器进行编码,而是决定提供一个接受过滤条件的过滤函数。
此外,为了简化设计,您决定在每次对对象调用操作时更改对象的状态。
迎接新的挑战
你做到了。现在,您不仅实现了所有的需求,还摆脱了涉及从数组中过滤值的新需求。如果您的客户现在想要奇数,而不是偶数,他们唯一要做的就是向 filter 方法传递一个新条件,这样就完成了。太神奇了!但是,您等待的新需求刚刚到来:他们现在需要您处理一个 3 TB 的阵列。
你考虑放弃。你自己的硬盘只有 500 GB 大,所以你需要 6 台像你一样的机器专门用来存储那个文件。但你的客户喜欢你,也很有说服力,在理所应当的加薪后,他们还承诺为你提供不是 6 台而是 30 台新机器来解决问题。
划分
有了 30 台新机器,你开始考虑如何解决这个问题。一台机器不可能包含整个文件,所以你必须把它分割成更小的块,以适应每个新的硬盘。此外,您考虑到既然您有足够的资源,那么您也可以将同一个片存储在多台机器上,作为备份。也许每个切片有两份拷贝,这意味着你可以在三个不同的地方找到那个切片。
您格式化硬盘并开始复制文件,在此过程中,您认为将所有切片保存在每台机器的同一规范父文件夹中是一个好主意,并且为每个切片添加一个标识符前缀,该标识符与它属于更大文件的哪个部分相关。您还认为在至少两台计算机中有另一个目录也是一个好主意,该目录包含元数据,这些元数据说明哪些目录包含存储片,哪些计算机包含每个存储片 id 的备份。
因为你的一些机器只包含数据,一些只包含给事物指明方向和名称的元数据,你决定把前一个称为数据机器,后一个称为机器。但是因为您实际上是在创建一个网络,所以将机器称为节点更合适,所以您将数据机器命名为数据节点,将元数据机器命名为名称节点。
在给事物命名的过程中,你会意识到 slice 与蛋糕和奶酪的关联要大于与数据块的关联。你觉得很有灵感和创造力,所以你决定给这些切片取一个更好的名字:分区。所以,无论你的程序最终变成什么,它都会处理被分成分区的整个文件。
在所有这些命名和决定之后,您会得到这样的结果:
您的第一个分布式文件系统
征服
现在,您已经将文件划分为跨越一组节点的分区(从现在开始,我们将创造性地称之为集群),备份和元数据帮助您的程序找到每个分区及其备份。既然移动分区没有任何意义,那么问题就变成了:在每台机器上执行同一段代码并得到一个结果?
每次需要运行程序时,是否应该将整个程序发送到每台机器上?或者你应该有一些已经存在的片段,这样你只需要发送你的客户写的部分?后者听起来更好,所以你跟着它走。在这个过程中,第一个要求是让您的 ArrayOperator 类在每台机器上都可用,并且只发送由 main 方法指定的部分。
您还希望在尽可能靠近数据的地方运行代码,因此您的数据节点也必须运行您的程序。从这个角度来看,节点不仅存储数据,还执行实际工作,因此您决定将它们称为 workers 。
代码的某些部分也可以并行运行。例如,对于上面的程序,您可以并行执行 average()、、sum() 和 size() ,因为它们彼此独立。为了实现这一点,您的工作人员需要支持独立的执行行,因此您决定将每个工作人员转换为某种守护进程,该守护进程将产生独立执行任务的新进程(同时,您意识到 task 是一个通用名称,足以指代可以独立执行的每个单元)。既然你仍然受到启发,你决定创造性地将执行任务的过程称为执行者。
现在,您所要做的就是设计您的 main 方法——它可以访问您的客户端代码——使它能够将您的客户端代码分离成将组成作业的任务,然后询问 name 节点哪些数据节点包含文件的每个分区,然后将任务并行发送到 worker 机器,worker 机器将准备启动将执行任务并返回结果的执行器。因为这段代码将驱动整个事情,你,仍然被同样的创造力所祝福,决定称它为驱动。
你的司机也需要知道如何把所有的结果放在一起。在这种情况下,它需要将从每个工人那里收到的所有总和加起来。但考虑到目前为止的进展,这只是小菜一碟。
总的来说,你的司机将会协调完成任务。这又是你的想象。哪个名称比作业更适合描述一组任务?
你美丽的工程作品
造破
几个晚上之后,你终于可以让所有的部分一起运行了。多么令人印象深刻的壮举!你测试它,一切都像预期的那样工作。你渴望看到一个演示,而你的客户在投入大量资金后,也同样渴望看到这个演示。
你通过表扬自己开始演示,这是应该的,然后继续解释架构。你的客户会更加兴奋。你运行程序。一切都崩溃了,因为你的 5 台机器离线了,2 台是因为内核崩溃,2 台是因为硬盘故障,还有 1 台是因为一个未测试的特性导致了一个错误。每个人都开始哭泣,除了你。你的客户失去了信心,但你的信心却坚如磐石。你实际上再次表扬了自己,因为你已经想通了所有的事情。这些备份并非出于巧合。
你保证一周后会有新的演示。你的客户离开的时候很生气,有点悲伤,但是你保持冷静。
**“少就是少,不是多”,**2020 年前后备份
你猜对了。由于每个分区包含两个其他副本,并且您有 28 台机器(请记住,您为名称节点保留了 2 台),如果 5 台机器的故障导致整个集群停机,您将非常不幸。
但是如何利用冗余呢?你可以确定的一件事是,它应该从驱动程序端开始,因为这是与所有节点通信的部分。如果您有一个失败的节点,它将首先被驱动程序注意到。由于在作业开始时,驱动程序已经与名称节点取得联系,以查找分区的位置,因此它可能还会向名称节点询问故障 worker/data 节点中所有副本的位置。有了这些信息,它只需重新发送要在副本上执行的任务,您就大功告成了!
使用前面的方法,您可以用一种弹性的方式让分布式处理分布式数据。
你去争取吧。
重新开始
你打电话给你的客户,要求一个新的演示。他们假装仍然很沮丧,但几乎无法掩饰他们的兴奋。他们来看你,进入这个地方说一些关于上次的笑话。你只看到“蓝屏”,却不太关心笑点。
在开始之前,您做了一些令人震惊的事情:您要求他们随机关闭两个工作人员/数据节点。他们看起来很惊讶,但进入了状态(看到他们带着奸诈的微笑随机选择机器,试图比你更聪明,这很有趣)。
少了两个节点,您就可以开始演示了,效果非常好。他们哭了,但这一次的眼泪不一样。他们让你振作起来,为不相信而道歉,给你加薪,当然,还带来了新的要求:数组现在将包含具有多种属性的对象,而不是数字。更具体地说,记录将包含姓名、年龄和工资,他们想知道被称为费利佩的人的平均年龄和最高工资是多少。他们还想保存结果,以便以后可以访问而无需重新处理。
你一点都不惊讶。
蛋糕上的樱桃
这个时候你不用多想。你一直在玩抽象,所以现在只需要再向上移动一级。
您偏离了之前的设计,并像这样进行了更改:
有了新的设计,您现在可以处理任何类型的记录(这就是为什么您将其名称改为 GeneralOperator )。
这真是太神奇了!想想吧。你有一个系统,可以以分布式和弹性的方式读取、写入和处理任何类型的数据集。更自由地说,你可以宣称你有一个支持处理任何种类的弹性和分布式 数据集的框架。
你感觉到了掌握在你手中的力量,但是你认为你的魔法的核心 GeneralOperator 没有一个足够吸引人的名字。或者至少它不是非常自明的。但是你没有更好的想法,所以你决定称它为弹性和分布式数据集读取器、写入器和处理器。但是那太大了。所以可能是首字母缩写,比如 RDDRWP?哎哟,更惨。那只有 RDD 的呢?容易发音和发音,以防有人请你翻译。听起来不错,你完成了。
TLDR;
以下是您所做的工作:
1.您已经设计了一个以分布式方式存储复制数据的基础设施分区,它由保存数据的数据节点和包含元数据的名称节点组成(这一对难道不应该有自己的名称吗?那么 HDFS 呢?)
2.您创建了一个名为弹性分布式数据集(简称 RDD )的结构,可以读取、写入和处理存储在 Hadoop 集群中的数据。
3.您已经构建了一个基础架构,通过控制给定节点上的执行的工作器和实际执行任务的执行器,在分布式分区上并行执行任务。
4.您设计了一个驱动程序应用程序,它将客户提供的作业分解成多个任务,与命名节点对话以找出分区的位置,并将任务发送给远程工作人员。
伙计,你真棒!但是你的创造难道不应该有一个好听的名字吗?有太多的想法,一个接一个的火花。耶,火花!听起来像个名字!
你可以这样推销它
按比例放大
你创造的这个东西当然有很大的价值,但也许它有一个有点陡峭的学习曲线。另一方面,长期以来(可能太久了),处理数据的语言选择是结构化查询语言,或者 SQL 。把这种能力带入你的火花怎么样?
让我们与客户聊天。
重要的
以上是 Spark 组件的一个非常简化的视图,其主要目的是提供对 Spark 架构的一个总体把握。与催化剂、调度、转换类型、洗牌、计划、资源分配、专门的 API 方法等相关的元素被有意省略,以使文本更简单。他们将在以后的作品中接触。
进一步阅读
关于 Spark:https://data-flair . training/blogs/Apache-Spark-ecosystem-components/
关于 rdd:https://spark . Apache . org/docs/latest/rdd-programming-guide . html
在 OOP 上:https://en.wikipedia.org/wiki/Object-oriented_programming
关于 S.O.L.I.D 原则:https://scotch . io/bar-talk/S-o-l-I-d-first-first-five-principles of-object-oriented-design
https://martinfowler.com/bliki/Yagni.htmlYAGNI
使用 Python 中的单变量和多变量图表和绘图理解数据
学习一些使用 python 中的医疗数据来理解和可视化数据的技术
单变量数据分析是最简单的数据分析形式。顾名思义,它处理一个变量。它没有发现变量之间的因果或关系。单变量数据分析的目的是总结和描述一个数据或一个变量。如果包含两个变量,它就变成了二元变量。在本文中,我们将使用单变量和双变量数据分析来理解和可视化一些数据。在一些实践中,我们也将包括三个变量。所有的信息只适用于本文中使用的特定数据集。
了解数据集
我们将使用来自 Kaggle 的心脏数据集。首先导入包和数据集。
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from statsmodels import api as sm
import numpy as npdf = pd.read_csv("Heart.csv")
让我们清楚地看到列名:
df.columns
#Output:
Index(['Unnamed: 0', 'Age', 'Sex', 'ChestPain', 'RestBP', 'Chol', 'Fbs', 'RestECG', 'MaxHR', 'ExAng', 'Oldpeak', 'Slope', 'Ca', 'Thal', 'AHD'], dtype='object')
你现在可能不明白每一列是什么意思。在这篇文章中,我将只使用几个列,并且我将继续解释列名的含义。
解决一些问题
- 找出不同类型血液疾病的人口比例。
我们会在“Thal”栏中找到。在这里,“地中海贫血”是指一种叫做地中海贫血的血液疾病。Pandas 中有一个名为“value_counts”的函数,用于计算一个系列中每个类别的值。
x = df.Thal.value_counts()
x
这些是患有正常、可逆和固定疾病的人数。现在,将它们分别除以总人口,得出人口比例。
x / x.sum()
如果你注意到,这些比例加起来不等于 1。在这次计算中,我们漏掉了一件事。可能会有一些值。用“缺失”填充这些空格,然后再次计算比例。
df["Thal"] = df.Thal.fillna("Missing")
x = df.Thal.value_counts()
x / x.sum()
所以,有一些缺失的值。在这个数据集中,54.79%的人患有正常地中海贫血。下一个大的是 38.16%,他们有可逆的地中海贫血。
2.找出胆固醇数据的最小值、最大值、平均值和标准偏差。
有一个功能叫做‘描述’。让我们利用这一点。我们将获得我们需要的所有信息以及一些其他有用的参数,这些参数将帮助我们更好地理解数据。
所以,我们得到了一些额外的有用参数。人口总数是 303。我们不打算在本文中使用它。但它在统计分析中很重要。尤其是在推断统计学中。“describe”函数还返回 25%、50%和 75%的百分比数据,这些数据提供了数据分布的概念。
3.绘制胆固醇数据的分布图。
sns.distplot(df.Chol.dropna())
分布稍微向右倾斜,有一些异常值。
4.求 RestBP(静息血压)的平均值。然后,计算 RestBP 高于平均 RestBP 的人口比例。
mean_rbp = df.RestBP.dropna().mean()
平均 RestBP 为 131.69。首先,找到 RestBP 大于均值 RestBP 的数据集。除以总数据集的长度。
len(df[df["RestBP"] > mean_rbp])/len(df)
结果是 0.44 或 44%。
5.绘制胆固醇数据与年龄组对比图,观察不同年龄组人群中胆固醇水平的差异。
这是解决方案。在数据集中创建一个新列,该列将返回不同年龄组的人数。
df["agegrp"]=pd.cut(df.Age, [29,40,50,60,70,80])
现在,制作方框图。将年龄组放在 x 轴上,胆固醇水平放在 y 轴上。
plt.figure(figsize=(12,5))
sns.boxplot(x = "agegrp", y = "Chol", data=df)
箱线图显示胆固醇随着年龄的增加而增加。检查性别是否起作用是一个好主意。如果不同性别的人胆固醇水平不同。在我们的性别专栏中,数字 0 和 1 分别代表女性和男性。我们将创建一个新列,用“男性”和“女性”替换 0 或 1。
df["Sex1"] = df.Sex.replace({1: "Male", 0: "Female"})
plt.figure(figsize=(12, 4))
sns.boxplot(x = "agegrp", y = "Chol", hue = "Sex1", data=df)
总体而言,该数据集中的女性人群胆固醇水平较高。在 29 岁到 40 岁的年龄组,情况就不同了。在 70 至 80 岁的年龄组中,胆固醇水平仅见于女性人口中。这并不意味着该年龄段的男性没有胆固醇。在我们的数据集中,该年龄组的男性人口不足。如果我们按年龄绘制男性和女性人口图,将有助于理解。
sns.boxplot(x = "Sex1", y = "Age", data=df)
6.制作一个图表,显示每个年龄组中患有各种类型胸痛的人数。
df.groupby('agegrp')["ChestPain"].value_counts().unstack()
对于每种类型的胸痛,最大值出现在 50 至 60 岁的年龄段。可能是因为我们的数据集中该年龄组的人数最多。看上面的图片。
7.制作与之前练习相同的图表,并添加性别变量。按性别区分数字。
dx = df.dropna().groupby(["agegrp", "Sex1"])["ChestPain"].value_counts().unstack()
8.在前一张图表中,列出同一组中每种类型胸痛的人群比例。
dx = dx.apply(lambda x: x/x.sum(), axis=1)
那是最后一次练习。这些是制作单变量和多变量图表和绘图的一些技巧。我希望这有所帮助。在这里,我有一些相关文章的链接:
- 通过直方图和箱线图了解数据
用 ML 解释器理解机器学习黑盒
一个自动解释 XGBoost 等算法决策的 web 应用程序
让模型来管理这个世界并做出从雇佣到刑事司法的决策是有危险的。虽然拥有既可解释又准确的模型是理想的,但许多流行且强大的算法仍然是黑盒。
其中有高性能的树集成模型,如 lightGBM,XGBoost,random forest。了解他们的内部运作会带来很多好处,包括透明、信任、合规和公平。否则,人们可能不得不求助于更易解释的原始模型。
为了让解释更容易,我构建了一个 web 应用 ML-interpreter 。
在深入详细的功能之前,这里有一个关于可解释性的初级读本。
解释框架的简要概述
现在有很多可解释性框架,包括 Python 包 LIME 、 SHAP 、 ELI5 、 Skater ,以及不太知名的如 ALIBI 、R 包 Dalex 以及 SHAP 和 LIME 的 R 包装器。一开始,处理许多不同框架的机制、语法和用法可能会令人不知所措,这促使我开发了这个应用程序。以下是一些关键概念。
冲击与方向
为了解释一个模型,我们通常想知道(1)特性对结果的影响和(2)影响的方向。
图片来源: SHAP
全球对本地
我们还想知道:
- 哪些功能总体影响最大(全局解释)
- 如何批准每个决策点(当地解释)
大多数框架既可以进行全局解释,也可以进行局部解释,有些侧重于局部层次,比如 LIME。
与型号无关与与型号相关
像 SHAP 这样的框架是模型不可知的。其他人专门研究特定的模型,如 ELI5,它主要涵盖基于树的模型,但也可以用于解释 sklearn 线性模型和文本/图像用例。
这些框架是如何工作的
排列重要性
对于全局重要性,一种常用的方法是排列重要性。通过改变特定特性的值并观察它对模型性能的影响程度,可以推断出该特性的重要性。这种方法被认为比某些包中默认的特征重要性度量更一致。你可以在这里阅读更多关于为什么的内容。
图片来源:ML Kaggle 的可解释性
代理模型
对于局部解释,可以训练简单的可解释模型(如决策树)作为代理,然后使用该代理模型将原始数据与作为解释目标的黑盒模型的预测进行拟合,并使用该简单模型进行解释。
博弈论
另一种方法是基于博弈论的沙普利值。单个特征的影响可以加起来解释为什么预测与基线不同。你可以在这篇博客帖子或这篇 arXiv 论文中了解更多关于 SHAP 的树解释器是如何工作的。
接下来,我们将使用 app 进行一些解读。
ML 解释程序
该应用程序由 4 个主要部分组成:用户选择、分类报告、每个决策点的全局解释和局部解释。
人们可以选择一个演示数据或更新一个小的表格 csv,运行一个分类器(randomforest,XGBoost,lightGBM),并在可解释性框架之间进行选择。treeSHAP 和 ELI5,因为它们覆盖了全局和局部解释,并且很好地处理了树集合模型。
示例
现在,我们将在 UCI 的成人收入数据集(包含在应用程序的演示数据中)上测试它,该数据集根据人口普查预测个人年收入超过 5 万美元的概率。首先我们可以选择一个内置模型(比如 lightGBM),选择一个框架(SHAP)。
视察模特表演
我们可以从查看分类报告和混淆矩阵开始。请注意,这是没有任何数据清理或特征工程的输出。因此,如果预先对数据进行预处理,比如删除零方差或 id 列(如果有的话),结果可能会更好。
理解全局解释
当我们对模型的结果满意后,我们可以检查最重要的特征并发现关系、教育和年龄对收入的影响最大。
全局解释
放大局部解释
然后,为了检查这些特征如何影响收入,我们可以使用滑块放大到单个数据点**。你可以在应用中阅读更多关于如何阅读这个情节的信息,或者点击这里。**
示例 1
在这个例子 1 中,这个人是一个 21 岁的人,每周只工作 8 个小时,他被正确地归入低收入类别。
示例 2
在另一个例子 2 中,被归类为高收入预算的人工作了相当长的时间,已婚并具有高学历,但是位置似乎降低了机会。
示例 3
在最后一个例子中,这个人工作了相当长的时间,但是许多因素都有负面影响。人们可以从最长的箭头看到最重要的因素。如果这个因素不合理,就应该重新处理数据,找到混淆因素或者进行调试。
如果我们转而使用 ELI5 框架,我们可以以表格形式查看不同的视图,总结出对个人决策影响最大的因素。
这是一个自动解释的应用程序,它运行一个选择的算法,显示模型的性能,并指出是什么使模型进行预测。
如果你对如何改进它有任何想法/建议,这里有一个反馈表格和到演示应用和 Github 的链接。
一路上的挑战
- 速度
构建这一框架的主要挑战是可解释性框架会变得计算密集型。像 SHAP 这样的软件包有 C++加速,而其他的需要更长的计算时间。一些分类器也比其他分类器花费更长的时间,数据大小加剧了这种情况,尽管人们可以首先尝试数据的样本。
最初,我将 PDPbox 中的 PDP/ICE 图作为图之一,显示每个特性如何随结果变化,但在其他计算之上呈现之前计算 20 秒的图表并不理想,所以最终我将它作为可选图表移动,可以通过选择复选框按需查看。
另一个选择是 ALEplot (最初是一个 R 包),它应该更快更好(可以处理 PDP 不能处理的相关特性),当其羽翼未丰的 python 版本稳定下来时,这可能会很方便。SHAP 也有自己版本的部分相关图,显示散点图中的每个数据点,但有时数据点只是相互遮挡(尽管其局部解释图超级棒)。
2.翻译口译员
另一个挑战是这些框架没有一个是完全不言自明的。感谢几位 ML/DS 人员的反馈,他们友好地帮助了初始测试,我在每个部分添加了操作按钮和链接来详细解释输出。
学习
- 我开始理解不同可解释性框架的复杂性。虽然重点是用树集成模型进行分类,但这些框架中的许多可以应用于更广泛的用例,包括回归、文本和图像。拥有黑盒中的可解释性可以为我们提供许多见解,但它可能不是万灵药,仍然需要谨慎使用。
- 通过这个项目,我使用了 Atair 进行绘图,并发现它的声明性语法非常简洁,这让我想起了 ggplot2 中的图形语法。现在为了保持功能简单,我没有用图表过载它。
- 使用构建和部署 ML 应用程序简化了。我最近在这里和这里写了详细的操作方法。
后续步骤
口译组
与重要特性的俯视图相比,理解单个决策点是相当大的进步。然而,更有趣和实用的是了解模型对特定群体的预测,基于输入要素或输出类别进行聚类,而交互式应用程序是实现这一点的好方法。我还没有找到一个适合解释集群的框架。这对于评估模型的公平性也有积极的意义。
反光
在学习这些框架时,我注意到它们在架构和实现上有多么不同。例如,Eli5 以(1)表格数据框格式和(2)图表两种形式提供结果,尽管它们的图表也是彩色的 HTML 表格。SHAP 的方法相当不同——它直接生成高度紧凑的可视化。但是,您可以运行一些脚本来获得数据框格式的输出。
所有这些框架都可以归结为两个部分——解释算法(T0)和视觉表现(T2)两部分。他们的大多数图表都有相同的目标,比如特性重要性或部分相关性,但他们都使用不同的图表来呈现相同的见解。对于新用户来说,在解释释义时转换齿轮并不那么容易。我开始想,将解释算法和它们的视觉表示分离开来,并拥有一种通用设计语言用于机器学习解释是否会更好,这种语言与框架无关,并且可以使交流更容易。
资源
我参考了这些文章/书籍,发现它们信息量很大:
机器学习的可解释性由 Kaggle
关于解释机器学习的想法奥赖利
机器学习可解释性介绍作者 Patrick Hall 和 Navdeep Gill 在 H2o
如果你喜欢这篇文章,请分享。建议或反馈总是受欢迎的。
理解数据中的模式
导入数据后的下一步
威廉·艾文在 Unsplash 上的照片
在我之前的博客中,我简单解释了如何清理数据、执行 EDA(探索性数据分析)以及什么是基本特征工程。
比如说,你做了一个“read_csv”并导入了数据。现在,接下来呢?接下来要谈的重要事情是,我们如何理解和分析数据集中的各种模式。这将帮助我们解决几个问题:
a. 它将帮助我们了解当前数据中有多少个缺失值/重复值(这是我们未来模型的一个问题)——在前一篇文章 中解释过
b. 确定自变量(特征/属性)和因变量(目标)?如果没有目标(在大多数真实情况下),了解如何创建一个。这将基于你正在做的问题陈述,我将在以后的博客中用一些例子来讨论。
c. 了解每个特征/属性相对于我们的目标列以及相对于彼此的总体分布。
d. 有没有可以去掉的特征因为它们最终解释的是同一个东西!(将在下一篇博客中解决这个问题)
来源:作者
数据分布分析
要了解数据分布和关系,有很多 python 库(seaborn,plotly,matplotlib,sweetviz 等。),这样会让你的工作更轻松。在此阶段需要记住的一些事情是:
- 确定你的数字和分类变量。如果您认为 zipcode 的一列是数字型的,因为它有数字,那么您可能想回头详细阅读一下分类和数字属性!出于本文的目的,分类特征是那些包含有限数量的类别的特征,而数字属性是那些包含连续实数的特征。我发现这个链接同样非常有用!这一部分看起来很小,但在机器学习建模的后期起着重要的作用。
- 一旦你成功地识别了不同类型的变量,就该看看它们的模式和分布了。对于不同类型的变量,您可能希望使用不同的图表/解释来更好地理解它们。
- 分类特征 —这里有一些有用的基本统计数据——统计每个特征中的唯一值、每个特征中的频率分布、每个类别与目标列的关系等。你可以在这里使用很多图表,我使用饼状图(用于较小的类别),条形图用于更多的类别,或者有时只是一个普通的表格来进行快速分析。这完全取决于是什么让发行版更容易让你理解!
- 数值型特征—工资、年龄、温度等数值型属性。,为他们创建一个直方图,了解他们的分布。我的首选代码行是 df[’ <列> ']。形容()。这让我可以快速分析一个属性的最小值-最大值-平均值等,而无需在编码上投入大量时间。这里有很多图表,你也可以用来分析数字特征!
- 为了分析目标列,查看数据分布—分类问题中 1&0 的条形图/饼图,或回归问题(连续数)的直方图/散点图。
需要记住的一点是,EDA(以及整个数据科学)非常直观。
你可能认为这个过程看起来像导入数据-> EDA ->特征工程->建模->结果,但实际上,这个过程是一个循环,一切都是相互连接的。
因此,即使您到达了建模阶段(在跨越了数据预处理阶段的所有障碍之后),您也必须回来分析更多的模式,创建/删除更多的特性,并再次进行建模!耐心是关键:)
有很多库提供了很多“奇特的”交互式图形,但是除了好看的图形,您应该选择一个有助于您很好地理解数据,并且易于向他人解释的图形。
现在你已经在文章末尾了,感谢你的阅读!如果有什么想让我补充/更改/地址的,请留言回复!😃
理解转置卷积
了解转置卷积的概念,从头开始构建自己的转置卷积层
生成对抗网络(GAN)是用于新数据生成的最先进的人工神经网络之一。它广泛应用于照片生成、照片编辑、人脸老化等领域。GAN 的核心是发生器和鉴别器。他们两个玩一个对抗性的游戏,在这个游戏中,生成器正在学习通过制作看起来非常像真实图像的假图像来欺骗鉴别器,而鉴别器正在学习更好地检测假图像。在理想情况下,在训练结束时,生成器可以生成看起来与真实图像完全一样的假图像,但鉴别器仍然可以发现它们是假的。
虽然卷积层在鉴别器中起着重要作用,但转置卷积层是生成器的主要构建模块。多亏了 TensorFlow API — Keras,构建 GAN 变成了一个非常方便的过程。然而,为参数设置正确的值,比如内核大小、步长和填充,需要我们理解转置卷积是如何工作的。在这本笔记本里,我想分享一些我个人对转置卷积的理解,希望能帮助你揭开其中的奥秘。在整个笔记本中,我将使用卷积作为比较,以更好地解释转置卷积。我还将向您展示我如何实现这些理解来构建我自己的卷积和转置卷积层,它们的行为就像 Keras 的 Conv2D 和 Conv2DTranspose 层的简单版本。笔记本由三部分组成:
- 什么是转置卷积?
- Keras conv 2d transpose 中有哪些参数(内核大小、步幅、填充)?
- 从头开始构建我自己的 Conv2D 和 Conv2DTranspose 图层
第一节:什么是转置卷积?
我把转置卷积理解为卷积的反义词。在卷积层中,我们使用一种称为互相关的特殊操作(在机器学习中,该操作通常被称为卷积,因此这些层被称为“卷积层”)来计算输出值。该操作将输入层中所有相邻的数字加在一起,由一个卷积矩阵(内核)加权。例如,在下图中,输出值 55 通过输入层的 3x3 部分和 3x3 内核之间的元素乘法计算得出,并将所有结果相加:
对输入图层的 3x3 部分和 3x3 内核进行一次卷积运算(图片由作者提供)
没有任何填充,该操作将 4x4 矩阵转换为 2x2 矩阵。这看起来像有人从左向右投射光线,并通过一个孔(3x3 内核)投射一个对象(4x4 矩阵),产生一个更小的对象(2x2 矩阵)。**现在,我们的问题是:如果我们想从 2x2 矩阵倒退到 4x4 矩阵呢?**好吧,直观的方法是,我们只要把光向后投!在数学上,我们可以将输入层中的每个值乘以 3x3 内核,而不是乘以两个 3x3 矩阵,从而得到一个 3x3 矩阵。然后,我们根据输入层中的初始位置将它们组合在一起,并将重叠的值相加:
将输入层中的每个元素乘以内核中的每个值
将所有四个结果层组合在一起,并将重叠值相加(图片由作者提供)
这样,总是可以确定转置卷积运算的输出可以与前一个卷积运算的输入具有完全相同的形状,因为我们只是进行了完全相反的操作。但是,您可能会注意到号码没有恢复。因此,必须使用完全不同的核来恢复初始输入矩阵,并且该核可以通过训练来确定。
为了证明我的结果不仅仅是一些随机数,我通过 Keras 使用上述条件建立了卷积神经网络。从下面的代码可以看出,输出完全相同。
为什么“换位”?
现在你可能想知道:嘿,这看起来就像一个反向卷积。为什么命名为“转置”卷积?
说实话,我也不知道自己为什么要纠结这个问题,但是我做到了。我相信它被命名为“转置”卷积是有原因的。为了回答这个问题,我看了很多关于转置卷积的网上资源。一篇名为“转置卷积上采样”的文章帮了我大忙。在这篇文章中,作者 Naoki Shibuya 使用一个零填充卷积矩阵来表示卷积运算,而不是普通的方形卷积矩阵。本质上,在执行卷积变换时,我们可以将其表示为 4x16 矩阵,而不是将上述核表示为 3x3 矩阵。除了将上述输入表示为 4x4 矩阵,我们还可以将其表示为 16x1 向量:
将 3x3 内核表示为 4x16 补零卷积矩阵(图片由作者提供)
采用 4x16 矩阵的原因是:
- 4 行:总共,我们可以通过将一个 4x4 的输入矩阵拆分成四个 3x3 的矩阵来执行四次卷积;
- 16 列:输入矩阵将被转换成 16x1 的向量。为了执行矩阵乘法,它必须是 16 列。
将 4x4 输入矩阵表示为 16x1 向量(图片由作者提供)
这样,我们可以直接执行矩阵乘法来得到一个输出层。整形后的输出图层将与通过常规卷积运算得到的图层完全相同。
4x16 卷积矩阵和 16x1 输入向量之间的矩阵乘法(图片由作者提供)
**最有趣的部分来了!**当我们执行转置卷积运算时,我们只是简单地转置补零卷积矩阵,并将其乘以输入向量(这是卷积层的输出)。在下图中,中间阶段的四个彩色向量代表矩阵乘法的中间步骤:
16x4 转置卷积矩阵和 4x1 输入向量之间的矩阵乘法(图片由作者提供)
如果我们在中间阶段重新排列四个向量,我们将获得四个 4x4 矩阵,其数量与我们通过将 3x3 内核与输入层中的每个单独元素相乘而获得的 3x3 矩阵完全相同,多余的槽用零填充。这四个矩阵还可以进一步组合,以获得最终的 4x4 输出矩阵:
将 16×1 的中间向量重新排列成 4×4 的中间矩阵(图片由作者提供)
将所有四个中间矩阵组合在一起,产生最终结果(图片由作者提供)
因此,该操作被称为“转置”卷积,因为我们执行了完全相同的操作,只是我们转置了卷积矩阵!
第二节:Keras conv 2d transpose 中的参数(内核大小、步幅、填充)有哪些?
1.内核大小
在卷积中,内核大小影响着你在输入层中“投射”多少个数字以在输出层中形成一个数字。内核越大,使用的数字就越多,因此输出层中的每个数字都是输入层的更广泛的表示,并携带来自输入层的更多信息。但同时,使用更大的内核会给你一个更小的输出。例如,具有 3x3 内核的 4x4 输入矩阵将产生 2x2 输出矩阵,而具有 2x2 内核的 4x 4 输入矩阵将产生 3x3 输出矩阵(如果没有添加填充):
(图片由作者提供)
在转置卷积中,当内核变大时,我们将输入层的每个数字“分散”到更大的区域。因此,内核越大,输出矩阵也越大(如果不添加填充):
(图片由作者提供)
2.大步
在卷积中,步长参数表示内核在输入层上沿行和列移动的速度。如果步幅为(1,1),则内核每走一步移动一行/一列;如果步幅是(2,2),则内核每走一步移动两行/列。因此,步幅越大,到达行/列末尾的速度越快,因此输出矩阵越小(如果没有添加填充)。设置更大的步幅也可以减少相同数字的重复使用。
(图片由作者提供)
在转置卷积中,步长参数表示内核在输出层上移动的速度,如下图所示。注意,内核总是在输入层一次只移动一个数字。因此,步幅越大,输出矩阵就越大(如果没有填充)。
(图片由作者提供)
3.填料
在卷积中,我们经常想要保持输入层的形状,我们通过零填充来实现。在 Keras 中,填充参数可以是两个字符串之一:“valid”或“same”。当填充为“有效”时,意味着不实现零填充。当填充“相同”时,输入层以某种方式被填充,使得输出层具有输入形状除以步幅的形状。当跨距等于 1 时,输出形状与输入形状相同。
(图片由作者提供)
在转置卷积中,填充参数也可以是两个字符串:“valid”和“same”。但是,由于我们在转置卷积中扩展输入层,如果选择“有效”,输出形状将大于输入形状。如果使用“相同”,则输出形状被强制变成输入形状乘以步幅。如果此输出形状小于原始输出形状,则仅保留输出的中间部分。
(图片由作者提供)
记住卷积和转置卷积中的“有效”和“相同”的一种更简单的方法是:
- “有效”:不执行额外的操作。输出保持其应有的状态。
- “相同”:输出形状是输入形状除以步幅(卷积)或乘以步幅(转置卷积)。当跨距等于 1 时,输出形状总是与输入形状相同。
从头开始构建我自己的 Conv2D 和 conv 2d 转置层
到目前为止,我已经解释了所有关于转置卷积层的概念及其重要参数。对你来说,它们可能仍然非常抽象,我完全理解你,因为我也很难理解转置卷积层是如何工作的。但是不要担心,现在我们可以使用我们所学的概念来构建我们自己的卷积和转置卷积层——这一定会揭示转置卷积层的奥秘!
我们先从 Conv2D 开始:
让我们来看看我自制的 Conv2D 层:
- 首先,我定义了需要添加的零填充的数量。如果填充是“有效的”,那么我不需要添加任何填充。如果填充“相同”,我将根据以下公式计算输入层每一侧的填充数:
其中:
- o 为输出大小
- s 为步长
- m 为内核大小
- n 为输入大小
- p 为原输入层每边的填充数
此公式由计算输出形状的公式导出:
输出形状为 o = n/s.
- 然后,我通过构造一个用零填充的大矩阵来填充输入,并将原始输入放在中间。
- 之后,我使用卷积运算计算输出。在核 W 和输入 X_sub 的子集(其具有与核相同的形状)之间执行卷积运算。输出索引 i,j 的范围从 0 到最后一个适合内核的索引。例如,如果 X_padded 的形状为 4x4,而内核的形状为 3x3,那么可以装入内核的最后一个索引是 1(可以从索引 1 到索引 4 装入内核)。下面是该过程的图解说明:
使用 4x4 输入矩阵和 3X3 内核计算输出。输出中每步更新的数字以粗体显示(图片由作者提供)
我还比较了使用我的 Conv2D 和 Keras Conv2D 的结果。结果是一样的!
- “有效”填充
- “相同”填充
现在让我们建立转置卷积层:
让我们反对拆分代码:
- 输出形状首先由下面的公式定义:
如果与计算 Conv2D 输出形状的公式进行比较,您会注意到在 Conv2DTranspose 中,步长和内核大小对输出形状的影响相反。
- 为了计算输出,我使用了两对指数: i,j 沿着输入移动, i_prime,j_prime 沿着输出移动。当 i,j 变化时, i_prime,j_prime 以给定步幅的步长变化。举个例子,如果 i 从 0 变到 1,strides = (2,2),那么 i_prime 从 0 变到 2。输入矩阵中的每个值乘以内核中的所有值,结果记录在输出矩阵中。
- 然后,我定义了填充的长度。同样,如果填充是“有效的”,我不需要修改任何东西。如果填充“相同”,则输出形状必须是输入形状乘以步幅。那就是:
填充必须将原始输出形状转换为所需的输出形状:
因此,设置填充值的简单方法是:
- 最后,通过只选择与输入矩阵形状相同的中间矩阵来填充输出。
计算输出过程的图解说明如下所示:
(图片由作者提供)
现在,我们可以通过将结果与 Keras 中的 Conv2DTranspose 进行比较来验证 Conv2DTranspose 函数:
- “有效”填充:
- “相同”填充:
结果完全一样!
结论
恭喜你,你成功了!如前所述,这个笔记本是基于我个人的理解,我实际上是在 GAN 和其他所有机器学习知识上自学的。因此,如果你发现任何错误,我会很高兴知道!