Python中类和实例学习笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunJW_2017/article/details/81133948

Python中类和实例学习笔记

以创建一个命名实体识别(Named Entity Recognition,NER)模型为例,学习Python中类和实例的相关内容。预处理函数参考了这篇博客

类(Class)和实例(Instance)

类和实例是面向对象编程最重要的概念之二,类是抽象的模板,实例是类的具体表现。下面用具体的例子分析说明这一点。

建模流程及本文引言

在Python中,以sklearn为例,一般训练一个模型的步骤可以分为:

  1. 准备数据
  2. 搭建模型
  3. 将数据“喂入”模型(即:model.fit()方法)
  4. 模型的应用(预测、分类等)

其中,第2、3、4步只需要利用sklearn进行傻瓜式操作即可,但第1步准备数据则需要我自己操作。
第1步即所谓的“数据预处理”,在搭建此NER模型时,我也利用了第三方包为我们完成后续步骤;对于数据预处理,我准备编写一个pre_precessing类来集成所有在第1步用到的处理方法。

数据一览

关于NER的理论及技术细节,可以去这里,本文只给出源数据格式以及模型需要的数据格式。
训练语料库为人民日报199801语料库,具体长这样:

19980101-01-001-001/m  迈向/v  充满/v  希望/n  的/u  新/a  世纪/n  ——/w  一九九八年/t  新年/t  讲话/n  (/w  附/v  图片/n  1/m  张/q  )/w  
19980101-01-001-002/m  中共中央/nt  总书记/n  、/w  国家/n  主席/n  江/nr  泽民/nr  

而模型需要的数据,是这样:

[[{'bias': 1.0, 'w': '迈', 'w+1': '向', 'w-1': '<BOS>',
   'w-1:w': '<BOS>迈', 'w:w+1': '迈向'},
  {'bias': 1.0, 'w': '向', 'w+1': '充', 'w-1': '迈',
   'w-1:w': '迈向', 'w:w+1': '向充'},
  {'bias': 1.0, 'w': '充', 'w+1': '满', 'w-1': '向',
   'w-1:w': '向充', 'w:w+1': '充满'}, 
  {'bias': 1.0, 'w': '满', 'w+1': '希', 'w-1': '充',
   'w-1:w': '充满', 'w:w+1': '满希'},
  {'bias': 1.0, 'w': '希', 'w+1': '望', 'w-1': '满',
   'w-1:w': '满希', 'w:w+1': '希望'}
  ...
]]

当然,还需要对应的实体标签,即Y_train,但这里只用X_train就足以说明问题了。
我需要定义一个类,以完成上面的转换过程。

类的声明及实例的创建

定义一个类pre_processing

很简单,代码如下:

class pre_processing(obj):
    pass

其中,pre_processing是类的名字,参数obj是表明所定义的类是从哪里继承来的,该项参数可以为空(在本例中我就是这么做的)。关于什么是继承先不要管,如果不想为空也可以设为object,事实上所有的类追根溯源都继承自object类。

创建一个实例pp

更简单,代码如下:

pp = pre_processing()

至此,我想应该可以对上文说的类是抽象的模板,实例是类的具体表现有了一个更加深入的认识了吧?如果没有,继续往下看。
如果我定义多个实例会出现什么情况呢?输入以下代码:

pp1 = pre_processing()
pp2 = pre_processing()

亦即从一个类定义了两个实例pp1pp2,接着输入:

print(pp1)
print(pp2)

那么会打印出类似如下的内容:

<__main__.pre_processing object at 0x00000162F15CB9B0>
<__main__.pre_processing object at 0x00000162F15CB7B8>

这说明,在同一类pre_processing下定义的两个不同的实例具有不同的内存地址。

实例的属性(attribute)

对于上文定义的实例pp1,我可以如下为其添加属性

pp1.attribute = "This is an attribute"

如此之后,pp1就多了一个属性attribute,其值为This is an attribute。要想查看某个实例所有的属性,可以用dir函数:

print(dir(pp1))

输出为:

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

注意,最后一项为我们刚刚定义的属性。而前面那些诸多以__开头的属性表明该属性为私有属性,只有在类内可以访问,而类外无法访问,要想获取这些属性的值,可以在类内定义相应功能的函数(即方法)。这样看似麻烦的操作,实际上是保证了外部的代码无法随意修改内部的对象,增加代码的健壮性。

实例的方法及__init__

实例的方法指的就是在类的内部定义的函数。上文说可以对已经定义的实例添加各种属性,这属于“后天”的做法,实际上可以强制在创建实例时传递给实例必要的属性,这就要依赖于类中特殊的__init__方法了。
在本例中,我定义的类的目的是为了完成数据预处理,因此在创建实例时需要让它具有训练集数据(X_train)这一必备属性,于是__init__方法按如下定义:

class pre_processing():
    def __init__(self, X_train):
        self.X_train = X_train

值得注意的是,在类中定义方法与定义函数有一点不同:所有方法的必须含有self参数,且该参数必须是第一个,表示此类创建的实例本身,于是在__init__内部就可以把各种属性直接赋给self,因为它指向该实例。
这样定义类之后,执行:

pp1 = pre_processing()

则会提示错误:

TypeError: _ _ init _ _ () missing 1 required positional argument: 'X_train'

这说明,在利用新定义的类创建实例时,必须给实例以必要的X_train属性。例如:

X_train = u'19980101-01-001-001/m  迈向/v  充满/v  希望/n  的/u  新/a  世纪/n  ——/w  一九九八年/t  新年/t  讲话/n  (/w  附/v  图片/n  1/m  张/q  )/w  ' \
          u'19980101-01-001-002/m  中共中央/nt  总书记/n  、/w  国家/n  主席/n  江/nr  泽民/nr  '
pp1 = pre_processing(X_train=X_train)

则不会报错,说明实例创建成功。这时候,pp1就具有了属性X_train,可以按如下方式访问:

print(pp1.X_train)
19980101-01-001-001/m  迈向/v  充满/v  希望/n  的/u  新/a  世纪/n  ——/w  一九九八年/t  新年/t  讲话/n  (/w  附/v  图片/n  1/m/q  )/w  19980101-01-001-002/m  中共中央/nt  总书记/n  、/w  国家/n  主席/n  江/nr  泽民/nr  

若将__init__中的self.X_train改为self.__X_trian重复运行上述代码,会出现什么情况呢?实验表明,在创建实例时仍需要传入一个必须的X_train参数,但此时的属性以__开头,表明类外不可访问,因此执行print(pp1.__X_train)时提示:'pre_processing' object has no attribute '__X_train'
通过dir(pp1)查看时会发现该实例具有_pre_processing__X_train属性,意思就是说,该属性为pre_processing内部的属性,外部无法直接访问。如果想获取,怎么处理呢?可以新加一个get__X_train的方法:

def get__X_train(self):
    return self.__X_train

那么,要想获取__X_train属性的值,只需要调用pp1.get__X_train()即可。这也说明了,类的内部是可以任意调用该属性的。

类内数据预处理方法的定义

明确了上述内容之后,就可以在类内定义一系列方法进行数据预处理了。类的名字仍然延续上文,强制限定的属性为原始数据路径(data_path)。
首先,给出类中应该包含的方法列表:

  1. 从本地读取数据(load_data(self, data_path)
  2. 读取的内容以一个字符串存储,因此将其中的非中文字符全部转化为半角类型(q2b(self, train_data)
  3. 合并原始数据中分开标注的姓和名(process_nr(self, train_data)
  4. 合并原始数据中分开标注的时间词(process_t(self, train_data)
  5. 合并原始数据中以[]小粒度词(process_k(self, train_data)

下面是该类的完整代码:

import re

class pre_processing():
    def __init__(self, data_path):
        self.data_path = data_path

    def load_data(self):
        data = open(self.data_path, encoding='utf-8').read()
        self.data =  re.sub('199801.{13}/m  ', '', data) 


    def q2b(self):
        b_str = ""
        for uchar in self.data:
            inside_code = ord(uchar)
            if inside_code == 12288:  
                inside_code = 32
            elif 65374 >= inside_code >= 65281:  
                inside_code -= 65248
            b_str += chr(inside_code)
        self.data = b_str

    def str2list(self):
        self.data = self.data.split()

    def process_t(self):
        pro_words = []
        index = 0
        temp = u''
        while True:
            word = self.data[index] if index < len(self.data) else u''
            if u'/t' in word:
                temp = temp.replace(u'/t', u'') + word
            elif temp:
                pro_words.append(temp)
                pro_words.append(word)
                temp = u''
            elif word:
                pro_words.append(word)
            else:
                break
            index += 1
        self.data = pro_words

    def process_nr(self):
        pro_words = []
        index = 0
        while True:
            word = self.data[index] if index < len(self.data) else u''
            if u'/nr' in word:
                next_index = index + 1
                if next_index < len(self.data) and u'/nr' in self.data[next_index]:
                    pro_words.append(word.replace(u'/nr', u'') + self.data[next_index])
                    index = next_index
                else:
                    pro_words.append(word)
            elif word:
                pro_words.append(word)
            else:
                break
            index += 1
        self.data = pro_words 


    def process_k(self):
        pro_words = []
        index = 0
        temp = u''
        while True:
            word = self.data[index] if index < len(self.data) else u''
            if u'[' in word:
                temp += re.sub(pattern=u'/[a-zA-Z]*', repl=u'', string=word.replace(u'[', u''))
            elif u']' in word:
                w = word.split(u']')
                temp += re.sub(pattern=u'/[a-zA-Z]*', repl=u'', string=w[0])
                pro_words.append(temp+u'/'+w[1])
                temp = u''
            elif temp:
                temp += re.sub(pattern=u'/[a-zA-Z]*', repl=u'', string=word)
            elif word:
                pro_words.append(word)
            else:
                break
            index += 1
        self.data = pro_words

定义好类之后,接下来就是要调用类中的方法来对数据进行预处理。假设代码文件与数据文件在同一目录下,则数据路径只需要输入文件名即可:

data = pre_processing("rmrb199801.txt")

data.load_data()

data.q2b()

data.str2list()

data.process_t()

data.process_nr()

data.process_k()

猜你喜欢

转载自blog.csdn.net/SunJW_2017/article/details/81133948
今日推荐