基于深度学习的文本分类

学习目标

PART 1

  • 学习FastText的使用和基础原理
  • 学会使用验证集进行调参

PART 2

  • 学习Word2Vec的使用和基础原理
  • 学习使用TextCNN、TextRNN进行文本表示
  • 学习使用HAN网络结构完成文本分类

PART3

  • 了解Transformer的原理和基于预训练语言模型(Bert)的词表示
  • 学会Bert的使用,具体包括pretrain和finetune

FastText

一、fastText简介

fastText是一个快速文本分类算法,与基于神经网络的分类算法相比有两大优点:
1、fastText在保持高精度的情况下加快了训练速度和测试速度
2、fastText不需要预训练好的词向量,fastText会自己训练词向量
3、fastText两个重要的优化:Hierarchical Softmax、N-gram

二、fastText模型架构

fastText模型架构是一个三层的神经网络,输入层、隐含层和输出层,和word2vec中的CBOW很相似, 不同之处是fastText预测标签,CBOW预测的是中间词,即模型架构类似但是模型的任务不同。

img

其中x~1~,x~2~,…,x~(N−1)~,x~N~表示一个文本中的n-gram向量,每个特征是词向量的平均值。

三、层次softmax

softmax函数常在神经网络输出层充当激活函数,目的就是将输出层的值归一化到0-1区间,将神经元输出构造成概率分布。

在标准的softmax中,计算一个类别的softmax概率时,我们需要对所有类别概率做归一化,在这类别很大情况下非常耗时,因此提出了分层softmax(Hierarchical Softmax),思想是根据类别的频率构造霍夫曼树来代替标准softmax,通过分层softmax可以将复杂度从N降低到logN,下图给出分层softmax示例:

img

四、N-gram特征

基本思想是将文本内容按照子节顺序进行大小为N的窗口滑动操作,最终形成窗口为N的字节片段序列。n-gram可以根据粒度不同有不同的含义,有字粒度的n-gram和词粒度的n-gram。

优点:

1、为罕见的单词生成更好的单词向量

2、在词汇单词中,即使单词没有出现在训练语料库中,仍然可以从字符级n-gram中构造单词的词向量

3、n-gram可以让模型学习到局部单词顺序的部分信息,也可理解为上下文信息,

内存优化:

1、过滤掉出现次数少的单词
2、使用hash存储
3、由采用字粒度变化为采用词粒度

基于FastText的文本分类

准备数据

所有标签__label__均以前缀开头,这是fastText识别标签或单词的方式

1
2
3
4
#转换格式
train_df = pd.read_csv('./train_set.csv', sep='\t',nrows=15000)
train_df['label_ft'] = '__label__' + train_df['label'].astype(str)
train_df[['text','label_ft']].iloc[:-5000].to_csv('train.csv', index = None,header = None, sep='\t')

分类器

1
2
3
4
5
6
7
8
9
import fasttext
model = fasttext.train_supervised('train.csv',
lr = 1.0,
dim = 300
wordNgrams = 2,
verbose = 2,
minCount = 1,
epoch = 25,
loss = 'hs')
参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
input             # training file path (required)    
lr # learning rate [0.1]
dim # size of word vectors [100]
ws # size of the context window [5]
epoch # number of epochs [5]
minCount # minimal number of word occurences [1]
minCountLabel # minimal number of label occurences [1]
minn # min length of char ngram [0]
maxn # max length of char ngram [0]
neg # number of negatives sampled [5]
wordNgrams # max length of word ngram [1]
loss # loss function {ns, hs, softmax, ova} [softmax]
bucket # number of buckets [2000000]
thread # number of threads [number of cpus]
lrUpdateRate # change the rate of updates for the learning rate [100]
t # sampling threshold [0.0001]
label # label prefix ['__label__']
verbose # verbose [2]
pretrainedVectors # pretrained word vectors (.vec file) for supervised learning []

F1

1
2
3
val_pred = [model.predict(x)[0][0].split('__')[-1] for x in train_df.iloc[-5000:]['text']]
s = f1_score(train_df['label'].values[-5000:].astype(str),val_pred,average = 'macro')
print(s)

Task4 基于深度学习的文本分类1

fastText原理和文本分类实战

Word2Vec

word2vec的主要思路:通过单词和上下文彼此预测,对应的两个算法分别为:

  • Skip-grams (SG):预测上下文
  • Continuous Bag of Words (CBOW):预测目标单词

另外提出两种更加高效的训练方法:

  • Hierarchical softmax
  • Negative sampling

image-20200731164752291

Skip-grams

对于句子:“The dog barked at the mailman”

skip_window=2 ,则,对于 barked :将会得到 [‘The’, ‘dog’,’barked’, ‘at’, ’the’] .左侧2个词和右侧2个词。

num_skips=2, 将会得到 (’barked’,’dog’),(’barken’,’at’)==>[input,out]

image-20200731171156500

Skip-grams训练

input word和output word都会被我们进行one-hot编码。为了高效计算,它仅仅会选择矩阵中对应的向量中维度值为1的索引行:

image-20200731171539993

Word pairs and “phases

将常见的单词组合(word pairs)或者词组作为单个“words”来处理

对高频次单词进行抽样来减少训练样本的个数

​ 对于“the”这种常用高频单词,我们将会有大量的(”the“,…)这样的训练样本,而这些样本数量远远超过了我们学习“the”这个词向量所需的训练样本数。

Word2Vec通过“抽样”模式来解决这种高频词问题。它的基本思想如下:对于我们在训练原始文本中遇到的每一个单词,它们都有一定概率被我们从文本中删掉,而这个被删除的概率与单词的频率有关。

ωi 是一个单词,Z(ωi) 是 ωi 这个单词在所有语料中出现的频次,例如:如果单词“peanut”在10亿规模大小的语料中出现了1000次,那么 Z(peanut) = 1000/1000000000 = 1e - 6。

P(ωi) 代表着保留某个单词的概率:$P\left(w_{i}\right)=(\sqrt{\frac{Z\left(w_{i}\right)}{0.001}}+1) \times \frac{0.001}{Z\left(w_{i}\right)}$

负采样negative sampling

对优化目标采用“negative sampling”方法,这样每个训练样本的训练只会更新一小部分的模型权重,从而降低计算负担

负采样(是用来提高训练速度并且改善所得到词向量的质量的一种方法。不同于原本每个训练样本更新所有的权重,负采样每次让一个训练样本仅仅更新一小部分的权重,这样就会降低梯度下降过程中的计算量。

当我们用训练样本 ( input word: “fox”,output word: “quick”) 来训练我们的神经网络时,“ fox”和“quick”都是经过one-hot编码的。如果我们的词典大小为10000时,在输出层,我们期望对应“quick”单词的那个神经元结点输出1,其余9999个都应该输出0。在这里,这9999个我们期望输出为0的神经元结点所对应的单词我们称为“negative” word。

当使用负采样时,我们将随机选择一小部分的negative words(比如选5个negative words)来更新对应的权重。我们也会对我们的“positive” word进行权重更新(在我们上面的例子中,这个单词指的是”quick“)。

对于小规模数据集,选择5-20个negative words会比较好,对于大规模数据集可以仅选择2-5个negative words。

我们使用“一元模型分布(unigram distribution)”来选择“negative words”。个单词被选作negative sample的概率跟它出现的频次有关,出现频次越高的单词越容易被选作negative words。

一个单词的负采样概率越大,那么它在这个表中出现的次数就越多,它被选中的概率就越大。

每个单词被选为“negative words”的概率计算公式:$P\left(w_{i}\right)=\frac{f\left(w_{i}\right)^{3 / 4}}{\sum_{j=0}^{n}\left(f\left(w_{j}\right)^{3 / 4}\right)}$

Hierarchical Softmax

为了避免要计算所有词的softmax概率,word2vec采样了霍夫曼树来代替从隐藏层到输出softmax层的映射。

霍夫曼树的建立:

  • 根据标签(label)和频率建立霍夫曼树(label出现的频率越高,Huffman树的路径越短)
  • Huffman树中每一叶子结点代表一个label

image-20200731214743683

image-20200731214814429

使用gensim训练word2vec

1
2
from gensim.models.word2vec import Word2Vec
model = Word2Vec(sentences, workers=num_workers, size=num_features)

TextCNN

采用了100个大小为2,3,4的卷积核,最后得到的文本向量大小为100*3=300维。

image-20200731220108081

TextRNN

TextRNN将句子中每个词的词向量依次输入到双向双层LSTM,分别将两个方向最后一个有效位置的隐藏层拼接成一个向量作为文本的表示。

image-20200731220217036

基于TextCNN、TextRNN的文本表示

TextCNN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
self.filter_sizes = [2, 3, 4]  # n-gram window
self.out_channel = 100
self.convs = nn.ModuleList([nn.Conv2d(1, self.out_channel, (filter_size, input_size), bias=True) for filter_size in self.filter_sizes])

pooled_outputs = []
for i in range(len(self.filter_sizes)):
filter_height = sent_len - self.filter_sizes[i] + 1
conv = self.convs[i](batch_embed)
hidden = F.relu(conv) # sen_num x out_channel x filter_height x 1

mp = nn.MaxPool2d((filter_height, 1)) # (filter_height, filter_width)
# sen_num x out_channel x 1 x 1 -> sen_num x out_channel
pooled = mp(hidden).reshape(sen_num, self.out_channel)

pooled_outputs.append(pooled)

TextRNN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
input_size = config.word_dims

self.word_lstm = LSTM(
input_size=input_size,
hidden_size=config.word_hidden_size,
num_layers=config.word_num_layers,
batch_first=True,
bidirectional=True,
dropout_in=config.dropout_input,
dropout_out=config.dropout_hidden,
)

hiddens, _ = self.word_lstm(batch_embed, batch_masks) # sent_len x sen_num x hidden*2
hiddens.transpose_(1, 0) # sen_num x sent_len x hidden*2

if self.training:
hiddens = drop_sequence_sharedmask(hiddens, self.dropout_mlp)

使用HAN用于文本分类

Hierarchical Attention Network for Document Classification(HAN)基于层级注意力,在单词和句子级别分别编码并基于注意力获得文档的表示,然后经过Softmax进行分类。其中word encoder的作用是获得句子的表示.

image-20200803134803347

本章作业

  • 尝试通过Word2Vec训练词向量
  • 尝试使用TextCNN、TextRNN完成文本表示
  • 尝试使用HAN进行文本分类

Transformer

编码部分:结构完全相同,但是并不共享参数,每一个编码器都可以拆解成两部分。在对输入序列做词的向量化之后,它们首先流过一个self-attention层,该层帮助编码器在它编码单词的时候能够看到输入序列中的其他单词。self-attention的输出流向一个前向网络(Feed Forward Neural Network),每个输入位置对应的前向网络是独立互不干扰的。最后将输出传入下一个编码器。

关键特性:每个位置的词仅仅流过它自己的编码器路径。

基于预训练语言模型的词表示

传统方法生成的单词映射表的形式,即先为每个单词生成一个静态的词向量,之后这个单词的表示就被固定住了,不会跟着上下文的变化而做出改变。

基于预训练语言模型的词表示可以建模上下文信息,解决传统静态词向量不能建模“一词多义”语言现象的问题

最早提出的ELMo基于两个单向LSTM,将从左到右和从右到左两个方向的隐藏层向量表示拼接学习上下文词嵌入。而GPT用Transformer代替LSTM作为编码器,首先进行了语言模型预训练,然后在下游任务微调模型参数。但GPT由于仅使用了单向语言模型,因此难以建模上下文信息。为了解决以上问题,研究者们提出了BERT,BERT模型结构如下图所示,它是一个基于Transformer的多层Encoder,通过执行一系列预训练,进而得到深层的上下文表示。

ELMo首先进行了语言模型预训练,然后在下游任务中动态调整Word Embedding,因此最后输出的词表示能够充分表达单词在上下文中的特定语义,进而解决一词多义的问题。

GPT来自于openai,是一种生成式预训练模型。GPT 除了将ELMo中的LSTM替换为Transformer 的Encoder外,更开创了NLP界基于预训练-微调的新范式。尽管GPT采用的也是和ELMo相同的两阶段模式,但GPT在第一个阶段并没有采取ELMo中使用两个单向双层LSTM拼接的结构,而是采用基于自回归式的单向语言模型。

与GPT相同,BERT也采用了预训练-微调这一两阶段模式。在模型结构方面,BERT采用了ELMO的方式式,即使用双向语言模型代替GPT中的单向语言模型。

第一阶段的预训练过程中,BERT提出掩码语言模型,通过上下文来预测单词本身,而不是从右到左或从左到右建模,这允许模型能够自由地编码每个层中来自两个方向的信息;为了学习句子的词序关系,BERT将Transformer中的三角函数位置表示替换为可学习的参数;为了区别单句和双句输入,BERT还引入了句子类型表征。

第二阶段,与GPT相同,BERT也使用Fine-Tuning模式来微调下游任务。并且极大的减少了改造下游任务的要求,只需在BERT模型的基础上,通过额外添加Linear分类器,就可以完成下游任务。

image-20200803140309123

基于Bert的文本分类

Bert Pretrain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class WhitespaceTokenizer(object):
"""WhitespaceTokenizer with vocab."""
def __init__(self, vocab_file):
self.vocab = load_vocab(vocab_file)
self.inv_vocab = {v: k for k, v in self.vocab.items()}

def tokenize(self, text):
split_tokens = whitespace_tokenize(text)
output_tokens = []
for token in split_tokens:
if token in self.vocab:
output_tokens.append(token)
else:
output_tokens.append("[UNK]")
return output_tokens

def convert_tokens_to_ids(self, tokens):
return convert_by_vocab(self.vocab, tokens)

def convert_ids_to_tokens(self, ids):
return convert_by_vocab(self.inv_vocab, ids)

预训练由于去除了NSP预训练任务,因此将文档处理多个最大长度为256的段,如果最后一个段的长度小于256/2则丢弃。每一个段执行按照BERT原文中执行掩码语言模型,然后处理成tfrecord格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def create_segments_from_document(document, max_segment_length):
"""Split single document to segments according to max_segment_length."""
assert len(document) == 1
document = document[0]
document_len = len(document)

index = list(range(0, document_len, max_segment_length))
other_len = document_len % max_segment_length
if other_len > max_segment_length / 2:
index.append(document_len)

segments = []
for i in range(len(index) - 1):
segment = document[index[i]: index[i+1]]
segments.append(segment)

return segments

预训练过程中,也只执行掩码语言模型任务

1
2
3
4
5
(masked_lm_loss, masked_lm_example_loss, masked_lm_log_probs) = get_masked_lm_output(
bert_config, model.get_sequence_output(), model.get_embedding_table(),
masked_lm_positions, masked_lm_ids, masked_lm_weights)

total_loss = masked_lm_loss

为了适配句子的长度,以及减小模型的训练时间,采取了BERT-mini模型

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"hidden_size": 256,
"hidden_act": "gelu",
"initializer_range": 0.02,
"vocab_size": 5981,
"hidden_dropout_prob": 0.1,
"num_attention_heads": 4,
"type_vocab_size": 2,
"max_position_embeddings": 256,
"num_hidden_layers": 4,
"intermediate_size": 1024,
"attention_probs_dropout_prob": 0.1
}

使用Pytorch

1
2
3
4
5
6
7
8
9
10
11
12
def convert_tf_checkpoint_to_pytorch(tf_checkpoint_path, bert_config_file, pytorch_dump_path):
# Initialise PyTorch model
config = BertConfig.from_json_file(bert_config_file)
print("Building PyTorch model from configuration: {}".format(str(config)))
model = BertForPreTraining(config)

# Load weights from tf checkpoint
load_tf_weights_in_bert(model, config, tf_checkpoint_path)

# Save pytorch-model
print("Save PyTorch model to {}".format(pytorch_dump_path))
torch.save(model.state_dict(), pytorch_dump_path)

Bert Finetune

微调将最后一层的第一个token即[CLS]的隐藏向量作为句子的表示,然后输入到softmax层进行分类。

1
2
3
4
5
6
7
8
9
10
sequence_output, pooled_output = \
self.bert(input_ids=input_ids, token_type_ids=token_type_ids)

if self.pooled:
reps = pooled_output
else:
reps = sequence_output[:, 0, :] # sen_num x 256

if self.training:
reps = self.dropout(reps)

本章作业

  • 完成Bert Pretrain和Finetune的过程
  • 阅读Bert官方文档,找到相关参数进行调参

基于机器学习的文本分类

学习目标

  • 学会TF-IDF的原理和使用
  • 使用sklearn的机器学习模型完成文本分类

机器学习模型

文本表示方法

One-hot
1
2
3
4
5
6
7
8
9
10
11
句子1:我 爱 北 京 天 安 门
句子2:我 喜 欢 上 海

{
'我': 1, '爱': 2, '北': 3, '京': 4, '天': 5, '安': 6, '门': 7, '喜': 8, '欢': 9, '上': 10, '海': 11
}

我:[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
爱:[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
...
海:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
Bag of Words

每个文档的字/词可以使用其出现次数来进行表示。

1
2
3
4
5
句子1:我 爱 北 京 天 安 门
转换为 [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]

句子2:我 喜 欢 上 海
转换为 [1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
1
2
3
4
5
6
7
8
9
10
from sklearn.feature_extraction.text import CountVectorizer
corpus = [
'我爱北京天安门',
'我爱上海的门',
]
vectorizer = CountVectorizer(analyzer ='char')
count = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names())
print(vectorizer.vocabulary_)
print(count.toarray())

image-20200725173247152

N-gram

相邻单词组合成为新的单词

1
2
3
# N = 2
句子1:我爱 爱北 北京 京天 天安 安门
句子2:我喜 喜欢 欢上 上海
1
2
3
4
5
6
7
8
9
from sklearn.feature_extraction.text import CountVectorizer
data = ['我爱北京天安门',
'我喜欢上海']
vec = CountVectorizer(analyzer ='char',min_df=1, ngram_range=(2,2))
X = vec.fit_transform(data) # transform text to metrix
vec.get_feature_names() # get features
X.toarray()
df = pd.DataFrame(X.toarray(), columns=vec.get_feature_names()) # to DataFrame
df.head()

image-20200725172538480

TF-IDF

TF:词语频率。该词语在当前文档出现的次数 / 当前文档中词语的总数

IDF:逆文档频率:语料库中文档总数除以含有该词语的文档数量,然后再取对数。log_e(文档总数 / 出现该词语的文档总数)

1
2
3
4
5
6
7
8
9
10
from sklearn.feature_extraction.text import TfidfVectorizer, TfidfTransformer
corpus = [
'我爱北京天安门',
'我喜欢上海',
]
tfidf_vec = TfidfVectorizer(analyzer ='char')
tfidf_matrix = tfidf_vec.fit_transform(corpus)
#print(tfidf_vec.get_feature_names())
#print(tfidf_vec.vocabulary_)
print(tfidf_matrix.toarray())

image-20200725173908953

文本分类

RidgeClassifier——岭回归分类器

1
2
3
4
5
6
7
8
9
10
sklearn.linear_model.RidgeClassifier(
alpha=1.0, # 正则化强度;必须是正的浮点数。
fit_intercept=True, # 计算截距
normalize=False, # 当fit_intercept被设置为False时,将忽略该参数。若为真,则回归前将对回归数X进行归一化处理,方法是减去均值再除以l2-范数。
copy_X=True, #
max_iter=None, # 共轭梯度求解器的最大迭代次数
tol=0.001, # 求解的精度
class_weight=None,
solver='auto', # “auto”:根据数据类型自动选择求解器。
random_state=None)

Count Vectors + RidgeClassifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import RidgeClassifier
from sklearn.metrics import f1_score

train_df = pd.read_csv('../input/train_set.csv', sep='\t', nrows=15000)

vectorizer = CountVectorizer(max_features=3000)
train_test = vectorizer.fit_transform(train_df['text'])

clf = RidgeClassifier()
clf.fit(train_test[:10000], train_df['label'].values[:10000])

val_pred = clf.predict(train_test[10000:])
print(f1_score(train_df['label'].values[10000:], val_pred, average='macro'))
# 0.74

TF-IDF + RidgeClassifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pandas as pd

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import RidgeClassifier
from sklearn.metrics import f1_score

train_df = pd.read_csv('../input/train_set.csv', sep='\t', nrows=15000)

tfidf = TfidfVectorizer(ngram_range=(1,3), max_features=3000)
train_test = tfidf.fit_transform(train_df['text'])

clf = RidgeClassifier()
clf.fit(train_test[:10000], train_df['label'].values[:10000])

val_pred = clf.predict(train_test[10000:])
print(f1_score(train_df['label'].values[10000:], val_pred, average='macro'))

类与对象

类主要定义对象的结构,类不但包含方法定义,而且还包含所有实例共享的数据。

对象是类的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Exp: #类名大写
#属性
color = 'green'
weight = 100
legs = 4

#方法
def climb(self):
print('爬行')

def run(self):
print("跑步")

e = Exp()
e.climb()
e.run()

多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Live:
def run(self):
raise AttributeRrror('子类必须实现这个方法')

class Pepole(Live):
def run(self):
print("人再走")

class Pig(LIve):
def run(self):
print('猪在跑')

def func(live):
live.run()

func(Pig())

self

Python 的 self 相当于 C++ 的 this 指针。

类的方法与普通的函数只有一个特别的区别 —— 它们必须有一个额外的第一个参数名称(对应于该实例,即该对象本身)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Ball:
def setName(self, name):
self.name = name

def kick(self):
print("我叫%s,该死的,有人在踢我..." % self.name)


a = Ball()
a.setName("球A")
b = Ball()
b.setName("球B")

a.kick()
# 我叫球A,该死的,谁踢我...
b.kick()
# 我叫球B,该死的,谁踢我...

__init__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Ball:
def __init__(self, name):
self.name = name

def kick(self):
print("我叫%s,该死的,谁踢我..." % self.name)


a = Ball("球A")
b = Ball("球B")
c = Ball("球C")
a.kick()
# 我叫球A,该死的,谁踢我...
b.kick()
# 我叫球B,该死的,谁踢我...

公有和私有

1
在 Python 中定义私有变量只需要在变量名或函数名前加上“__”两个下划线,那么这个函数或变量就会为私有的了。

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class people:
# 定义基本属性
name = ''
age = 0
# 定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0

# 定义构造方法
def __init__(self, n, a, w):
self.name = n
self.age = a
self.__weight = w

def speak(self):
print("%s 说: 我 %d 岁。" % (self.name, self.age))


# 单继承示例
class student(people):
grade = ''

def __init__(self, n, a, w, g):
# 调用父类的构函
people.__init__(self, n, a, w)
self.grade = g

# 覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级" % (self.name, self.age, self.grade))


s = student('小马的程序人生', 10, 60, 3)
s.speak()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import random

class Fish:
def __init__(self):
self.x = random.randint(0, 10)
self.y = random.randint(0, 10)

def move(self):
self.x -= 1
print("我的位置", self.x, self.y)


class GoldFish(Fish): # 金鱼
pass

class Shark(Fish): # 鲨鱼
def __init__(self):
self.hungry = True

def eat(self):
if self.hungry:
print("吃货的梦想就是天天有得吃!")
self.hungry = False
else:
print("太撑了,吃不下了!")
self.hungry = True

g = GoldFish()
g.move() # 我的位置 9 4
s = Shark()
s.eat() # 吃货的梦想就是天天有得吃!
s.move()
# AttributeError: 'Shark' object has no attribute 'x'

# 解决该问题可用以下两种方式:

# 一、调用未绑定的父类方法Fish.__init__(self)
class Shark(Fish): # 鲨鱼
def __init__(self):
Fish.__init__(self)
self.hungry = True

# 二 使用super函数super().__init__()
class Shark(Fish): # 鲨鱼
def __init__(self):
super().__init__()
self.hungry = True

组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Turtle:
def __init__(self, x):
self.num = x


class Fish:
def __init__(self, x):
self.num = x


class Pool:
def __init__(self, x, y):
self.turtle = Turtle(x)
self.fish = Fish(y)

def print_num(self):
print("水池里面有乌龟%s只,小鱼%s条" % (self.turtle.num, self.fish.num))


p = Pool(2, 3)
p.print_num()
# 水池里面有乌龟2只,小鱼3条

Taks6 函数与Lambda表达式

函数

1
2
def function(arg1,arg2=V,*args,**kw,*, nkw,):

arg1 - 位置参数 ,这些参数在调用函数 (call function) 时位置要固定。
arg2 = v - 默认参数 = 默认值,默认参数一定要放在位置参数 后面
args - 可变参数,可以是从零个到任意个,会存放所有未命名的变量参数。自动组装成元组(tuple)。
**kw - 关键字参数,可以是从零个到任意个,自动组装成字典。 (dict)
, nkw - 命名关键字参数,用户想要输入的关键字参数,定义方式是在nkw 前面加个分隔符 *。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#### 闭包

如果在一个内部函数里对外层非全局作用域的变量进行引用,那么内部函数就被认为是闭包。

```python
def funX(x):
def funY(y):
return x * y

return funY


i = funX(8)
print(type(i)) # <class 'function'>
print(i(5)) # 40

递归

n! = 1 x 2 x 3 x ... x n

1
2
3
4
5
6
7
8
9
10
# 利用递归
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)

# 利用循环
n = 5
for k in range(1, 5):
n = n * k

斐波那契数列 f(n)=f(n-1)+f(n-2), f(0)=0 f(1)=1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 利用递归
def recur_fibo(n):
if n <= 1:
return n
return recur_fibo(n - 1) + recur_fibo(n - 2)


lst = list()
for k in range(11):
lst.append(recur_fibo(k))


# 利用循环
i = 0
j = 1
lst = list([i, j])
for k in range(2, 11):
k = i + j
lst.append(k)
i = j
j = k
print(lst)

2. Lambda 表达式

1
lambda argument_list: expression

匿名函数 常常应用于函数式编程的高阶函数 (high-order function)中,主要有两种形式:

  • 参数是函数 (filter, map)
  • 返回值是函数 (closure)

filter(function, iterable) 过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。

1
2
3
odd = lambda x: x % 2 == 1
templist = filter(odd, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(templist)) # [1, 3, 5, 7, 9]

map(function, *iterables) 根据提供的函数对指定序列做映射。

1
2
3
4
5
m1 = map(lambda x: x ** 2, [1, 2, 3, 4, 5])
print(list(m1))

m2 = map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
print(list(m2))

练习

  • 给函数参数和返回值注解?

    1
    def a(x: int, y: int) -> int:
  • 分别根据每一行的首元素和尾元素大小对二维列表 a = [[6, 5], [3, 7], [2, 8]] 排序。(利用lambda表达式)

    1
    2
    3
    a= [[6, 5], [3, 7], [2, 8]]
    b=sorted(a,key=(lambda x:x[0]))
    c=sorted(a,key=(lambda x:x[1]))
  • 利用python解决汉诺塔

    image-20200803083445044

    规律:

    每次都是以 a 或 b 中一根柱子为缓冲

    先将除了最下面的圆盘之外的其它圆盘移动到辅助柱子

    再将最底下的圆盘移到 目标柱子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def hanoi(n, a, b, c):
    # a 上只有一个盘子(肯定是最大的),直接搬到 c
    if n == 1:
    i += 1
    print('移动第 {0} 次 {1} --> {2}'.format(i, a, c))
    return
    else:
    #
    hanoi(n - 1, a, c, b)
    hanoi(1,a, b, c)
    hanoi(n - 1, b, a, c)

    hanoi(64, 'A', 'B', 'C')

Task5 字典 集合 序列

字典

{(key:value)(key:value)(key:value)}

字典以”关键字”为索引,关键字可以是任意不可变类型,通常用字符串或数值。

快速判断数据类型 X 是不是可变类型:

hash(X),只要不报错,证明 X 可被哈希,即不可变。

方法

  • dict.fromkeys(seq[, value]) 用于创建一个新字典,以序列 seq 中元素做字典的键,value 为字典所有键对应的初始值。
  • dict.keys()返回一个可迭代对象,可以使用 list() 来转换为列表,列表为字典中的所有键。
  • dict.values()返回一个迭代器,可以使用 list() 来转换为列表,列表为字典中的所有值。
  • dict.items()以列表返回可遍历的 (键, 值) 元组数组。
  • dict.get(key, default=None) 返回指定键的值,如果值不在字典中返回默认值。
  • dict.setdefault(key, default=None)get()方法 类似, 如果键不存在于字典中,将会添加键并将值设为默认值。
  • key in dict in 操作符用于判断键是否存在于字典中,如果键在字典 dict 里返回true,否则返回false。而not in操作符刚好相反,如果键在字典 dict 里返回false,否则返回true
  • dict.pop(key[,default])删除字典给定键 key 所对应的值,返回值为被删除的值。key 值必须给出。若key不存在,则返回 default 值。
  • del dict[key] 删除字典给定键 key 所对应的值
  • dict.popitem()随机返回并删除字典中的一对键和值
  • dict.clear()用于删除字典内所有元素。
  • dict.update(dict2)把字典参数 dict2key:value对 更新到字典 dict 里。

练习一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
dic = {
'python': 95,
'java': 99,
'c': 100
}
print('字典长度为:',len(dic))
# 字典长度为: 3
dic['java'] = 98
print(dic)
# {'python': 95, 'java': 98, 'c': 100}
del dic['c']
print(dic)
# {'python': 95, 'java': 98}
dic['php'] = 90
print(dic)
# {'python': 95, 'java': 99, 'c': 100, 'php': 90}
key_list = list(dic.keys())
print(key_list)
# ['python', 'java', 'c', 'php']
value_list = list(dic.values())
print(value_list)
# [95, 99, 100, 90]
print('javascript' in dic)
# False
value_list = list(dic.values())
print(sum(value_list))

for key,value in dic.items():
if(value == max(dic.values())):
print(key,value)

for key,value in dic.items():
if(value == min(dic.values())):
print(key,value)
dic1 = {'php': 97}
dic.update(dic1)
print(dic)

2、字典中的value

有一个字典,保存的是学生各个编程语言的成绩,内容如下

1
2
3
4
5
data = {
'python': {'上学期': '90', '下学期': '95'},
'c++': ['95', '96', '97'],
'java': [{'月考':'90', '期中考试': '94', '期末考试': '98'}]
}

请实现函数transfer_score(score_dict),将分数修改成int类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def transfer_score(data):
# your code here
for value in data.values():
if isinstance(value,dict):
for k,v in value.items():
value[k] = int(v)

if isinstance(value,list):
for l in value:
if isinstance(l,str):
value[value.index(l)] = int(l)

if isinstance(l,dict):
for k,v in l.items():
l[k] = int(v)

print(data)

集合 set

使用s = set()创建集合,s = {}创建空字典。

  • 重复元素在set中会被自动被过滤。

  • set(value)列表或元组转换成集合

  • 集合的两个特点:无序 (unordered) 和唯一 (unique)。

  • set.add(elmnt):添加元素

  • `set.update(set):添加新的元素或集合

  • set.remove(item) :移除存在的元素

  • set.discard(value) 移除元素,不存在也可以

  • set.pop() 用于随机移除一个元素。

  • 交集

    1
    2
    3
    set.intersection(set1, set2) 
    set1 & set2
    set.intersection_update(set1, set2) #在原始的集合上移除不重叠的元素
  • 并集

    1
    2
    set.union(set1, set2)
    set1 | set2
  • 差集

    1
    2
    3
    set.difference(set) 
    set1 - set2
    set.difference_update(set) # 直接在原来的集合中移除元素,没有返回值
  • 异或

    1
    2
    3
    set.symmetric_difference(set)
    set1 ^ set2
    set.symmetric_difference_update(set) # 移除当前集合中在另外一个指定集合相同的元素,并将另外一个指定集合中不同的元素插入到当前集合中
  • 包含、被包含

    1
    2
    3
    set1 <= set2   /  set.issubset(set)

    set1 >= set2 / set.issuperset(set)
  • set.isdisjoint(set) 用于判断两个集合是否 不相交

练习二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
set={1}
print(set,type(set))
# {1} <class 'set'>

new_set = set()
for i in {'x','y','z'}:
new_set.add(i)
print(new_set)

set(['A', 'B', 'A', 'B'])

set1 = {6, 7, 8}
set2 = {7, 8, 9}
set1.difference(set2) | set2.difference(set1)

for i in {'A', 'B', 'C'}:
print(i,'出现次数:',int(i in {'B', 'C', 'D'}))
# A 出现次数: 0
# C 出现次数: 1
# B 出现次数: 1

序列(sub)

  • list(sub)tuple(sub)str(obj)len(s)max(sub)min(sub)sum(iterable[, start=0])sorted(iterable, key=None, reverse=False)reversed(seq)

  • enumerate(sequence, [start=0])用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    seasons = ['Spring', 'Summer', 'Fall', 'Winter']
    a = list(enumerate(seasons))
    print(a)
    # [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]

    for i, element in a:
    print('{0},{1}'.format(i, element))
    # 0,Spring
    # 1,Summer
    # 2,Fall
    # 3,Winter
  • zip(iter1 [,iter2 [...]])

    • 用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象
    • 利用 * 号操作符,可以将元组解压为列表。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      a = [1, 2, 3]
      b = [4, 5, 6]
      c = [4, 5, 6, 7, 8]

      zipped = zip(a, b)
      print(zipped) # <zip object at 0x000000C5D89EDD88>
      print(list(zipped)) # [(1, 4), (2, 5), (3, 6)]
      zipped = zip(a, c)
      print(list(zipped)) # [(1, 4), (2, 5), (3, 6)]

      a1, a2 = zip(*zip(a, b))
      print(list(a1)) # [1, 2, 3]
      print(list(a2)) # [4, 5, 6]

练习三

  1. sort() 和 sorted() 区别

    (1)sorted(iterable, key=None, reverse=False) 对所有可迭代的对象进行排序操作。

    iterable – 可迭代对象。

    key – 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。

    reverse – 排序规则,reverse = True 降序 , reverse = False 升序(默认)。
    返回重新排序的列表

    (2)sort()是列表类型的方法,只适用于列表;

    (3)sorted()是内置函数,支持各种容器类型。它们都可以排序,且用法类似,但sort()是在原地排序的,不会返回排序后的列表,而sorted()是返回新的排序列表

1
2
3
4
5
6
7
8
9
# 1 到 100 所有整数相加
sum(range(1,101))
# [2,3,4,5] 中每个元素的立方根
for i in [2,3,4,5]:
print(i,'的立方根是:',i**3)
# 将[‘x’,‘y’,‘z’] 和 [1,2,3] 转成 [(‘x’,1),(‘y’,2),(‘z’,3)] 的形式
list1 = ['x','y','z']
list2 = [1,2,3]
print([*zip(list1,list2)])

Task4 List Tuple String

List

定义

1
2
3
list = [ , , ]
' ' 中可以是任意类型的 Python 对象
列表内容可更改 (mutable)、附加 (append, extend)、插入 (insert)、删除 (remove, pop)

创建

  • 直接创建:list = [‘x’,’y’,’z’]list = [[1,2],[3,4]]

  • range()创建列表:x = list(range(10))x = [[0] * 3 for row in range(4)]

  • 推导式创建列表:

    1
    2
    3
    4
    5
    x = [0] * 5   # [0, 0, 0, 0, 0] 

    x = [i for i in range(5)] # [0, 1, 2, 3, 4]

    x = [[0 for col in range(3)] for row in range(4)] # 4×3的二维数组

添加

  • list.append(obj):把一个东西整体添加在列表后
  • list.extend(obj):把一个东西里的所有元素添加在列表后
  • list.insert(index,obj):在编号 index 位置插入 obj

删除

  • lsit.remove(obj):移除列表中的第一个匹配项
  • list.pop([index=-1]) :移除列表中的一个元素(默认最后一个元素),并且返回该元素的值
  • del var1[, var2 ……] :删除单个或多个对象

获取

  • 列表索引值是从0开始,索引-1返回最后一个列表元素,索引 -2 返回倒数第二个列表元素
切片写法是 start : stop : step
  • start

    1
    2
    3
    x = ['0, '1', '2', '3', '4']
    print(x[3:]) # ['3', '4']
    print(x[-3:]) # ['2', '3', '4']
  • stop

    1
    2
    3
    week = ['0', '1', '2', '3', '4']
    print(week[:3]) # ['0', '1', '2']
    print(week[:-3]) # ['0', '1']
  • start : stop

    1
    2
    3
    x = ['0, '1', '2', '3', '4']
    print(x[1:3]) # ['1', '2']
    print(x[-3:-1]) # ['2', '3']
  • start : stop : step

    1
    2
    3
    4
    5
    x = ['0, '1', '2', '3', '4']
    print(x[1:4:2]) # ['1', '3']
    print(x[:4:2]) # ['0', '2']
    print(x[1::2]) # ['1', '3']
    print(x[::-1]) #['4', '3', '2', '1', '0']

其它方法

  • list.count(obj) 统计某个元素在列表中出现的次数

  • list.index(x[, start[, end]]) 从列表中找出某个值第一个匹配项的索引位置

  • list.reverse() 反向列表中元素

  • list.sort(key=None, reverse=False) 对原列表进行排序。

    1
    2
    3
    4
    5
    6
    7
    8
    x = [(2, 2), (3, 4), (4, 1), (1, 3)]
    x.sort(key=takeSecond)
    print(x)
    # [(4, 1), (2, 2), (1, 3), (3, 4)]

    x.sort(key=lambda a: a[0])
    print(x)
    # [(1, 3), (2, 2), (3, 4), (4, 1)]

作业

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    l = [2, 5, 6, 7, 8, 9, 2, 9, 9]

    l.append(15) #在列表的末尾增加元素15
    l.insert(len(l)//2,20) #在列表的中间位置插入元素20
    l = l + [2,5,6] #将列表[2, 5, 6]合并到lst中
    l.pop(3) #移除列表中索引为3的元素
    l.reverse() #翻转列表里的所有元素
    l.sort() #排序,从小到大一次,从大到小一次
    l.sort(reverse = True)
  2. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    lst = [1, [4, 6], True]
    #将列表里所有数字修改成原来的两倍
    def double_list (l):
    for i in range(len(l)):
    if type(l[i]) is int:
    l[i]=2*l[i]
    elif type(l[i]) is list:
    l[i]=double_list(l[i])
    return l
    a=double_list(lst)
    print(a)
  3. leetcode 852题 山脉数组的峰顶索引

    • 数组的长度大于等于3
    • $\mathrm{k}[0]<\mathrm{k}[1]<\ldots<\mathrm{k}[\mathrm{i}-1]<\mathrm{k}[\mathrm{j}]>\mathrm{k}[\mathrm{i}+1] \ldots>\mathrm{k}[\operatorname{len}(\mathrm{k})-1]$
    1
    2
    3
    4
    5
    class Solution:
    def peakIndexInMountainArray(self, A: List[int]) -> int:
    for i in range(1,len(A)):
    if A[i-1]<A[i]>A[i+1]:
    return i

Tuple

定义:(元素1, 元素2, ..., 元素n),被创建后就不能对其进行修改

更新和删除一个元组

1
2
3
4
5
6
7
8
week = ('Monday', 'Tuesday', 'Thursday', 'Friday')
week = week[:2] + ('Wednesday',) + week[2:]
print(week) # ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday')

# 无法通过赋值语句更改元组内的元素。但倘若元组内的元素是可更改的(如列表),那就可以更改元组中的元素。
t1 = (1, 2, 3, [4, 5, 6])
t1[3][0] = 9
print(t1) # (1, 2, 3, [9, 5, 6])

内置方法

只有 countindex 两种方法。

count记录在元组 t 中该元素出现几次,index 找到在元组 t 的索引

解压元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
t = (1, 10.31, 'python')
(a, b, c) = t

t = (1, 10.31, ('OK', 'python'))
(a, b, (c, d)) = t

t = 1, 2, 3, 4, 5
a, b, *rest, c = t
print(a, b, c) # 1 2 5
print(rest) # [3, 4]

t = 1, 2, 3, 4, 5
a, b, *_ = t
print(a, b) # 1 2

==String==

常用内置方法

  • capitalize() 将字符串的第一个字符转换为大写。

  • lower() 转换字符串中所有大写字符为小写。

  • upper() 转换字符串中的小写字母为大写。

  • swapcase() 将字符串中大写转换为小写,小写转换为大写。

  • count(str, beg= 0,end=len(string))

  • endswith(suffix, beg=0, end=len(string)) 检查字符串是否以指定子字符串结束,True、FALSE

  • startswith(substr, beg=0,end=len(string)) 检查字符串是否以指定子字符串开始,True、FALSE

  • find(str, beg=0, end=len(string)) 检测 str 是否包含在字符串中,包含,返回开始的索引值,否则返回 -1。

  • isnumeric() 如果字符串中只包含数字字符,则返回 True,否则返回 False。

  • ljust(width[, fillchar])返回一个原字符串左对齐,并使用fillchar(默认空格)填充至长度width的新字符串。

  • rjust(width[, fillchar])返回一个原字符串右对齐,并使用fillchar(默认空格)填充至长度width的新字符串。

    1
    2
    3
    str4 = '1101'
    print(str4.ljust(8, '0')) # 11010000
    print(str4.rjust(8, '0')) # 00001101
  • lstrip([chars]) 截掉字符串左边的空格或指定字符。

  • rstrip([chars]) 删除字符串末尾的空格或指定字符。

  • ==strip([chars]) 在字符串上执行lstrip()rstrip()。==

  • partition(sub) 找到子字符串sub,把字符串分为一个三元组(pre_sub,sub,fol_sub),如果字符串中不包含sub则返回('原字符串','','')

    1
    2
    3
    4
    str5 = ' I Love LsgoGroup '
    print(str5.strip().partition('o')) # ('I L', 'o', 've LsgoGroup')
    print(str5.strip().partition('m')) # ('I Love LsgoGroup', '', '')
    print(str5.strip().rpartition('o')) # ('I Love LsgoGr', 'o', 'up')
  • ==replace(old, new [, max]) 把 将字符串中的old替换成new,如果max指定,则替换不超过max次。==

  • ==split(str="", num) 不带参数默认是以空格为分隔符切片字符串,如果num参数有设置,则仅分隔num个子字符串,返回切片后的子字符串拼接的列表。==

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    str5 = ' I Love LsgoGroup '
    print(str5.strip().split()) # ['I', 'Love', 'LsgoGroup']
    print(str5.strip().split('o')) # ['I L', 've Lsg', 'Gr', 'up']

    u = "www.baidu.com.cn"
    # 使用默认分隔符
    print(u.split()) # ['www.baidu.com.cn']

    # 以"."为分隔符
    print((u.split('.'))) # ['www', 'baidu', 'com', 'cn']

    # 分割0次
    print((u.split(".", 0))) # ['www.baidu.com.cn']

    # 分割一次
    print((u.split(".", 1))) # ['www', 'baidu.com.cn']

    # 分割两次
    print(u.split(".", 2)) # ['www', 'baidu', 'com.cn']

    # 分割两次,并取序列为1的项
    print((u.split(".", 2)[1])) # baidu

    # 分割两次,并把分割后的三个部分保存到三个变量
    u1, u2, u3 = u.split(".", 2)
    print(u1) # www
    print(u2) # baidu
    print(u3) # com.cn


    c = '''say
    hello
    baby'''

    print(c)
    # say
    # hello
    # baby

    print(c.split('\n')) # ['say', 'hello', 'baby']

    string = "hello boy<[www.baidu.com]>byebye"
    print(string.split('[')[1].split(']')[0]) # www.baidu.com
    print(string.split('[')[1].split(']')[0].split('.')) # ['www', 'baidu', 'com']
  • splitlines([keepends]) 按照行(‘\r’, ‘\r\n’, \n’)分隔,返回一个包含各行作为元素的列表,如果参数keepends为 False,不包含换行符,如果为 True,则保留换行符。

    1
    2
    3
    str6 = 'I \n Love \n LsgoGroup'
    print(str6.splitlines()) # ['I ', ' Love ', ' LsgoGroup']
    print(str6.splitlines(True)) # ['I \n', ' Love \n', ' LsgoGroup']
  • maketrans(intab, outtab) 创建字符映射的转换表,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。

  • translate(table, deletechars="") 根据参数table给出的表,转换字符串的字符,要过滤掉的字符放到deletechars参数中。

    1
    2
    3
    4
    5
    6
    str7 = 'this is string example....wow!!!'
    intab = 'aeiou'
    outtab = '12345'
    trantab = str7.maketrans(intab, outtab)
    print(trantab) # {97: 49, 111: 52, 117: 53, 101: 50, 105: 51}
    print(str7.translate(trantab)) # th3s 3s str3ng 2x1mpl2....w4w!!!

字符串格式化

1
2
3
4
5
6
7
8
9
10
11
str8 = "{0} Love {1}".format('I', 'Lsgogroup')  # 位置参数
print(str8) # I Love Lsgogroup

str8 = "{a} Love {b}".format(a='I', b='Lsgogroup') # 关键字参数
print(str8) # I Love Lsgogroup

str8 = "{0} Love {b}".format('I', b='Lsgogroup') # 位置参数要在关键字参数之前
print(str8) # I Love Lsgogroup

str8 = '{0:.2f}{1}'.format(27.658, 'GB') # 保留小数点后两位
print(str8) # 27.66GB
符 号 描述
%c 格式化字符及其ASCII码
%s 格式化字符串,用str()方法处理对象
%r 格式化字符串,用rper()方法处理对象
%d 格式化整数
%o 格式化无符号八进制数
%x 格式化无符号十六进制数
%X 格式化无符号十六进制数(大写)
%f 格式化浮点数字,可指定小数点后的精度
%e 用科学计数法格式化浮点数
%E 作用同%e,用科学计数法格式化浮点数
%g 根据值的大小决定使用%f或%e
%G 作用同%g,根据值的大小决定使用%f或%E
1
2
3
4
5
6
7
8
9
10
11
12
13
14
print('%c' % 97)  # a
print('%c %c %c' % (97, 98, 99)) # a b c
print('%d + %d = %d' % (4, 5, 9)) # 4 + 5 = 9
print("我叫 %s 今年 %d 岁!" % ('小明', 10)) # 我叫 小明 今年 10 岁!
print('%o' % 10) # 12
print('%x' % 10) # a
print('%X' % 10) # A
print('%f' % 27.658) # 27.658000
print('%e' % 27.658) # 2.765800e+01
print('%E' % 27.658) # 2.765800E+01
print('%g' % 27.658) # 27.658
text = "I am %d years old." % 22
print("I said: %s." % text) # I said: I am 22 years old..
print("I said: %r." % text) # I said: 'I am 22 years old.'
符号 功能
m.n m 是显示的最小总宽度,n 是小数点后的位数(如果可用的话)
- 用作左对齐
+ 在正数前面显示加号( + )
# 在八进制数前面显示零(‘0’),在十六进制前面显示’0x’或者’0X’(取决于用的是’x’还是’X’)
0 显示的数字前面填充’0’而不是默认的空格

作业

  1. 字符串函数回顾

  2. 实现isdigit函数:字符串里是否只包含数字0~9

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def isdigit(string):
    """
    判断字符串只包含数字
    :param string:
    :return:
    """
    # your code here
    flag = True if string.isnumeric() else False
    return flag
    a = input("请输入:")
    flag = isdigit(a)
    print(flag)
  3. leetcode 5题 最长回文子串

    给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

    “回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。

    示例:
    输入: “babad”
    输出: “bab”
    输入: “cbbd”
    输出: “bb”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Solution:
    def longestPalindrome(self, s: str) -> str:
    l = len(s)
    maxlen = 0
    maxstr = ''
    for i in range(l):
    for j in range(i+1,l+1):
    temp =s[i:j]
    if temp == temp[::-1] and j-i > maxlength:
    maxstr = s[i:j]
    maxlength = j-i
    return maxstr

Task3_作业

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import random

num = random.randint(0,100)
i = 0
while 1:
i+=1
print('第%s次猜,请输入0到100的整数'%i)
n = input("输入一个0到100的整数:")
try:
n=int(n)
except:
print('输入无效')
else:
if n<num:
print('小了')
elif n > num:
print("大了")
elif n == num:
print("恭喜你猜对了答案是%s"%num)
break

image-20200725185322609

Python标准警告总结

  • Warning:警告的基类
  • DeprecationWarning:关于被弃用的特征的警告
  • FutureWarning:关于构造将来语义会有改变的警告
  • UserWarning:用户代码生成的警告
  • PendingDeprecationWarning:关于特性将会被废弃的警告
  • RuntimeWarning:可疑的运行时行为(runtime behavior)的警告
  • SyntaxWarning:可疑语法的警告
  • ImportWarning:用于在导入模块过程中触发的警告
  • UnicodeWarning:与Unicode相关的警告
  • BytesWarning:与字节或字节码相关的警告
  • ResourceWarning:与资源使用相关的警告

random

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import random
import string

# 随机整数:
print random.randint(1,50)

# 随机选取0到100间的偶数:
print random.randrange(0, 101, 2)

# 随机浮点数:
print random.random()
print random.uniform(1, 10)

# 随机字符:
print random.choice('abcdefghijklmnopqrstuvwxyz!@#$%^&*()')

# 多个字符中生成指定数量的随机字符:
print random.sample('zyxwvutsrqponmlkjihgfedcba',5)

# 从a-zA-Z0-9生成指定数量的随机字符:
ran_str = ''.join(random.sample(string.ascii_letters + string.digits, 8))
print ran_str

# 多个字符中选取指定数量的字符组成新字符串:
print ''.join(random.sample(['z','y','x','w','v','u','t','s','r','q','p','o','n','m','l','k','j','i','h','g','f','e','d','c','b','a'], 5))

# 随机选取字符串:
print random.choice(['剪刀', '石头', '布'])

# 打乱排序
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
print random.shuffle(items)

Task2_作业

  1. 编写一个Python程序来查找那些既可以被7整除又可以被5整除的数字,介于1500和2700之间。
1
2
3
x = [i for i in range(1500,2700) if (i%5) == 0 and (i%7) == 0]

==>[1505, 1540, 1575, 1610, 1645, 1680, 1715, 1750, 1785, 1820, 1855, 1890, 1925, 1960, 1995, 2030, 2065, 2100, 2135, 2170, 2205, 2240, 2275, 2310, 2345, 2380, 2415, 2450, 2485, 2520, 2555, 2590, 2625, 2660, 2695]
  1. 龟兔赛跑游戏

题目描述:任一秒结束后兔子发现自己领先**x米或以上,会停下s秒**。对于不同的兔子,x,s的数值是不同的,但是所有的乌龟却是一致——它们不到终点决不停止。

兔子的速度v1(表示每秒兔子能跑v1 米),乌龟的速度v2,以及兔子对应的x,s值,以及赛道的长度l——就能预测出比赛的结果。写一个程序,对于输入的一场比赛的数据v1,v2,x,s,l,预测该场比赛的结果。

输入: 输入只有一行,包含用空格隔开的五个正整数v1,v2,x,s,l,其中(v1,v2< =100;x< =300;s< =10;l< =10000且为v1,v2的公倍数)

输出: 输出包含两行,第一行输出比赛结果——一个大写字母“T”或“R”或“D”,分别表示乌龟获胜,兔子获胜,或者两者同时到达终点。

第二行输出一个正整数,表示获胜者(或者双方同时)到达终点所耗费的时间(秒数)。

样例输入:

10 5 5 2 20

样例输出

D
4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def end(l_1,l_2,l):
if l_1 >= l or l_2 >= l: #有一方到达终点
if l_1 == l_2 == l:
return ("D") #同时
elif l_1 > l_2:
return ("R") #兔子赢
elif l_1 < l_2 :
return "T" #乌龟赢
pass

def competition(v1:int,v2:int,x:int,s:int,l:int):
l_1,l_2,t = 0,0,0
#l_1 = v1*t
#l_2 = v2*t

while 1 :
back = end(l_1,l_2,l)
if back:
break

if l_1-l_2 < x : #兔子不领先
t+=1
l_1 += v1
l_2 += v2
else:
while s>0:
t+=1
l_2 += v2
s-=1

print(back,'\n',t)



if __name__ == "__main__":
competition(10,5,5,2,20)

Task1_作业

  1. 怎样对python中的代码进行注释?
  • #注释单行

  • ''' ''' 或者 """ """ 表示区间注释,中间内容被注释

  • 快捷键cmd+?,可以通过#快速注释多行
  1. python有哪些运算符,这些运算符的优先级是怎样的?
  • 算术运算符:+、-、*、/、%、**、//

  • 比较运算符:==、!=、>、<、>=、<=

  • 赋值运算符:=、 +=、 -=、 =、 /=、 %=、 *=、 //=、 ==:=(海象运算符,3.8新增)==

  • 位运算符:&、 | 、^ 、~

  • 逻辑运算符:and 、or、not

  • 成员运算符:in、not in

  • 身份运算符:is 、not is

    优先级:

    | 运算符 | 描述 |
    | ———————————— | ——————————————————————————— |
    | | 指数 (最高优先级) |
    | ~ + - | 按位翻转, 一元加号和减号 (最后两个的方法名为 +@ 和 -@) |
    | / % // | 乘,除,求余数和取整除 |
    | + - | 加法减法 |
    | >> << | 右移,左移运算符 |
    | & | 位 ‘AND’ |
    | ^ | | 位运算符 |
    | <= < > >= | 比较运算符 |
    | == != | 等于运算符 |
    | = %= /= //= -= +=
    =
    = | 赋值运算符 |
    | is is not | 身份运算符 |
    | in not in | 成员运算符 |
    | not and or | 逻辑运算符 |

  1. python 中 is, is not==, != 的区别是什么?
1
2
身份运算符用于比较两个对象的存储单元,
is 用于判断两个变量引用对象是否为同一个, == 用于判断引用变量的值是否相等。
  1. python 中包含哪些数据类型?这些数据类型之间如何转换?
  • Number(数字)——不可变数据

  • String(字符串)——不可变数据

  • Tuple(元组)——不可变数据

  • List(列表)——可变数据

  • Set(集合)——可变数据

  • Dictionary(字典)——可变数据

函数 描述
int(x [,base]) 将x转换为一个整数
float(x) 将x转换到一个浮点数
complex(real [,imag]) 创建一个复数
str(x) 将对象 x 转换为字符串
repr(x) 将对象 x 转换为表达式字符串
eval(str) 用来计算在字符串中的有效Python表达式,并返回一个对象
tuple(s) 将序列 s 转换为一个元组
list(s) 将序列 s 转换为一个列表
set(s) 转换为可变集合
dict(d) 创建一个字典。d 必须是一个 (key, value)元组序列。
frozenset(s) 转换为不可变集合
chr(x) 将一个整数转换为一个字符
ord(x) 将一个字符转换为它的整数值
hex(x) 将一个整数转换为一个十六进制字符串
oct(x) 将一个整数转换为一个八进制字符串

一、赛题数据

数据说明

数据为新闻文本,并按照字符级别进行匿名处理。整合划分出14个候选分类类别。

数据:训练集20w条样本,测试集A包括5w条样本,测试集B包括5w条样本。

label text
6 57 44 66 56 2 3 3 37 5 41 9 57 44 47 45 33 13 63 58 31 17 47 0 1 1 69 26 60 62 15 21 12 49 18 38 20 50 23 57 44 45 33 25 28 47 22 52 35 30 14 24 69 54 7 48 19 11 51 16 43 26 34 53 27 64 8 4 42 36 46 65 69 29 39 15 37 57 44 45 33 69 54 7 25 40 35 30 66 56 47 55 69 61 10 60 42 36 46 65 37 5 41 32 67 6 59 47 0 1 1 68
1
{'科技': 0, '股票': 1, '体育': 2, '娱乐': 3, '时政': 4, '社会': 5, '教育': 6, '财经': 7, '家居': 8, '游戏': 9, '房产': 10, '时尚': 11, '彩票': 12, '星座': 13}

数据展示

1
2
3
import pandas as pd
train_df = pd.read_csv('./train_set.csv', sep='\t')
train_df.head(10)

image-20200721190250430

二、评测标准

1
2
3
4
from sklearn.metrics import f1_score
y_true = [0, 1, 2, 0, 1, 2]
y_pred = [0, 2, 1, 0, 0, 1]
f1_score(y_true, y_pred, average='macro')

三、解题思路

赛题给出的数据是匿名化的,不能直接使用中文分词等操作,这个是赛题的难点。文本数据是一种典型的非结构化数据,因此可能涉及到特征提取分类模型两个部分。提供了一些解题思路供大家参考:

  • 思路1:TF-IDF + 机器学习分类器

    直接使用TF-IDF对文本提取特征,并使用分类器进行分类。在分类器的选择上,可以使用SVM、LR、或者XGBoost。

  • 思路2:FastText

    FastText是入门款的词向量,利用Facebook提供的FastText工具,可以快速构建出分类器。

  • 思路3:WordVec + 深度学习分类器

    WordVec是进阶款的词向量,并通过构建深度学习分类完成分类。深度学习分类的网络结构可以选择TextCNN、TextRNN或者BiLSTM。

  • 思路4:Bert词向量

    Bert是高配款的词向量,具有强大的建模学习能力。

四、数据分析

句子长度分析

1
2
3
%pylab inline
train_df['text_len'] = train_df['text'].apply(lambda x: len(x.split(' ')))
print(train_df['text_len'].describe())

image-20200723094940092

直方图展示

1
2
3
_ = plt.hist(train_df['text_len'], bins=200)
plt.xlabel('Text char count')
plt.title("Histogram of char count")

image-20200723095221681

类别分布

1
2
3
train_df['label'].value_counts().plot(kind='bar')
plt.title('News class count')
plt.xlabel("category")

image-20200723095143548

字符分布统计

1
2
3
4
5
6
7
8
9
10
11
from collections import Counter
all_lines = ' '.join(list(train_df['text']))
word_count = Counter(all_lines.split(" "))
word_count = sorted(word_count.items(), key=lambda d:d[1], reverse = True)
print(len(word_count))
print(word_count[0])
print(word_count[-1])

==> 6969
==> ('3750',7482224)
==> ('3133',1)

分析结论

  1. 字符个数平均为1000个,可能需要截断
  2. 类别分布不均匀,会严重影响模型的精度
  3. 总共包括7000-8000个字符

作业

  1. 假设字符3750,字符900和字符648是句子的标点符号,请分析赛题每篇新闻平均由多少个句子构成?
1
2
3
4
5
import re
train_df['sent'] = train_df['text'].apply(lambda x: len(re.split('3750|900|648',x)))
average = sum(train_df['sent'])/len(train_df['sent'])
print('平均字符:{:.2f}'.format(average))
train_df['sent'].describe()
  1. 统计每类新闻中出现次数对多的字符。
1
2
3
4
5
6
7
8
%time

from collections import Counter


train_df['text'] = train_df['text'].apply(lambda x: x.replace(' 3750',"").replace(' 900',"").replace(' 648',""))

print(train_df.groupby('label').text.apply(lambda x: Counter(' '.join(list(x)).split(" ")).most_common(1)))

NVM

Installing and Updating

1
2
3
#任选其一
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash

在bash_profile和zshrc添加如下语句。source更新。(用那个就添加那个,我两个都添加了)

1
2
3
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion

输入 nvm 检查能否正常

1
2
3
$ nvm
--> Node Version Manager (v0.35.3)
(....略)

Usage

查看当前ndoe

1
2
3
4
5
6
$ nvm ls
--> v12.14.1
system
default -> 12.14.1 (-> v12.14.1)
node -> stable (-> v12.14.1) (default)
stable -> 12.14 (-> v12.14.1) (default)

可以看到目前系统使用的node是12.14.1

使用 “nvm install ” 安装指定版本的 node

1
2
3
4
5
6
7
8
9
$ nvm install v11.10.0

$ nvm ls
v11.10.1
-> v12.14.1
system
default -> 12.14.1 (-> v12.14.1)
node -> stable (-> v12.14.1) (default)
stable -> 12.14 (-> v12.14.1) (default)

现在已经有两个版本的 node 了,使用 “nvm use ” 可以切换 node 版本

1
$ nvm use 11.10.1

题目描述:在一个由 'L' , 'R''X' 三个字符组成的字符串(例如"RXXLRXRXL")中进行移动操作。一次移动操作指用一个"LX"替换一个"XL",或者用一个"XR"替换一个"RX"。现给定起始字符串start和结束字符串end,请编写代码,当且仅当存在一系列移动操作使得start可以转换成end时, 返回True

1
2
3
4
5
6
7
8
9
输入: start = "RXXLRXRXL", end = "XRLXXRRLX"
输出: True
解释:
我们可以通过以下几步将start转换成end:
RXXLRXRXL ->
XRXLRXRXL ->
XRLXRXRXL ->
XRLXXRRXL ->
XRLXXRRLX

题解

官方法一

  • 转换不变性:字符串中的 'L''R' 是不会互相穿插的,意味着开始和结束的字符串如果只看 'L''R' 的话是一模一样的。

  • 可到达性:设 $(i_1, i_2, \cdots ),(i^{‘}_1, i^{‘}_2, \cdots )$为每个字符 ‘L’ 在原始字符串和目标字符串的位置,$(j_1, j_2, \cdots )(j^{‘}_1, j^{‘}_2, \cdots )$为每个字符 ‘R’ 在原始字符串和目标字符串的位置,如果$i_k \geq i^{‘}_k $和 $j_k \leq j^{‘}_k $都能满足,这个字符串就是 ”可到达的“。

    L只能向左移,R只能向右移

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution(object):
def canTransform(start, end):
if start.replace('X','') != end.replace('X', ''):
return False

for (symbol, op) in (('L', operator.ge), ('R', operator.le)):
# ge() >= ; le() <=
B = (i for i, c in enumerate(end) if c == symbol)
for i, c in enumerate(start):
if c == symbol and not op(i, next(B)):
return False

return True
  • enumerate() 函数

    1
    2
    3
    4
    5
    enumerate(sequence, [start=0])

    sequence -- 一个序列、迭代器或其他支持迭代对象。
    start -- 下标起始位置。
    返回 enumerate(枚举) 对象
    1
    2
    3
    4
    5
    6
    7
    >>>seq = ['one', 'two', 'three']
    >>> for i, element in enumerate(seq):
    ... print( i, element)
    ...
    0 one
    1 two
    2 three

官方解二:双指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution(object):
def canTransform(self, start, end):
# For (i, x) and (j, y) in enumerate(start), enumerate(end)
# where x != 'X' and y != 'X',
# and where if one exhausts early, it's elements are (None, None),...
for (i, x), (j, y) in itertools.zip_longest(
((i, x) for i, x in enumerate(start) if x != 'X'),
((j, y) for j, y in enumerate(end) if y != 'X'),
fillvalue = (None, None)):

# If not solid or accessible, return False
if x != y or (x == 'L' and i < j) or (x == 'R' and i > j):
return False

return True
  • zip()、zip.longest()

    1
    2
    3
    4
    5
    6
    7
    a = [1,2,3,4]
    b = [5,6,7]
    print(list(zip(a,b))) # 两个可迭代参数长度不同时,按最短的组合输出
    # 输出:[(1, 5), (2, 6), (3, 7)]

    print(list(zip_longest(a,b)))#按最长的输出,长度不足的用None进行代替
    # 输出:[(1, 5), (2, 6), (3, 7), (4, None)]

用时最快

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution:
def canTransform(self,star,end):
if start.replace('X','')!= end.replace('x',''):
return False
sl,el,sr,er = 0,0,0,0
for i in range(len(start)):
if start[i]=='L':
sl+=1
if start[i]=='R':
sr+=1
if end[i]=='L':
el+=1
if end[i]=='R':
er+=1
if sl>el or sr<er:
return False

return True

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

1
2
3
4
5
输入: [2,2,1]
输出: 1

输入: [4,1,2,1,2]
输出: 4

官方最优解

1
2
3
4
5
6
7
8
9
class Solution:
def singleNumber(self, nums: List[int]) -> int:
a = 0
for i in nums:
a ^= i
#a = a^i
return a
#时间复杂度:O(n)
#空间复杂度:O(1)

位操作:a⊕0=aaa=0

最快用例

1
2
3
4
5
6
class Solution:
def singleNumber(self, nums: List[int]) -> int:
res = 0
for n in nums:
res ^= n
return res

融合类型

  1. 简单加权融合:

    • 回归(分类概率):算术平均融合(Arithmetic mean),几何平均融合(Geometric mean);
    • 分类:投票(Voting)
    • 综合:排序融合(Rank averaging),log融合
  2. stacking/blending:

    • 构建多层模型,并利用预测结果再拟合预测。
  3. boosting/bagging:

    • 多树的提升方法

Stacking

用初始训练数据学习出若干个基学习器后,将这几个学习器的预测结果作为新的训练集,来学习一个新的学习器。

image-20200404195716171

结合策略:

​ 对于分类问题,我们可以使用投票法来选择输出最多的类。

​ 对于回归问题,我们可以将分类器输出的结果求平均值。

​ 使用另外一个机器学习算法来将个体机器学习器的结果结合在一起,这个方法就是Stacking。

在stacking方法中,我们把个体学习器叫做初级学习器,用于结合的学习器叫做次级学习器或元学习器(meta-learner),次级学习器用于训练的数据叫做次级训练集。次级训练集是在训练集上用初级学习器得到的。

Stacking的方法讲解(便于理解的例子)

假设我们有2个基模型 Model1_1、Model1_2 和 一个次级模型Model2

Step 1. 基模型 Model1_1

对训练集train训练,然后用于预测 train 和 test 的标签列,分别是P1,T1

  • image-20200404200112072
  • image-20200404200123787

Step 2. 基模型 Model1_2

​ 对训练集train训练,然后用于预测train和test的标签列,分别是P2,T2

Step 3. 分别把P1,P2以及T1,T2合并,得到一个新的训练集和测试集train2,test2.

image-20200404200335862

Step 4. 用 次级模型 Model2 以真实训练集标签为标签训练,以train2为特征进行训练,预测test2,得到最终的测试集预测的标签列 YPre

image-20200404200434954

  • 如何降低再训练的过拟合性:

    • 次级模型尽量选择简单的线性模型
    • 利用K折交叉验证

代码学习

回归\分类概率-融合

简单加权平均,结果直接融合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
## 生成一些简单的样本数据,test_prei 代表第i个模型的预测值
test_pre1 = [1.2, 3.2, 2.1, 6.2]
test_pre2 = [0.9, 3.1, 2.0, 5.9]
test_pre3 = [1.1, 2.9, 2.2, 6.0]

# y_test_true 代表第模型的真实值
y_test_true = [1, 3, 2, 6]



import numpy as np
import pandas as pd

## 定义结果的加权平均函数
def Weighted_method(test_pre1,test_pre2,test_pre3,w=[1/3,1/3,1/3]):
Weighted_result = w[0]*pd.Series(test_pre1)+w[1]*pd.Series(test_pre2)+w[2]*pd.Series(test_pre3)
return Weighted_result



from sklearn import metrics
# 各模型的预测结果计算MAE
print('Pred1 MAE:',metrics.mean_absolute_error(y_test_true, test_pre1))
print('Pred2 MAE:',metrics.mean_absolute_error(y_test_true, test_pre2))
print('Pred3 MAE:',metrics.mean_absolute_error(y_test_true, test_pre3))



## 根据加权计算MAE
w = [0.3,0.4,0.3] # 定义比重权值
Weighted_pre = Weighted_method(test_pre1,test_pre2,test_pre3,w)
print('Weighted_pre MAE:',metrics.mean_absolute_error(y_test_true, Weighted_pre))



## 定义结果的加权平均函数
def Mean_method(test_pre1,test_pre2,test_pre3):
Mean_result = pd.concat([pd.Series(test_pre1),pd.Series(test_pre2),pd.Series(test_pre3)],axis=1).mean(axis=1)
return Mean_result

Mean_pre = Mean_method(test_pre1,test_pre2,test_pre3)
print('Mean_pre MAE:',metrics.mean_absolute_error(y_test_true, Mean_pre))



## 定义结果的加权平均函数
def Median_method(test_pre1,test_pre2,test_pre3):
Median_result = pd.concat([pd.Series(test_pre1),pd.Series(test_pre2),pd.Series(test_pre3)],axis=1).median(axis=1)
return Median_result

Median_pre = Median_method(test_pre1,test_pre2,test_pre3)
print('Median_pre MAE:',metrics.mean_absolute_error(y_test_true, Median_pre))

Stacking融合(回归)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from sklearn import linear_model

def Stacking_method(train_reg1,train_reg2,train_reg3,y_train_true,test_pre1,test_pre2,test_pre3,model_L2= linear_model.LinearRegression()):
model_L2.fit(pd.concat([pd.Series(train_reg1),pd.Series(train_reg2),pd.Series(train_reg3)],axis=1).values,y_train_true)
Stacking_result = model_L2.predict(pd.concat([pd.Series(test_pre1),pd.Series(test_pre2),pd.Series(test_pre3)],axis=1).values)
return Stacking_result



## 生成一些简单的样本数据,test_prei 代表第i个模型的预测值
train_reg1 = [3.2, 8.2, 9.1, 5.2]
train_reg2 = [2.9, 8.1, 9.0, 4.9]
train_reg3 = [3.1, 7.9, 9.2, 5.0]
# y_test_true 代表第模型的真实值
y_train_true = [3, 8, 9, 5]

test_pre1 = [1.2, 3.2, 2.1, 6.2]
test_pre2 = [0.9, 3.1, 2.0, 5.9]
test_pre3 = [1.1, 2.9, 2.2, 6.0]

# y_test_true 代表第模型的真实值
y_test_true = [1, 3, 2, 6]



model_L2= linear_model.LinearRegression()
Stacking_pre = Stacking_method(train_reg1,train_reg2,train_reg3,y_train_true,
test_pre1,test_pre2,test_pre3,model_L2)
print('Stacking_pre MAE:',metrics.mean_absolute_error(y_test_true, Stacking_pre))

分类模型融合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.datasets import make_blobs
from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from xgboost import XGBClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_moons
from sklearn.metrics import accuracy_score,roc_auc_score
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import StratifiedKFold

Voting投票机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'''
硬投票:对多个模型直接进行投票,不区分模型结果的相对重要度,最终投票数最多的类为最终被预测的类。
'''
iris = datasets.load_iris()

x=iris.data
y=iris.target
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3)

clf1 = XGBClassifier(learning_rate=0.1, n_estimators=150, max_depth=3, min_child_weight=2, subsample=0.7,
colsample_bytree=0.6, objective='binary:logistic')
clf2 = RandomForestClassifier(n_estimators=50, max_depth=1, min_samples_split=4,
min_samples_leaf=63,oob_score=True)
clf3 = SVC(C=0.1)

# 硬投票
eclf = VotingClassifier(estimators=[('xgb', clf1), ('rf', clf2), ('svc', clf3)], voting='hard')
for clf, label in zip([clf1, clf2, clf3, eclf], ['XGBBoosting', 'Random Forest', 'SVM', 'Ensemble']):
scores = cross_val_score(clf, x, y, cv=5, scoring='accuracy')
print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'''
软投票:和硬投票原理相同,增加了设置权重的功能,可以为不同模型设置不同权重,进而区别模型不同的重要度。
'''
x=iris.data
y=iris.target
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3)

clf1 = XGBClassifier(learning_rate=0.1, n_estimators=150, max_depth=3, min_child_weight=2, subsample=0.8,
colsample_bytree=0.8, objective='binary:logistic')
clf2 = RandomForestClassifier(n_estimators=50, max_depth=1, min_samples_split=4,
min_samples_leaf=63,oob_score=True)
clf3 = SVC(C=0.1, probability=True)

# 软投票
eclf = VotingClassifier(estimators=[('xgb', clf1), ('rf', clf2), ('svc', clf3)], voting='soft', weights=[2, 1, 1])
clf1.fit(x_train, y_train)

for clf, label in zip([clf1, clf2, clf3, eclf], ['XGBBoosting', 'Random Forest', 'SVM', 'Ensemble']):
scores = cross_val_score(clf, x, y, cv=5, scoring='accuracy')
print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

分类的Stacking\Blending融合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
'''
5-Fold Stacking
'''
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import ExtraTreesClassifier,GradientBoostingClassifier
import pandas as pd
#创建训练的数据集
data_0 = iris.data
data = data_0[:100,:]

target_0 = iris.target
target = target_0[:100]

#模型融合中使用到的各个单模型
clfs = [LogisticRegression(solver='lbfgs'),
RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=5)]

#切分一部分数据作为测试集
X, X_predict, y, y_predict = train_test_split(data, target, test_size=0.3, random_state=2020)

dataset_blend_train = np.zeros((X.shape[0], len(clfs)))
dataset_blend_test = np.zeros((X_predict.shape[0], len(clfs)))

#5折stacking
n_splits = 5
skf = StratifiedKFold(n_splits)
skf = skf.split(X, y)

for j, clf in enumerate(clfs):
#依次训练各个单模型
dataset_blend_test_j = np.zeros((X_predict.shape[0], 5))
for i, (train, test) in enumerate(skf):
#5-Fold交叉训练,使用第i个部分作为预测,剩余的部分来训练模型,获得其预测的输出作为第i部分的新特征。
X_train, y_train, X_test, y_test = X[train], y[train], X[test], y[test]
clf.fit(X_train, y_train)
y_submission = clf.predict_proba(X_test)[:, 1]
dataset_blend_train[test, j] = y_submission
dataset_blend_test_j[:, i] = clf.predict_proba(X_predict)[:, 1]
#对于测试集,直接用这k个模型的预测值均值作为新的特征。
dataset_blend_test[:, j] = dataset_blend_test_j.mean(1)
print("val auc Score: %f" % roc_auc_score(y_predict, dataset_blend_test[:, j]))

clf = LogisticRegression(solver='lbfgs')
clf.fit(dataset_blend_train, y)
y_submission = clf.predict_proba(dataset_blend_test)[:, 1]

print("Val auc Score of Stacking: %f" % (roc_auc_score(y_predict, y_submission)))

Blending

  • 把原始的训练集先分成两部分,例如 7\3
  • 在第一层,我们在这70%的数据上训练多个模型,然后去预测那30%数据的label,同时也预测test集的label。
  • 在第二层,我们就直接用这30%数据在第一层预测的结果做为新特征继续训练,然后用test集第一层预测的label做特征,用第二层训练的模型做进一步预测

优点:

​ 简单;避开了信息泄露问题:generlizers和stacker使用了不一样的数据集。

缺点:

​ 使用了很少的数据;可能会过拟合;stacking使用多次的交叉验证会比较稳健

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
'''
Blending
'''

#创建训练的数据集
#创建训练的数据集
data_0 = iris.data
data = data_0[:100,:]

target_0 = iris.target
target = target_0[:100]

#模型融合中使用到的各个单模型
clfs = [LogisticRegression(solver='lbfgs'),
RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
#ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=5)]

#切分一部分数据作为测试集
X, X_predict, y, y_predict = train_test_split(data, target, test_size=0.3, random_state=2020)

#切分训练数据集为d1,d2两部分
X_d1, X_d2, y_d1, y_d2 = train_test_split(X, y, test_size=0.5, random_state=2020)
dataset_d1 = np.zeros((X_d2.shape[0], len(clfs)))
dataset_d2 = np.zeros((X_predict.shape[0], len(clfs)))

for j, clf in enumerate(clfs):
#依次训练各个单模型
clf.fit(X_d1, y_d1)
y_submission = clf.predict_proba(X_d2)[:, 1]
dataset_d1[:, j] = y_submission
#对于测试集,直接用这k个模型的预测值作为新的特征。
dataset_d2[:, j] = clf.predict_proba(X_predict)[:, 1]
print("val auc Score: %f" % roc_auc_score(y_predict, dataset_d2[:, j]))

#融合使用的模型
clf = GradientBoostingClassifier(learning_rate=0.02, subsample=0.5, max_depth=6, n_estimators=30)
clf.fit(dataset_d1, y_d2)
y_submission = clf.predict_proba(dataset_d2)[:, 1]
print("Val auc Score of Blending: %f" % (roc_auc_score(y_predict, y_submission)))

分类的Stacking融合(利用mlxtend)

基模型 用 ‘KNN’, ‘Random Forest’, ‘Naive Bayes’ 然后再这基础上 次级模型加一个 ‘LogisticRegression’,模型测试效果有着很好的提升。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
!pip install mlxtend

import warnings
warnings.filterwarnings('ignore')
import itertools
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from mlxtend.classifier import StackingClassifier

from sklearn.model_selection import cross_val_score
from mlxtend.plotting import plot_learning_curves
from mlxtend.plotting import plot_decision_regions

# 以python自带的鸢尾花数据集为例
iris = datasets.load_iris()
X, y = iris.data[:, 1:3], iris.target

clf1 = KNeighborsClassifier(n_neighbors=1)
clf2 = RandomForestClassifier(random_state=1)
clf3 = GaussianNB()
lr = LogisticRegression()
sclf = StackingClassifier(classifiers=[clf1, clf2, clf3],
meta_classifier=lr)

label = ['KNN', 'Random Forest', 'Naive Bayes', 'Stacking Classifier']
clf_list = [clf1, clf2, clf3, sclf]

fig = plt.figure(figsize=(10,8))
gs = gridspec.GridSpec(2, 2)
grid = itertools.product([0,1],repeat=2)

clf_cv_mean = []
clf_cv_std = []
for clf, label, grd in zip(clf_list, label, grid):

scores = cross_val_score(clf, X, y, cv=3, scoring='accuracy')
print("Accuracy: %.2f (+/- %.2f) [%s]" %(scores.mean(), scores.std(), label))
clf_cv_mean.append(scores.mean())
clf_cv_std.append(scores.std())

clf.fit(X, y)
ax = plt.subplot(gs[grd[0], grd[1]])
fig = plot_decision_regions(X=X, y=y, clf=clf)
plt.title(label)

plt.show()

其它方法:

将特征放进模型中预测,并将预测结果变换并作为新的特征加入原有特征中再经过模型预测结果 (Stacking变化)

可以反复预测多次将结果加入最后的特征中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def Ensemble_add_feature(train,test,target,clfs):

# n_flods = 5
# skf = list(StratifiedKFold(y, n_folds=n_flods))

train_ = np.zeros((train.shape[0],len(clfs*2)))
test_ = np.zeros((test.shape[0],len(clfs*2)))

for j,clf in enumerate(clfs):
'''依次训练各个单模型'''
# print(j, clf)
'''使用第1个部分作为预测,第2部分来训练模型,获得其预测的输出作为第2部分的新特征。'''
# X_train, y_train, X_test, y_test = X[train], y[train], X[test], y[test]

clf.fit(train,target)
y_train = clf.predict(train)
y_test = clf.predict(test)

## 新特征生成
train_[:,j*2] = y_train**2
test_[:,j*2] = y_test**2
train_[:, j+1] = np.exp(y_train)
test_[:, j+1] = np.exp(y_test)
# print("val auc Score: %f" % r2_score(y_predict, dataset_d2[:, j]))
print('Method ',j)

train_ = pd.DataFrame(train_)
test_ = pd.DataFrame(test_)
return train_,test_


from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()

data_0 = iris.data
data = data_0[:100,:]

target_0 = iris.target
target = target_0[:100]

x_train,x_test,y_train,y_test=train_test_split(data,target,test_size=0.3)
x_train = pd.DataFrame(x_train) ; x_test = pd.DataFrame(x_test)

#模型融合中使用到的各个单模型
clfs = [LogisticRegression(),
RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=5)]

New_train,New_test = Ensemble_add_feature(x_train,x_test,y_train,clfs)

clf = LogisticRegression()
# clf = GradientBoostingClassifier(learning_rate=0.02, subsample=0.5, max_depth=6, n_estimators=30)
clf.fit(New_train, y_train)
y_emb = clf.predict_proba(New_test)[:, 1]

print("Val auc Score of stacking: %f" % (roc_auc_score(y_test, y_emb)))

本赛题示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import pandas as pd
import numpy as np
import warnings
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns

warnings.filterwarnings('ignore')
%matplotlib inline

import itertools
import matplotlib.gridspec as gridspec
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
# from mlxtend.classifier import StackingClassifier
from sklearn.model_selection import cross_val_score, train_test_split
# from mlxtend.plotting import plot_learning_curves
# from mlxtend.plotting import plot_decision_regions

from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import train_test_split

from sklearn import linear_model
from sklearn import preprocessing
from sklearn.svm import SVR
from sklearn.decomposition import PCA,FastICA,FactorAnalysis,SparsePCA

import lightgbm as lgb
import xgboost as xgb
from sklearn.model_selection import GridSearchCV,cross_val_score
from sklearn.ensemble import RandomForestRegressor,GradientBoostingRegressor

from sklearn.metrics import mean_squared_error, mean_absolute_error



## 数据读取
Train_data = pd.read_csv('datalab/231784/used_car_train_20200313.csv', sep=' ')
TestA_data = pd.read_csv('datalab/231784/used_car_testA_20200313.csv', sep=' ')

print(Train_data.shape)
print(TestA_data.shape)

Train_data.head()

numerical_cols = Train_data.select_dtypes(exclude = 'object').columns
print(numerical_cols)

feature_cols = [col for col in numerical_cols if col not in ['SaleID','name','regDate','price']]

X_data = Train_data[feature_cols]
Y_data = Train_data['price']

X_test = TestA_data[feature_cols]

print('X train shape:',X_data.shape)
print('X test shape:',X_test.shape)



def Sta_inf(data):
print('_min',np.min(data))
print('_max:',np.max(data))
print('_mean',np.mean(data))
print('_ptp',np.ptp(data))
print('_std',np.std(data))
print('_var',np.var(data))

print('Sta of label:')
Sta_inf(Y_data)


X_data = X_data.fillna(-1)
X_test = X_test.fillna(-1)



def build_model_lr(x_train,y_train):
reg_model = linear_model.LinearRegression()
reg_model.fit(x_train,y_train)
return reg_model

def build_model_ridge(x_train,y_train):
reg_model = linear_model.Ridge(alpha=0.8)#alphas=range(1,100,5)
reg_model.fit(x_train,y_train)
return reg_model

def build_model_lasso(x_train,y_train):
reg_model = linear_model.LassoCV()
reg_model.fit(x_train,y_train)
return reg_model

def build_model_gbdt(x_train,y_train):
estimator =GradientBoostingRegressor(loss='ls',subsample= 0.85,max_depth= 5,n_estimators = 100)
param_grid = {
'learning_rate': [0.05,0.08,0.1,0.2],
}
gbdt = GridSearchCV(estimator, param_grid,cv=3)
gbdt.fit(x_train,y_train)
print(gbdt.best_params_)
# print(gbdt.best_estimator_ )
return gbdt

def build_model_xgb(x_train,y_train):
model = xgb.XGBRegressor(n_estimators=120, learning_rate=0.08, gamma=0, subsample=0.8,\
colsample_bytree=0.9, max_depth=5) #, objective ='reg:squarederror'
model.fit(x_train, y_train)
return model

def build_model_lgb(x_train,y_train):
estimator = lgb.LGBMRegressor(num_leaves=63,n_estimators = 100)
param_grid = {
'learning_rate': [0.01, 0.05, 0.1],
}
gbm = GridSearchCV(estimator, param_grid)
gbm.fit(x_train, y_train)
return gbm

XGBoost的五折交叉回归验证实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
## xgb
xgr = xgb.XGBRegressor(n_estimators=120, learning_rate=0.1, subsample=0.8,\
colsample_bytree=0.9, max_depth=7) # ,objective ='reg:squarederror'

scores_train = []
scores = []

## 5折交叉验证方式
sk=StratifiedKFold(n_splits=5,shuffle=True,random_state=0)
for train_ind,val_ind in sk.split(X_data,Y_data):

train_x=X_data.iloc[train_ind].values
train_y=Y_data.iloc[train_ind]
val_x=X_data.iloc[val_ind].values
val_y=Y_data.iloc[val_ind]

xgr.fit(train_x,train_y)
pred_train_xgb=xgr.predict(train_x)
pred_xgb=xgr.predict(val_x)

score_train = mean_absolute_error(train_y,pred_train_xgb)
scores_train.append(score_train)
score = mean_absolute_error(val_y,pred_xgb)
scores.append(score)

print('Train mae:',np.mean(score_train))
print('Val mae',np.mean(scores))

划分数据集,并用多种方法训练和预测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## Split data with val
x_train,x_val,y_train,y_val = train_test_split(X_data,Y_data,test_size=0.3)

## Train and Predict
print('Predict LR...')
model_lr = build_model_lr(x_train,y_train)
val_lr = model_lr.predict(x_val)
subA_lr = model_lr.predict(X_test)

print('Predict Ridge...')
model_ridge = build_model_ridge(x_train,y_train)
val_ridge = model_ridge.predict(x_val)
subA_ridge = model_ridge.predict(X_test)

print('Predict Lasso...')
model_lasso = build_model_lasso(x_train,y_train)
val_lasso = model_lasso.predict(x_val)
subA_lasso = model_lasso.predict(X_test)

print('Predict GBDT...')
model_gbdt = build_model_gbdt(x_train,y_train)
val_gbdt = model_gbdt.predict(x_val)
subA_gbdt = model_gbdt.predict(X_test)

一般比赛中效果最为显著的两种方法

1
2
3
4
5
6
7
8
9
10
11
12
print('predict XGB...')
model_xgb = build_model_xgb(x_train,y_train)
val_xgb = model_xgb.predict(x_val)
subA_xgb = model_xgb.predict(X_test)

print('predict lgb...')
model_lgb = build_model_lgb(x_train,y_train)
val_lgb = model_lgb.predict(x_val)
subA_lgb = model_lgb.predict(X_test)

print('Sta inf of lgb:')
Sta_inf(subA_lgb)

加权融合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def Weighted_method(test_pre1,test_pre2,test_pre3,w=[1/3,1/3,1/3]):
Weighted_result = w[0]*pd.Series(test_pre1)+w[1]*pd.Series(test_pre2)+w[2]*pd.Series(test_pre3)
return Weighted_result

## Init the Weight
w = [0.3,0.4,0.3]

## 测试验证集准确度
val_pre = Weighted_method(val_lgb,val_xgb,val_gbdt,w)
MAE_Weighted = mean_absolute_error(y_val,val_pre)
print('MAE of Weighted of val:',MAE_Weighted)

## 预测数据部分
subA = Weighted_method(subA_lgb,subA_xgb,subA_gbdt,w)
print('Sta inf:')
Sta_inf(subA)
## 生成提交文件
sub = pd.DataFrame()
sub['SaleID'] = X_test.index
sub['price'] = subA
sub.to_csv('./sub_Weighted.csv',index=False)


## 与简单的LR(线性回归)进行对比
val_lr_pred = model_lr.predict(x_val)
MAE_lr = mean_absolute_error(y_val,val_lr_pred)
print('MAE of lr:',MAE_lr)

Starking融合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
## Starking

## 第一层
train_lgb_pred = model_lgb.predict(x_train)
train_xgb_pred = model_xgb.predict(x_train)
train_gbdt_pred = model_gbdt.predict(x_train)

Strak_X_train = pd.DataFrame()
Strak_X_train['Method_1'] = train_lgb_pred
Strak_X_train['Method_2'] = train_xgb_pred
Strak_X_train['Method_3'] = train_gbdt_pred

Strak_X_val = pd.DataFrame()
Strak_X_val['Method_1'] = val_lgb
Strak_X_val['Method_2'] = val_xgb
Strak_X_val['Method_3'] = val_gbdt

Strak_X_test = pd.DataFrame()
Strak_X_test['Method_1'] = subA_lgb
Strak_X_test['Method_2'] = subA_xgb
Strak_X_test['Method_3'] = subA_gbdt


Strak_X_test.head()


## level2-method
model_lr_Stacking = build_model_lr(Strak_X_train,y_train)
## 训练集
train_pre_Stacking = model_lr_Stacking.predict(Strak_X_train)
print('MAE of Stacking-LR:',mean_absolute_error(y_train,train_pre_Stacking))

## 验证集
val_pre_Stacking = model_lr_Stacking.predict(Strak_X_val)
print('MAE of Stacking-LR:',mean_absolute_error(y_val,val_pre_Stacking))

## 预测集
print('Predict Stacking-LR...')
subA_Stacking = model_lr_Stacking.predict(Strak_X_test)



subA_Stacking[subA_Stacking<10]=10 ## 去除过小的预测值

sub = pd.DataFrame()
sub['SaleID'] = TestA_data.SaleID
sub['price'] = subA_Stacking
sub.to_csv('./sub_Stacking.csv',index=False)



print('Sta inf:')
Sta_inf(subA_Stacking)

经验总结

提分和提升模型鲁棒性的一种重要方法

  • 结果层面的融合:
  • 特征层面的融合(分割)
  • 模型层面的融合

基础学习:

Liner Regression

$f(x)=w^{\prime} x+b$

1
2
3
4
5
#w是列向量 矩阵由一个个列向量构成 y = dot(w_t,X)+b
import numpy as np
w_t,b = np.array([1,2,3,4,5]),1
X = np.array([[1,1,1,1,1],[1,2,5,3,4],[5,5,5,5,5]]).T
y_hat = np.dot(w,X) + b

策略:通过loss衡量w和b。$loss=(f(x)-y)^{2}$

优化:

  • 最小二乘[公式]^{-1})

    1
    w_t = np.dot(np.dot(y,X.T), np.linalg.inv(np.dot(X,X.T)))
  • 梯度下降

    [公式]X^T)

    [公式]

    1
    2
    3
    4
    5
    while True:
    grad = 2 *np.dot((np.dot(w_t,x)-y),x.t)
    w_t -=0.1 * grad
    if np.linalg.norm(w_t,ord = 2)<1e-3:
    break

梯度提升树GBDT

CART回归树

GBDT是一个集成模型,可以看做是很多个基模型的线性相加,其中的基模型就是CART回归树。

CART树是一个二分决策树模型,每个节点特征取值为“是”和“不是”。

  • 回归树生成:一个回归树对应的输入空间的一个划分,假设已经将输入空间划分为M个单元R1,R2,R3….Rm,并且每个单元都有固定的输出值cm,其中I为判别函数。

    输入:训练数据集D={(x1,y1),(x2,y2),….,(xn,yn)}

    输出:一颗回归树 $f(x)=\sum_{m=1}^{M} \hat{c}_{m} I\left(x \in R_{m}\right)$

    [公式]

    可知:Rm上的最优输出值就是Rm内所有样本xi对应yi的均值。

    如何对输入空间进行划分?:选择最优的特征作为划分。如果特征是连续变量,R1 = {x|x^j==s} and R2 = {x|x^j != s};寻找最优的变量j和最优切分点s:

  • 回归树剪枝

    先从整体树T0开始,对内部任意节点t,比较以t作为单节点树的损失与t作为根节点树的损失,如果单节点比下面一棵树还要好,就给剪掉。剪完后的树继续重复此过程。

    loss:$C_{a}(T)=C(T)+\alpha|T|$

    C(T)为对训练数据的误差,|T|为树的节点数量,alpha调节的是要精准度还是要模型的简单性。(过拟合与欠拟合)

GBDT

模型定义如下:$f_{t}(x)=\sum_{t=1}^{T} h_{t}(x)$;f­­­t­(x)表示第t轮的模型,ht(x)表示第t颗决策树

采用前向分步算法:$f_{t}(x)=f_{t-1}(x)+h_{t}(x)$

Loss:[公式]

第t轮的第i个样本的损失函数的负梯度表示为:[公式]

[公式]

利用(xi, $r_{ti}$) (i=1,2,…m),我们可以拟合一颗CART回归树,得到了第t颗回归树,其对应的叶节点区域Rtj, j=1,2,…,J。其中J为叶子节点的个数。

针对每一个叶子节点里的样本,我们求出使损失函数最小,也就是拟合叶子节点最好的的输出值ctj如下(注意这里的yi是真实值,不是残差):

[公式]

此时本轮的决策树拟合函数就得到了:

然后本轮的强学习器也就得到了:

之后一直迭代下去,直到损失函数收敛.

Xgboos

目标函数:$\mathcal{L}^{(t)}=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}^{(t-1)}+f_{t}\left(\mathbf{x}_{i}\right)\right)+\Omega\left(f_{t}\right)$

  • 规整项$\Omega\left(f_{t}\right)$是一个递归的式子,规整项仅仅是第t颗树的,具体起来就是这棵树所有叶子节点权重向量的二范数。

$\mathcal{L}^{(t)} \simeq \sum_{i=1}^{n}\left[l\left(y_{i}, \hat{y}^{(t-1)}\right)+g_{i} f_{t}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right]+\Omega\left(f_{t}\right)$

  • 泰勒展开,假设残差接近于零。

  • 麦克劳林:$f(x)=f(0)+\frac{f^{\prime}(0)}{1 !} x+\frac{f^{\prime \prime}(0)}{2 !} x^{2}+\frac{f^{\prime \prime \prime}(0)}{3 !} x^{3}+\dots+\frac{f^{(n)}(0)}{n !} x^{n}+R_{n}(x)$

$\begin{aligned} \tilde{\mathcal{L}}^{(t)} &=\sum_{i=1}^{n}\left[g_{i} f_{t}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right]+\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2} \ &=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T \end{aligned}$

  • 把样本 i 归类到所在的叶子节点 j 上,改写目标函数的形式如上

w的解析解:$w_{j}^{*}=-\frac{\sum_{i \in I_{j}} g_{i}}{\sum_{i \in I_{j}} h_{i}+\lambda}$

代入可得到:$\tilde{\mathcal{L}}^{(t)}(q)=-\frac{1}{2} \sum_{j=1}^{T} \frac{\left(\sum_{i \in I_{j}} g_{i}\right)^{2}}{\sum_{i \in I_{j}} h_{i}+\lambda}+\gamma T$

LightGBM

算法学习

线性回归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#简单建模
from sklearn.linear_model import LinearRegression
model = LinearRegression(normalize=True)
model = model.fit(train_X, train_y)


#查看训练的线性回归模型的截距(intercept)与权重(coef)
'intercept:'+ str(model.intercept_)
sorted(dict(zip(continuous_feature_names, model.coef_)).items(), key=lambda x:x[1], reverse=True)


#绘制特征v_9的值与标签的散点图
from matplotlib import pyplot as plt
subsample_index = np.random.randint(low=0, high=len(train_y), size=50)

plt.scatter(train_X['v_9'][subsample_index], train_y[subsample_index], color='black')
plt.scatter(train_X['v_9'][subsample_index], model.predict(train_X.loc[subsample_index]), color='blue')
plt.xlabel('v_9')
plt.ylabel('price')
plt.legend(['True Price','Predicted Price'],loc='upper right')
print('The predicted price is obvious different from true price')
plt.show()

观察标签分布。

1
2
3
4
5
6
7
import seaborn as sns
print('It is clear to see the price shows a typical exponential distribution')
plt.figure(figsize=(15,5))
plt.subplot(1,2,1)
sns.distplot(train_y)
plt.subplot(1,2,2)
sns.distplot(train_y[train_y < np.quantile(train_y, 0.9)])

log(x+1)变换

1
2
3
4
5
6
7
8
9
train_y_ln = np.log(train_y + 1)

import seaborn as sns
print('The transformed price seems like normal distribution')
plt.figure(figsize=(15,5))
plt.subplot(1,2,1)
sns.distplot(train_y_ln)
plt.subplot(1,2,2)
sns.distplot(train_y_ln[train_y_ln < np.quantile(train_y_ln, 0.9)])

再次拟合验证

1
2
3
4
model = model.fit(train_X, train_y_ln)

print('intercept:'+ str(model.intercept_))
sorted(dict(zip(continuous_feature_names, model.coef_)).items(), key=lambda x:x[1], reverse=True)

五折交叉验证

不会把所有的数据集都拿来训练,而是分出一部分来(这一部分不参加训练)对训练集生成的参数进行测试,相对客观的判断这些参数对训练集之外的数据的符合程度。这种思想就称为交叉验证(Cross Validation)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sklearn.model_selection import cross_val_score
from sklearn.metrics import mean_absolute_error, make_scorer

def log_transfer(func):
def wrapper(y, yhat):
result = func(np.log(y), np.nan_to_num(np.log(yhat)))
return result
return wrapper

#使用线性回归模型,对未处理标签的特征数据进行五折交叉验证
scores = cross_val_score(model, X=train_X, y=train_y, verbose=1, cv = 5, scoring=make_scorer(log_transfer(mean_absolute_error)))
print('AVG:', np.mean(scores))

#使用线性回归模型,对处理过标签的特征数据进行五折交叉验证
scores = cross_val_score(model, X=train_X, y=train_y_ln, verbose=1, cv = 5, scoring=make_scorer(mean_absolute_error))
print('AVG:', np.mean(scores))


scores = pd.DataFrame(scores.reshape(1,-1))
scores.columns = ['cv' + str(x) for x in range(1, 6)]
scores.index = ['MAE']
scores

绘制学习率曲线与验证曲线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from sklearn.model_selection import learning_curve, validation_curve
? learning_curve

def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None,n_jobs=1, train_size=np.linspace(.1, 1.0, 5 )):
plt.figure()
plt.title(title)
if ylim is not None:
plt.ylim(*ylim)
plt.xlabel('Training example')
plt.ylabel('score')
train_sizes, train_scores, test_scores = learning_curve(estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_size, scoring = make_scorer(mean_absolute_error))
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
plt.grid()#区域
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="r")
plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1,
color="g")
plt.plot(train_sizes, train_scores_mean, 'o-', color='r',
label="Training score")
plt.plot(train_sizes, test_scores_mean,'o-',color="g",
label="Cross-validation score")
plt.legend(loc="best")
return plt

plot_learning_curve(LinearRegression(), 'Liner_model', train_X[:1000], train_y_ln[:1000], ylim=(0.0, 0.5), cv=5, n_jobs=1)

多模型对比

1
2
3
4
5
train = sample_feature[continuous_feature_names + ['price']].dropna()

train_X = train[continuous_feature_names]
train_y = train['price']
train_y_ln = np.log(train_y + 1)

嵌入式选择最常用的是L1正则化与L2正则化。在对线性回归模型加入两种正则化方法后,他们分别变成了岭回归与Lasso回归。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso

models = [LinearRegression(),
Ridge(),
Lasso()]

result = dict()
for model in models:
model_name = str(model).split('(')[0]
scores = cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error))
result[model_name] = scores
print(model_name + ' is finished')

result = pd.DataFrame(result)
result.index = ['cv' + str(x) for x in range(1, 6)]
result
1
2
3
model = LinearRegression().fit(train_X, train_y_ln)
print('intercept:'+ str(model.intercept_))
sns.barplot(abs(model.coef_), continuous_feature_names)

L2正则化在拟合过程中通常都倾向于让权值尽可能小,最后构造一个所有参数都比较小的模型。

1
2
3
model = Ridge().fit(train_X, train_y_ln)
print('intercept:'+ str(model.intercept_))
sns.barplot(abs(model.coef_), continuous_feature_names)

L1正则化有助于生成一个稀疏权值矩阵,进而可以用于特征选择。

1
2
3
model = Lasso().fit(train_X, train_y_ln)
print('intercept:'+ str(model.intercept_))
sns.barplot(abs(model.coef_), continuous_feature_names)

部分常用非线性模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from sklearn.linear_model import LinearRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.neural_network import MLPRegressor
from xgboost.sklearn import XGBRegressor
from lightgbm.sklearn import LGBMRegressor

models = [LinearRegression(),
DecisionTreeRegressor(),
RandomForestRegressor(),
GradientBoostingRegressor(),
MLPRegressor(solver='lbfgs', max_iter=100),
XGBRegressor(n_estimators = 100, objective='reg:squarederror'),
LGBMRegressor(n_estimators = 100)]

result = dict()
for model in models:
model_name = str(model).split('(')[0]
scores = cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error))
result[model_name] = scores
print(model_name + ' is finished')

result = pd.DataFrame(result)
result.index = ['cv' + str(x) for x in range(1, 6)]
result

模型调参

1
2
3
4
5
6
7
8
9
## LGB的参数集合:

objective = ['regression', 'regression_l1', 'mape', 'huber', 'fair']

num_leaves = [3,5,10,15,20,40, 55]
max_depth = [3,5,10,15,20,40, 55]
bagging_fraction = []
feature_fraction = []
drop_rate = []
  • 贪心调参

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    best_obj = dict()
    for obj in objective:
    model = LGBMRegressor(objective=obj)
    score = np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))
    best_obj[obj] = score

    best_leaves = dict()
    for leaves in num_leaves:
    model = LGBMRegressor(objective=min(best_obj.items(), key=lambda x:x[1])[0], num_leaves=leaves)
    score = np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))
    best_leaves[leaves] = score

    best_depth = dict()
    for depth in max_depth:
    model = LGBMRegressor(objective=min(best_obj.items(), key=lambda x:x[1])[0],
    num_leaves=min(best_leaves.items(), key=lambda x:x[1])[0],
    max_depth=depth)
    score = np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))
    best_depth[depth] = score


    sns.lineplot(x=['0_initial','1_turning_obj','2_turning_leaves','3_turning_depth'], y=[0.143 ,min(best_obj.values()), min(best_leaves.values()), min(best_depth.values())])
  • Grid Search 调参

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from sklearn.model_selection import GridSearchCV

    parameters = {'objective': objective , 'num_leaves': num_leaves, 'max_depth': max_depth}
    model = LGBMRegressor()
    clf = GridSearchCV(model, parameters, cv=5)
    clf = clf.fit(train_X, train_y)

    clf.best_params_
    model = LGBMRegressor(objective='regression',
    num_leaves=55,
    max_depth=15)

    np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))
  • 贝叶斯调参

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    from bayes_opt import BayesianOptimization

    def rf_cv(num_leaves, max_depth, subsample, min_child_samples):
    val = cross_val_score(
    LGBMRegressor(objective = 'regression_l1',
    num_leaves=int(num_leaves),
    max_depth=int(max_depth),
    subsample = subsample,
    min_child_samples = int(min_child_samples)
    ),
    X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)
    ).mean()
    return 1 - val

    rf_bo = BayesianOptimization(
    rf_cv,
    {
    'num_leaves': (2, 100),
    'max_depth': (2, 100),
    'subsample': (0.1, 1),
    'min_child_samples' : (2, 100)
    }
    )

    rf_bo.maximize()
    1 - rf_bo.max['target']

还采用了一些基本方法来提高预测的精度

1
2
plt.figure(figsize=(13,5))
sns.lineplot(x=['0_origin','1_log_transfer','2_L1_&_L2','3_change_model','4_parameter_turning'], y=[1.36 ,0.19, 0.19, 0.14, 0.13])

主要学习内容

  1. 异常处理:

    • 删除异常值:箱线图;3-Sigma

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      def outliers_proc(data, col_name, scale=3):
      """
      用于清洗异常值,默认用 box_plot(scale=3)进行清洗
      :param data: 接收 pandas 数据格式
      :param col_name: pandas 列名
      :param scale: 尺度
      :return:
      """

      def box_plot_outliers(data_ser, box_scale):
      """
      利用箱线图去除异常值
      :param data_ser: 接收 pandas.Series 数据格式
      :param box_scale: 箱线图尺度,
      :return:
      """

      image-20200328163018012

    • 处理有偏分布:BOX-COX 转换

    • 长尾截断

  2. 归一化/标准化:标准正态;[0,1]区间;幂律分布

    1
    2
    3
    4
    5
    6
    # 取 log,做归一化
    from sklearn import preprocessing
    min_max_scaler = preprocessing.MinMaxScaler()
    data['power'] = np.log(data['power'] + 1)
    data['power'] = ((data['power'] - np.min(data['power'])) / (np.max(data['power']) - np.min(data['power'])))
    data['power'].plot.hist()

    image-20200328163821458image-20200328163830133

  3. 数据分桶

    优点:

    • 离散后稀疏向量内积乘法运算速度更快,计算结果也方便存储,容易扩展;

    • 离散后的特征对异常值更具鲁棒性

    • LR 属于广义线性模型,表达能力有限,经过离散化后,每个变量有单独的权重,相当于引入了非线性,能够提升模型的表达能力,加大拟合;

    • 离散后特征可以进行特征交叉,提升表达能力

    • 特征离散后模型更稳定

  4. 缺失值处理:

    • 不处理(针对类似 XGBoost 等树模型);
    • 删除(缺失太多)
    • 插值补全
    • 分箱,缺失值一个箱
  5. 特征构造

  6. 特征筛选:

    • 过滤式(filter):先对数据进行特征选择,然后在训练学习器,常见的方法有 Relief/方差选择发/相关系 数法/卡方检验法/互信息法;

      image-20200328164358998

    • 包裹式(wrapper):直接把最终将要使用的学习器的性能作为特征子集的评价准则,常见方法有 LVM(Las Vegas Wrapper) ;

    • 嵌入式(embedding):结合过滤式和包裹式,学习器训练过程中自动进行了特征选择,常见的有 lasso 回归;

  7. 数据降维